ip_eval(self, str)
    VALUE self;
    VALUE str;
{
    struct eval_queue *evq;
#ifdef RUBY_USE_NATIVE_THREAD
    struct tcltkip *ptr;
#endif
    char *eval_str;
    int  *alloc_done;
    int  thr_crit_bup;
    volatile VALUE current = rb_thread_current();
    volatile VALUE ip_obj = self;
    volatile VALUE result;
    volatile VALUE ret;
    Tcl_QueuePosition position;
    struct timeval t;

    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;
    StringValue(str);
    rb_thread_critical = thr_crit_bup;

#ifdef RUBY_USE_NATIVE_THREAD
    ptr = get_ip(ip_obj);
    DUMP2("eval status: ptr->tk_thread_id %p", ptr->tk_thread_id);
    DUMP2("eval status: Tcl_GetCurrentThread %p", Tcl_GetCurrentThread());
#else
    DUMP2("status: Tcl_GetCurrentThread %p", Tcl_GetCurrentThread());
#endif
    DUMP2("status: eventloopt_thread %"PRIxVALUE, eventloop_thread);

    if (
#ifdef RUBY_USE_NATIVE_THREAD
	(ptr->tk_thread_id == 0 || ptr->tk_thread_id == Tcl_GetCurrentThread())
	&&
#endif
	(NIL_P(eventloop_thread) || current == eventloop_thread)
	) {
        if (NIL_P(eventloop_thread)) {
            DUMP2("eval from thread:%"PRIxVALUE" but no eventloop", current);
        } else {
            DUMP2("eval from current eventloop %"PRIxVALUE, current);
        }
        result = ip_eval_real(self, RSTRING_PTR(str), RSTRING_LENINT(str));
        if (rb_obj_is_kind_of(result, rb_eException)) {
            rb_exc_raise(result);
        }
        return result;
    }

    DUMP2("eval from thread %"PRIxVALUE" (NOT current eventloop)", current);

    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;

    /* allocate memory (keep result) */
    /* alloc_done = (int*)ALLOC(int); */
    alloc_done = RbTk_ALLOC_N(int, 1);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)alloc_done); /* XXXXXXXX */
#endif
    *alloc_done = 0;

    /* eval_str = ALLOC_N(char, RSTRING_LEN(str) + 1); */
    eval_str = ckalloc(RSTRING_LENINT(str) + 1);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)eval_str); /* XXXXXXXX */
#endif
    memcpy(eval_str, RSTRING_PTR(str), RSTRING_LEN(str));
    eval_str[RSTRING_LEN(str)] = 0;

    /* allocate memory (freed by Tcl_ServiceEvent) */
    /* evq = (struct eval_queue *)Tcl_Alloc(sizeof(struct eval_queue)); */
    evq = RbTk_ALLOC_N(struct eval_queue, 1);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve(evq);
#endif

    /* allocate result obj */
    result = rb_ary_new3(1, Qnil);

    /* construct event data */
    evq->done = alloc_done;
    evq->str = eval_str;
    evq->len = RSTRING_LENINT(str);
    evq->interp = ip_obj;
    evq->result = result;
    evq->thread = current;
    evq->safe_level = rb_safe_level();
    evq->ev.proc = eval_queue_handler;

    position = TCL_QUEUE_TAIL;

    /* add the handler to Tcl event queue */
    DUMP1("add handler");
#ifdef RUBY_USE_NATIVE_THREAD
    if (ptr->tk_thread_id) {
      /* Tcl_ThreadQueueEvent(ptr->tk_thread_id, &(evq->ev), position); */
      Tcl_ThreadQueueEvent(ptr->tk_thread_id, (Tcl_Event*)evq, position);
      Tcl_ThreadAlert(ptr->tk_thread_id);
    } else if (tk_eventloop_thread_id) {
      Tcl_ThreadQueueEvent(tk_eventloop_thread_id, (Tcl_Event*)evq, position);
      /* Tcl_ThreadQueueEvent(tk_eventloop_thread_id,
			   &(evq->ev), position); */
      Tcl_ThreadAlert(tk_eventloop_thread_id);
    } else {
      /* Tcl_QueueEvent(&(evq->ev), position); */
      Tcl_QueueEvent((Tcl_Event*)evq, position);
    }
#else
    /* Tcl_QueueEvent(&(evq->ev), position); */
    Tcl_QueueEvent((Tcl_Event*)evq, position);
#endif

    rb_thread_critical = thr_crit_bup;

    /* wait for the handler to be processed */
    t.tv_sec  = 0;
    t.tv_usec = (long)((EVENT_HANDLER_TIMEOUT)*1000.0);

    DUMP2("evq wait for handler (current thread:%"PRIxVALUE")", current);
    while(*alloc_done >= 0) {
      DUMP2("*** evq wait for handler (current thread:%"PRIxVALUE")", current);
      /* rb_thread_stop(); */
      /* rb_thread_sleep_forever(); */
      rb_thread_wait_for(t);
      DUMP2("*** evq wakeup (current thread:%"PRIxVALUE")", current);
      DUMP2("***          (eventloop thread:%"PRIxVALUE")", eventloop_thread);
      if (NIL_P(eventloop_thread)) {
	DUMP1("*** evq lost eventloop thread");
	break;
      }
    }
    DUMP2("back from handler (current thread:%"PRIxVALUE")", current);

    /* get result & free allocated memory */
    ret = RARRAY_PTR(result)[0];

#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)alloc_done, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release((ClientData)alloc_done); /* XXXXXXXX */
#else
    /* free(alloc_done); */
    ckfree((char*)alloc_done);
#endif
#endif
#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)eval_str, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release((ClientData)eval_str); /* XXXXXXXX */
#else
    /* free(eval_str); */
    ckfree(eval_str);
#endif
#endif
#if 0 /* evq is freed by Tcl_ServiceEvent */
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release(evq);
#else
    ckfree((char*)evq);
#endif
#endif

    if (rb_obj_is_kind_of(ret, rb_eException)) {
        DUMP1("raise exception");
        /* rb_exc_raise(ret); */
	rb_exc_raise(rb_exc_new3(rb_obj_class(ret),
				 rb_funcallv(ret, ID_to_s, 0, 0)));
    }

    return ret;
}

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RSTRING_PTR(s) (RSTRING(s)->ptr)

#define RSTRING_LEN(s) (RSTRING(s)->len)

#define RSTRING_LENINT(s) ((int)RSTRING_LEN(s))

#define RARRAY_PTR(s) (RARRAY(s)->ptr)

#define RARRAY_LEN(s) (RARRAY(s)->len)

#define RbTk_OBJ_UNTRUST(x)  do {OBJ_TAINT(x); OBJ_UNTRUST(x);} while (0)

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

extern VALUE rb_proc_new

#define RUBY_USE_NATIVE_THREAD 1

VALUE rb_errinfo(void);

#define rb_safe_level() (ruby_safe_level+0)

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

#define DUMP1(ARG1) if (ruby_debug) { fprintf(stderr, "tcltklib: %s\n", ARG1); fflush(stderr); }

#define DUMP2(ARG1, ARG2) if (ruby_debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2); fprintf(stderr, "\n"); fflush(stderr); }

static const char tcltklib_release_date[] = TCLTKLIB_RELEASE_DATE;

static const char finalize_hook_name[] = "INTERP_FINALIZE_HOOK";

static int at_exit = 0;

static VALUE cRubyEncoding;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static VALUE ENCODING_NAME_UTF8;

static VALUE ENCODING_NAME_BINARY;

static VALUE create_dummy_encoding_for_tk_core

static VALUE create_dummy_encoding_for_tk

static int update_encoding_table

static VALUE encoding_table_get_name_core

static VALUE encoding_table_get_obj_core

static VALUE encoding_table_get_name

static VALUE encoding_table_get_obj

static VALUE create_encoding_table

static VALUE ip_get_encoding_table

static VALUE eTkCallbackReturn;

static VALUE eTkCallbackBreak;

static VALUE eTkCallbackContinue;

static VALUE eLocalJumpError;

static VALUE eTkLocalJumpError;

static VALUE eTkCallbackRetry;

static VALUE eTkCallbackRedo;

static VALUE eTkCallbackThrow;

static VALUE tcltkip_class;

static ID ID_to_s;

static VALUE ip_invoke_real

_((int, VALUE*, VALUE));

static VALUE ip_invoke

static VALUE ip_invoke_with_position

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static VALUE tk_funcall

static VALUE callq_safelevel_handler

static const char Tcl_ObjTypeName_ByteArray[] = "bytearray";

static const char Tcl_ObjTypeName_String[]    = "string";

#define IS_TCL_BYTEARRAY(obj)    ((obj)->typePtr == Tcl_ObjType_ByteArray)

#define IS_TCL_STRING(obj)       ((obj)->typePtr == Tcl_ObjType_String)

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

#define rb_thread_alive_p(thread) rb_funcall2((thread), ID_alive_p, 0, NULL)

Tcl_Interp *interp;

const char *cmd;

#define Tcl_IncrRefCount(obj) (1)

#define Tcl_DecrRefCount(obj) (1)

#define Tcl_GetStringResult(interp) ((interp)->result)

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

CONST char *name1;

CONST char *name2;

int flags;

static Tcl_Obj *
Tcl_SetVar2Ex(interp, name1, name2, newValObj, flags)
    Tcl_Interp *interp;

extern int matherr();

int *tclDummyMathPtr = (int *) matherr;


call_queue_handler(evPtr, flags)
    Tcl_Event *evPtr;
    int flags;
{
    struct call_queue *q = (struct call_queue *)evPtr;
    volatile VALUE ret;
    volatile VALUE q_dat;
    volatile VALUE thread = q->thread;
    struct tcltkip *ptr;

    DUMP2("do_call_queue_handler : evPtr = %p", evPtr);
    DUMP2("call_queue_handler thread : %"PRIxVALUE, rb_thread_current());
    DUMP2("added by thread : %"PRIxVALUE, thread);

    if (*(q->done)) {
        DUMP1("processed by another event-loop");
        return 0;
    } else {
        DUMP1("process it on current event-loop");
    }

    if (RTEST(rb_thread_alive_p(thread))
	&& ! RTEST(rb_funcall(thread, ID_stop_p, 0))) {
      DUMP1("caller is not yet ready to receive the result -> pending");
      return 0;
    }

    /* process it */
    *(q->done) = 1;

    /* deleted ipterp ? */
    ptr = get_ip(q->interp);
    if (deleted_ip(ptr)) {
        /* deleted IP --> ignore */
        return 1;
    }

    /* incr internal handler mark */
    rbtk_internal_eventloop_handler++;

    /* check safe-level */
    if (rb_safe_level() != q->safe_level) {
        /* q_dat = Data_Wrap_Struct(rb_cData,0,-1,q); */
        q_dat = Data_Wrap_Struct(0,call_queue_mark,-1,q);
        ret = rb_funcall(rb_proc_new(callq_safelevel_handler, q_dat),
                         ID_call, 0);
        rb_gc_force_recycle(q_dat);
	q_dat = (VALUE)NULL;
    } else {
        DUMP2("call function (for caller thread:%"PRIxVALUE")", thread);
        DUMP2("call function (current thread:%"PRIxVALUE")", rb_thread_current());
        ret = (q->func)(q->interp, q->argc, q->argv);
    }

    /* set result */
    RARRAY_PTR(q->result)[0] = ret;
    ret = (VALUE)NULL;

    /* decr internal handler mark */
    rbtk_internal_eventloop_handler--;

    /* complete */
    *(q->done) = -1;

    /* unlink ruby objects */
    q->argv = (VALUE*)NULL;
    q->interp = (VALUE)NULL;
    q->result = (VALUE)NULL;
    q->thread = (VALUE)NULL;

    /* back to caller */
    if (RTEST(rb_thread_alive_p(thread))) {
      DUMP2("back to caller (caller thread:%"PRIxVALUE")", thread);
      DUMP2("               (current thread:%"PRIxVALUE")", rb_thread_current());
#if CONTROL_BY_STATUS_OF_RB_THREAD_WAITING_FOR_VALUE
      have_rb_thread_waiting_for_value = 1;
      rb_thread_wakeup(thread);
#else
      rb_thread_run(thread);
#endif
      DUMP1("finish back to caller");
#if DO_THREAD_SCHEDULE_AT_CALLBACK_DONE
      rb_thread_schedule();
#endif
    } else {
      DUMP2("caller is dead (caller thread:%"PRIxVALUE")", thread);
      DUMP2("               (current thread:%"PRIxVALUE")", rb_thread_current());
    }

    /* end of handler : remove it */
    return 1;
}

tcltklib.c: check argument

* ext/tk/tcltklib.c (ip_cancel_eval_core): check argument type and
  length.

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@51468 b2dd03c8-39d4-4d8f-98ff-823fe69b080e

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RSTRING_PTR(s) (RSTRING(s)->ptr)

#define RSTRING_LENINT(s) ((int)RSTRING_LEN(s))

#define RARRAY_PTR(s) (RARRAY(s)->ptr)

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

extern VALUE rb_proc_new

VALUE rb_errinfo(void);

#define rb_safe_level() (ruby_safe_level+0)

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

#define DUMP1(ARG1) if (ruby_debug) { fprintf(stderr, "tcltklib: %s\n", ARG1); fflush(stderr); }

#define DUMP2(ARG1, ARG2) if (ruby_debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2); fprintf(stderr, "\n"); fflush(stderr); }

static int at_exit = 0;

static VALUE cRubyEncoding;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static VALUE ENCODING_NAME_UTF8;

static VALUE ENCODING_NAME_BINARY;

static VALUE create_dummy_encoding_for_tk_core

static VALUE create_dummy_encoding_for_tk

static int update_encoding_table

static VALUE encoding_table_get_name_core

static VALUE encoding_table_get_obj_core

static VALUE encoding_table_get_name

static VALUE encoding_table_get_obj

static VALUE create_encoding_table

static VALUE ip_get_encoding_table

static VALUE eTkCallbackReturn;

static VALUE eTkCallbackBreak;

static VALUE eTkCallbackContinue;

static VALUE eLocalJumpError;

static VALUE eTkLocalJumpError;

static VALUE eTkCallbackRetry;

static VALUE eTkCallbackRedo;

static VALUE eTkCallbackThrow;

static VALUE tcltkip_class;

static ID ID_stop_p;

static ID ID_call;

static VALUE ip_invoke_real

_((int, VALUE*, VALUE));

static VALUE ip_invoke

static VALUE ip_invoke_with_position

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static VALUE tk_funcall

static VALUE callq_safelevel_handler

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

#define rb_thread_alive_p(thread) rb_funcall2((thread), ID_alive_p, 0, NULL)

Tcl_Interp *interp;

#define Tcl_GetStringResult(interp) ((interp)->result)

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

int flags;

static Tcl_Obj *
Tcl_SetVar2Ex(interp, name1, name2, newValObj, flags)
    Tcl_Interp *interp;

extern int matherr();

int *tclDummyMathPtr = (int *) matherr;

struct invoke_queue {
    Tcl_Event ev;
    int argc;
#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **argv;
#else /* TCL_MAJOR_VERSION < 8 */
    char **argv;
#endif
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

struct eval_queue {
    Tcl_Event ev;
    char *str;
    int len;
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

struct call_queue {
    Tcl_Event ev;
    VALUE (*func)();
    int argc;
    VALUE *argv;
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

static VALUE eventloop_thread;

static VALUE eventloop_stack;

static int   window_event_mode = ~0;

static VALUE watchdog_thread;

#define CONTROL_BY_STATUS_OF_RB_THREAD_WAITING_FOR_VALUE 1


eval_queue_handler(evPtr, flags)
    Tcl_Event *evPtr;
    int flags;
{
    struct eval_queue *q = (struct eval_queue *)evPtr;
    volatile VALUE ret;
    volatile VALUE q_dat;
    volatile VALUE thread = q->thread;
    struct tcltkip *ptr;

    DUMP2("do_eval_queue_handler : evPtr = %p", evPtr);
    DUMP2("eval_queue_thread : %"PRIxVALUE, rb_thread_current());
    DUMP2("added by thread : %"PRIxVALUE, thread);

    if (*(q->done)) {
        DUMP1("processed by another event-loop");
        return 0;
    } else {
        DUMP1("process it on current event-loop");
    }

    if (RTEST(rb_thread_alive_p(thread))
	&& ! RTEST(rb_funcall(thread, ID_stop_p, 0))) {
      DUMP1("caller is not yet ready to receive the result -> pending");
      return 0;
    }

    /* process it */
    *(q->done) = 1;

    /* deleted ipterp ? */
    ptr = get_ip(q->interp);
    if (deleted_ip(ptr)) {
        /* deleted IP --> ignore */
        return 1;
    }

    /* incr internal handler mark */
    rbtk_internal_eventloop_handler++;

    /* check safe-level */
    if (rb_safe_level() != q->safe_level) {
#ifdef HAVE_NATIVETHREAD
#ifndef RUBY_USE_NATIVE_THREAD
    if (!ruby_native_thread_p()) {
      rb_bug("cross-thread violation on eval_queue_handler()");
    }
#endif
#endif
        /* q_dat = Data_Wrap_Struct(rb_cData,0,-1,q); */
        q_dat = Data_Wrap_Struct(0,eval_queue_mark,-1,q);
        ret = rb_funcall(rb_proc_new(evq_safelevel_handler, q_dat),
                         ID_call, 0);
        rb_gc_force_recycle(q_dat);
	q_dat = (VALUE)NULL;
    } else {
        ret = ip_eval_real(q->interp, q->str, q->len);
    }

    /* set result */
    RARRAY_PTR(q->result)[0] = ret;
    ret = (VALUE)NULL;

    /* decr internal handler mark */
    rbtk_internal_eventloop_handler--;

    /* complete */
    *(q->done) = -1;

    /* unlink ruby objects */
    q->interp = (VALUE)NULL;
    q->result = (VALUE)NULL;
    q->thread = (VALUE)NULL;

    /* back to caller */
    if (RTEST(rb_thread_alive_p(thread))) {
      DUMP2("back to caller (caller thread:%"PRIxVALUE")", thread);
      DUMP2("               (current thread:%"PRIxVALUE")", rb_thread_current());
#if CONTROL_BY_STATUS_OF_RB_THREAD_WAITING_FOR_VALUE
      have_rb_thread_waiting_for_value = 1;
      rb_thread_wakeup(thread);
#else
      rb_thread_run(thread);
#endif
      DUMP1("finish back to caller");
#if DO_THREAD_SCHEDULE_AT_CALLBACK_DONE
      rb_thread_schedule();
#endif
    } else {
      DUMP2("caller is dead (caller thread:%"PRIxVALUE")", thread);
      DUMP2("               (current thread:%"PRIxVALUE")", rb_thread_current());
    }

    /* end of handler : remove it */
    return 1;
}

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RSTRING_PTR(s) (RSTRING(s)->ptr)

#define RSTRING_LEN(s) (RSTRING(s)->len)

#define RSTRING_LENINT(s) ((int)RSTRING_LEN(s))

#define RARRAY_PTR(s) (RARRAY(s)->ptr)

#define RARRAY_LEN(s) (RARRAY(s)->len)

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

extern VALUE rb_proc_new

#define ruby_native_thread_p() is_ruby_native_thread()

#define RUBY_USE_NATIVE_THREAD 1

VALUE rb_errinfo(void);

#define rb_safe_level() (ruby_safe_level+0)

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

#define DUMP1(ARG1) if (ruby_debug) { fprintf(stderr, "tcltklib: %s\n", ARG1); fflush(stderr); }

#define DUMP2(ARG1, ARG2) if (ruby_debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2); fprintf(stderr, "\n"); fflush(stderr); }

static int at_exit = 0;

static VALUE cRubyEncoding;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static VALUE ENCODING_NAME_UTF8;

static VALUE ENCODING_NAME_BINARY;

static VALUE create_dummy_encoding_for_tk_core

static VALUE create_dummy_encoding_for_tk

static int update_encoding_table

static VALUE encoding_table_get_name_core

static VALUE encoding_table_get_obj_core

static VALUE encoding_table_get_name

static VALUE encoding_table_get_obj

static VALUE create_encoding_table

static VALUE ip_get_encoding_table

static VALUE eTkCallbackReturn;

static VALUE eTkCallbackBreak;

static VALUE eTkCallbackContinue;

static VALUE eLocalJumpError;

static VALUE eTkLocalJumpError;

static VALUE eTkCallbackRetry;

static VALUE eTkCallbackRedo;

static VALUE eTkCallbackThrow;

static VALUE tcltkip_class;

static ID ID_stop_p;

static ID ID_call;

static VALUE ip_invoke_real

_((int, VALUE*, VALUE));

static VALUE ip_invoke

static VALUE ip_invoke_with_position

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static VALUE tk_funcall

static VALUE callq_safelevel_handler

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

#define rb_thread_alive_p(thread) rb_funcall2((thread), ID_alive_p, 0, NULL)

Tcl_Interp *interp;

#define Tcl_GetStringResult(interp) ((interp)->result)

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

int flags;

static Tcl_Obj *
Tcl_SetVar2Ex(interp, name1, name2, newValObj, flags)
    Tcl_Interp *interp;

extern int matherr();

int *tclDummyMathPtr = (int *) matherr;

struct invoke_queue {
    Tcl_Event ev;
    int argc;
#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **argv;
#else /* TCL_MAJOR_VERSION < 8 */
    char **argv;
#endif
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

struct eval_queue {
    Tcl_Event ev;
    char *str;
    int len;
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}


ip_ruby_cmd(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char *argv[];
#endif
{
    volatile VALUE receiver;
    volatile ID method;
    volatile VALUE args;
    char *str;
    int i;
    int  len;
    struct cmd_body_arg *arg;
    int thr_crit_bup;
    VALUE old_gc;
    int code;

    if (interp == (Tcl_Interp*)NULL) {
        rbtk_pending_exception = rb_exc_new2(rb_eRuntimeError,
                                             "IP is deleted");
        return TCL_ERROR;
    }

    if (argc < 3) {
#if 0
        rb_raise(rb_eArgError, "too few arguments");
#else
        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp, "too few arguments", (char *)NULL);
        rbtk_pending_exception = rb_exc_new2(rb_eArgError,
                                             Tcl_GetStringResult(interp));
        return TCL_ERROR;
#endif
    }

    /* get arguments from Tcl objects */
    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;
    old_gc = rb_gc_disable();

    /* get receiver */
#if TCL_MAJOR_VERSION >= 8
    str = Tcl_GetStringFromObj(argv[1], &len);
#else /* TCL_MAJOR_VERSION < 8 */
    str = argv[1];
#endif
    DUMP2("receiver:%s",str);
    /* receiver = rb_protect(ip_ruby_cmd_receiver_get, (VALUE)str, &code); */
    receiver = ip_ruby_cmd_receiver_get(str);
    if (NIL_P(receiver)) {
#if 0
        rb_raise(rb_eArgError,
                 "unknown class/module/global-variable '%s'", str);
#else
        Tcl_ResetResult(interp);
        Tcl_AppendResult(interp, "unknown class/module/global-variable '",
                         str, "'", (char *)NULL);
        rbtk_pending_exception = rb_exc_new2(rb_eArgError,
                                             Tcl_GetStringResult(interp));
        if (old_gc == Qfalse) rb_gc_enable();
        return TCL_ERROR;
#endif
    }

    /* get metrhod */
#if TCL_MAJOR_VERSION >= 8
    str = Tcl_GetStringFromObj(argv[2], &len);
#else /* TCL_MAJOR_VERSION < 8 */
    str = argv[2];
#endif
    method = rb_intern(str);

    /* get args */
    args = rb_ary_new2(argc - 2);
    for(i = 3; i < argc; i++) {
        VALUE s;
#if TCL_MAJOR_VERSION >= 8
        str = Tcl_GetStringFromObj(argv[i], &len);
        s = rb_tainted_str_new(str, len);
#else /* TCL_MAJOR_VERSION < 8 */
        str = argv[i];
        s = rb_tainted_str_new2(str);
#endif
        DUMP2("arg:%s",str);
#ifndef HAVE_STRUCT_RARRAY_LEN
        rb_ary_push(args, s);
#else
        RARRAY(args)->ptr[RARRAY(args)->len++] = s;
#endif
    }

    if (old_gc == Qfalse) rb_gc_enable();
    rb_thread_critical = thr_crit_bup;

    /* allocate */
    arg = ALLOC(struct cmd_body_arg);
    /* arg = RbTk_ALLOC_N(struct cmd_body_arg, 1); */

    arg->receiver = receiver;
    arg->method = method;
    arg->args = args;

    /* evaluate the argument string by ruby */
    code = tcl_protect(interp, ip_ruby_cmd_core, (VALUE)arg);

    xfree(arg);
    /* ckfree((char*)arg); */

    return code;
}

#define RUBY_VERSION "(unknown version)"

#define RUBY_RELEASE_DATE "unknown release-date"

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RSTRING_PTR(s) (RSTRING(s)->ptr)

#define RSTRING_LEN(s) (RSTRING(s)->len)

#define RSTRING_LENINT(s) ((int)RSTRING_LEN(s))

#define RARRAY_PTR(s) (RARRAY(s)->ptr)

#define RARRAY_LEN(s) (RARRAY(s)->len)

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

extern VALUE rb_proc_new

VALUE rb_errinfo(void);

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

#define DUMP2(ARG1, ARG2) if (ruby_debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2); fprintf(stderr, "\n"); fflush(stderr); }

static const char tcltklib_release_date[] = TCLTKLIB_RELEASE_DATE;

static const char finalize_hook_name[] = "INTERP_FINALIZE_HOOK";

static int at_exit = 0;

static VALUE cRubyEncoding;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static VALUE ENCODING_NAME_UTF8;

static VALUE ENCODING_NAME_BINARY;

static VALUE create_dummy_encoding_for_tk_core

static VALUE create_dummy_encoding_for_tk

static int update_encoding_table

static VALUE encoding_table_get_name_core

static VALUE encoding_table_get_obj_core

static VALUE encoding_table_get_name

static VALUE encoding_table_get_obj

static VALUE create_encoding_table

static VALUE ip_get_encoding_table

static VALUE eTkCallbackReturn;

static VALUE eTkCallbackBreak;

static VALUE eTkCallbackContinue;

static VALUE eLocalJumpError;

static VALUE eTkLocalJumpError;

static VALUE eTkCallbackRetry;

static VALUE eTkCallbackRedo;

static VALUE eTkCallbackThrow;

static VALUE tcltkip_class;

static VALUE ip_invoke_real

_((int, VALUE*, VALUE));

static VALUE ip_invoke

static VALUE ip_invoke_with_position

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static VALUE tk_funcall

static VALUE callq_safelevel_handler

static const char Tcl_ObjTypeName_ByteArray[] = "bytearray";

static const char Tcl_ObjTypeName_String[]    = "string";

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

#define rb_thread_alive_p(thread) rb_funcall2((thread), ID_alive_p, 0, NULL)

Tcl_Interp *interp;

const char *cmd;

#define Tcl_GetStringResult(interp) ((interp)->result)

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

CONST char *name1;

CONST char *name2;

int flags;

static Tcl_Obj *
Tcl_SetVar2Ex(interp, name1, name2, newValObj, flags)
    Tcl_Interp *interp;

extern int matherr();

int *tclDummyMathPtr = (int *) matherr;

struct invoke_queue {
    Tcl_Event ev;
    int argc;
#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **argv;
#else /* TCL_MAJOR_VERSION < 8 */
    char **argv;
#endif
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

struct eval_queue {
    Tcl_Event ev;
    char *str;
    int len;
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}


ip_RubyExitCommand(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char *argv[];
#endif
#endif
{
    int state;
    char *cmd, *param;
#if TCL_MAJOR_VERSION < 8
    char *endptr;
    cmd = argv[0];
#endif

    DUMP1("start ip_RubyExitCommand");

#if TCL_MAJOR_VERSION >= 8
    /* cmd = Tcl_GetString(argv[0]); */
    cmd = Tcl_GetStringFromObj(argv[0], (int*)NULL);
#endif

    if (argc < 1 || argc > 2) {
        /* argument error */
        Tcl_AppendResult(interp,
                         "wrong number of arguments: should be \"",
                         cmd, " ?returnCode?\"", (char *)NULL);
        return TCL_ERROR;
    }

    if (interp == (Tcl_Interp*)NULL) return TCL_OK;

    Tcl_ResetResult(interp);

    if (Tcl_IsSafe(interp)) {
	if (!Tcl_InterpDeleted(interp)) {
	  ip_finalize(interp);

	  Tcl_DeleteInterp(interp);
	  Tcl_Release(interp);
	}
        return TCL_OK;
    }

    switch(argc) {
    case 1:
        /* rb_exit(0); */ /* not return if succeed */
        Tcl_AppendResult(interp,
                         "fail to call \"", cmd, "\"", (char *)NULL);

        rbtk_pending_exception = rb_exc_new2(rb_eSystemExit,
                                             Tcl_GetStringResult(interp));
        rb_iv_set(rbtk_pending_exception, "status", INT2FIX(0));

        return TCL_RETURN;

    case 2:
#if TCL_MAJOR_VERSION >= 8
        if (Tcl_GetIntFromObj(interp, argv[1], &state) == TCL_ERROR) {
            return TCL_ERROR;
        }
        /* param = Tcl_GetString(argv[1]); */
        param = Tcl_GetStringFromObj(argv[1], (int*)NULL);
#else /* TCL_MAJOR_VERSION < 8 */
        state = (int)strtol(argv[1], &endptr, 0);
        if (*endptr) {
            Tcl_AppendResult(interp,
                             "expected integer but got \"",
                             argv[1], "\"", (char *)NULL);
            return TCL_ERROR;
        }
        param = argv[1];
#endif
        /* rb_exit(state); */ /* not return if succeed */

        Tcl_AppendResult(interp, "fail to call \"", cmd, " ",
                         param, "\"", (char *)NULL);

        rbtk_pending_exception = rb_exc_new2(rb_eSystemExit,
                                             Tcl_GetStringResult(interp));
        rb_iv_set(rbtk_pending_exception, "status", INT2FIX(state));

        return TCL_RETURN;

    default:
        /* arguemnt error */
        Tcl_AppendResult(interp,
                         "wrong number of arguments: should be \"",
                         cmd, " ?returnCode?\"", (char *)NULL);
        return TCL_ERROR;
    }
}

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RSTRING_LENINT(s) ((int)RSTRING_LEN(s))

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

#define DUMP1(ARG1) if (ruby_debug) { fprintf(stderr, "tcltklib: %s\n", ARG1); fflush(stderr); }

static const char tcltklib_release_date[] = TCLTKLIB_RELEASE_DATE;

static const char finalize_hook_name[] = "INTERP_FINALIZE_HOOK";

static void ip_finalize

static int at_exit = 0;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static int update_encoding_table

_((int, VALUE*, VALUE));

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static const char Tcl_ObjTypeName_ByteArray[] = "bytearray";

static const char Tcl_ObjTypeName_String[]    = "string";

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

#define rb_thread_alive_p(thread) rb_funcall2((thread), ID_alive_p, 0, NULL)

Tcl_Interp *interp;

const char *cmd;

#define Tcl_GetStringResult(interp) ((interp)->result)

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

CONST char *name1;

CONST char *name2;

int flags;

static Tcl_Obj *
Tcl_SetVar2Ex(interp, name1, name2, newValObj, flags)
    Tcl_Interp *interp;

extern int matherr();

int *tclDummyMathPtr = (int *) matherr;

struct invoke_queue {
    Tcl_Event ev;
    int argc;
#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **argv;
#else /* TCL_MAJOR_VERSION < 8 */
    char **argv;
#endif
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

struct eval_queue {
    Tcl_Event ev;
    char *str;
    int len;
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

struct call_queue {
    Tcl_Event ev;
    VALUE (*func)();
    int argc;
    VALUE *argv;
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

static Tcl_Interp *eventloop_interp;

static int   window_event_mode = ~0;

Tcl_Interp  *current_interp;

static int have_rb_thread_waiting_for_value = 0;

static int event_loop_max = DEFAULT_EVENT_LOOP_MAX;

static int no_event_tick  = DEFAULT_NO_EVENT_TICK;

static int no_event_wait  = DEFAULT_NO_EVENT_WAIT;

static int timer_tick     = DEFAULT_TIMER_TICK;

static int req_timer_tick = DEFAULT_TIMER_TICK;

static int run_timer_flag = 0;

static int event_loop_wait_event   = 0;

static int event_loop_abort_on_exc = 1;

static int loop_counter = 0;

static int check_rootwidget_flag = 0;

static int ip_ruby_eval

static int ip_ruby_cmd

struct DummyTclIntStubs_for_GetCurrentNamespace {
    int magic;
    struct TclIntStubHooks *hooks;
    void (*func[FunctionNum_of_GetCurrentNamespace])();
    Tcl_Namespace * (*tcl_GetCurrentNamespace) _((Tcl_Interp *));
}

#define ip_null_namespace(interp) (0)


ip_invoke_with_position(argc, argv, obj, position)
    int argc;
    VALUE *argv;
    VALUE obj;
    Tcl_QueuePosition position;
{
    struct invoke_queue *ivq;
#ifdef RUBY_USE_NATIVE_THREAD
    struct tcltkip *ptr;
#endif
    int  *alloc_done;
    int  thr_crit_bup;
    volatile VALUE current = rb_thread_current();
    volatile VALUE ip_obj = obj;
    volatile VALUE result;
    volatile VALUE ret;
    struct timeval t;

#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **av = (Tcl_Obj **)NULL;
#else /* TCL_MAJOR_VERSION < 8 */
    char **av = (char **)NULL;
#endif

    if (argc < 1) {
        rb_raise(rb_eArgError, "command name missing");
    }

#ifdef RUBY_USE_NATIVE_THREAD
    ptr = get_ip(ip_obj);
    DUMP2("invoke status: ptr->tk_thread_id %p", ptr->tk_thread_id);
    DUMP2("invoke status: Tcl_GetCurrentThread %p", Tcl_GetCurrentThread());
#else
    DUMP2("status: Tcl_GetCurrentThread %p", Tcl_GetCurrentThread());
#endif
    DUMP2("status: eventloopt_thread %"PRIxVALUE, eventloop_thread);

    if (
#ifdef RUBY_USE_NATIVE_THREAD
	(ptr->tk_thread_id == 0 || ptr->tk_thread_id == Tcl_GetCurrentThread())
	&&
#endif
	(NIL_P(eventloop_thread) || current == eventloop_thread)
	) {
        if (NIL_P(eventloop_thread)) {
            DUMP2("invoke from thread:%"PRIxVALUE" but no eventloop", current);
        } else {
            DUMP2("invoke from current eventloop %"PRIxVALUE, current);
        }
        result = ip_invoke_real(argc, argv, ip_obj);
        if (rb_obj_is_kind_of(result, rb_eException)) {
            rb_exc_raise(result);
        }
        return result;
    }

    DUMP2("invoke from thread %"PRIxVALUE" (NOT current eventloop)", current);

    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;

    /* allocate memory (for arguments) */
    av = alloc_invoke_arguments(argc, argv);

    /* allocate memory (keep result) */
    /* alloc_done = (int*)ALLOC(int); */
    alloc_done = RbTk_ALLOC_N(int, 1);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)alloc_done); /* XXXXXXXX */
#endif
    *alloc_done = 0;

    /* allocate memory (freed by Tcl_ServiceEvent) */
    /* ivq = (struct invoke_queue *)Tcl_Alloc(sizeof(struct invoke_queue)); */
    ivq = RbTk_ALLOC_N(struct invoke_queue, 1);
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Preserve((ClientData)ivq); /* XXXXXXXX */
#endif

    /* allocate result obj */
    result = rb_ary_new3(1, Qnil);

    /* construct event data */
    ivq->done = alloc_done;
    ivq->argc = argc;
    ivq->argv = av;
    ivq->interp = ip_obj;
    ivq->result = result;
    ivq->thread = current;
    ivq->safe_level = rb_safe_level();
    ivq->ev.proc = invoke_queue_handler;

    /* add the handler to Tcl event queue */
    DUMP1("add handler");
#ifdef RUBY_USE_NATIVE_THREAD
    if (ptr->tk_thread_id) {
      /* Tcl_ThreadQueueEvent(ptr->tk_thread_id, &(ivq->ev), position); */
      Tcl_ThreadQueueEvent(ptr->tk_thread_id, (Tcl_Event*)ivq, position);
      Tcl_ThreadAlert(ptr->tk_thread_id);
    } else if (tk_eventloop_thread_id) {
      /* Tcl_ThreadQueueEvent(tk_eventloop_thread_id,
			   &(ivq->ev), position); */
      Tcl_ThreadQueueEvent(tk_eventloop_thread_id,
			   (Tcl_Event*)ivq, position);
      Tcl_ThreadAlert(tk_eventloop_thread_id);
    } else {
      /* Tcl_QueueEvent(&(ivq->ev), position); */
      Tcl_QueueEvent((Tcl_Event*)ivq, position);
    }
#else
    /* Tcl_QueueEvent(&(ivq->ev), position); */
    Tcl_QueueEvent((Tcl_Event*)ivq, position);
#endif

    rb_thread_critical = thr_crit_bup;

    /* wait for the handler to be processed */
    t.tv_sec  = 0;
    t.tv_usec = (long)((EVENT_HANDLER_TIMEOUT)*1000.0);

    DUMP2("ivq wait for handler (current thread:%"PRIxVALUE")", current);
    while(*alloc_done >= 0) {
      /* rb_thread_stop(); */
      /* rb_thread_sleep_forever(); */
      rb_thread_wait_for(t);
      DUMP2("*** ivq wakeup (current thread:%"PRIxVALUE")", current);
      DUMP2("***          (eventloop thread:%"PRIxVALUE")", eventloop_thread);
      if (NIL_P(eventloop_thread)) {
	DUMP1("*** ivq lost eventloop thread");
	break;
      }
    }
    DUMP2("back from handler (current thread:%"PRIxVALUE")", current);

    /* get result & free allocated memory */
    ret = RARRAY_PTR(result)[0];
#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)alloc_done, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release((ClientData)alloc_done); /* XXXXXXXX */
#else
    /* free(alloc_done); */
    ckfree((char*)alloc_done);
#endif
#endif

#if 0 /* ivq is freed by Tcl_ServiceEvent */
#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)ivq, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
    Tcl_Release(ivq);
#else
    ckfree((char*)ivq);
#endif
#endif
#endif

    /* free allocated memory */
    free_invoke_arguments(argc, av);

    /* exception? */
    if (rb_obj_is_kind_of(ret, rb_eException)) {
        DUMP1("raise exception");
        /* rb_exc_raise(ret); */
	rb_exc_raise(rb_exc_new3(rb_obj_class(ret),
				 rb_funcallv(ret, ID_to_s, 0, 0)));
    }

    DUMP1("exit ip_invoke");
    return ret;
}

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RSTRING_PTR(s) (RSTRING(s)->ptr)

#define RSTRING_LENINT(s) ((int)RSTRING_LEN(s))

#define RARRAY_PTR(s) (RARRAY(s)->ptr)

#define RbTk_OBJ_UNTRUST(x)  do {OBJ_TAINT(x); OBJ_UNTRUST(x);} while (0)

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

extern VALUE rb_proc_new

#define RUBY_USE_NATIVE_THREAD 1

VALUE rb_errinfo(void);

#define rb_safe_level() (ruby_safe_level+0)

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

#define DUMP1(ARG1) if (ruby_debug) { fprintf(stderr, "tcltklib: %s\n", ARG1); fflush(stderr); }

#define DUMP2(ARG1, ARG2) if (ruby_debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2); fprintf(stderr, "\n"); fflush(stderr); }

static const char tcltklib_release_date[] = TCLTKLIB_RELEASE_DATE;

static const char finalize_hook_name[] = "INTERP_FINALIZE_HOOK";

static int at_exit = 0;

static VALUE cRubyEncoding;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static VALUE ENCODING_NAME_UTF8;

static VALUE ENCODING_NAME_BINARY;

static VALUE create_dummy_encoding_for_tk_core

static VALUE create_dummy_encoding_for_tk

static int update_encoding_table

static VALUE encoding_table_get_name_core

static VALUE encoding_table_get_obj_core

static VALUE encoding_table_get_name

static VALUE encoding_table_get_obj

static VALUE create_encoding_table

static VALUE ip_get_encoding_table

static VALUE eTkCallbackReturn;

static VALUE eTkCallbackBreak;

static VALUE eTkCallbackContinue;

static VALUE eLocalJumpError;

static VALUE eTkLocalJumpError;

static VALUE eTkCallbackRetry;

static VALUE eTkCallbackRedo;

static VALUE eTkCallbackThrow;

static VALUE tcltkip_class;

static ID ID_to_s;

static VALUE ip_invoke_real

_((int, VALUE*, VALUE));

static VALUE ip_invoke

static VALUE ip_invoke_with_position

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static VALUE tk_funcall

static VALUE callq_safelevel_handler

static const char Tcl_ObjTypeName_ByteArray[] = "bytearray";

static const char Tcl_ObjTypeName_String[]    = "string";

#define IS_TCL_BYTEARRAY(obj)    ((obj)->typePtr == Tcl_ObjType_ByteArray)

#define IS_TCL_STRING(obj)       ((obj)->typePtr == Tcl_ObjType_String)

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

#define rb_thread_alive_p(thread) rb_funcall2((thread), ID_alive_p, 0, NULL)

Tcl_Interp *interp;

const char *cmd;

#define Tcl_IncrRefCount(obj) (1)

#define Tcl_DecrRefCount(obj) (1)

#define Tcl_GetStringResult(interp) ((interp)->result)

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

CONST char *name1;

CONST char *name2;

int flags;

static Tcl_Obj *
Tcl_SetVar2Ex(interp, name1, name2, newValObj, flags)
    Tcl_Interp *interp;

Tcl_Obj *newValObj;

extern int matherr();

int *tclDummyMathPtr = (int *) matherr;


tcltklib_compile_info(void)
{
    volatile VALUE ret;
    size_t size;
    static CONST char form[]
      = "tcltklib %s :: Ruby%s (%s) %s pthread :: Tcl%s(%s)/Tk%s(%s) %s";
    char *info;

    size = strlen(form)
        + strlen(TCLTKLIB_RELEASE_DATE)
        + strlen(RUBY_VERSION)
        + strlen(RUBY_RELEASE_DATE)
        + strlen("without")
        + strlen(TCL_PATCH_LEVEL)
        + strlen("without stub")
        + strlen(TK_PATCH_LEVEL)
        + strlen("without stub")
        + strlen("unknown tcl_threads");

    info = ALLOC_N(char, size);
    /* info = ckalloc(sizeof(char) * size); */ /* SEGV */

    sprintf(info, form,
            TCLTKLIB_RELEASE_DATE,
            RUBY_VERSION, RUBY_RELEASE_DATE,
#ifdef HAVE_NATIVETHREAD
            "with",
#else
            "without",
#endif
            TCL_PATCH_LEVEL,
#ifdef USE_TCL_STUBS
            "with stub",
#else
            "without stub",
#endif
            TK_PATCH_LEVEL,
#ifdef USE_TK_STUBS
            "with stub",
#else
            "without stub",
#endif
#ifdef WITH_TCL_ENABLE_THREAD
# if WITH_TCL_ENABLE_THREAD
            "with tcl_threads"
# else
            "without tcl_threads"
# endif
#else
            "unknown tcl_threads"
#endif
        );

    ret = rb_obj_freeze(rb_str_new2(info));

    xfree(info);
    /* ckfree(info); */

    return ret;
}

#define TCLTKLIB_RELEASE_DATE "2010-08-25"

#define RUBY_VERSION "(unknown version)"

#define RUBY_RELEASE_DATE "unknown release-date"

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

extern VALUE rb_proc_new

VALUE rb_errinfo(void);

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

#    define CONST84 CONST

#  define CONST84 CONST

#  define CONST

#define DUMP1(ARG1) if (ruby_debug) { fprintf(stderr, "tcltklib: %s\n", ARG1); fflush(stderr); }

#define DUMP2(ARG1, ARG2) if (ruby_debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2); fprintf(stderr, "\n"); fflush(stderr); }

#define DUMP3(ARG1, ARG2, ARG3) if (ruby_debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2, ARG3); fprintf(stderr, "\n"); fflush(stderr); }

static const char tcltklib_release_date[] = TCLTKLIB_RELEASE_DATE;

static const char finalize_hook_name[] = "INTERP_FINALIZE_HOOK";

static void ip_finalize

static void ip_free

static int at_exit = 0;

static VALUE cRubyEncoding;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static VALUE ENCODING_NAME_UTF8;

static VALUE ENCODING_NAME_BINARY;

static VALUE create_dummy_encoding_for_tk_core

static VALUE create_dummy_encoding_for_tk

static int update_encoding_table

static VALUE encoding_table_get_name_core

static VALUE encoding_table_get_obj_core

static VALUE encoding_table_get_name

static VALUE encoding_table_get_obj

static VALUE create_encoding_table

static VALUE ip_get_encoding_table

static VALUE eTkCallbackReturn;

static VALUE eTkCallbackBreak;

static VALUE eTkCallbackContinue;

static VALUE eLocalJumpError;

static VALUE eTkLocalJumpError;

static VALUE eTkCallbackRetry;

static VALUE eTkCallbackRedo;

static VALUE eTkCallbackThrow;

static VALUE tcltkip_class;

static ID ID_at_enc;

static ID ID_at_interp;

static ID ID_encoding_name;

static ID ID_encoding_table;

static ID ID_stop_p;

static ID ID_alive_p;

static ID ID_kill;

static ID ID_join;

static ID ID_value;

static ID ID_call;

static ID ID_backtrace;

static ID ID_message;

static ID ID_at_reason;

static ID ID_return;

static ID ID_break;

static ID ID_next;

static ID ID_to_s;

static ID ID_inspect;

static VALUE ip_invoke_real

_((int, VALUE*, VALUE));

static VALUE ip_invoke

static VALUE ip_invoke_with_position

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static VALUE tk_funcall

static VALUE callq_safelevel_handler

static const char Tcl_ObjTypeName_ByteArray[] = "bytearray";

static CONST86 Tcl_ObjType

static const char Tcl_ObjTypeName_String[]    = "string";

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

const char *cmd;

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

CONST char *name1;

CONST char *name2;

static Tcl_Obj *
Tcl_SetVar2Ex(interp, name1, name2, newValObj, flags)
    Tcl_Interp *interp;


ip_invoke_core(interp, argc, argv)
    VALUE interp;
    int argc;
    char **argv;
#endif
{
    struct tcltkip *ptr;
    Tcl_CmdInfo info;
    char *cmd;
    int  len;
    int  thr_crit_bup;
    int unknown_flag = 0;

#if 1 /* wrap tcl-proc call */
    struct invoke_info inf;
    int status;
#else
#if TCL_MAJOR_VERSION >= 8
    int argc = objc;
    char **argv = (char **)NULL;
    /* Tcl_Obj *resultPtr; */
#endif
#endif

    /* get the command name string */
#if TCL_MAJOR_VERSION >= 8
    cmd = Tcl_GetStringFromObj(objv[0], &len);
#else /* TCL_MAJOR_VERSION < 8 */
    cmd = argv[0];
#endif

    /* get the data struct */
    ptr = get_ip(interp);

    /* ip is deleted? */
    if (deleted_ip(ptr)) {
        return rb_tainted_str_new2("");
    }

    /* Tcl_Preserve(ptr->ip); */
    rbtk_preserve_ip(ptr);

    /* map from the command name to a C procedure */
    DUMP2("call Tcl_GetCommandInfo, %s", cmd);
    if (!Tcl_GetCommandInfo(ptr->ip, cmd, &info)) {
        DUMP1("error Tcl_GetCommandInfo");
        DUMP1("try auto_load (call 'unknown' command)");
        if (!Tcl_GetCommandInfo(ptr->ip,
#if TCL_MAJOR_VERSION >= 8
                                "::unknown",
#else
                                "unknown",
#endif
                                &info)) {
            DUMP1("fail to get 'unknown' command");
            /* if (event_loop_abort_on_exc || cmd[0] != '.') { */
            if (event_loop_abort_on_exc > 0) {
                /* Tcl_Release(ptr->ip); */
                rbtk_release_ip(ptr);
                /*rb_ip_raise(obj,rb_eNameError,"invalid command name `%s'",cmd);*/
                return create_ip_exc(interp, rb_eNameError,
                                     "invalid command name `%s'", cmd);
            } else {
                if (event_loop_abort_on_exc < 0) {
                    rb_warning("invalid command name `%s' (ignore)", cmd);
                } else {
                    rb_warn("invalid command name `%s' (ignore)", cmd);
                }
                Tcl_ResetResult(ptr->ip);
                /* Tcl_Release(ptr->ip); */
                rbtk_release_ip(ptr);
                return rb_tainted_str_new2("");
            }
        } else {
#if TCL_MAJOR_VERSION >= 8
            Tcl_Obj **unknown_objv;
#else
            char **unknown_argv;
#endif
            DUMP1("find 'unknown' command -> set arguemnts");
            unknown_flag = 1;

#if TCL_MAJOR_VERSION >= 8
            /* unknown_objv = (Tcl_Obj **)ALLOC_N(Tcl_Obj *, objc+2); */
            unknown_objv = RbTk_ALLOC_N(Tcl_Obj *, (objc+2));
#if 0 /* use Tcl_Preserve/Release */
	    Tcl_Preserve((ClientData)unknown_objv); /* XXXXXXXX */
#endif
            unknown_objv[0] = Tcl_NewStringObj("::unknown", 9);
            Tcl_IncrRefCount(unknown_objv[0]);
            memcpy(unknown_objv + 1, objv, sizeof(Tcl_Obj *)*objc);
            unknown_objv[++objc] = (Tcl_Obj*)NULL;
            objv = unknown_objv;
#else
            /* unknown_argv = (char **)ALLOC_N(char *, argc+2); */
            unknown_argv = RbTk_ALLOC_N(char *, (argc+2));
#if 0 /* use Tcl_Preserve/Release */
	    Tcl_Preserve((ClientData)unknown_argv); /* XXXXXXXX */
#endif
            unknown_argv[0] = strdup("unknown");
            memcpy(unknown_argv + 1, argv, sizeof(char *)*argc);
            unknown_argv[++argc] = (char *)NULL;
            argv = unknown_argv;
#endif
        }
    }
    DUMP1("end Tcl_GetCommandInfo");

    thr_crit_bup = rb_thread_critical;
    rb_thread_critical = Qtrue;

#if 1 /* wrap tcl-proc call */
    /* setup params */
    inf.ptr = ptr;
    inf.cmdinfo = info;
#if TCL_MAJOR_VERSION >= 8
    inf.objc = objc;
    inf.objv = objv;
#else
    inf.argc = argc;
    inf.argv = argv;
#endif

    /* invoke tcl-proc */
    DUMP1("invoke tcl-proc");
    rb_protect(invoke_tcl_proc, (VALUE)&inf, &status);
    DUMP2("status of tcl-proc, %d", status);
    switch(status) {
    case TAG_RAISE:
        if (NIL_P(rb_errinfo())) {
            rbtk_pending_exception = rb_exc_new2(rb_eException,
                                                 "unknown exception");
        } else {
            rbtk_pending_exception = rb_errinfo();
        }
        break;

    case TAG_FATAL:
        if (NIL_P(rb_errinfo())) {
            rbtk_pending_exception = rb_exc_new2(rb_eFatal, "FATAL");
        } else {
            rbtk_pending_exception = rb_errinfo();
        }
    }

#else /* !wrap tcl-proc call */

    /* memory allocation for arguments of this command */
#if TCL_MAJOR_VERSION >= 8
    if (!info.isNativeObjectProc) {
        int i;

        /* string interface */
        /* argv = (char **)ALLOC_N(char *, argc+1); */
        argv = RbTk_ALLOC_N(char *, (argc+1));
#if 0 /* use Tcl_Preserve/Release */
	Tcl_Preserve((ClientData)argv); /* XXXXXXXX */
#endif
        for (i = 0; i < argc; ++i) {
            argv[i] = Tcl_GetStringFromObj(objv[i], &len);
        }
        argv[argc] = (char *)NULL;
    }
#endif

    Tcl_ResetResult(ptr->ip);

    /* Invoke the C procedure */
#if TCL_MAJOR_VERSION >= 8
    if (info.isNativeObjectProc) {
        ptr->return_value = (*info.objProc)(info.objClientData, ptr->ip,
                                            objc, objv);
#if 0
        /* get the string value from the result object */
        resultPtr = Tcl_GetObjResult(ptr->ip);
        Tcl_SetResult(ptr->ip, Tcl_GetStringFromObj(resultPtr, &len),
                      TCL_VOLATILE);
#endif
    }
    else
#endif
    {
#if TCL_MAJOR_VERSION >= 8
        ptr->return_value = (*info.proc)(info.clientData, ptr->ip,
                                         argc, (CONST84 char **)argv);

#if 0 /* use Tcl_EventuallyFree */
    Tcl_EventuallyFree((ClientData)argv, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
	Tcl_Release((ClientData)argv); /* XXXXXXXX */
#else
        /* free(argv); */
        ckfree((char*)argv);
#endif
#endif

#else /* TCL_MAJOR_VERSION < 8 */
        ptr->return_value = (*info.proc)(info.clientData, ptr->ip,
                                         argc, argv);
#endif
    }
#endif /* ! wrap tcl-proc call */

    /* free allocated memory for calling 'unknown' command */
    if (unknown_flag) {
#if TCL_MAJOR_VERSION >= 8
        Tcl_DecrRefCount(objv[0]);
#if 0 /* use Tcl_EventuallyFree */
	Tcl_EventuallyFree((ClientData)objv, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
	Tcl_Release((ClientData)objv); /* XXXXXXXX */
#else
        /* free(objv); */
        ckfree((char*)objv);
#endif
#endif
#else /* TCL_MAJOR_VERSION < 8 */
        free(argv[0]);
        /* ckfree(argv[0]); */
#if 0 /* use Tcl_EventuallyFree */
	Tcl_EventuallyFree((ClientData)argv, TCL_DYNAMIC); /* XXXXXXXX */
#else
#if 0 /* use Tcl_Preserve/Release */
	Tcl_Release((ClientData)argv); /* XXXXXXXX */
#else
        /* free(argv); */
        ckfree((char*)argv);
#endif
#endif
#endif
    }

    /* exception on mainloop */
    if (pending_exception_check1(thr_crit_bup, ptr)) {
        return rbtk_pending_exception;
    }

    rb_thread_critical = thr_crit_bup;

    /* if (ptr->return_value == TCL_ERROR) { */
    if (ptr->return_value != TCL_OK) {
        if (event_loop_abort_on_exc > 0 && !Tcl_InterpDeleted(ptr->ip)) {
	    switch (ptr->return_value) {
	    case TCL_RETURN:
	      return create_ip_exc(interp, eTkCallbackReturn,
				   "ip_invoke_core receives TCL_RETURN");
	    case TCL_BREAK:
	      return create_ip_exc(interp, eTkCallbackBreak,
				   "ip_invoke_core receives TCL_BREAK");
	    case TCL_CONTINUE:
	      return create_ip_exc(interp, eTkCallbackContinue,
				   "ip_invoke_core receives TCL_CONTINUE");
	    default:
	      return create_ip_exc(interp, rb_eRuntimeError, "%s",
				   Tcl_GetStringResult(ptr->ip));
	    }

        } else {
            if (event_loop_abort_on_exc < 0) {
                rb_warning("%s (ignore)", Tcl_GetStringResult(ptr->ip));
            } else {
                rb_warn("%s (ignore)", Tcl_GetStringResult(ptr->ip));
            }
            Tcl_ResetResult(ptr->ip);
            return rb_tainted_str_new2("");
        }
    }

    /* pass back the result (as string) */
    return ip_get_result_string_obj(ptr->ip);
}

#define RUBY_VERSION "(unknown version)"

#define RUBY_RELEASE_DATE "unknown release-date"

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RSTRING_PTR(s) (RSTRING(s)->ptr)

#define RSTRING_LEN(s) (RSTRING(s)->len)

#define RSTRING_LENINT(s) ((int)RSTRING_LEN(s))

#define RARRAY_PTR(s) (RARRAY(s)->ptr)

#define RARRAY_LEN(s) (RARRAY(s)->len)

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

extern VALUE rb_proc_new

#define rb_errinfo() (ruby_errinfo+0) /* cannot be l-value */

VALUE rb_errinfo(void);

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

#   define CONST84

#    define CONST84 CONST

#    define CONST84

#  define CONST84 CONST

#  define CONST84

#  define CONST86 CONST84

#define TAG_RAISE       0x6

#define TAG_FATAL       0x8

#define DUMP1(ARG1) if (ruby_debug) { fprintf(stderr, "tcltklib: %s\n", ARG1); fflush(stderr); }

#define DUMP2(ARG1, ARG2) if (ruby_debug) { fprintf(stderr, "tcltklib: ");\
fprintf(stderr, ARG1, ARG2); fprintf(stderr, "\n"); fflush(stderr); }

static const char tcltklib_release_date[] = TCLTKLIB_RELEASE_DATE;

static const char finalize_hook_name[] = "INTERP_FINALIZE_HOOK";

static int at_exit = 0;

static VALUE cRubyEncoding;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static VALUE ENCODING_NAME_UTF8;

static VALUE ENCODING_NAME_BINARY;

static VALUE create_dummy_encoding_for_tk_core

static VALUE create_dummy_encoding_for_tk

static int update_encoding_table

static VALUE encoding_table_get_name_core

static VALUE encoding_table_get_obj_core

static VALUE encoding_table_get_name

static VALUE encoding_table_get_obj

static VALUE create_encoding_table

static VALUE ip_get_encoding_table

static VALUE eTkCallbackReturn;

static VALUE eTkCallbackBreak;

static VALUE eTkCallbackContinue;

static VALUE eLocalJumpError;

static VALUE eTkLocalJumpError;

static VALUE eTkCallbackRetry;

static VALUE eTkCallbackRedo;

static VALUE eTkCallbackThrow;

static VALUE tcltkip_class;

static VALUE ip_invoke_real

_((int, VALUE*, VALUE));

static VALUE ip_invoke

static VALUE ip_invoke_with_position

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static VALUE tk_funcall

static VALUE callq_safelevel_handler

static const char Tcl_ObjTypeName_ByteArray[] = "bytearray";

static const char Tcl_ObjTypeName_String[]    = "string";

#define IS_TCL_BYTEARRAY(obj)    ((obj)->typePtr == Tcl_ObjType_ByteArray)

#define IS_TCL_STRING(obj)       ((obj)->typePtr == Tcl_ObjType_String)

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

#define rb_thread_alive_p(thread) rb_funcall2((thread), ID_alive_p, 0, NULL)

Tcl_Interp *interp;

const char *cmd;

#define Tcl_IncrRefCount(obj) (1)

#define Tcl_DecrRefCount(obj) (1)

#define Tcl_GetStringResult(interp) ((interp)->result)

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

CONST char *name1;

CONST char *name2;

int flags;


lib_do_one_event_core(argc, argv, self, is_ip)
    int   argc;
    VALUE *argv;
    VALUE self;
    int   is_ip;
{
    volatile VALUE vflags;
    int flags;
    int found_event;

    if (!NIL_P(eventloop_thread)) {
        rb_raise(rb_eRuntimeError, "eventloop is already running");
    }

    tcl_stubs_check();

    if (rb_scan_args(argc, argv, "01", &vflags) == 0) {
        flags = TCL_ALL_EVENTS | TCL_DONT_WAIT;
    } else {
        Check_Type(vflags, T_FIXNUM);
        flags = FIX2INT(vflags);
    }

    if (rb_safe_level() >=1 && OBJ_TAINTED(vflags)) {
      flags |= TCL_DONT_WAIT;
    }

    if (is_ip) {
        /* check IP */
        struct tcltkip *ptr = get_ip(self);

        /* ip is deleted? */
        if (deleted_ip(ptr)) {
            return Qfalse;
        }

        if (Tcl_GetMaster(ptr->ip) != (Tcl_Interp*)NULL) {
            /* slave IP */
            flags |= TCL_DONT_WAIT;
        }
    }

    /* found_event = Tcl_DoOneEvent(TCL_ALL_EVENTS | TCL_DONT_WAIT); */
    found_event = Tcl_DoOneEvent(flags);

    if (pending_exception_check0()) {
        return Qfalse;
    }

    if (found_event) {
        return Qtrue;
    } else {
        return Qfalse;
    }
}

static int rb_thread_critical;

int rb_thread_check_trap_pending(void);

#define RSTRING_PTR(s) (RSTRING(s)->ptr)

#define RSTRING_LENINT(s) ((int)RSTRING_LEN(s))

#define RARRAY_PTR(s) (RARRAY(s)->ptr)

#define RbTk_ALLOC_N(type, n) (type *)ckalloc((int)(sizeof(type) * (n)))

extern VALUE rb_proc_new

VALUE rb_errinfo(void);

#define rb_safe_level() (ruby_safe_level+0)

static struct {
  int major;
  int minor;
  int type;  /* ALPHA==0, BETA==1, FINAL==2 */
  int patchlevel;
} tcltk_version = {0, 0, 0, 0};

static int at_exit = 0;

static VALUE cRubyEncoding;

static int ENCODING_INDEX_UTF8;

static int ENCODING_INDEX_BINARY;

static VALUE ENCODING_NAME_UTF8;

static VALUE ENCODING_NAME_BINARY;

static VALUE create_dummy_encoding_for_tk_core

static VALUE create_dummy_encoding_for_tk

static int update_encoding_table

static VALUE encoding_table_get_name_core

static VALUE encoding_table_get_obj_core

static VALUE encoding_table_get_name

static VALUE encoding_table_get_obj

static VALUE create_encoding_table

static VALUE ip_get_encoding_table

static VALUE eTkCallbackReturn;

static VALUE eTkCallbackBreak;

static VALUE eTkCallbackContinue;

static VALUE eLocalJumpError;

static VALUE eTkLocalJumpError;

static VALUE eTkCallbackRetry;

static VALUE eTkCallbackRedo;

static VALUE eTkCallbackThrow;

static VALUE tcltkip_class;

static VALUE ip_invoke_real

_((int, VALUE*, VALUE));

static VALUE ip_invoke

static VALUE ip_invoke_with_position

_((int, VALUE*, VALUE, Tcl_QueuePosition));

static VALUE tk_funcall

static VALUE callq_safelevel_handler

#define IS_TCL_VALID_STRING(obj) ((obj)->bytes != (char*)NULL)

#define rb_thread_alive_p(thread) rb_funcall2((thread), ID_alive_p, 0, NULL)

Tcl_Interp *interp;

static Tcl_Obj *
Tcl_GetVar2Ex(interp, name1, name2, flags)
    Tcl_Interp *interp;

int flags;

static Tcl_Obj *
Tcl_SetVar2Ex(interp, name1, name2, newValObj, flags)
    Tcl_Interp *interp;

extern int matherr();

int *tclDummyMathPtr = (int *) matherr;

struct invoke_queue {
    Tcl_Event ev;
    int argc;
#if TCL_MAJOR_VERSION >= 8
    Tcl_Obj **argv;
#else /* TCL_MAJOR_VERSION < 8 */
    char **argv;
#endif
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

struct eval_queue {
    Tcl_Event ev;
    char *str;
    int len;
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

struct call_queue {
    Tcl_Event ev;
    VALUE (*func)();
    int argc;
    VALUE *argv;
    VALUE interp;
    int *done;
    int safe_level;
    VALUE result;
    VALUE thread;
}

static VALUE eventloop_thread;

static Tcl_Interp *eventloop_interp;

static VALUE eventloop_stack;

static int   window_event_mode = ~0;

static VALUE watchdog_thread;

Tcl_Interp  *current_interp;

static int have_rb_thread_waiting_for_value = 0;

static int event_loop_max = DEFAULT_EVENT_LOOP_MAX;

static int no_event_tick  = DEFAULT_NO_EVENT_TICK;

static int no_event_wait  = DEFAULT_NO_EVENT_WAIT;

static int timer_tick     = DEFAULT_TIMER_TICK;

static int req_timer_tick = DEFAULT_TIMER_TICK;

static int run_timer_flag = 0;

static int event_loop_wait_event   = 0;

static int event_loop_abort_on_exc = 1;


static BOOL update_gdi_glyph_index(rdpContext* context, GLYPH_INDEX_ORDER* glyphIndex)
{
	INT32 bkWidth = 0, bkHeight = 0, opWidth = 0, opHeight = 0;

	if (!context || !glyphIndex || !context->cache)
		return FALSE;

	if (glyphIndex->bkRight > glyphIndex->bkLeft)
		bkWidth = glyphIndex->bkRight - glyphIndex->bkLeft + 1;

	if (glyphIndex->opRight > glyphIndex->opLeft)
		opWidth = glyphIndex->opRight - glyphIndex->opLeft + 1;

	if (glyphIndex->bkBottom > glyphIndex->bkTop)
		bkHeight = glyphIndex->bkBottom - glyphIndex->bkTop + 1;

	if (glyphIndex->opBottom > glyphIndex->opTop)
		opHeight = glyphIndex->opBottom - glyphIndex->opTop + 1;

	return update_process_glyph_fragments(
	    context, glyphIndex->data, glyphIndex->cbData, glyphIndex->cacheId, glyphIndex->ulCharInc,
	    glyphIndex->flAccel, glyphIndex->backColor, glyphIndex->foreColor, glyphIndex->x,
	    glyphIndex->y, glyphIndex->bkLeft, glyphIndex->bkTop, bkWidth, bkHeight, glyphIndex->opLeft,
	    glyphIndex->opTop, opWidth, opHeight, glyphIndex->fOpRedundant);
}

Fixed Out-of-bound read in glyph_cache_put

CVE-2020-11098 thanks to @antonio-morales for finding this.

#define TAG FREERDP_TAG("cache.glyph")

static rdpGlyph* glyph_cache_get(rdpGlyphCache* glyph_cache, UINT32 id, UINT32 index);

static BOOL glyph_cache_put(rdpGlyphCache* glyph_cache, UINT32 id, UINT32 index, rdpGlyph* entry);

static const void* glyph_cache_fragment_get(rdpGlyphCache* glyph, UINT32 index, UINT32* count);

static BOOL glyph_cache_fragment_put(rdpGlyphCache* glyph, UINT32 index, UINT32 count,
                                     const void* entry);


static BOOL update_gdi_cache_glyph(rdpContext* context, const CACHE_GLYPH_ORDER* cacheGlyph)
{
	UINT32 i;
	rdpCache* cache;

	if (!context || !cacheGlyph || !context->cache)
		return FALSE;

	cache = context->cache;

	for (i = 0; i < cacheGlyph->cGlyphs; i++)
	{
		const GLYPH_DATA* glyph_data = &cacheGlyph->glyphData[i];
		rdpGlyph* glyph;

		if (!glyph_data)
			return FALSE;

		if (!(glyph = Glyph_Alloc(context, glyph_data->x, glyph_data->y, glyph_data->cx,
		                          glyph_data->cy, glyph_data->cb, glyph_data->aj)))
			return FALSE;

		if (!glyph_cache_put(cache->glyph, cacheGlyph->cacheId, glyph_data->cacheIndex, glyph))
		{
			glyph->Free(context, glyph);
			return FALSE;
		}
	}

	return TRUE;
}


CACHE_GLYPH_V2_ORDER* copy_cache_glyph_v2_order(rdpContext* context,
                                                const CACHE_GLYPH_V2_ORDER* glyph)
{
	size_t x;
	CACHE_GLYPH_V2_ORDER* dst = calloc(1, sizeof(CACHE_GLYPH_V2_ORDER));

	if (!dst || !glyph)
		goto fail;

	*dst = *glyph;

	for (x = 0; x < glyph->cGlyphs; x++)
	{
		const GLYPH_DATA_V2* src = &glyph->glyphData[x];
		GLYPH_DATA_V2* data = &dst->glyphData[x];

		if (src->aj)
		{
			const size_t size = src->cb;
			data->aj = malloc(size);

			if (!data->aj)
				goto fail;

			memcpy(data->aj, src->aj, size);
		}
	}

	if (glyph->unicodeCharacters)
	{
		if (glyph->cGlyphs == 0)
			goto fail;

		dst->unicodeCharacters = calloc(glyph->cGlyphs, sizeof(WCHAR));

		if (!dst->unicodeCharacters)
			goto fail;

		memcpy(dst->unicodeCharacters, glyph->unicodeCharacters, sizeof(WCHAR) * glyph->cGlyphs);
	}

	return dst;
fail:
	free_cache_glyph_v2_order(context, dst);
	return NULL;
}

#define TAG FREERDP_TAG("cache.glyph")

static const void* glyph_cache_fragment_get(rdpGlyphCache* glyph, UINT32 index, UINT32* count);

static BOOL glyph_cache_fragment_put(rdpGlyphCache* glyph, UINT32 index, UINT32 count,
                                     const void* entry);


static UINT32 update_glyph_offset(const BYTE* data, size_t length, UINT32 index, INT32* x, INT32* y,
                                  UINT32 ulCharInc, UINT32 flAccel)
{
	if ((ulCharInc == 0) && (!(flAccel & SO_CHAR_INC_EQUAL_BM_BASE)))
	{
		UINT32 offset = data[index++];

		if (offset & 0x80)
		{

			if (index + 1 < length)
			{
				offset = data[index++];
				offset |= ((UINT32)data[index++]) << 8;
			}
			else
				WLog_WARN(TAG, "[%s] glyph index out of bound %" PRIu32 " [max %" PRIuz "]", index,
				          length);
		}

		if (flAccel & SO_VERTICAL)
			*y += offset;

		if (flAccel & SO_HORIZONTAL)
			*x += offset;
	}

	return index;
}


void free_cache_glyph_order(rdpContext* context, CACHE_GLYPH_ORDER* glyph)
{
	if (glyph)
	{
		size_t x;

		for (x = 0; x < ARRAYSIZE(glyph->glyphData); x++)
			free(glyph->glyphData[x].aj);

		free(glyph->unicodeCharacters);
	}

	free(glyph);
}


static BOOL update_gdi_fast_glyph(rdpContext* context, const FAST_GLYPH_ORDER* fastGlyph)
{
	INT32 x, y;
	BYTE text_data[4] = { 0 };
	INT32 opLeft, opTop;
	INT32 opRight, opBottom;
	INT32 opWidth = 0, opHeight = 0;
	INT32 bkWidth = 0, bkHeight = 0;
	rdpCache* cache;

	if (!context || !fastGlyph || !context->cache)
		return FALSE;

	cache = context->cache;
	opLeft = fastGlyph->opLeft;
	opTop = fastGlyph->opTop;
	opRight = fastGlyph->opRight;
	opBottom = fastGlyph->opBottom;
	x = fastGlyph->x;
	y = fastGlyph->y;

	if (opBottom == -32768)
	{
		BYTE flags = (BYTE)(opTop & 0x0F);

		if (flags & 0x01)
			opBottom = fastGlyph->bkBottom;

		if (flags & 0x02)
			opRight = fastGlyph->bkRight;

		if (flags & 0x04)
			opTop = fastGlyph->bkTop;

		if (flags & 0x08)
			opLeft = fastGlyph->bkLeft;
	}

	if (opLeft == 0)
		opLeft = fastGlyph->bkLeft;

	if (opRight == 0)
		opRight = fastGlyph->bkRight;

	/* See update_gdi_fast_index opRight comment. */
	if (opRight > (INT64)context->instance->settings->DesktopWidth)
		opRight = (int)context->instance->settings->DesktopWidth;

	if (x == -32768)
		x = fastGlyph->bkLeft;

	if (y == -32768)
		y = fastGlyph->bkTop;

	if ((fastGlyph->cbData > 1) && (fastGlyph->glyphData.aj))
	{
		/* got option font that needs to go into cache */
		rdpGlyph* glyph;
		const GLYPH_DATA_V2* glyphData = &fastGlyph->glyphData;

		glyph = Glyph_Alloc(context, glyphData->x, glyphData->y, glyphData->cx, glyphData->cy,
		                    glyphData->cb, glyphData->aj);

		if (!glyph)
			return FALSE;

		if (!glyph_cache_put(cache->glyph, fastGlyph->cacheId, fastGlyph->data[0], glyph))
		{
			glyph->Free(context, glyph);
			return FALSE;
		}
	}

	text_data[0] = fastGlyph->data[0];
	text_data[1] = 0;

	if (fastGlyph->bkRight > fastGlyph->bkLeft)
		bkWidth = fastGlyph->bkRight - fastGlyph->bkLeft + 1;

	if (fastGlyph->bkBottom > fastGlyph->bkTop)
		bkHeight = fastGlyph->bkBottom - fastGlyph->bkTop + 1;

	if (opRight > opLeft)
		opWidth = opRight - opLeft + 1;

	if (opBottom > opTop)
		opHeight = opBottom - opTop + 1;

	return update_process_glyph_fragments(
	    context, text_data, sizeof(text_data), fastGlyph->cacheId, fastGlyph->ulCharInc,
	    fastGlyph->flAccel, fastGlyph->backColor, fastGlyph->foreColor, x, y, fastGlyph->bkLeft,
	    fastGlyph->bkTop, bkWidth, bkHeight, opLeft, opTop, opWidth, opHeight, FALSE);
}


rdpGlyph* glyph_cache_get(rdpGlyphCache* glyphCache, UINT32 id, UINT32 index)
{
	rdpGlyph* glyph;
	WLog_Print(glyphCache->log, WLOG_DEBUG, "GlyphCacheGet: id: %" PRIu32 " index: %" PRIu32 "", id,
	           index);

	if (id > 9)
	{
		WLog_ERR(TAG, "invalid glyph cache id: %" PRIu32 "", id);
		return NULL;
	}

	if (index > glyphCache->glyphCache[id].number)
	{
		WLog_ERR(TAG, "index %" PRIu32 " out of range for cache id: %" PRIu32 "", index, id);
		return NULL;
	}

	glyph = glyphCache->glyphCache[id].entries[index];

	if (!glyph)
		WLog_ERR(TAG, "no glyph found at cache index: %" PRIu32 " in cache id: %" PRIu32 "", index,
		         id);

	return glyph;
}


void glyph_cache_register_callbacks(rdpUpdate* update)
{
	update->primary->GlyphIndex = update_gdi_glyph_index;
	update->primary->FastIndex = update_gdi_fast_index;
	update->primary->FastGlyph = update_gdi_fast_glyph;
	update->secondary->CacheGlyph = update_gdi_cache_glyph;
	update->secondary->CacheGlyphV2 = update_gdi_cache_glyph_v2;
}

static const void* glyph_cache_fragment_get(rdpGlyphCache* glyph, UINT32 index, UINT32* count);

static BOOL glyph_cache_fragment_put(rdpGlyphCache* glyph, UINT32 index, UINT32 count,
                                     const void* entry);


BOOL glyph_cache_put(rdpGlyphCache* glyphCache, UINT32 id, UINT32 index, rdpGlyph* glyph)
{
	rdpGlyph* prevGlyph;

	if (id > 9)
	{
		WLog_ERR(TAG, "invalid glyph cache id: %" PRIu32 "", id);
		return FALSE;
	}

	if (index >= glyphCache->glyphCache[id].number)
	{
		WLog_ERR(TAG, "invalid glyph cache index: %" PRIu32 " in cache id: %" PRIu32 "", index, id);
		return FALSE;
	}

	WLog_Print(glyphCache->log, WLOG_DEBUG, "GlyphCachePut: id: %" PRIu32 " index: %" PRIu32 "", id,
	           index);
	prevGlyph = glyphCache->glyphCache[id].entries[index];

	if (prevGlyph)
		prevGlyph->Free(glyphCache->context, prevGlyph);

	glyphCache->glyphCache[id].entries[index] = glyph;
	return TRUE;
}


void glyph_cache_free(rdpGlyphCache* glyphCache)
{
	if (glyphCache)
	{
		int i;
		GLYPH_CACHE* cache = glyphCache->glyphCache;

		for (i = 0; i < 10; i++)
		{
			UINT32 j;
			rdpGlyph** entries = cache[i].entries;

			if (!entries)
				continue;

			for (j = 0; j < cache[i].number; j++)
			{
				rdpGlyph* glyph = entries[j];

				if (glyph)
				{
					glyph->Free(glyphCache->context, glyph);
					entries[j] = NULL;
				}
			}

			free(entries);
			cache[i].entries = NULL;
		}

		if (glyphCache->fragCache.entries)
		{
			for (i = 0; i < 256; i++)
			{
				free(glyphCache->fragCache.entries[i].fragment);
				glyphCache->fragCache.entries[i].fragment = NULL;
			}
		}

		free(glyphCache->fragCache.entries);
		free(glyphCache);
	}
}


static BOOL update_gdi_cache_glyph_v2(rdpContext* context, const CACHE_GLYPH_V2_ORDER* cacheGlyphV2)
{
	UINT32 i;
	rdpCache* cache;

	if (!context || !cacheGlyphV2 || !context->cache)
		return FALSE;

	cache = context->cache;

	for (i = 0; i < cacheGlyphV2->cGlyphs; i++)
	{
		const GLYPH_DATA_V2* glyphData = &cacheGlyphV2->glyphData[i];
		rdpGlyph* glyph;

		if (!glyphData)
			return FALSE;

		glyph = Glyph_Alloc(context, glyphData->x, glyphData->y, glyphData->cx, glyphData->cy,
		                    glyphData->cb, glyphData->aj);

		if (!glyph)
			return FALSE;

		if (!glyph_cache_put(cache->glyph, cacheGlyphV2->cacheId, glyphData->cacheIndex, glyph))
		{
			glyph->Free(context, glyph);
			return FALSE;
		}
	}

	return TRUE;
}


void free_cache_glyph_v2_order(rdpContext* context, CACHE_GLYPH_V2_ORDER* glyph)
{
	if (glyph)
	{
		size_t x;

		for (x = 0; x < ARRAYSIZE(glyph->glyphData); x++)
			free(glyph->glyphData[x].aj);

		free(glyph->unicodeCharacters);
	}

	free(glyph);
}


rdpGlyphCache* glyph_cache_new(rdpSettings* settings)
{
	int i;
	rdpGlyphCache* glyphCache;
	glyphCache = (rdpGlyphCache*)calloc(1, sizeof(rdpGlyphCache));

	if (!glyphCache)
		return NULL;

	glyphCache->log = WLog_Get("com.freerdp.cache.glyph");
	glyphCache->settings = settings;
	glyphCache->context = ((freerdp*)settings->instance)->update->context;

	for (i = 0; i < 10; i++)
	{
		glyphCache->glyphCache[i].number = settings->GlyphCache[i].cacheEntries;
		glyphCache->glyphCache[i].maxCellSize = settings->GlyphCache[i].cacheMaximumCellSize;
		glyphCache->glyphCache[i].entries =
		    (rdpGlyph**)calloc(glyphCache->glyphCache[i].number, sizeof(rdpGlyph*));

		if (!glyphCache->glyphCache[i].entries)
			goto fail;
	}

	glyphCache->fragCache.entries = calloc(256, sizeof(FRAGMENT_CACHE_ENTRY));

	if (!glyphCache->fragCache.entries)
		goto fail;

	return glyphCache;
fail:
	glyph_cache_free(glyphCache);
	return NULL;
}


BOOL glyph_cache_fragment_put(rdpGlyphCache* glyphCache, UINT32 index, UINT32 size,
                              const void* fragment)
{
	void* prevFragment;
	void* copy;

	if (index > 255)
	{
		WLog_ERR(TAG, "invalid glyph cache fragment index: %" PRIu32 "", index);
		return FALSE;
	}

	copy = malloc(size);

	if (!copy)
		return FALSE;

	WLog_Print(glyphCache->log, WLOG_DEBUG,
	           "GlyphCacheFragmentPut: index: %" PRIu32 " size: %" PRIu32 "", index, size);
	CopyMemory(copy, fragment, size);
	prevFragment = glyphCache->fragCache.entries[index].fragment;
	glyphCache->fragCache.entries[index].fragment = copy;
	glyphCache->fragCache.entries[index].size = size;
	free(prevFragment);
	return TRUE;
}


CACHE_GLYPH_ORDER* copy_cache_glyph_order(rdpContext* context, const CACHE_GLYPH_ORDER* glyph)
{
	size_t x;
	CACHE_GLYPH_ORDER* dst = calloc(1, sizeof(CACHE_GLYPH_ORDER));

	if (!dst || !glyph)
		goto fail;

	*dst = *glyph;

	for (x = 0; x < glyph->cGlyphs; x++)
	{
		const GLYPH_DATA* src = &glyph->glyphData[x];
		GLYPH_DATA* data = &dst->glyphData[x];

		if (src->aj)
		{
			const size_t size = src->cb;
			data->aj = malloc(size);

			if (!data->aj)
				goto fail;

			memcpy(data->aj, src->aj, size);
		}
	}

	if (glyph->unicodeCharacters)
	{
		if (glyph->cGlyphs == 0)
			goto fail;

		dst->unicodeCharacters = calloc(glyph->cGlyphs, sizeof(WCHAR));

		if (!dst->unicodeCharacters)
			goto fail;

		memcpy(dst->unicodeCharacters, glyph->unicodeCharacters, sizeof(WCHAR) * glyph->cGlyphs);
	}

	return dst;
fail:
	free_cache_glyph_order(context, dst);
	return NULL;
}


const void* glyph_cache_fragment_get(rdpGlyphCache* glyphCache, UINT32 index, UINT32* size)
{
	void* fragment;

	if (index > 255)
	{
		WLog_ERR(TAG, "invalid glyph cache fragment index: %" PRIu32 "", index);
		return NULL;
	}

	fragment = glyphCache->fragCache.entries[index].fragment;
	*size = (BYTE)glyphCache->fragCache.entries[index].size;
	WLog_Print(glyphCache->log, WLOG_DEBUG,
	           "GlyphCacheFragmentGet: index: %" PRIu32 " size: %" PRIu32 "", index, *size);

	if (!fragment)
		WLog_ERR(TAG, "invalid glyph fragment at index:%" PRIu32 "", index);

	return fragment;
}


static BOOL update_process_glyph(rdpContext* context, const BYTE* data, UINT32 cacheIndex, INT32* x,
                                 INT32* y, UINT32 cacheId, UINT32 flAccel, BOOL fOpRedundant,
                                 const RDP_RECT* bound)
{
	INT32 sx = 0, sy = 0;
	INT32 dx, dy;
	rdpGlyph* glyph;
	rdpGlyphCache* glyph_cache;

	if (!context || !data || !x || !y || !context->graphics || !context->cache ||
	    !context->cache->glyph)
		return FALSE;

	glyph_cache = context->cache->glyph;
	glyph = glyph_cache_get(glyph_cache, cacheId, cacheIndex);

	if (!glyph)
		return FALSE;

	dx = glyph->x + *x;
	dy = glyph->y + *y;

	if (dx < bound->x)
	{
		sx = bound->x - dx;
		dx = bound->x;
	}

	if (dy < bound->y)
	{
		sy = bound->y - dy;
		dy = bound->y;
	}

	if ((dx <= (bound->x + bound->width)) && (dy <= (bound->y + bound->height)))
	{
		INT32 dw = glyph->cx - sx;
		INT32 dh = glyph->cy - sy;

		if ((dw + dx) > (bound->x + bound->width))
			dw = (bound->x + bound->width) - (dw + dx);

		if ((dh + dy) > (bound->y + bound->height))
			dh = (bound->y + bound->height) - (dh + dy);

		if ((dh > 0) && (dw > 0))
		{
			if (!glyph->Draw(context, glyph, dx, dy, dw, dh, sx, sy, fOpRedundant))
				return FALSE;
		}
	}

	if (flAccel & SO_CHAR_INC_EQUAL_BM_BASE)
		*x += glyph->cx;

	return TRUE;
}


static BOOL update_process_glyph_fragments(rdpContext* context, const BYTE* data, UINT32 length,
                                           UINT32 cacheId, UINT32 ulCharInc, UINT32 flAccel,
                                           UINT32 bgcolor, UINT32 fgcolor, INT32 x, INT32 y,
                                           INT32 bkX, INT32 bkY, INT32 bkWidth, INT32 bkHeight,
                                           INT32 opX, INT32 opY, INT32 opWidth, INT32 opHeight,
                                           BOOL fOpRedundant)
{
	UINT32 n;
	UINT32 id;
	UINT32 size;
	UINT32 index = 0;
	BYTE* fragments;
	rdpGraphics* graphics;
	rdpGlyphCache* glyph_cache;
	rdpGlyph* glyph;
	RDP_RECT bound;

	if (!context || !data || !context->graphics || !context->cache || !context->cache->glyph)
		return FALSE;

	graphics = context->graphics;
	glyph_cache = context->cache->glyph;
	glyph = graphics->Glyph_Prototype;

	if (!glyph)
		return FALSE;

	/* Limit op rectangle to visible screen. */
	if (opX < 0)
	{
		opWidth += opX;
		opX = 0;
	}

	if (opY < 0)
	{
		opHeight += opY;
		opY = 0;
	}

	if (opWidth < 0)
		opWidth = 0;

	if (opHeight < 0)
		opHeight = 0;

	/* Limit bk rectangle to visible screen. */
	if (bkX < 0)
	{
		bkWidth += bkX;
		bkX = 0;
	}

	if (bkY < 0)
	{
		bkHeight += bkY;
		bkY = 0;
	}

	if (bkWidth < 0)
		bkWidth = 0;

	if (bkHeight < 0)
		bkHeight = 0;

	if (opX + opWidth > (INT64)context->settings->DesktopWidth)
	{
		/**
		 * Some Microsoft servers send erroneous high values close to the
		 * sint16 maximum in the OpRight field of the GlyphIndex, FastIndex and
		 * FastGlyph drawing orders, probably a result of applications trying to
		 * clear the text line to the very right end.
		 * One example where this can be seen is typing in notepad.exe within
		 * a RDP session to Windows XP Professional SP3.
		 * This workaround prevents resulting problems in the UI callbacks.
		 */
		opWidth = context->settings->DesktopWidth - opX;
	}

	if (bkX + bkWidth > (INT64)context->settings->DesktopWidth)
	{
		/**
		 * Some Microsoft servers send erroneous high values close to the
		 * sint16 maximum in the OpRight field of the GlyphIndex, FastIndex and
		 * FastGlyph drawing orders, probably a result of applications trying to
		 * clear the text line to the very right end.
		 * One example where this can be seen is typing in notepad.exe within
		 * a RDP session to Windows XP Professional SP3.
		 * This workaround prevents resulting problems in the UI callbacks.
		 */
		bkWidth = context->settings->DesktopWidth - bkX;
	}

	bound.x = bkX;
	bound.y = bkY;
	bound.width = bkWidth;
	bound.height = bkHeight;

	if (!glyph->BeginDraw(context, opX, opY, opWidth, opHeight, bgcolor, fgcolor, fOpRedundant))
		return FALSE;

	if (!IFCALLRESULT(TRUE, glyph->SetBounds, context, bkX, bkY, bkWidth, bkHeight))
		return FALSE;

	while (index < length)
	{
		const UINT32 op = data[index++];

		switch (op)
		{
			case GLYPH_FRAGMENT_USE:
				if (index + 1 >= length)
					return FALSE;

				id = data[index++];
				fragments = (BYTE*)glyph_cache_fragment_get(glyph_cache, id, &size);

				if (fragments == NULL)
					return FALSE;

				for (n = 0; n < size;)
				{
					const UINT32 fop = fragments[n++];
					n = update_glyph_offset(fragments, size, n, &x, &y, ulCharInc, flAccel);

					if (!update_process_glyph(context, fragments, fop, &x, &y, cacheId, flAccel,
					                          fOpRedundant, &bound))
						return FALSE;
				}

				break;

			case GLYPH_FRAGMENT_ADD:
				if (index + 2 > length)
					return FALSE;

				id = data[index++];
				size = data[index++];
				glyph_cache_fragment_put(glyph_cache, id, size, data);
				break;

			default:
				index = update_glyph_offset(data, length, index, &x, &y, ulCharInc, flAccel);

				if (!update_process_glyph(context, data, op, &x, &y, cacheId, flAccel, fOpRedundant,
				                          &bound))
					return FALSE;

				break;
		}
	}

	return glyph->EndDraw(context, opX, opY, opWidth, opHeight, bgcolor, fgcolor);
}


static BOOL update_gdi_fast_index(rdpContext* context, const FAST_INDEX_ORDER* fastIndex)
{
	INT32 x, y;
	INT32 opLeft, opTop;
	INT32 opRight, opBottom;
	INT32 opWidth = 0, opHeight = 0;
	INT32 bkWidth = 0, bkHeight = 0;

	if (!context || !fastIndex || !context->cache)
		return FALSE;

	opLeft = fastIndex->opLeft;
	opTop = fastIndex->opTop;
	opRight = fastIndex->opRight;
	opBottom = fastIndex->opBottom;
	x = fastIndex->x;
	y = fastIndex->y;

	if (opBottom == -32768)
	{
		BYTE flags = (BYTE)(opTop & 0x0F);

		if (flags & 0x01)
			opBottom = fastIndex->bkBottom;

		if (flags & 0x02)
			opRight = fastIndex->bkRight;

		if (flags & 0x04)
			opTop = fastIndex->bkTop;

		if (flags & 0x08)
			opLeft = fastIndex->bkLeft;
	}

	if (opLeft == 0)
		opLeft = fastIndex->bkLeft;

	if (opRight == 0)
		opRight = fastIndex->bkRight;

	/* Server can send a massive number (32766) which appears to be
	 * undocumented special behavior for "Erase all the way right".
	 * X11 has nondeterministic results asking for a draw that wide. */
	if (opRight > (INT64)context->instance->settings->DesktopWidth)
		opRight = (int)context->instance->settings->DesktopWidth;

	if (x == -32768)
		x = fastIndex->bkLeft;

	if (y == -32768)
		y = fastIndex->bkTop;

	if (fastIndex->bkRight > fastIndex->bkLeft)
		bkWidth = fastIndex->bkRight - fastIndex->bkLeft + 1;

	if (fastIndex->bkBottom > fastIndex->bkTop)
		bkHeight = fastIndex->bkBottom - fastIndex->bkTop + 1;

	if (opRight > opLeft)
		opWidth = opRight - opLeft + 1;

	if (opBottom > opTop)
		opHeight = opBottom - opTop + 1;

	return update_process_glyph_fragments(
	    context, fastIndex->data, fastIndex->cbData, fastIndex->cacheId, fastIndex->ulCharInc,
	    fastIndex->flAccel, fastIndex->backColor, fastIndex->foreColor, x, y, fastIndex->bkLeft,
	    fastIndex->bkTop, bkWidth, bkHeight, opLeft, opTop, opWidth, opHeight, FALSE);
}


static BOOL autodetect_send_bandwidth_measure_stop(rdpContext* context, UINT16 payloadLength,
                                                   UINT16 sequenceNumber, UINT16 requestType)
{
	wStream* s;
	UCHAR* buffer = NULL;
	BOOL bResult = FALSE;
	s = rdp_message_channel_pdu_init(context->rdp);

	if (!s)
		return FALSE;

	WLog_VRB(AUTODETECT_TAG, "sending Bandwidth Measure Stop PDU -> payloadLength=%" PRIu16 "",
	         payloadLength);
	/* 4-bytes aligned */
	payloadLength &= ~3;
	Stream_Write_UINT8(s, requestType == RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME
	                          ? 0x08
	                          : 0x06);                 /* headerLength (1 byte) */
	Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
	Stream_Write_UINT16(s, sequenceNumber);            /* sequenceNumber (2 bytes) */
	Stream_Write_UINT16(s, requestType);               /* requestType (2 bytes) */

	if (requestType == RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME)
	{
		Stream_Write_UINT16(s, payloadLength); /* payloadLength (2 bytes) */

		if (payloadLength > 0)
		{
			if (!Stream_EnsureRemainingCapacity(s, payloadLength))
			{
				Stream_Release(s);
				return FALSE;
			}

			/* Random data (better measurement in case the line is compressed) */
			buffer = malloc(payloadLength);

			if (NULL == buffer)
			{
				Stream_Release(s);
				return FALSE;
			}

			winpr_RAND(buffer, payloadLength);
			Stream_Write(s, buffer, payloadLength);
		}
	}

	bResult = rdp_send_message_channel_pdu(context->rdp, s, SEC_AUTODETECT_REQ);
	free(buffer);
	return bResult;
}

Fixed #6009: Bounds checks in autodetect_recv_bandwidth_measure_results

#define RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME 0x002B

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


static BOOL autodetect_recv_netchar_result(rdpRdp* rdp, wStream* s,
                                           AUTODETECT_REQ_PDU* autodetectReqPdu)
{
	BOOL success = TRUE;

	switch (autodetectReqPdu->requestType)
	{
		case 0x0840:

			/* baseRTT and averageRTT fields are present (bandwidth field is not) */
			if ((autodetectReqPdu->headerLength != 0x0E) || (Stream_GetRemainingLength(s) < 8))
				return FALSE;

			Stream_Read_UINT32(s, rdp->autodetect->netCharBaseRTT);    /* baseRTT (4 bytes) */
			Stream_Read_UINT32(s, rdp->autodetect->netCharAverageRTT); /* averageRTT (4 bytes) */
			break;

		case 0x0880:

			/* bandwidth and averageRTT fields are present (baseRTT field is not) */
			if ((autodetectReqPdu->headerLength != 0x0E) || (Stream_GetRemainingLength(s) < 8))
				return FALSE;

			Stream_Read_UINT32(s, rdp->autodetect->netCharBandwidth);  /* bandwidth (4 bytes) */
			Stream_Read_UINT32(s, rdp->autodetect->netCharAverageRTT); /* averageRTT (4 bytes) */
			break;

		case 0x08C0:

			/* baseRTT, bandwidth, and averageRTT fields are present */
			if ((autodetectReqPdu->headerLength != 0x12) || (Stream_GetRemainingLength(s) < 12))
				return FALSE;

			Stream_Read_UINT32(s, rdp->autodetect->netCharBaseRTT);    /* baseRTT (4 bytes) */
			Stream_Read_UINT32(s, rdp->autodetect->netCharBandwidth);  /* bandwidth (4 bytes) */
			Stream_Read_UINT32(s, rdp->autodetect->netCharAverageRTT); /* averageRTT (4 bytes) */
			break;
	}

	WLog_VRB(AUTODETECT_TAG,
	         "received Network Characteristics Result PDU -> baseRTT=%" PRIu32
	         ", bandwidth=%" PRIu32 ", averageRTT=%" PRIu32 "",
	         rdp->autodetect->netCharBaseRTT, rdp->autodetect->netCharBandwidth,
	         rdp->autodetect->netCharAverageRTT);
	IFCALLRET(rdp->autodetect->NetworkCharacteristicsResult, success, rdp->context,
	          autodetectReqPdu->sequenceNumber);
	return success;
}

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


void autodetect_free(rdpAutoDetect* autoDetect)
{
	free(autoDetect);
}

#define RDP_RTT_RESPONSE_TYPE 0x0000

#define RDP_BW_PAYLOAD_REQUEST_TYPE 0x0002

#define RDP_NETCHAR_SYNC_RESPONSE_TYPE 0x0018

#define RDP_RTT_REQUEST_TYPE_CONNECTTIME 0x1001

#define RDP_RTT_REQUEST_TYPE_CONTINUOUS 0x0001

#define RDP_BW_START_REQUEST_TYPE_TUNNEL 0x0114

#define RDP_BW_START_REQUEST_TYPE_CONNECTTIME 0x1014

#define RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME 0x002B

#define RDP_BW_STOP_REQUEST_TYPE_TUNNEL 0x0629

#define RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME 0x0003

#define RDP_BW_START_REQUEST_TYPE_CONTINUOUS 0x0014

#define RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS 0x0429

#define RDP_BW_RESULTS_RESPONSE_TYPE_CONTINUOUS 0x000B

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;


void autodetect_register_server_callbacks(rdpAutoDetect* autodetect)
{
	autodetect->RTTMeasureRequest = autodetect_send_continuous_rtt_measure_request;
	autodetect->BandwidthMeasureStart = autodetect_send_continuous_bandwidth_measure_start;
	autodetect->BandwidthMeasureStop = autodetect_send_continuous_bandwidth_measure_stop;
	autodetect->NetworkCharacteristicsResult = autodetect_send_netchar_result;
}


BOOL autodetect_send_connecttime_bandwidth_measure_start(rdpContext* context, UINT16 sequenceNumber)
{
	return autodetect_send_bandwidth_measure_start(context, sequenceNumber,
	                                               RDP_BW_START_REQUEST_TYPE_CONNECTTIME);
}

#define RDP_BW_START_REQUEST_TYPE_CONNECTTIME 0x1014

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


static BOOL autodetect_send_bandwidth_measure_start(rdpContext* context, UINT16 sequenceNumber,
                                                    UINT16 requestType)
{
	wStream* s;
	s = rdp_message_channel_pdu_init(context->rdp);

	if (!s)
		return FALSE;

	WLog_VRB(AUTODETECT_TAG, "sending Bandwidth Measure Start PDU");
	Stream_Write_UINT8(s, 0x06);                       /* headerLength (1 byte) */
	Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
	Stream_Write_UINT16(s, sequenceNumber);            /* sequenceNumber (2 bytes) */
	Stream_Write_UINT16(s, requestType);               /* requestType (2 bytes) */
	return rdp_send_message_channel_pdu(context->rdp, s, SEC_AUTODETECT_REQ);
}


static BOOL autodetect_send_bandwidth_measure_results(rdpRdp* rdp, UINT16 responseType,
                                                      UINT16 sequenceNumber)
{
	BOOL success = TRUE;
	wStream* s;
	UINT64 timeDelta;
	/* Compute the total time */
	timeDelta = GetTickCount64() - rdp->autodetect->bandwidthMeasureStartTime;
	/* Send the result PDU to the server */
	s = rdp_message_channel_pdu_init(rdp);

	if (!s)
		return FALSE;

	WLog_VRB(AUTODETECT_TAG,
	         "sending Bandwidth Measure Results PDU -> timeDelta=%" PRIu32 ", byteCount=%" PRIu32
	         "",
	         timeDelta, rdp->autodetect->bandwidthMeasureByteCount);
	Stream_Write_UINT8(s, 0x0E);                        /* headerLength (1 byte) */
	Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_RESPONSE); /* headerTypeId (1 byte) */
	Stream_Write_UINT16(s, sequenceNumber);             /* sequenceNumber (2 bytes) */
	Stream_Write_UINT16(s, responseType);               /* responseType (1 byte) */
	Stream_Write_UINT32(s, timeDelta);                  /* timeDelta (4 bytes) */
	Stream_Write_UINT32(s, rdp->autodetect->bandwidthMeasureByteCount); /* byteCount (4 bytes) */
	IFCALLRET(rdp->autodetect->ClientBandwidthMeasureResult, success, rdp->context,
	          rdp->autodetect);

	if (!success)
		return FALSE;

	return rdp_send_message_channel_pdu(rdp, s, SEC_AUTODETECT_RSP);
}


static BOOL autodetect_send_rtt_measure_response(rdpRdp* rdp, UINT16 sequenceNumber)
{
	wStream* s;
	/* Send the response PDU to the server */
	s = rdp_message_channel_pdu_init(rdp);

	if (!s)
		return FALSE;

	WLog_VRB(AUTODETECT_TAG, "sending RTT Measure Response PDU");
	Stream_Write_UINT8(s, 0x06);                        /* headerLength (1 byte) */
	Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_RESPONSE); /* headerTypeId (1 byte) */
	Stream_Write_UINT16(s, sequenceNumber);             /* sequenceNumber (2 bytes) */
	Stream_Write_UINT16(s, RDP_RTT_RESPONSE_TYPE);      /* responseType (1 byte) */
	return rdp_send_message_channel_pdu(rdp, s, SEC_AUTODETECT_RSP);
}

#define RDP_RTT_RESPONSE_TYPE 0x0000

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


int rdp_recv_autodetect_request_packet(rdpRdp* rdp, wStream* s)
{
	AUTODETECT_REQ_PDU autodetectReqPdu;
	BOOL success = FALSE;

	if (Stream_GetRemainingLength(s) < 6)
		return -1;

	Stream_Read_UINT8(s, autodetectReqPdu.headerLength);    /* headerLength (1 byte) */
	Stream_Read_UINT8(s, autodetectReqPdu.headerTypeId);    /* headerTypeId (1 byte) */
	Stream_Read_UINT16(s, autodetectReqPdu.sequenceNumber); /* sequenceNumber (2 bytes) */
	Stream_Read_UINT16(s, autodetectReqPdu.requestType);    /* requestType (2 bytes) */
	WLog_VRB(AUTODETECT_TAG,
	         "rdp_recv_autodetect_request_packet: headerLength=%" PRIu8 ", headerTypeId=%" PRIu8
	         ", sequenceNumber=%" PRIu16 ", requestType=%04" PRIx16 "",
	         autodetectReqPdu.headerLength, autodetectReqPdu.headerTypeId,
	         autodetectReqPdu.sequenceNumber, autodetectReqPdu.requestType);

	if (autodetectReqPdu.headerTypeId != TYPE_ID_AUTODETECT_REQUEST)
		return -1;

	switch (autodetectReqPdu.requestType)
	{
		case RDP_RTT_REQUEST_TYPE_CONTINUOUS:
		case RDP_RTT_REQUEST_TYPE_CONNECTTIME:
			/* RTT Measure Request (RDP_RTT_REQUEST) - MS-RDPBCGR 2.2.14.1.1 */
			success = autodetect_recv_rtt_measure_request(rdp, s, &autodetectReqPdu);
			break;

		case RDP_BW_START_REQUEST_TYPE_CONTINUOUS:
		case RDP_BW_START_REQUEST_TYPE_TUNNEL:
		case RDP_BW_START_REQUEST_TYPE_CONNECTTIME:
			/* Bandwidth Measure Start (RDP_BW_START) - MS-RDPBCGR 2.2.14.1.2 */
			success = autodetect_recv_bandwidth_measure_start(rdp, s, &autodetectReqPdu);
			break;

		case RDP_BW_PAYLOAD_REQUEST_TYPE:
			/* Bandwidth Measure Payload (RDP_BW_PAYLOAD) - MS-RDPBCGR 2.2.14.1.3 */
			success = autodetect_recv_bandwidth_measure_payload(rdp, s, &autodetectReqPdu);
			break;

		case RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME:
		case RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS:
		case RDP_BW_STOP_REQUEST_TYPE_TUNNEL:
			/* Bandwidth Measure Stop (RDP_BW_STOP) - MS-RDPBCGR 2.2.14.1.4 */
			success = autodetect_recv_bandwidth_measure_stop(rdp, s, &autodetectReqPdu);
			break;

		case 0x0840:
		case 0x0880:
		case 0x08C0:
			/* Network Characteristics Result (RDP_NETCHAR_RESULT) - MS-RDPBCGR 2.2.14.1.5 */
			success = autodetect_recv_netchar_result(rdp, s, &autodetectReqPdu);
			break;

		default:
			break;
	}

	return success ? 0 : -1;
}

#define RDP_RTT_REQUEST_TYPE_CONTINUOUS 0x0001

#define RDP_RTT_REQUEST_TYPE_CONNECTTIME 0x1001

#define RDP_BW_START_REQUEST_TYPE_CONTINUOUS 0x0014

#define RDP_BW_START_REQUEST_TYPE_TUNNEL 0x0114

#define RDP_BW_START_REQUEST_TYPE_CONNECTTIME 0x1014

#define RDP_BW_PAYLOAD_REQUEST_TYPE 0x0002

#define RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME 0x002B

#define RDP_BW_STOP_REQUEST_TYPE_CONTINUOUS 0x0429

#define RDP_BW_STOP_REQUEST_TYPE_TUNNEL 0x0629

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


static BOOL autodetect_send_netchar_sync(rdpRdp* rdp, UINT16 sequenceNumber)
{
	wStream* s;
	/* Send the response PDU to the server */
	s = rdp_message_channel_pdu_init(rdp);

	if (!s)
		return FALSE;

	WLog_VRB(AUTODETECT_TAG,
	         "sending Network Characteristics Sync PDU -> bandwidth=%" PRIu32 ", rtt=%" PRIu32 "",
	         rdp->autodetect->netCharBandwidth, rdp->autodetect->netCharAverageRTT);
	Stream_Write_UINT8(s, 0x0E);                                /* headerLength (1 byte) */
	Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_RESPONSE);         /* headerTypeId (1 byte) */
	Stream_Write_UINT16(s, sequenceNumber);                     /* sequenceNumber (2 bytes) */
	Stream_Write_UINT16(s, RDP_NETCHAR_SYNC_RESPONSE_TYPE);     /* responseType (1 byte) */
	Stream_Write_UINT32(s, rdp->autodetect->netCharBandwidth);  /* bandwidth (4 bytes) */
	Stream_Write_UINT32(s, rdp->autodetect->netCharAverageRTT); /* rtt (4 bytes) */
	return rdp_send_message_channel_pdu(rdp, s, SEC_AUTODETECT_RSP);
}

#define RDP_NETCHAR_SYNC_RESPONSE_TYPE 0x0018

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


static BOOL autodetect_send_rtt_measure_request(rdpContext* context, UINT16 sequenceNumber,
                                                UINT16 requestType)
{
	wStream* s;
	s = rdp_message_channel_pdu_init(context->rdp);

	if (!s)
		return FALSE;

	WLog_VRB(AUTODETECT_TAG, "sending RTT Measure Request PDU");
	Stream_Write_UINT8(s, 0x06);                       /* headerLength (1 byte) */
	Stream_Write_UINT8(s, TYPE_ID_AUTODETECT_REQUEST); /* headerTypeId (1 byte) */
	Stream_Write_UINT16(s, sequenceNumber);            /* sequenceNumber (2 bytes) */
	Stream_Write_UINT16(s, requestType);               /* requestType (2 bytes) */
	context->rdp->autodetect->rttMeasureStartTime = GetTickCount64();
	return rdp_send_message_channel_pdu(context->rdp, s, SEC_AUTODETECT_REQ);
}


int rdp_recv_autodetect_response_packet(rdpRdp* rdp, wStream* s)
{
	AUTODETECT_RSP_PDU autodetectRspPdu;
	BOOL success = FALSE;

	if (Stream_GetRemainingLength(s) < 6)
		return -1;

	Stream_Read_UINT8(s, autodetectRspPdu.headerLength);    /* headerLength (1 byte) */
	Stream_Read_UINT8(s, autodetectRspPdu.headerTypeId);    /* headerTypeId (1 byte) */
	Stream_Read_UINT16(s, autodetectRspPdu.sequenceNumber); /* sequenceNumber (2 bytes) */
	Stream_Read_UINT16(s, autodetectRspPdu.responseType);   /* responseType (2 bytes) */
	WLog_VRB(AUTODETECT_TAG,
	         "rdp_recv_autodetect_response_packet: headerLength=%" PRIu8 ", headerTypeId=%" PRIu8
	         ", sequenceNumber=%" PRIu16 ", requestType=%04" PRIx16 "",
	         autodetectRspPdu.headerLength, autodetectRspPdu.headerTypeId,
	         autodetectRspPdu.sequenceNumber, autodetectRspPdu.responseType);

	if (autodetectRspPdu.headerTypeId != TYPE_ID_AUTODETECT_RESPONSE)
		return -1;

	switch (autodetectRspPdu.responseType)
	{
		case RDP_RTT_RESPONSE_TYPE:
			/* RTT Measure Response (RDP_RTT_RESPONSE) - MS-RDPBCGR 2.2.14.2.1 */
			success = autodetect_recv_rtt_measure_response(rdp, s, &autodetectRspPdu);
			break;

		case RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME:
		case RDP_BW_RESULTS_RESPONSE_TYPE_CONTINUOUS:
			/* Bandwidth Measure Results (RDP_BW_RESULTS) - MS-RDPBCGR 2.2.14.2.2 */
			success = autodetect_recv_bandwidth_measure_results(rdp, s, &autodetectRspPdu);
			break;

		default:
			break;
	}
	return success ? 0 : -1;
}

#define RDP_RTT_RESPONSE_TYPE 0x0000

#define RDP_BW_RESULTS_RESPONSE_TYPE_CONNECTTIME 0x0003

#define RDP_BW_RESULTS_RESPONSE_TYPE_CONTINUOUS 0x000B

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


static BOOL autodetect_send_continuous_rtt_measure_request(rdpContext* context,
                                                           UINT16 sequenceNumber)
{
	return autodetect_send_rtt_measure_request(context, sequenceNumber,
	                                           RDP_RTT_REQUEST_TYPE_CONTINUOUS);
}

#define RDP_RTT_REQUEST_TYPE_CONTINUOUS 0x0001

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


static BOOL autodetect_recv_bandwidth_measure_results(rdpRdp* rdp, wStream* s,
                                                      AUTODETECT_RSP_PDU* autodetectRspPdu)
{
	BOOL success = TRUE;

	if (autodetectRspPdu->headerLength != 0x0E)
		return FALSE;

	WLog_VRB(AUTODETECT_TAG, "received Bandwidth Measure Results PDU");
	if (Stream_GetRemainingLength(s) < 8)
		return -1;
	Stream_Read_UINT32(s, rdp->autodetect->bandwidthMeasureTimeDelta); /* timeDelta (4 bytes) */
	Stream_Read_UINT32(s, rdp->autodetect->bandwidthMeasureByteCount); /* byteCount (4 bytes) */

	if (rdp->autodetect->bandwidthMeasureTimeDelta > 0)
		rdp->autodetect->netCharBandwidth = rdp->autodetect->bandwidthMeasureByteCount * 8 /
		                                    rdp->autodetect->bandwidthMeasureTimeDelta;
	else
		rdp->autodetect->netCharBandwidth = 0;

	IFCALLRET(rdp->autodetect->BandwidthMeasureResults, success, rdp->context,
	          autodetectRspPdu->sequenceNumber);
	return success;
}


static BOOL autodetect_send_continuous_bandwidth_measure_start(rdpContext* context,
                                                               UINT16 sequenceNumber)
{
	return autodetect_send_bandwidth_measure_start(context, sequenceNumber,
	                                               RDP_BW_START_REQUEST_TYPE_CONTINUOUS);
}

#define RDP_BW_START_REQUEST_TYPE_CONTINUOUS 0x0014

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 requestType;
} AUTODETECT_REQ_PDU;

typedef struct
{
	UINT8 headerLength;
	UINT8 headerTypeId;
	UINT16 sequenceNumber;
	UINT16 responseType;
} AUTODETECT_RSP_PDU;


BOOL autodetect_send_connecttime_bandwidth_measure_stop(rdpContext* context, UINT16 payloadLength,
                                                        UINT16 sequenceNumber)
{
	return autodetect_send_bandwidth_measure_stop(context, payloadLength, sequenceNumber,
	                                              RDP_BW_STOP_REQUEST_TYPE_CONNECTTIME);
}


static BOOL autodetect_recv_bandwidth_measure_start(rdpRdp* rdp, wStream* s,
                                                    AUTODETECT_REQ_PDU* autodetectReqPdu)
{
	if (autodetectReqPdu->headerLength != 0x06)
		return FALSE;

	WLog_VRB(AUTODETECT_TAG, "received Bandwidth Measure Start PDU - time=%" PRIu64 "",
	         GetTickCount64());
	/* Initialize bandwidth measurement parameters */
	rdp->autodetect->bandwidthMeasureStartTime = GetTickCount64();
	rdp->autodetect->bandwidthMeasureByteCount = 0;

	/* Continuous Auto-Detection: mark the start of the measurement */
	if (autodetectReqPdu->requestType == RDP_BW_START_REQUEST_TYPE_CONTINUOUS)
	{
		rdp->autodetect->bandwidthMeasureStarted = TRUE;
	}

	return TRUE;
}