check_keypad(TERMTYPE2 *tp)
{
    char show[80];

    if (VALID_STRING(key_a1) &&
	VALID_STRING(key_a3) &&
	VALID_STRING(key_b2) &&
	VALID_STRING(key_c1) &&
	VALID_STRING(key_c3)) {
	char final[MAX_KP + 1];
	long list[MAX_KP];
	int increase = 0;
	int j, k, kk;
	long last;
	long test;

	final[0] = keypad_final(key_a1);
	final[1] = keypad_final(key_a3);
	final[2] = keypad_final(key_b2);
	final[3] = keypad_final(key_c1);
	final[4] = keypad_final(key_c3);
	final[5] = '\0';

	/* special case: legacy coding using 1,2,3,0,. on the bottom */
	assert(strlen(final) <= MAX_KP);
	if (!strcmp(final, "qsrpn"))
	    return;

	list[0] = keypad_index(key_a1);
	list[1] = keypad_index(key_a3);
	list[2] = keypad_index(key_b2);
	list[3] = keypad_index(key_c1);
	list[4] = keypad_index(key_c3);

	/* check that they're all vt100 keys */
	for (j = 0; j < MAX_KP; ++j) {
	    if (list[j] < 0) {
		return;
	    }
	}

	/* check if they're all in increasing order */
	for (j = 1; j < MAX_KP; ++j) {
	    if (list[j] > list[j - 1]) {
		++increase;
	    }
	}
	if (increase != (MAX_KP - 1)) {
	    show[0] = '\0';

	    for (j = 0, last = -1; j < MAX_KP; ++j) {
		for (k = 0, kk = -1, test = 100; k < 5; ++k) {
		    if (list[k] > last &&
			list[k] < test) {
			test = list[k];
			kk = k;
		    }
		}
		last = test;
		assert(strlen(show) < (MAX_KP * 4));
		switch (kk) {
		case 0:
		    _nc_STRCAT(show, " ka1", sizeof(show));
		    break;
		case 1:
		    _nc_STRCAT(show, " ka3", sizeof(show));
		    break;
		case 2:
		    _nc_STRCAT(show, " kb2", sizeof(show));
		    break;
		case 3:
		    _nc_STRCAT(show, " kc1", sizeof(show));
		    break;
		case 4:
		    _nc_STRCAT(show, " kc3", sizeof(show));
		    break;
		}
	    }

	    _nc_warning("vt100 keypad order inconsistent: %s", show);
	}

    } else if (VALID_STRING(key_a1) ||
	       VALID_STRING(key_a3) ||
	       VALID_STRING(key_b2) ||
	       VALID_STRING(key_c1) ||
	       VALID_STRING(key_c3)) {
	show[0] = '\0';
	if (keypad_index(key_a1) >= 0)
	    _nc_STRCAT(show, " ka1", sizeof(show));
	if (keypad_index(key_a3) >= 0)
	    _nc_STRCAT(show, " ka3", sizeof(show));
	if (keypad_index(key_b2) >= 0)
	    _nc_STRCAT(show, " kb2", sizeof(show));
	if (keypad_index(key_c1) >= 0)
	    _nc_STRCAT(show, " kc1", sizeof(show));
	if (keypad_index(key_c3) >= 0)
	    _nc_STRCAT(show, " kc3", sizeof(show));
	if (*show != '\0')
	    _nc_warning("vt100 keypad map incomplete:%s", show);
    }

    /*
     * These warnings are useful for consistency checks - it is possible that
     * there are real terminals with mismatches in these
     */
    ANDMISSING(key_ic, key_dc);
}


NCURSES_SP_NAME(vidattr) (NCURSES_SP_DCLx chtype newmode)
{
    T((T_CALLED("vidattr(%p,%s)"), (void *) SP_PARM, _traceattr(newmode)));
    returnCode(NCURSES_SP_NAME(vidputs) (NCURSES_SP_ARGx
					 newmode,
					 NCURSES_SP_NAME(_nc_putchar)));
}


out_char(int c)
{
    return putc(c, my_file);
}


NCURSES_SP_NAME(_nc_mvcur_init) (NCURSES_SP_DCL0)
/* initialize the cost structure */
{
    if (SP_PARM->_ofp && NC_ISATTY(fileno(SP_PARM->_ofp))) {
	SP_PARM->_char_padding = ((BAUDBYTE * 1000 * 10)
				  / (BAUDRATE(SP_PARM) > 0
				     ? BAUDRATE(SP_PARM)
				     : 9600));
    } else {
	SP_PARM->_char_padding = 1;	/* must be nonzero */
    }
    if (SP_PARM->_char_padding <= 0)
	SP_PARM->_char_padding = 1;	/* must be nonzero */
    TR(TRACE_CHARPUT | TRACE_MOVE, ("char_padding %d msecs", SP_PARM->_char_padding));

    /* non-parameterized local-motion strings */
    SP_PARM->_cr_cost = CostOf(carriage_return, 0);
    SP_PARM->_home_cost = CostOf(cursor_home, 0);
    SP_PARM->_ll_cost = CostOf(cursor_to_ll, 0);
#if USE_HARD_TABS
    if (getenv("NCURSES_NO_HARD_TABS") == 0
	&& dest_tabs_magic_smso == 0
	&& HasHardTabs()) {
	SP_PARM->_ht_cost = CostOf(tab, 0);
	SP_PARM->_cbt_cost = CostOf(back_tab, 0);
    } else {
	SP_PARM->_ht_cost = INFINITY;
	SP_PARM->_cbt_cost = INFINITY;
    }
#endif /* USE_HARD_TABS */
    SP_PARM->_cub1_cost = CostOf(cursor_left, 0);
    SP_PARM->_cuf1_cost = CostOf(cursor_right, 0);
    SP_PARM->_cud1_cost = CostOf(cursor_down, 0);
    SP_PARM->_cuu1_cost = CostOf(cursor_up, 0);

    SP_PARM->_smir_cost = CostOf(enter_insert_mode, 0);
    SP_PARM->_rmir_cost = CostOf(exit_insert_mode, 0);
    SP_PARM->_ip_cost = 0;
    if (insert_padding) {
	SP_PARM->_ip_cost = CostOf(insert_padding, 0);
    }

    /*
     * Assumption: if the terminal has memory_relative addressing, the
     * initialization strings or smcup will set single-page mode so we
     * can treat it like absolute screen addressing.  This seems to be true
     * for all cursor_mem_address terminal types in the terminfo database.
     */
    SP_PARM->_address_cursor = cursor_address ? cursor_address : cursor_mem_address;

    /*
     * Parametrized local-motion strings.  This static cost computation
     * depends on the following assumptions:
     *
     * (1) They never have * padding.  In the entire master terminfo database
     *     as of March 1995, only the obsolete Zenith Z-100 pc violates this.
     *     (Proportional padding is found mainly in insert, delete and scroll
     *     capabilities).
     *
     * (2) The average case of cup has two two-digit parameters.  Strictly,
     *     the average case for a 24 * 80 screen has ((10*10*(1 + 1)) +
     *     (14*10*(1 + 2)) + (10*70*(2 + 1)) + (14*70*4)) / (24*80) = 3.458
     *     digits of parameters.  On a 25x80 screen the average is 3.6197.
     *     On larger screens the value gets much closer to 4.
     *
     * (3) The average case of cub/cuf/hpa/ech/rep has 2 digits of parameters
     *     (strictly, (((10 * 1) + (70 * 2)) / 80) = 1.8750).
     *
     * (4) The average case of cud/cuu/vpa has 2 digits of parameters
     *     (strictly, (((10 * 1) + (14 * 2)) / 24) = 1.5833).
     *
     * All these averages depend on the assumption that all parameter values
     * are equally probable.
     */
    SP_PARM->_cup_cost = CostOf(TIPARM_2(SP_PARM->_address_cursor, 23, 23), 1);
    SP_PARM->_cub_cost = CostOf(TIPARM_1(parm_left_cursor, 23), 1);
    SP_PARM->_cuf_cost = CostOf(TIPARM_1(parm_right_cursor, 23), 1);
    SP_PARM->_cud_cost = CostOf(TIPARM_1(parm_down_cursor, 23), 1);
    SP_PARM->_cuu_cost = CostOf(TIPARM_1(parm_up_cursor, 23), 1);
    SP_PARM->_hpa_cost = CostOf(TIPARM_1(column_address, 23), 1);
    SP_PARM->_vpa_cost = CostOf(TIPARM_1(row_address, 23), 1);

    /* non-parameterized screen-update strings */
    SP_PARM->_ed_cost = NormalizedCost(clr_eos, 1);
    SP_PARM->_el_cost = NormalizedCost(clr_eol, 1);
    SP_PARM->_el1_cost = NormalizedCost(clr_bol, 1);
    SP_PARM->_dch1_cost = NormalizedCost(delete_character, 1);
    SP_PARM->_ich1_cost = NormalizedCost(insert_character, 1);

    /*
     * If this is a bce-terminal, we want to bias the choice so we use clr_eol
     * rather than spaces at the end of a line.
     */
    if (back_color_erase)
	SP_PARM->_el_cost = 0;

    /* parameterized screen-update strings */
    SP_PARM->_dch_cost = NormalizedCost(TIPARM_1(parm_dch, 23), 1);
    SP_PARM->_ich_cost = NormalizedCost(TIPARM_1(parm_ich, 23), 1);
    SP_PARM->_ech_cost = NormalizedCost(TIPARM_1(erase_chars, 23), 1);
    SP_PARM->_rep_cost = NormalizedCost(TIPARM_2(repeat_char, ' ', 23), 1);

    SP_PARM->_cup_ch_cost = NormalizedCost(TIPARM_2(SP_PARM->_address_cursor,
						    23, 23),
					   1);
    SP_PARM->_hpa_ch_cost = NormalizedCost(TIPARM_1(column_address, 23), 1);
    SP_PARM->_cuf_ch_cost = NormalizedCost(TIPARM_1(parm_right_cursor, 23), 1);
    SP_PARM->_inline_cost = min(SP_PARM->_cup_ch_cost,
				min(SP_PARM->_hpa_ch_cost,
				    SP_PARM->_cuf_ch_cost));

    /*
     * If save_cursor is used within enter_ca_mode, we should not use it for
     * scrolling optimization, since the corresponding restore_cursor is not
     * nested on the various terminals (vt100, xterm, etc.) which use this
     * feature.
     */
    if (save_cursor != 0
	&& enter_ca_mode != 0
	&& strstr(enter_ca_mode, save_cursor) != 0) {
	T(("...suppressed sc/rc capability due to conflict with smcup/rmcup"));
	save_cursor = 0;
	restore_cursor = 0;
    }

    /*
     * A different, possibly better way to arrange this would be to set the
     * SCREEN's _endwin at window initialization time and let this be called by
     * doupdate's return-from-shellout code.
     */
    NCURSES_SP_NAME(_nc_mvcur_resume) (NCURSES_SP_ARG);
}


check_colors(TERMTYPE2 *tp)
{
    char *value;

    if ((max_colors > 0) != (max_pairs > 0)
	|| ((max_colors > max_pairs) && (initialize_pair == 0)))
	_nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)",
		    max_colors, max_pairs);

    PAIRED(set_foreground, set_background);
    PAIRED(set_a_foreground, set_a_background);
    PAIRED(set_color_pair, initialize_pair);

    if (VALID_STRING(set_foreground)
	&& VALID_STRING(set_a_foreground)) {
	if (!_nc_capcmp(set_foreground, set_a_foreground)) {
	    _nc_warning("expected setf/setaf to be different");
	} else if (same_color(set_foreground, set_a_foreground, max_colors)) {
	    _nc_warning("setf/setaf are equivalent");
	}
    }

    if (VALID_STRING(set_background)
	&& VALID_STRING(set_a_background)) {
	if (!_nc_capcmp(set_background, set_a_background)) {
	    _nc_warning("expected setb/setab to be different");
	} else if (same_color(set_background, set_a_background, max_colors)) {
	    _nc_warning("setb/setab are equivalent");
	}
    }

    /* see: has_colors() */
    if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs)
	&& ((VALID_STRING(set_foreground)
	     && VALID_STRING(set_background))
	    || (VALID_STRING(set_a_foreground)
		&& VALID_STRING(set_a_background))
	    || set_color_pair)) {
	if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors))
	    _nc_warning("expected either op/oc string for resetting colors");
    }
    if (can_change) {
	if (!VALID_STRING(initialize_pair) &&
	    !VALID_STRING(initialize_color)) {
	    _nc_warning("expected initc or initp because ccc is given");
	}
    } else {
	if (VALID_STRING(initialize_pair) ||
	    VALID_STRING(initialize_color)) {
	    _nc_warning("expected ccc because initc is given");
	}
    }
    value = tigetstr("RGB");
    if (VALID_STRING(value)) {
	int r, g, b;
	char bad;
	int code = sscanf(value, "%d/%d/%d%c", &r, &g, &b, &bad);
	if (code != 3 || r <= 0 || g <= 0 || b <= 0) {
	    _nc_warning("unexpected value for RGB capability: %s", value);
	}
    }
}


drv_setfilter(TERMINAL_CONTROL_BLOCK * TCB)
{
    AssertTCB();

    /* *INDENT-EQLS* */
    clear_screen     = ABSENT_STRING;
    cursor_address   = ABSENT_STRING;
    cursor_down      = ABSENT_STRING;
    cursor_up        = ABSENT_STRING;
    parm_down_cursor = ABSENT_STRING;
    parm_up_cursor   = ABSENT_STRING;
    row_address      = ABSENT_STRING;
    cursor_home      = carriage_return;

    if (back_color_erase)
	clr_eos = ABSENT_STRING;
}


set_window_size(int fd, short *high, short *wide)
{
    STRUCT_WINSIZE win;
    (void) ioctl(fd, IOCTL_GET_WINSIZE, &win);
    if (WINSIZE_ROWS(win) == 0 &&
	WINSIZE_COLS(win) == 0) {
	if (*high > 0 && *wide > 0) {
	    WINSIZE_ROWS(win) = (unsigned short) *high;
	    WINSIZE_COLS(win) = (unsigned short) *wide;
	    (void) ioctl(fd, IOCTL_SET_WINSIZE, &win);
	}
    } else if (WINSIZE_ROWS(win) > 0 &&
	       WINSIZE_COLS(win) > 0) {
	*high = (short) WINSIZE_ROWS(win);
	*wide = (short) WINSIZE_COLS(win);
    }
}


_nc_real_mvcur(NCURSES_SP_DCLx
	       int yold, int xold,
	       int ynew, int xnew,
	       NCURSES_SP_OUTC myOutCh,
	       int ovw)
{
    NCURSES_CH_T oldattr;
    int code;

    TR(TRACE_CALLS | TRACE_MOVE, (T_CALLED("_nc_tinfo_mvcur(%p,%d,%d,%d,%d)"),
				  (void *) SP_PARM, yold, xold, ynew, xnew));

    if (SP_PARM == 0) {
	code = ERR;
    } else if (yold == ynew && xold == xnew) {
	code = OK;
    } else {

	/*
	 * Most work here is rounding for terminal boundaries getting the
	 * column position implied by wraparound or the lack thereof and
	 * rolling up the screen to get ynew on the screen.
	 */
	if (xnew >= screen_columns(SP_PARM)) {
	    ynew += xnew / screen_columns(SP_PARM);
	    xnew %= screen_columns(SP_PARM);
	}

	/*
	 * Force restore even if msgr is on when we're in an alternate
	 * character set -- these have a strong tendency to screw up the CR &
	 * LF used for local character motions!
	 */
	oldattr = SCREEN_ATTRS(SP_PARM);
	if ((AttrOf(oldattr) & A_ALTCHARSET)
	    || (AttrOf(oldattr) && !move_standout_mode)) {
	    TR(TRACE_CHARPUT, ("turning off (%#lx) %s before move",
			       (unsigned long) AttrOf(oldattr),
			       _traceattr(AttrOf(oldattr))));
	    VIDPUTS(SP_PARM, A_NORMAL, 0);
	}

	if (xold >= screen_columns(SP_PARM)) {

	    if (SP_PARM->_nl) {
		int l = (xold + 1) / screen_columns(SP_PARM);

		yold += l;
		if (yold >= screen_lines(SP_PARM))
		    l -= (yold - screen_lines(SP_PARM) - 1);

		if (l > 0) {
		    if (carriage_return) {
			NCURSES_PUTP2("carriage_return", carriage_return);
		    } else {
			myOutCh(NCURSES_SP_ARGx '\r');
		    }
		    xold = 0;

		    while (l > 0) {
			if (newline) {
			    NCURSES_PUTP2("newline", newline);
			} else {
			    myOutCh(NCURSES_SP_ARGx '\n');
			}
			l--;
		    }
		}
	    } else {
		/*
		 * If caller set nonl(), we cannot really use newlines to
		 * position to the next row.
		 */
		xold = -1;
		yold = -1;
	    }
	}

	if (yold > screen_lines(SP_PARM) - 1)
	    yold = screen_lines(SP_PARM) - 1;
	if (ynew > screen_lines(SP_PARM) - 1)
	    ynew = screen_lines(SP_PARM) - 1;

	/* destination location is on screen now */
	code = onscreen_mvcur(NCURSES_SP_ARGx yold, xold, ynew, xnew, ovw, myOutCh);

	/*
	 * Restore attributes if we disabled them before moving.
	 */
	if (!SameAttrOf(oldattr, SCREEN_ATTRS(SP_PARM))) {
	    TR(TRACE_CHARPUT, ("turning on (%#lx) %s after move",
			       (unsigned long) AttrOf(oldattr),
			       _traceattr(AttrOf(oldattr))));
	    VIDPUTS(SP_PARM, AttrOf(oldattr), GetPair(oldattr));
	}
    }
    returnCode(code);
}


usage(void)
{
    static const char *msg[] =
    {
	"Usage: test_tparm [options] [capability] [value1 [value2 [...]]]",
	"",
	"Print all distinct combinations of given capability.",
	"",
	"Options:",
	" -T TERM  override $TERM; this may be a comma-separated list or \"-\"",
	"          to read a list from standard-input",
	" -a       if capability is given, test all combinations of values",
	" -r NUM   repeat tests NUM times",
	" -v       show values and results",
    };
    unsigned n;
    for (n = 0; n < SIZEOF(msg); ++n) {
	fprintf(stderr, "%s\n", msg[n]);
    }
    ExitProgram(EXIT_FAILURE);
}


NCURSES_PUBLIC_VAR(_nc_outchars) (void)
{
    return CURRENT_SCREEN ? CURRENT_SCREEN->_outchars : _nc_prescreen._outchars;
}


name_of_type(int type)
{
    const char *result = "unknown";
    switch (type) {
    case BOOLEAN:
	result = "boolean";
	break;
    case NUMBER:
	result = "number";
	break;
    case STRING:
	result = "string";
	break;
    }
    return result;
}


_nc_mouse_parse(SCREEN *sp, int runcount)
/* parse a run of atomic mouse events into a gesture */
{
    MEVENT *eventp = sp->_mouse_eventp;
    MEVENT *next, *ep;
    MEVENT *first_valid = NULL;
    MEVENT *first_invalid = NULL;
    int n;
    int b;
    bool merge;
    bool endLoop;

    TR(MY_TRACE, ("_nc_mouse_parse(%d) called", runcount));

    /*
     * When we enter this routine, the event list next-free pointer
     * points just past a run of mouse events that we know were separated
     * in time by less than the critical click interval. The job of this
     * routine is to collapse this run into a single higher-level event
     * or gesture.
     *
     * We accomplish this in two passes.  The first pass merges press/release
     * pairs into click events.  The second merges runs of click events into
     * double or triple-click events.
     *
     * It's possible that the run may not resolve to a single event (for
     * example, if the user quadruple-clicks).  If so, leading events
     * in the run are ignored if user does not call getmouse in a loop (getting
     * them from newest to older).
     *
     * Note that this routine is independent of the format of the specific
     * format of the pointing-device's reports.  We can use it to parse
     * gestures on anything that reports press/release events on a per-
     * button basis, as long as the device-dependent mouse code puts stuff
     * on the queue in MEVENT format.
     */

    /*
     * Reset all events that were not set, in case the user sometimes calls
     * getmouse only once and other times until there are no more events in
     * queue.
     *
     * This also allows reaching the beginning of the run.
     */
    ep = eventp;
    for (n = runcount; n < EV_MAX; n++) {
	Invalidate(ep);
	ep = NEXT(ep);
    }

#ifdef TRACE
    if (USE_TRACEF(TRACE_IEVENT)) {
	_trace_slot(sp, "before mouse press/release merge:");
	_tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d",
		RunParams(sp, eventp, ep),
		runcount);
	_nc_unlock_global(tracef);
    }
#endif /* TRACE */

    /* first pass; merge press/release pairs */
    endLoop = FALSE;
    while (!endLoop) {
	next = NEXT(ep);
	if (next == eventp) {
	    /* Will end the loop, but compact before */
	    endLoop = TRUE;
	} else {

#define MASK_CHANGED(x) (!(ep->bstate & MASK_PRESS(x)) \
		      == !(next->bstate & MASK_RELEASE(x)))

	    if (ValidEvent(ep) && ValidEvent(next)
		&& ep->x == next->x && ep->y == next->y
		&& (ep->bstate & BUTTON_PRESSED)
		&& (!(next->bstate & BUTTON_PRESSED))) {
		bool changed = TRUE;

		for (b = 1; b <= MAX_BUTTONS; ++b) {
		    if (!MASK_CHANGED(b)) {
			changed = FALSE;
			break;
		    }
		}

		if (changed) {
		    merge = FALSE;
		    for (b = 1; b <= MAX_BUTTONS; ++b) {
			if ((sp->_mouse_mask & MASK_CLICK(b))
			    && (ep->bstate & MASK_PRESS(b))) {
			    next->bstate &= ~MASK_RELEASE(b);
			    next->bstate |= MASK_CLICK(b);
			    merge = TRUE;
			}
		    }
		    if (merge) {
			Invalidate(ep);
		    }
		}
	    }
	}

	/* Compact valid events */
	if (!ValidEvent(ep)) {
	    if ((first_valid != NULL) && (first_invalid == NULL)) {
		first_invalid = ep;
	    }
	} else {
	    if (first_valid == NULL) {
		first_valid = ep;
	    } else if (first_invalid != NULL) {
		*first_invalid = *ep;
		Invalidate(ep);
		first_invalid = NEXT(first_invalid);
	    }
	}

	ep = next;
    }

    if (first_invalid != NULL) {
	eventp = first_invalid;
    }
#ifdef TRACE
    if (USE_TRACEF(TRACE_IEVENT)) {
	_trace_slot(sp, "before mouse click merge:");
	if (first_valid == NULL) {
	    _tracef("_nc_mouse_parse: no valid event");
	} else {
	    _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d",
		    RunParams(sp, eventp, first_valid),
		    runcount);
	    _nc_unlock_global(tracef);
	}
    }
#endif /* TRACE */

    /*
     * Second pass; merge click runs.  We merge click events forward in the
     * queue.  For example, double click can be changed to triple click.
     *
     * NOTE: There is a problem with this design!  If the application
     * allows enough click events to pile up in the circular queue so
     * they wrap around, it will cheerfully merge the newest forward
     * into the oldest, creating a bogus doubleclick and confusing
     * the queue-traversal logic rather badly.  Generally this won't
     * happen, because calling getmouse() marks old events invalid and
     * ineligible for merges.  The true solution to this problem would
     * be to timestamp each MEVENT and perform the obvious sanity check,
     * but the timer element would have to have sub-second resolution,
     * which would get us into portability trouble.
     */
    first_invalid = NULL;
    endLoop = (first_valid == NULL);
    ep = first_valid;
    while (!endLoop) {
	next = NEXT(ep);

	if (next == eventp) {
	    /* Will end the loop, but check event type and compact before */
	    endLoop = TRUE;
	} else if (!ValidEvent(next)) {
	    continue;
	} else {
	    /* merge click events forward */
	    if ((ep->bstate & BUTTON_CLICKED)
		&& (next->bstate & BUTTON_CLICKED)) {
		merge = FALSE;
		for (b = 1; b <= MAX_BUTTONS; ++b) {
		    if ((sp->_mouse_mask & MASK_DOUBLE_CLICK(b))
			&& (ep->bstate & MASK_CLICK(b))
			&& (next->bstate & MASK_CLICK(b))) {
			next->bstate &= ~MASK_CLICK(b);
			next->bstate |= MASK_DOUBLE_CLICK(b);
			merge = TRUE;
		    }
		}
		if (merge) {
		    Invalidate(ep);
		}
	    }

	    /* merge double-click events forward */
	    if ((ep->bstate & BUTTON_DOUBLE_CLICKED)
		&& (next->bstate & BUTTON_CLICKED)) {
		merge = FALSE;
		for (b = 1; b <= MAX_BUTTONS; ++b) {
		    if ((sp->_mouse_mask & MASK_TRIPLE_CLICK(b))
			&& (ep->bstate & MASK_DOUBLE_CLICK(b))
			&& (next->bstate & MASK_CLICK(b))) {
			next->bstate &= ~MASK_CLICK(b);
			next->bstate |= MASK_TRIPLE_CLICK(b);
			merge = TRUE;
		    }
		}
		if (merge) {
		    Invalidate(ep);
		}
	    }
	}

	/* Discard event if it does not match event mask */
	if (!(ep->bstate & sp->_mouse_mask2)) {
	    Invalidate(ep);
	}

	/* Compact valid events */
	if (!ValidEvent(ep)) {
	    if (ep == first_valid) {
		first_valid = next;
	    } else if (first_invalid == NULL) {
		first_invalid = ep;
	    }
	} else if (first_invalid != NULL) {
	    *first_invalid = *ep;
	    Invalidate(ep);
	    first_invalid = NEXT(first_invalid);
	}

	ep = next;
    }

    if (first_invalid == NULL) {
	first_invalid = eventp;
    }
    sp->_mouse_eventp = first_invalid;

#ifdef TRACE
    if (first_valid != NULL) {
	if (USE_TRACEF(TRACE_IEVENT)) {
	    _trace_slot(sp, "after mouse event queue compaction:");
	    _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d",
		    RunParams(sp, first_invalid, first_valid),
		    runcount);
	    _nc_unlock_global(tracef);
	}
	for (ep = first_valid; ep != first_invalid; ep = NEXT(ep)) {
	    if (ValidEvent(ep))
		TR(MY_TRACE,
		   ("_nc_mouse_parse: returning composite mouse event %s at slot %ld",
		    _nc_tracemouse(sp, ep),
		    (long) IndexEV(sp, ep)));
	}
    }
#endif /* TRACE */

    /* after all this, do we have a valid event? */
    return ValidEvent(PREV(first_invalid));
}


_nc_mouse_wrap(SCREEN *sp)
/* release mouse -- called by endwin() before shellout/exit */
{
    TR(MY_TRACE, ("_nc_mouse_wrap() called"));

    switch (sp->_mouse_type) {
    case M_XTERM:
	if (sp->_mouse_mask)
	    mouse_activate(sp, FALSE);
	break;
#if USE_GPM_SUPPORT
	/* GPM: pass all mouse events to next client */
    case M_GPM:
	if (sp->_mouse_mask)
	    mouse_activate(sp, FALSE);
	break;
#endif
#if USE_SYSMOUSE
    case M_SYSMOUSE:
	mouse_activate(sp, FALSE);
	break;
#endif
#ifdef USE_TERM_DRIVER
    case M_TERM_DRIVER:
	mouse_activate(sp, FALSE);
	break;
#endif
    case M_NONE:
	break;
    }
}


check_screen(TERMTYPE2 *tp)
{
    if (_nc_user_definable) {
	int have_XT = tigetflag("XT");
	int have_XM = tigetflag("XM");
	int have_bce = back_color_erase;
	bool have_kmouse = FALSE;
	bool use_sgr_39_49 = FALSE;
	char *name = _nc_first_name(tp->term_names);
	bool is_screen = !strncmp(name, "screen", 6);
	bool screen_base = (is_screen
			    && strchr(name, '.') == 0);

	if (!VALID_BOOLEAN(have_bce)) {
	    have_bce = FALSE;
	}
	if (!VALID_BOOLEAN(have_XM)) {
	    have_XM = FALSE;
	}
	if (!VALID_BOOLEAN(have_XT)) {
	    have_XT = FALSE;
	}
	if (VALID_STRING(key_mouse)) {
	    have_kmouse = !strcmp("\033[M", key_mouse);
	}
	if (VALID_STRING(orig_colors)) {
	    use_sgr_39_49 = uses_SGR_39_49(orig_colors);
	} else if (VALID_STRING(orig_pair)) {
	    use_sgr_39_49 = uses_SGR_39_49(orig_pair);
	}

	if (have_XM && have_XT) {
	    _nc_warning("screen's XT capability conflicts with XM");
	} else if (have_XT && screen_base) {
	    _nc_warning("screen's \"screen\" entries should not have XT set");
	} else if (have_XT) {
	    if (!have_kmouse && is_screen) {
		if (VALID_STRING(key_mouse)) {
		    _nc_warning("value of kmous inconsistent with screen's usage");
		} else {
		    _nc_warning("expected kmous capability with XT");
		}
	    }
	    if (!have_bce && max_colors > 0)
		_nc_warning("expected bce capability with XT");
	    if (!use_sgr_39_49 && have_bce && max_colors > 0)
		_nc_warning("expected orig_colors capability with XT to have 39/49 parameters");
	    if (VALID_STRING(to_status_line))
		_nc_warning("\"tsl\" capability is redundant, given XT");
	} else {
	    if (have_kmouse
		&& !have_XM
		&& !screen_base && strchr(name, '+') == 0) {
		_nc_warning("expected XT to be set, given kmous");
	    }
	}
    }
}


_nc_retrace_char(int code)
{
    T((T_RETURN("%c"), code));
    return (char) code;
}


set_foreground_color(NCURSES_SP_DCLx int fg, NCURSES_SP_OUTC outc)
{
#ifdef USE_TERM_DRIVER
    CallDriver_3(SP_PARM, td_color, TRUE, fg, outc);
#else
    if (set_a_foreground) {
	TPUTS_TRACE("set_a_foreground");
	NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
				TIPARM_1(set_a_foreground, fg),
				1, outc);
    } else {
	TPUTS_TRACE("set_foreground");
	NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
				TIPARM_1(set_foreground, toggled_colors(fg)),
				1, outc);
    }
#endif
}


drv_rescolors(TERMINAL_CONTROL_BLOCK * TCB)
{
    int result = FALSE;
    SCREEN *sp;

    AssertTCB();
    SetSP();

    if (orig_colors != 0) {
	NCURSES_PUTP2("orig_colors", orig_colors);
	result = TRUE;
    }
    return result;
}


open_tempfile(char *filename)
{
    FILE *result = 0;

    _nc_STRCPY(filename, "/tmp/XXXXXX", PATH_MAX);
#if HAVE_MKSTEMP
    {
	int oldmask = (int) umask(077);
	int fd = mkstemp(filename);
	if (fd >= 0)
	    result = fdopen(fd, "w");
	umask((mode_t) oldmask);
    }
#else
    if (tmpnam(filename) != 0)
	result = fopen(filename, "w");
#endif
    return result;
}


_nc_mouse_event(SCREEN *sp)
{
    MEVENT *eventp = sp->_mouse_eventp;
    bool result = FALSE;

    (void) eventp;

    switch (sp->_mouse_type) {
    case M_XTERM:
	/* xterm: never have to query, mouse events are in the keyboard stream */
#if USE_EMX_MOUSE
	{
	    char kbuf[3];

	    int i, res = read(M_FD(sp), &kbuf, 3);	/* Eat the prefix */
	    if (res != 3)
		printf("Got %d chars instead of 3 for prefix.\n", res);
	    for (i = 0; i < res; i++) {
		if (kbuf[i] != key_mouse[i])
		    printf("Got char %d instead of %d for prefix.\n",
			   (int) kbuf[i], (int) key_mouse[i]);
	    }
	    result = TRUE;
	}
#endif /* USE_EMX_MOUSE */
	break;

#if USE_GPM_SUPPORT
    case M_GPM:
	if (sp->_mouse_fd >= 0) {
	    /* query server for event, return TRUE if we find one */
	    Gpm_Event ev;

	    switch (my_Gpm_GetEvent(&ev)) {
	    case 0:
		/* Connection closed, drop the mouse. */
		sp->_mouse_fd = -1;
		break;
	    case 1:
		/* there's only one mouse... */
		eventp->id = NORMAL_EVENT;

		eventp->bstate = 0;
		switch (ev.type & 0x0f) {
		case (GPM_DOWN):
		    if (ev.buttons & GPM_B_LEFT)
			eventp->bstate |= BUTTON1_PRESSED;
		    if (ev.buttons & GPM_B_MIDDLE)
			eventp->bstate |= BUTTON2_PRESSED;
		    if (ev.buttons & GPM_B_RIGHT)
			eventp->bstate |= BUTTON3_PRESSED;
		    break;
		case (GPM_UP):
		    if (ev.buttons & GPM_B_LEFT)
			eventp->bstate |= BUTTON1_RELEASED;
		    if (ev.buttons & GPM_B_MIDDLE)
			eventp->bstate |= BUTTON2_RELEASED;
		    if (ev.buttons & GPM_B_RIGHT)
			eventp->bstate |= BUTTON3_RELEASED;
		    break;
		default:
		    eventp->bstate |= REPORT_MOUSE_POSITION;
		    break;
		}

		eventp->x = ev.x - 1;
		eventp->y = ev.y - 1;
		eventp->z = 0;

		/* bump the next-free pointer into the circular list */
		sp->_mouse_eventp = NEXT(eventp);
		result = TRUE;
		break;
	    }
	}
	break;
#endif

#if USE_SYSMOUSE
    case M_SYSMOUSE:
	if (sp->_sysmouse_head < sp->_sysmouse_tail) {
	    *eventp = sp->_sysmouse_fifo[sp->_sysmouse_head];

	    /*
	     * Point the fifo-head to the next possible location.  If there
	     * are none, reset the indices.  This may be interrupted by the
	     * signal handler, doing essentially the same reset.
	     */
	    sp->_sysmouse_head += 1;
	    if (sp->_sysmouse_head == sp->_sysmouse_tail) {
		sp->_sysmouse_tail = 0;
		sp->_sysmouse_head = 0;
	    }

	    /* bump the next-free pointer into the circular list */
	    sp->_mouse_eventp = eventp = NEXT(eventp);
	    result = TRUE;
	}
	break;
#endif /* USE_SYSMOUSE */

#ifdef USE_TERM_DRIVER
    case M_TERM_DRIVER:
	while (sp->_drv_mouse_head < sp->_drv_mouse_tail) {
	    *eventp = sp->_drv_mouse_fifo[sp->_drv_mouse_head];

	    /*
	     * Point the fifo-head to the next possible location.  If there
	     * are none, reset the indices.
	     */
	    sp->_drv_mouse_head += 1;
	    if (sp->_drv_mouse_head == sp->_drv_mouse_tail) {
		sp->_drv_mouse_tail = 0;
		sp->_drv_mouse_head = 0;
	    }

	    /* bump the next-free pointer into the circular list */
	    sp->_mouse_eventp = eventp = NEXT(eventp);
	    result = TRUE;
	}
	break;
#endif

    case M_NONE:
	break;
    }

    return result;		/* true if we found an event */
}


scroll_idl(NCURSES_SP_DCLx int n, int del, int ins, NCURSES_CH_T blank)
{
    int i;

    if (!((parm_delete_line || delete_line) && (parm_insert_line || insert_line)))
	return ERR;

    GoTo(NCURSES_SP_ARGx del, 0);
    UpdateAttrs(SP_PARM, blank);
    if (n == 1 && delete_line) {
	NCURSES_PUTP2("delete_line", delete_line);
    } else if (parm_delete_line) {
	TPUTS_TRACE("parm_delete_line");
	NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
				TIPARM_1(parm_delete_line, n),
				n,
				NCURSES_SP_NAME(_nc_outch));
    } else {			/* if (delete_line) */
	for (i = 0; i < n; i++) {
	    NCURSES_PUTP2("delete_line", delete_line);
	}
    }

    GoTo(NCURSES_SP_ARGx ins, 0);
    UpdateAttrs(SP_PARM, blank);
    if (n == 1 && insert_line) {
	NCURSES_PUTP2("insert_line", insert_line);
    } else if (parm_insert_line) {
	TPUTS_TRACE("parm_insert_line");
	NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
				TIPARM_1(parm_insert_line, n),
				n,
				NCURSES_SP_NAME(_nc_outch));
    } else {			/* if (insert_line) */
	for (i = 0; i < n; i++) {
	    NCURSES_PUTP2("insert_line", insert_line);
	}
    }

    return OK;
}


add_to_tab_list(char **append, const char *value)
{
    char *result = *append;
    char *copied = trimmed_tab_list(value);

    if (copied != 0 && *copied != '\0') {
	const char *comma = ",";
	size_t need = 1 + strlen(copied);

	if (*copied == ',')
	    comma = "";
	else if (!comma_is_needed(*append))
	    comma = "";

	need += strlen(comma);
	if (*append != 0)
	    need += strlen(*append);

	result = malloc(need);
	if (result == 0)
	    failed("add_to_tab_list");

	*result = '\0';
	if (*append != 0) {
	    _nc_STRCPY(result, *append, need);
	    free(*append);
	}
	_nc_STRCAT(result, comma, need);
	_nc_STRCAT(result, copied, need);

	*append = result;
    }
    free(copied);
    return result;
}


usage(void)
{
#define KEEP(s) s "\n"
    static const char msg[] =
    {
	KEEP("")
	KEEP("Options:")
	KEEP("  -S <<       read commands from standard input")
	KEEP("  -T TERM     use this instead of $TERM")
	KEEP("  -V          print curses-version")
	KEEP("  -x          do not try to clear scrollback")
	KEEP("")
	KEEP("Commands:")
	KEEP("  clear       clear the screen")
	KEEP("  init        initialize the terminal")
	KEEP("  reset       reinitialize the terminal")
	KEEP("  capname     unlike clear/init/reset, print value for capability \"capname\"")
    };
#undef KEEP
    (void) fprintf(stderr, "Usage: %s [options] [command]\n", prg_name);
    fputs(msg, stderr);
    ExitProgram(ErrUsage);
}


termattrs(void)
{
    return NCURSES_SP_NAME(termattrs) (CURRENT_SCREEN);
}


drv_Name(TERMINAL_CONTROL_BLOCK * TCB)
{
    (void) TCB;
    return "tinfo";
}


free_namelist(char **src)
{
    if (src != 0) {
	int n;
	for (n = 0; src[n] != 0; ++n)
	    free(src[n]);
	free(src);
    }
}


compose_header_set (compose_env_t *env, const char *name,
		    const char *value, int mode)
{
  int status;
  char *expansion = NULL;

  if (!value || value[0] == 0)
    return EINVAL;

  if (is_address_field (name)
      && mailvar_is_true (mailvar_name_inplacealiases))
    {
      struct mu_address hint = MU_ADDRESS_HINT_INITIALIZER;
      mu_address_t a;

      status = mu_address_create_hint (&a, value, &hint, 0);
      if (status)
	{
	  mu_error (_("Cannot parse address `%s': %s"),
		    value, mu_strerror (status));
	  return status;
	}
      util_address_expand_aliases (&a);
      status = mu_address_aget_printable (a, &expansion);
      mu_address_destroy (&a);
      if (status)
	{
	  mu_diag_funcall (MU_DIAG_ERROR, "mu_address_aget_printable",
			   NULL, status);
	  return status;
	}
      value = expansion;
    }

  if (!env->header
      && (status = mu_header_create (&env->header, NULL, 0)) != 0)
    {
      mu_error (_("Cannot create header: %s"), mu_strerror (status));
    }
  else
    {
      switch (mode)
	{
	case COMPOSE_REPLACE:
	  status = mu_header_set_value (env->header, name, value, 1);
	  break;

	case COMPOSE_APPEND:
	  status = mu_header_append (env->header, name, value);
	  break;

	case COMPOSE_SINGLE_LINE:
	  {
	    char *old_value;

	    if (mu_header_aget_value (env->header, name, &old_value) == 0
		&& old_value[0])
	      {
		if (is_address_field (name))
		  {
		    status = util_merge_addresses (&old_value, value);
		    if (status == 0)
		      status = mu_header_set_value (env->header, name,
						    old_value, 1);
		  }
		else
		  {
		    size_t size = strlen (old_value) + strlen (value) + 2;
		    char *p = realloc (old_value, size);
		    if (!p)
		      status = ENOMEM;
		    else
		      {
			old_value = p;
			strcat (old_value, ",");
			strcat (old_value, value);
			status = mu_header_set_value (env->header,
						      name, old_value,
						      1);
		      }
		  }
		free (old_value);
	      }
	    else
	      status = mu_header_set_value (env->header, name, value, 1);
	  }
	}
    }
  free (expansion);
  return status;
}


add_header (char *name, char *value, int mode)
{
  struct add_header *hp;

  if (!add_header_list)
    {
      int rc = mu_list_create (&add_header_list);
      if (rc)
	{
	  mu_error (_("Cannot create header list: %s"), mu_strerror (rc));
	  exit (1);
	}
    }

  hp = mu_alloc (sizeof (*hp));
  hp->mode = mode;
  hp->name = name;
  hp->value = value;
  mu_list_append (add_header_list, hp);
}


atchinfo_free (void *p)
{
  struct atchinfo *ap = p;
  free (ap->id);
  free (ap->encoding);
  free (ap->content_type);
  free (ap->name);
  free (ap->filename);
  mu_stream_destroy (&ap->source);
  free (ap);
}


escape_remove_attachment (int argc, char **argv, compose_env_t *env)
{
  size_t count;
  unsigned long n;
  char *p;

  if (escape_check_args (argc, argv, 2, 2))
    return 1;
  n = strtoul (argv[1], &p, 10);
  if (*p)
    {
      mu_error (_("not a valid number: %s"), argv[1]);
      return 1;
    }

  mu_list_count (env->attlist, &count);
  if (n == 0 || n > count)
    {
      mu_error (_("index out of range"));
      return 1;
    }

  return mu_list_remove_nth (env->attlist, n - 1);
}


void llhttp_set_lenient_keep_alive(llhttp_t* parser, int enabled) {
  if (enabled) {
    parser->lenient_flags |= LENIENT_KEEP_ALIVE;
  } else {
    parser->lenient_flags &= ~LENIENT_KEEP_ALIVE;
  }
}


int llhttp__on_status_complete(llhttp_t* s, const char* p, const char* endp) {
  int err;
  CALLBACK_MAYBE(s, on_status_complete);
  return err;
}


const char* llhttp_method_name(llhttp_method_t method) {
#define HTTP_METHOD_GEN(NUM, NAME, STRING) case HTTP_##NAME: return #STRING;
  switch (method) {
    HTTP_ALL_METHOD_MAP(HTTP_METHOD_GEN)
    default: abort();
  }
#undef HTTP_METHOD_GEN
}


llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) {
  return llhttp__internal_execute(parser, data, data + len);
}


int llhttp__on_header_value_complete(llhttp_t* s, const char* p, const char* endp) {
  int err;
  CALLBACK_MAYBE(s, on_header_value_complete);
  return err;
}


const char* llhttp_get_error_reason(const llhttp_t* parser) {
  return parser->reason;
}


void llhttp_init(llhttp_t* parser, llhttp_type_t type,
                 const llhttp_settings_t* settings) {
  llhttp__internal_init(parser);

  parser->type = type;
  parser->settings = (void*) settings;
}


int ssl_do_client_cert_cb(SSL *s, X509 **px509, EVP_PKEY **ppkey)
{
    int i = 0;
#ifndef OPENSSL_NO_ENGINE
    if (s->ctx->client_cert_engine) {
        i = tls_engine_load_ssl_client_cert(s, px509, ppkey);
        if (i != 0)
            return i;
    }
#endif
    if (s->ctx->client_cert_cb)
        i = s->ctx->client_cert_cb(s, px509, ppkey);
    return i;
}


void SSL_set0_CA_list(SSL *s, STACK_OF(X509_NAME) *name_list)
{
    set0_CA_list(&s->ca_names, name_list);
}


STACK_OF(X509_NAME) *SSL_dup_CA_list(const STACK_OF(X509_NAME) *sk)
{
    int i;
    const int num = sk_X509_NAME_num(sk);
    STACK_OF(X509_NAME) *ret;
    X509_NAME *name;

    ret = sk_X509_NAME_new_reserve(NULL, num);
    if (ret == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_MALLOC_FAILURE);
        return NULL;
    }
    for (i = 0; i < num; i++) {
        name = X509_NAME_dup(sk_X509_NAME_value(sk, i));
        if (name == NULL) {
            ERR_raise(ERR_LIB_SSL, ERR_R_MALLOC_FAILURE);
            sk_X509_NAME_pop_free(ret, X509_NAME_free);
            return NULL;
        }
        sk_X509_NAME_push(ret, name);   /* Cannot fail after reserve call */
    }
    return ret;
}


STACK_OF(X509_NAME) *SSL_load_client_CA_file_ex(const char *file,
                                                OSSL_LIB_CTX *libctx,
                                                const char *propq)
{
    BIO *in = BIO_new(BIO_s_file());
    X509 *x = NULL;
    X509_NAME *xn = NULL;
    STACK_OF(X509_NAME) *ret = NULL;
    LHASH_OF(X509_NAME) *name_hash = lh_X509_NAME_new(xname_hash, xname_cmp);
    OSSL_LIB_CTX *prev_libctx = NULL;

    if ((name_hash == NULL) || (in == NULL)) {
        ERR_raise(ERR_LIB_SSL, ERR_R_MALLOC_FAILURE);
        goto err;
    }

    x = X509_new_ex(libctx, propq);
    if (x == NULL) {
        ERR_raise(ERR_LIB_SSL, ERR_R_MALLOC_FAILURE);
        goto err;
    }
    if (BIO_read_filename(in, file) <= 0)
        goto err;

    /* Internally lh_X509_NAME_retrieve() needs the libctx to retrieve SHA1 */
    prev_libctx = OSSL_LIB_CTX_set0_default(libctx);
    for (;;) {
        if (PEM_read_bio_X509(in, &x, NULL, NULL) == NULL)
            break;
        if (ret == NULL) {
            ret = sk_X509_NAME_new_null();
            if (ret == NULL) {
                ERR_raise(ERR_LIB_SSL, ERR_R_MALLOC_FAILURE);
                goto err;
            }
        }
        if ((xn = X509_get_subject_name(x)) == NULL)
            goto err;
        /* check for duplicates */
        xn = X509_NAME_dup(xn);
        if (xn == NULL)
            goto err;
        if (lh_X509_NAME_retrieve(name_hash, xn) != NULL) {
            /* Duplicate. */
            X509_NAME_free(xn);
            xn = NULL;
        } else {
            lh_X509_NAME_insert(name_hash, xn);
            if (!sk_X509_NAME_push(ret, xn))
                goto err;
        }
    }
    goto done;

 err:
    X509_NAME_free(xn);
    sk_X509_NAME_pop_free(ret, X509_NAME_free);
    ret = NULL;
 done:
    /* restore the old libctx */
    OSSL_LIB_CTX_set0_default(prev_libctx);
    BIO_free(in);
    X509_free(x);
    lh_X509_NAME_free(name_hash);
    if (ret != NULL)
        ERR_clear_error();
    return ret;
}


int ossl_statem_client_construct_message(SSL *s, WPACKET *pkt,
                                         confunc_f *confunc, int *mt)
{
    OSSL_STATEM *st = &s->statem;

    switch (st->hand_state) {
    default:
        /* Shouldn't happen */
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_HANDSHAKE_STATE);
        return 0;

    case TLS_ST_CW_CHANGE:
        if (SSL_IS_DTLS(s))
            *confunc = dtls_construct_change_cipher_spec;
        else
            *confunc = tls_construct_change_cipher_spec;
        *mt = SSL3_MT_CHANGE_CIPHER_SPEC;
        break;

    case TLS_ST_CW_CLNT_HELLO:
        *confunc = tls_construct_client_hello;
        *mt = SSL3_MT_CLIENT_HELLO;
        break;

    case TLS_ST_CW_END_OF_EARLY_DATA:
        *confunc = tls_construct_end_of_early_data;
        *mt = SSL3_MT_END_OF_EARLY_DATA;
        break;

    case TLS_ST_PENDING_EARLY_DATA_END:
        *confunc = NULL;
        *mt = SSL3_MT_DUMMY;
        break;

    case TLS_ST_CW_CERT:
        *confunc = tls_construct_client_certificate;
        *mt = SSL3_MT_CERTIFICATE;
        break;

    case TLS_ST_CW_KEY_EXCH:
        *confunc = tls_construct_client_key_exchange;
        *mt = SSL3_MT_CLIENT_KEY_EXCHANGE;
        break;

    case TLS_ST_CW_CERT_VRFY:
        *confunc = tls_construct_cert_verify;
        *mt = SSL3_MT_CERTIFICATE_VERIFY;
        break;

#if !defined(OPENSSL_NO_NEXTPROTONEG)
    case TLS_ST_CW_NEXT_PROTO:
        *confunc = tls_construct_next_proto;
        *mt = SSL3_MT_NEXT_PROTO;
        break;
#endif
    case TLS_ST_CW_FINISHED:
        *confunc = tls_construct_finished;
        *mt = SSL3_MT_FINISHED;
        break;

    case TLS_ST_CW_KEY_UPDATE:
        *confunc = tls_construct_key_update;
        *mt = SSL3_MT_KEY_UPDATE;
        break;
    }

    return 1;
}


MSG_PROCESS_RETURN ossl_statem_client_process_message(SSL *s, PACKET *pkt)
{
    OSSL_STATEM *st = &s->statem;

    switch (st->hand_state) {
    default:
        /* Shouldn't happen */
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
        return MSG_PROCESS_ERROR;

    case TLS_ST_CR_SRVR_HELLO:
        return tls_process_server_hello(s, pkt);

    case DTLS_ST_CR_HELLO_VERIFY_REQUEST:
        return dtls_process_hello_verify(s, pkt);

    case TLS_ST_CR_CERT:
        return tls_process_server_certificate(s, pkt);

    case TLS_ST_CR_CERT_VRFY:
        return tls_process_cert_verify(s, pkt);

    case TLS_ST_CR_CERT_STATUS:
        return tls_process_cert_status(s, pkt);

    case TLS_ST_CR_KEY_EXCH:
        return tls_process_key_exchange(s, pkt);

    case TLS_ST_CR_CERT_REQ:
        return tls_process_certificate_request(s, pkt);

    case TLS_ST_CR_SRVR_DONE:
        return tls_process_server_done(s, pkt);

    case TLS_ST_CR_CHANGE:
        return tls_process_change_cipher_spec(s, pkt);

    case TLS_ST_CR_SESSION_TICKET:
        return tls_process_new_session_ticket(s, pkt);

    case TLS_ST_CR_FINISHED:
        return tls_process_finished(s, pkt);

    case TLS_ST_CR_HELLO_REQ:
        return tls_process_hello_req(s, pkt);

    case TLS_ST_CR_ENCRYPTED_EXTENSIONS:
        return tls_process_encrypted_extensions(s, pkt);

    case TLS_ST_CR_KEY_UPDATE:
        return tls_process_key_update(s, pkt);
    }
}


static int tls_process_ske_psk_preamble(SSL *s, PACKET *pkt)
{
#ifndef OPENSSL_NO_PSK
    PACKET psk_identity_hint;

    /* PSK ciphersuites are preceded by an identity hint */

    if (!PACKET_get_length_prefixed_2(pkt, &psk_identity_hint)) {
        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_LENGTH_MISMATCH);
        return 0;
    }

    /*
     * Store PSK identity hint for later use, hint is used in
     * tls_construct_client_key_exchange.  Assume that the maximum length of
     * a PSK identity hint can be as long as the maximum length of a PSK
     * identity.
     */
    if (PACKET_remaining(&psk_identity_hint) > PSK_MAX_IDENTITY_LEN) {
        SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_R_DATA_LENGTH_TOO_LONG);
        return 0;
    }

    if (PACKET_remaining(&psk_identity_hint) == 0) {
        OPENSSL_free(s->session->psk_identity_hint);
        s->session->psk_identity_hint = NULL;
    } else if (!PACKET_strndup(&psk_identity_hint,
                               &s->session->psk_identity_hint)) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
        return 0;
    }

    return 1;
#else
    SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
    return 0;
#endif
}


WORK_STATE ossl_statem_client_post_work(SSL *s, WORK_STATE wst)
{
    OSSL_STATEM *st = &s->statem;

    s->init_num = 0;

    switch (st->hand_state) {
    default:
        /* No post work to be done */
        break;

    case TLS_ST_CW_CLNT_HELLO:
        if (s->early_data_state == SSL_EARLY_DATA_CONNECTING
                && s->max_early_data > 0) {
            /*
             * We haven't selected TLSv1.3 yet so we don't call the change
             * cipher state function associated with the SSL_METHOD. Instead
             * we call tls13_change_cipher_state() directly.
             */
            if ((s->options & SSL_OP_ENABLE_MIDDLEBOX_COMPAT) == 0) {
                if (!tls13_change_cipher_state(s,
                            SSL3_CC_EARLY | SSL3_CHANGE_CIPHER_CLIENT_WRITE)) {
                    /* SSLfatal() already called */
                    return WORK_ERROR;
                }
            }
            /* else we're in compat mode so we delay flushing until after CCS */
        } else if (!statem_flush(s)) {
            return WORK_MORE_A;
        }

        if (SSL_IS_DTLS(s)) {
            /* Treat the next message as the first packet */
            s->first_packet = 1;
        }
        break;

    case TLS_ST_CW_END_OF_EARLY_DATA:
        /*
         * We set the enc_write_ctx back to NULL because we may end up writing
         * in cleartext again if we get a HelloRetryRequest from the server.
         */
        EVP_CIPHER_CTX_free(s->enc_write_ctx);
        s->enc_write_ctx = NULL;
        break;

    case TLS_ST_CW_KEY_EXCH:
        if (tls_client_key_exchange_post_work(s) == 0) {
            /* SSLfatal() already called */
            return WORK_ERROR;
        }
        break;

    case TLS_ST_CW_CHANGE:
        if (SSL_IS_TLS13(s) || s->hello_retry_request == SSL_HRR_PENDING)
            break;
        if (s->early_data_state == SSL_EARLY_DATA_CONNECTING
                    && s->max_early_data > 0) {
            /*
             * We haven't selected TLSv1.3 yet so we don't call the change
             * cipher state function associated with the SSL_METHOD. Instead
             * we call tls13_change_cipher_state() directly.
             */
            if (!tls13_change_cipher_state(s,
                        SSL3_CC_EARLY | SSL3_CHANGE_CIPHER_CLIENT_WRITE))
                return WORK_ERROR;
            break;
        }
        s->session->cipher = s->s3.tmp.new_cipher;
#ifdef OPENSSL_NO_COMP
        s->session->compress_meth = 0;
#else
        if (s->s3.tmp.new_compression == NULL)
            s->session->compress_meth = 0;
        else
            s->session->compress_meth = s->s3.tmp.new_compression->id;
#endif
        if (!s->method->ssl3_enc->setup_key_block(s)) {
            /* SSLfatal() already called */
            return WORK_ERROR;
        }

        if (!s->method->ssl3_enc->change_cipher_state(s,
                                          SSL3_CHANGE_CIPHER_CLIENT_WRITE)) {
            /* SSLfatal() already called */
            return WORK_ERROR;
        }

        if (SSL_IS_DTLS(s)) {
#ifndef OPENSSL_NO_SCTP
            if (s->hit) {
                /*
                 * Change to new shared key of SCTP-Auth, will be ignored if
                 * no SCTP used.
                 */
                BIO_ctrl(SSL_get_wbio(s), BIO_CTRL_DGRAM_SCTP_NEXT_AUTH_KEY,
                         0, NULL);
            }
#endif

            dtls1_reset_seq_numbers(s, SSL3_CC_WRITE);
        }
        break;

    case TLS_ST_CW_FINISHED:
#ifndef OPENSSL_NO_SCTP
        if (wst == WORK_MORE_A && SSL_IS_DTLS(s) && s->hit == 0) {
            /*
             * Change to new shared key of SCTP-Auth, will be ignored if
             * no SCTP used.
             */
            BIO_ctrl(SSL_get_wbio(s), BIO_CTRL_DGRAM_SCTP_NEXT_AUTH_KEY,
                     0, NULL);
        }
#endif
        if (statem_flush(s) != 1)
            return WORK_MORE_B;

        if (SSL_IS_TLS13(s)) {
            if (!tls13_save_handshake_digest_for_pha(s)) {
                /* SSLfatal() already called */
                return WORK_ERROR;
            }
            if (s->post_handshake_auth != SSL_PHA_REQUESTED) {
                if (!s->method->ssl3_enc->change_cipher_state(s,
                        SSL3_CC_APPLICATION | SSL3_CHANGE_CIPHER_CLIENT_WRITE)) {
                    /* SSLfatal() already called */
                    return WORK_ERROR;
                }
            }
        }
        break;

    case TLS_ST_CW_KEY_UPDATE:
        if (statem_flush(s) != 1)
            return WORK_MORE_A;
        if (!tls13_update_key(s, 1)) {
            /* SSLfatal() already called */
            return WORK_ERROR;
        }
        break;
    }

    return WORK_FINISHED_CONTINUE;
}


void ssl_cert_set_cert_cb(CERT *c, int (*cb) (SSL *ssl, void *arg), void *arg)
{
    c->cert_cb = cb;
    c->cert_cb_arg = arg;
}


int ssl_get_security_level_bits(const SSL *s, const SSL_CTX *ctx, int *levelp)
{
    int level;
    /*
     * note that there's a corresponding minbits_table
     * in crypto/x509/x509_vfy.c that's used for checking the security level
     * of RSA and DSA keys
     */
    static const int minbits_table[5 + 1] = { 0, 80, 112, 128, 192, 256 };

    if (ctx != NULL)
        level = SSL_CTX_get_security_level(ctx);
    else
        level = SSL_get_security_level(s);

    if (level > 5)
        level = 5;
    else if (level < 0)
        level = 0;

    if (levelp != NULL)
        *levelp = level;

    return minbits_table[level];
}


static int tls_process_ske_dhe(SSL *s, PACKET *pkt, EVP_PKEY **pkey)
{
    PACKET prime, generator, pub_key;
    EVP_PKEY *peer_tmp = NULL;
    BIGNUM *p = NULL, *g = NULL, *bnpub_key = NULL;
    EVP_PKEY_CTX *pctx = NULL;
    OSSL_PARAM *params = NULL;
    OSSL_PARAM_BLD *tmpl = NULL;
    int ret = 0;

    if (!PACKET_get_length_prefixed_2(pkt, &prime)
        || !PACKET_get_length_prefixed_2(pkt, &generator)
        || !PACKET_get_length_prefixed_2(pkt, &pub_key)) {
        SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_LENGTH_MISMATCH);
        return 0;
    }

    p = BN_bin2bn(PACKET_data(&prime), (int)PACKET_remaining(&prime), NULL);
    g = BN_bin2bn(PACKET_data(&generator), (int)PACKET_remaining(&generator),
                  NULL);
    bnpub_key = BN_bin2bn(PACKET_data(&pub_key),
                          (int)PACKET_remaining(&pub_key), NULL);
    if (p == NULL || g == NULL || bnpub_key == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_BN_LIB);
        goto err;
    }

    tmpl = OSSL_PARAM_BLD_new();
    if (tmpl == NULL
            || !OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_FFC_P, p)
            || !OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_FFC_G, g)
            || !OSSL_PARAM_BLD_push_BN(tmpl, OSSL_PKEY_PARAM_PUB_KEY,
                                       bnpub_key)
            || (params = OSSL_PARAM_BLD_to_param(tmpl)) == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
        goto err;
    }

    pctx = EVP_PKEY_CTX_new_from_name(s->ctx->libctx, "DH", s->ctx->propq);
    if (pctx == NULL) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
        goto err;
    }
    if (EVP_PKEY_fromdata_init(pctx) <= 0
            || EVP_PKEY_fromdata(pctx, &peer_tmp, EVP_PKEY_KEYPAIR, params) <= 0) {
        SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_DH_VALUE);
        goto err;
    }

    EVP_PKEY_CTX_free(pctx);
    pctx = EVP_PKEY_CTX_new_from_pkey(s->ctx->libctx, peer_tmp, s->ctx->propq);
    if (pctx == NULL
            /*
             * EVP_PKEY_param_check() will verify that the DH params are using
             * a safe prime. In this context, because we're using ephemeral DH,
             * we're ok with it not being a safe prime.
             * EVP_PKEY_param_check_quick() skips the safe prime check.
             */
            || EVP_PKEY_param_check_quick(pctx) != 1
            || EVP_PKEY_public_check(pctx) != 1) {
        SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_BAD_DH_VALUE);
        goto err;
    }

    if (!ssl_security(s, SSL_SECOP_TMP_DH,
                      EVP_PKEY_get_security_bits(peer_tmp),
                      0, peer_tmp)) {
        SSLfatal(s, SSL_AD_HANDSHAKE_FAILURE, SSL_R_DH_KEY_TOO_SMALL);
        goto err;
    }

    s->s3.peer_tmp = peer_tmp;
    peer_tmp = NULL;

    /*
     * FIXME: This makes assumptions about which ciphersuites come with
     * public keys. We should have a less ad-hoc way of doing this
     */
    if (s->s3.tmp.new_cipher->algorithm_auth & (SSL_aRSA | SSL_aDSS))
        *pkey = X509_get0_pubkey(s->session->peer);
    /* else anonymous DH, so no certificate or pkey. */

    ret = 1;

 err:
    OSSL_PARAM_BLD_free(tmpl);
    OSSL_PARAM_free(params);
    EVP_PKEY_free(peer_tmp);
    EVP_PKEY_CTX_free(pctx);
    BN_free(p);
    BN_free(g);
    BN_free(bnpub_key);

    return ret;
}


int tls_process_initial_server_flight(SSL *s)
{
    /*
     * at this point we check that we have the required stuff from
     * the server
     */
    if (!ssl3_check_cert_and_algorithm(s)) {
        /* SSLfatal() already called */
        return 0;
    }

    /*
     * Call the ocsp status callback if needed. The |ext.ocsp.resp| and
     * |ext.ocsp.resp_len| values will be set if we actually received a status
     * message, or NULL and -1 otherwise
     */
    if (s->ext.status_type != TLSEXT_STATUSTYPE_nothing
            && s->ctx->ext.status_cb != NULL) {
        int ret = s->ctx->ext.status_cb(s, s->ctx->ext.status_arg);

        if (ret == 0) {
            SSLfatal(s, SSL_AD_BAD_CERTIFICATE_STATUS_RESPONSE,
                     SSL_R_INVALID_STATUS_RESPONSE);
            return 0;
        }
        if (ret < 0) {
            SSLfatal(s, SSL_AD_INTERNAL_ERROR,
                     SSL_R_OCSP_CALLBACK_FAILURE);
            return 0;
        }
    }
#ifndef OPENSSL_NO_CT
    if (s->ct_validation_callback != NULL) {
        /* Note we validate the SCTs whether or not we abort on error */
        if (!ssl_validate_ct(s) && (s->verify_mode & SSL_VERIFY_PEER)) {
            /* SSLfatal() already called */
            return 0;
        }
    }
#endif

    return 1;
}


void qxl_render_update_area_bh(void *opaque)
{
    PCIQXLDevice *qxl = opaque;

    qemu_mutex_lock(&qxl->ssd.lock);
    qxl_render_update_area_unlocked(qxl);
    qemu_mutex_unlock(&qxl->ssd.lock);
}


static inline void vmsvga_update_rect_flush(struct vmsvga_state_s *s)
{
    struct vmsvga_rect_s *rect;

    if (s->invalidated) {
        s->redraw_fifo_first = s->redraw_fifo_last;
        return;
    }
    /* Overlapping region updates can be optimised out here - if someone
     * knows a smart algorithm to do that, please share.  */
    while (s->redraw_fifo_first != s->redraw_fifo_last) {
        rect = &s->redraw_fifo[s->redraw_fifo_first++];
        s->redraw_fifo_first &= REDRAW_FIFO_LEN - 1;
        vmsvga_update_rect(s, rect->x, rect->y, rect->w, rect->h);
    }
}


QEMUCursor *cursor_builtin_hidden(void)
{
    return cursor_parse_xpm(cursor_hidden_xpm);
}


static void vmsvga_index_write(void *opaque, uint32_t address, uint32_t index)
{
    struct vmsvga_state_s *s = opaque;

    s->index = index;
}


bool SetGroups(SecureContext* sc, const char* groups) {
  return SSL_CTX_set1_groups_list(**sc, groups) == 1;
}


MaybeLocal<Value> GetCipherStandardName(
    Environment* env,
    const SSLPointer& ssl) {
  return GetCipherStandardName(env, SSL_get_current_cipher(ssl.get()));
}


MaybeLocal<Value> GetExponentString(
    Environment* env,
    const BIOPointer& bio,
    const BIGNUM* e) {
  uint64_t exponent_word = static_cast<uint64_t>(BN_get_word(e));
  uint32_t lo = static_cast<uint32_t>(exponent_word);
  uint32_t hi = static_cast<uint32_t>(exponent_word >> 32);
  if (hi == 0)
    BIO_printf(bio.get(), "0x%x", lo);
  else
    BIO_printf(bio.get(), "0x%x%08x", hi, lo);

  return ToV8Value(env, bio);
}


MaybeLocal<Value> GetCurveNistName(Environment* env, const int nid) {
  const char* nist = EC_curve_nid2nist(nid);
  return nist != nullptr ?
      MaybeLocal<Value>(OneByteString(env->isolate(), nist)) :
      MaybeLocal<Value>(Undefined(env->isolate()));
}


MaybeLocal<Value> GetFingerprintDigest(
    Environment* env,
    const EVP_MD* method,
    X509* cert) {
  unsigned char md[EVP_MAX_MD_SIZE];
  unsigned int md_size;
  char fingerprint[EVP_MAX_MD_SIZE * 3 + 1];

  if (X509_digest(cert, method, md, &md_size)) {
    AddFingerprintDigest(md, md_size, &fingerprint);
    return OneByteString(env->isolate(), fingerprint);
  }
  return Undefined(env->isolate());
}


MaybeLocal<Object> GetCipherInfo(Environment* env, const SSLPointer& ssl) {
  if (SSL_get_current_cipher(ssl.get()) == nullptr)
    return MaybeLocal<Object>();
  EscapableHandleScope scope(env->isolate());
  Local<Object> info = Object::New(env->isolate());

  if (!Set<Value>(env->context(),
                  info,
                  env->name_string(),
                  GetCipherName(env, ssl)) ||
      !Set<Value>(env->context(),
                  info,
                  env->standard_name_string(),
                  GetCipherStandardName(env, ssl)) ||
      !Set<Value>(env->context(),
                  info,
                  env->version_string(),
                  GetCipherVersion(env, ssl))) {
    return MaybeLocal<Object>();
  }

  return scope.Escape(info);
}


MaybeLocal<Value> GetCipherStandardName(
    Environment* env,
    const SSL_CIPHER* cipher) {
  return GetCipherValue(env, cipher, SSL_CIPHER_standard_name);
}


BaseObject::TransferMode X509Certificate::GetTransferMode() const {
  return BaseObject::TransferMode::kCloneable;
}


MaybeLocal<Object> X509Certificate::New(
    Environment* env,
    std::shared_ptr<ManagedX509> cert,
    STACK_OF(X509)* issuer_chain) {
  EscapableHandleScope scope(env->isolate());
  Local<Function> ctor;
  if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
    return MaybeLocal<Object>();

  Local<Object> obj;
  if (!ctor->NewInstance(env->context()).ToLocal(&obj))
    return MaybeLocal<Object>();

  new X509Certificate(env, obj, std::move(cert), issuer_chain);
  return scope.Escape(obj);
}


void X509Certificate::Fingerprint(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.Holder());
  Local<Value> ret;
  if (GetFingerprintDigest(env, EVP_sha1(), cert->get()).ToLocal(&ret))
    args.GetReturnValue().Set(ret);
}


void X509Certificate::SerialNumber(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.Holder());
  Local<Value> ret;
  if (GetSerialNumber(env, cert->get()).ToLocal(&ret))
    args.GetReturnValue().Set(ret);
}


MaybeLocal<Value> GetValidationErrorReason(Environment* env, int err) {
  if (err == 0)
    return Undefined(env->isolate());
  const char* reason = X509_verify_cert_error_string(err);
  return OneByteString(env->isolate(), reason);
}


void X509Certificate::PublicKey(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  X509Certificate* cert;
  ASSIGN_OR_RETURN_UNWRAP(&cert, args.Holder());

  EVPKeyPointer pkey(X509_get_pubkey(cert->get()));
  ManagedEVPPKey epkey(std::move(pkey));
  std::shared_ptr<KeyObjectData> key_data =
      KeyObjectData::CreateAsymmetric(kKeyTypePublic, epkey);

  Local<Value> ret;
  if (KeyObjectHandle::Create(env, key_data).ToLocal(&ret))
    args.GetReturnValue().Set(ret);
}


MaybeLocal<Value> GetECGroup(
    Environment* env,
    const EC_GROUP* group,
    const ECPointer& ec) {
  if (group == nullptr)
    return Undefined(env->isolate());

  int bits = EC_GROUP_order_bits(group);
  if (bits <= 0)
    return Undefined(env->isolate());

  return Integer::New(env->isolate(), bits);
}


MaybeLocal<Object> X509Certificate::GetPeerCert(
    Environment* env,
    const SSLPointer& ssl,
    GetPeerCertificateFlag flag) {
  ClearErrorOnReturn clear_error_on_return;
  Local<Object> obj;
  MaybeLocal<Object> maybe_cert;

  bool is_server =
      static_cast<int>(flag) & static_cast<int>(GetPeerCertificateFlag::SERVER);

  X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr);
  STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get());
  if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0))
    return MaybeLocal<Object>();

  std::vector<Local<Value>> certs;

  if (!cert) {
    cert.reset(sk_X509_value(ssl_certs, 0));
    sk_X509_delete(ssl_certs, 0);
  }

  return sk_X509_num(ssl_certs)
      ? New(env, std::move(cert), ssl_certs)
      : New(env, std::move(cert));
}


void X509Certificate::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("cert", cert_);
}


v8::MaybeLocal<v8::Value> GetSubjectAltNameString(
    Environment* env,
    const BIOPointer& bio,
    X509* cert) {
  int index = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
  if (index < 0)
    return Undefined(env->isolate());

  X509_EXTENSION* ext = X509_get_ext(cert, index);
  CHECK_NOT_NULL(ext);

  if (!SafeX509SubjectAltNamePrint(bio, ext)) {
    USE(BIO_reset(bio.get()));
    return v8::Null(env->isolate());
  }

  return ToV8Value(env, bio);
}


long VerifyPeerCertificate(  // NOLINT(runtime/int)
    const SSLPointer& ssl,
    long def) {  // NOLINT(runtime/int)
  long err = def;  // NOLINT(runtime/int)
  if (X509* peer_cert = SSL_get_peer_certificate(ssl.get())) {
    X509_free(peer_cert);
    err = SSL_get_verify_result(ssl.get());
  } else {
    const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(ssl.get());
    const SSL_SESSION* sess = SSL_get_session(ssl.get());
    // Allow no-cert for PSK authentication in TLS1.2 and lower.
    // In TLS1.3 check that session was reused because TLS1.3 PSK
    // looks like session resumption.
    if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk ||
        (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION &&
         SSL_session_reused(ssl.get()))) {
      return X509_V_OK;
    }
  }
  return err;
}


MaybeLocal<Object> GetRawDERCertificate(Environment* env, X509* cert) {
  int size = i2d_X509(cert, nullptr);

  std::unique_ptr<BackingStore> bs;
  {
    NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
    bs = ArrayBuffer::NewBackingStore(env->isolate(), size);
  }

  unsigned char* serialized = reinterpret_cast<unsigned char*>(bs->Data());
  CHECK_GE(i2d_X509(cert, &serialized), 0);

  Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
  return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Object>());
}


static int equalBN(const char *op, const BIGNUM *expected, const BIGNUM *actual)
{
    if (BN_cmp(expected, actual) == 0)
        return 1;

    TEST_error("unexpected %s value", op);
    TEST_BN_eq(expected, actual);
    return 0;
}


static int getint(STANZA *s, int *out, const char *attribute)
{
    BIGNUM *ret;
    BN_ULONG word;
    int st = 0;

    if (!TEST_ptr(ret = getBN(s, attribute))
            || !TEST_ulong_le(word = BN_get_word(ret), INT_MAX))
        goto err;

    *out = (int)word;
    st = 1;
 err:
    BN_free(ret);
    return st;
}


static int test_asc2bn(void)
{
    BIGNUM *bn = NULL;
    int st = 0;

    if (!TEST_ptr(bn = BN_new()))
        goto err;

    if (!TEST_true(BN_asc2bn(&bn, "0"))
            || !TEST_BN_eq_zero(bn)
            || !TEST_BN_ge_zero(bn))
        goto err;

    if (!TEST_true(BN_asc2bn(&bn, "256"))
            || !TEST_BN_eq_word(bn, 256)
            || !TEST_BN_ge_zero(bn))
        goto err;

    if (!TEST_true(BN_asc2bn(&bn, "-42"))
            || !TEST_BN_abs_eq_word(bn, 42)
            || !TEST_BN_lt_zero(bn))
        goto err;

    if (!TEST_true(BN_asc2bn(&bn, "0x1234"))
            || !TEST_BN_eq_word(bn, 0x1234)
            || !TEST_BN_ge_zero(bn))
        goto err;

    if (!TEST_true(BN_asc2bn(&bn, "0X1234"))
            || !TEST_BN_eq_word(bn, 0x1234)
            || !TEST_BN_ge_zero(bn))
        goto err;

    if (!TEST_true(BN_asc2bn(&bn, "-0xabcd"))
            || !TEST_BN_abs_eq_word(bn, 0xabcd)
            || !TEST_BN_lt_zero(bn))
        goto err;

    if (!TEST_true(BN_asc2bn(&bn, "-0"))
            || !TEST_BN_eq_zero(bn)
            || !TEST_BN_ge_zero(bn))
        goto err;

    if (!TEST_true(BN_asc2bn(&bn, "123trailing garbage is ignored"))
            || !TEST_BN_eq_word(bn, 123)
            || !TEST_BN_ge_zero(bn))
        goto err;

    st = 1;
 err:
    BN_free(bn);
    return st;
}


static int rand_neg(void)
{
    static unsigned int neg = 0;
    static int sign[8] = { 0, 0, 0, 1, 1, 0, 1, 1 };

    return sign[(neg++) % 8];
}


void cleanup_tests(void)
{
    BN_CTX_free(ctx);
}


static int test_ctx_consttime_flag(void)
{
    /*-
     * The constant-time flag should not "leak" among BN_CTX frames:
     *
     * - test_ctx_set_ct_flag() starts a frame in the given BN_CTX and
     *   sets the BN_FLG_CONSTTIME flag on some of the BIGNUMs obtained
     *   from the frame before ending it.
     * - test_ctx_check_ct_flag() then starts a new frame and gets a
     *   number of BIGNUMs from it. In absence of leaks, none of the
     *   BIGNUMs in the new frame should have BN_FLG_CONSTTIME set.
     *
     * In actual BN_CTX usage inside libcrypto the leak could happen at
     * any depth level in the BN_CTX stack, with varying results
     * depending on the patterns of sibling trees of nested function
     * calls sharing the same BN_CTX object, and the effect of
     * unintended BN_FLG_CONSTTIME on the called BN_* functions.
     *
     * This simple unit test abstracts away this complexity and verifies
     * that the leak does not happen between two sibling functions
     * sharing the same BN_CTX object at the same level of nesting.
     *
     */
    BN_CTX *nctx = NULL;
    BN_CTX *sctx = NULL;
    size_t i = 0;
    int st = 0;

    if (!TEST_ptr(nctx = BN_CTX_new())
            || !TEST_ptr(sctx = BN_CTX_secure_new()))
        goto err;

    for (i = 0; i < 2; i++) {
        BN_CTX *c = i == 0 ? nctx : sctx;
        if (!TEST_true(test_ctx_set_ct_flag(c))
                || !TEST_true(test_ctx_check_ct_flag(c)))
            goto err;
    }

    st = 1;
 err:
    BN_CTX_free(nctx);
    BN_CTX_free(sctx);
    return st;
}


static int test_signed_mod_replace_ab(int n)
{
    BIGNUM *a = NULL, *b = NULL, *c = NULL, *d = NULL;
    int st = 0;

    if (!TEST_ptr(a = set_signed_bn(signed_mod_tests[n].n))
            || !TEST_ptr(b = set_signed_bn(signed_mod_tests[n].divisor))
            || !TEST_ptr(c = set_signed_bn(signed_mod_tests[n].result))
            || !TEST_ptr(d = set_signed_bn(signed_mod_tests[n].remainder)))
        goto err;

    if (TEST_true(BN_div(a, b, a, b, ctx))
            && TEST_BN_eq(a, c)
            && TEST_BN_eq(b, d))
        st = 1;
 err:
    BN_free(a);
    BN_free(b);
    BN_free(c);
    BN_free(d);
    return st;
}


static int test_gf2m_modsqrt(void)
{
    BIGNUM *a = NULL, *b[2] = {NULL,NULL}, *c = NULL, *d = NULL;
    BIGNUM *e = NULL, *f = NULL;
    int i, j, st = 0;

    if (!TEST_ptr(a = BN_new())
            || !TEST_ptr(b[0] = BN_new())
            || !TEST_ptr(b[1] = BN_new())
            || !TEST_ptr(c = BN_new())
            || !TEST_ptr(d = BN_new())
            || !TEST_ptr(e = BN_new())
            || !TEST_ptr(f = BN_new()))
        goto err;

    if (!(TEST_true(BN_GF2m_arr2poly(p0, b[0]))
            && TEST_true(BN_GF2m_arr2poly(p1, b[1]))))
        goto err;

    for (i = 0; i < NUM0; i++) {
        if (!TEST_true(BN_bntest_rand(a, 512, 0, 0)))
            goto err;

        for (j = 0; j < 2; j++) {
            if (!(TEST_true(BN_GF2m_mod(c, a, b[j]))
                    && TEST_true(BN_GF2m_mod_sqrt(d, a, b[j], ctx))
                    && TEST_true(BN_GF2m_mod_sqr(e, d, b[j], ctx))
                    && TEST_true(BN_GF2m_add(f, c, e))
                    /* Test that d^2 = a, where d = sqrt(a). */
                    && TEST_BN_eq_zero(f)))
                goto err;
        }
    }
    st = 1;
 err:
    BN_free(a);
    BN_free(b[0]);
    BN_free(b[1]);
    BN_free(c);
    BN_free(d);
    BN_free(e);
    BN_free(f);
    return st;
}


static int test_gf2m_modsolvequad(void)
{
    BIGNUM *a = NULL, *b[2] = {NULL,NULL}, *c = NULL, *d = NULL;
    BIGNUM *e = NULL;
    int i, j, s = 0, t, st = 0;

    if (!TEST_ptr(a = BN_new())
            || !TEST_ptr(b[0] = BN_new())
            || !TEST_ptr(b[1] = BN_new())
            || !TEST_ptr(c = BN_new())
            || !TEST_ptr(d = BN_new())
            || !TEST_ptr(e = BN_new()))
        goto err;

    if (!(TEST_true(BN_GF2m_arr2poly(p0, b[0]))
            && TEST_true(BN_GF2m_arr2poly(p1, b[1]))))
        goto err;

    for (i = 0; i < NUM0; i++) {
        if (!TEST_true(BN_bntest_rand(a, 512, 0, 0)))
            goto err;
        for (j = 0; j < 2; j++) {
            t = BN_GF2m_mod_solve_quad(c, a, b[j], ctx);
            if (t) {
                s++;
                if (!(TEST_true(BN_GF2m_mod_sqr(d, c, b[j], ctx))
                        && TEST_true(BN_GF2m_add(d, c, d))
                        && TEST_true(BN_GF2m_mod(e, a, b[j]))
                        && TEST_true(BN_GF2m_add(e, e, d))
                        /*
                         * Test that solution of quadratic c
                         * satisfies c^2 + c = a.
                         */
                        && TEST_BN_eq_zero(e)))
                    goto err;
            }
        }
    }
    if (!TEST_int_ge(s, 0)) {
        TEST_info("%d tests found no roots; probably an error", NUM0);
        goto err;
    }
    st = 1;
 err:
    BN_free(a);
    BN_free(b[0]);
    BN_free(b[1]);
    BN_free(c);
    BN_free(d);
    BN_free(e);
    return st;
}


insert_fields(THD *thd, Name_resolution_context *context, const char *db_name,
	      const char *table_name, List_iterator<Item> *it,
              bool any_privileges, uint *hidden_bit_fields)
{
  Field_iterator_table_ref field_iterator;
  bool found;
  char name_buff[SAFE_NAME_LEN+1];
  DBUG_ENTER("insert_fields");
  DBUG_PRINT("arena", ("stmt arena: %p",thd->stmt_arena));

  if (db_name && lower_case_table_names)
  {
    /*
      convert database to lower case for comparison
      We can't do this in Item_field as this would change the
      'name' of the item which may be used in the select list
    */
    strmake_buf(name_buff, db_name);
    my_casedn_str(files_charset_info, name_buff);
    db_name= name_buff;
  }

  found= FALSE;

  /*
    If table names are qualified, then loop over all tables used in the query,
    else treat natural joins as leaves and do not iterate over their underlying
    tables.
  */
  for (TABLE_LIST *tables= (table_name ? context->table_list :
                            context->first_name_resolution_table);
       tables;
       tables= (table_name ? tables->next_local :
                tables->next_name_resolution_table)
       )
  {
    Field *field;
    TABLE *table= tables->table;

    DBUG_ASSERT(tables->is_leaf_for_name_resolution());

    if ((table_name && my_strcasecmp(table_alias_charset, table_name,
                                     tables->alias)) ||
        (db_name && strcmp(tables->db,db_name)))
      continue;

#ifndef NO_EMBEDDED_ACCESS_CHECKS
    /*
       Ensure that we have access rights to all fields to be inserted
       the table 'tables'. Under some circumstances, this check may be skipped.

       The check is skipped in the following cases:

       - any_privileges is true

       - the table is a derived table

       - the table is a view with SELECT privilege

       - the table is a base table with SELECT privilege
    */
    if (!any_privileges &&
        !tables->is_derived() &&
        !(tables->is_view() && (tables->grant.privilege & SELECT_ACL)) &&
        !(table && (table->grant.privilege & SELECT_ACL)))
    {
      field_iterator.set(tables);
      if (check_grant_all_columns(thd, SELECT_ACL, &field_iterator))
        DBUG_RETURN(TRUE);
    }
#endif

    /*
      Update the tables used in the query based on the referenced fields. For
      views and natural joins this update is performed inside the loop below.
    */
    if (table)
    {
      thd->lex->used_tables|= table->map;
      thd->lex->current_select->select_list_tables|= table->map;
    }

    /*
      Initialize a generic field iterator for the current table reference.
      Notice that it is guaranteed that this iterator will iterate over the
      fields of a single table reference, because 'tables' is a leaf (for
      name resolution purposes).
    */
    field_iterator.set(tables);

    for (; !field_iterator.end_of_fields(); field_iterator.next())
    {
      Item *item;

      if (!(item= field_iterator.create_item(thd)))
        DBUG_RETURN(TRUE);

      /* cache the table for the Item_fields inserted by expanding stars */
      if (item->type() == Item::FIELD_ITEM && tables->cacheable_table)
        ((Item_field *)item)->cached_table= tables;

      if (!found)
      {
        found= TRUE;
        it->replace(item); /* Replace '*' with the first found item. */
      }
      else
        it->after(item);   /* Add 'item' to the SELECT list. */

      if (item->type() == Item::FIELD_ITEM && item->field_type() == MYSQL_TYPE_BIT)
        (*hidden_bit_fields)++;

#ifndef NO_EMBEDDED_ACCESS_CHECKS
      /*
        Set privilege information for the fields of newly created views.
        We have that (any_priviliges == TRUE) if and only if we are creating
        a view. In the time of view creation we can't use the MERGE algorithm,
        therefore if 'tables' is itself a view, it is represented by a
        temporary table. Thus in this case we can be sure that 'item' is an
        Item_field.
      */
      if (any_privileges && !tables->is_with_table() && !tables->is_derived())
      {
        DBUG_ASSERT((tables->field_translation == NULL && table) ||
                    tables->is_natural_join);
        DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
        Item_field *fld= (Item_field*) item;
        const char *field_table_name= field_iterator.get_table_name();

        if (!tables->schema_table &&
            !(fld->have_privileges=
              (get_column_grant(thd, field_iterator.grant(),
                                field_iterator.get_db_name(),
                                field_table_name, fld->field_name) &
               VIEW_ANY_ACL)))
        {
          my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0), "ANY",
                   thd->security_ctx->priv_user,
                   thd->security_ctx->host_or_ip,
                   field_table_name);
          DBUG_RETURN(TRUE);
        }
      }
#endif

      if ((field= field_iterator.field()))
      {
        /* Mark fields as used to allow storage engine to optimze access */
        bitmap_set_bit(field->table->read_set, field->field_index);
        /*
          Mark virtual fields for write and others that the virtual fields
          depend on for read.
        */
        if (field->vcol_info)
          field->table->mark_virtual_col(field);
        if (table)
        {
          table->covering_keys.intersect(field->part_of_key);
        }
        if (tables->is_natural_join)
        {
          TABLE *field_table;
          /*
            In this case we are sure that the column ref will not be created
            because it was already created and stored with the natural join.
          */
          Natural_join_column *nj_col;
          if (!(nj_col= field_iterator.get_natural_column_ref()))
            DBUG_RETURN(TRUE);
          DBUG_ASSERT(nj_col->table_field);
          field_table= nj_col->table_ref->table;
          if (field_table)
          {
            thd->lex->used_tables|= field_table->map;
            thd->lex->current_select->select_list_tables|=
              field_table->map;
            field_table->covering_keys.intersect(field->part_of_key);
            field_table->used_fields++;
          }
        }
      }
      else
        thd->lex->used_tables|= item->used_tables();
      thd->lex->current_select->cur_pos_in_select_list++;
    }
    /*
      In case of stored tables, all fields are considered as used,
      while in the case of views, the fields considered as used are the
      ones marked in setup_tables during fix_fields of view columns.
      For NATURAL joins, used_tables is updated in the IF above.
    */
    if (table)
      table->used_fields= table->s->fields;
  }
  if (found)
    DBUG_RETURN(FALSE);

  /*
    TODO: in the case when we skipped all columns because there was a
    qualified '*', and all columns were coalesced, we have to give a more
    meaningful message than ER_BAD_TABLE_ERROR.
  */
  if (!table_name)
    my_error(ER_NO_TABLES_USED, MYF(0));
  else if (!db_name && !thd->db)
    my_error(ER_NO_DB_ERROR, MYF(0));
  else
  {
    char name[FN_REFLEN];
    my_snprintf(name, sizeof(name), "%s.%s",
                db_name ? db_name : thd->db, table_name);
    my_error(ER_BAD_TABLE_ERROR, MYF(0), name);
  }

  DBUG_RETURN(TRUE);
}


bool Lock_tables_prelocking_strategy::
handle_table(THD *thd, Query_tables_list *prelocking_ctx,
             TABLE_LIST *table_list, bool *need_prelocking)
{
  TABLE_LIST **last= prelocking_ctx->query_tables_last;

  if (DML_prelocking_strategy::handle_table(thd, prelocking_ctx, table_list,
                                            need_prelocking))
    return TRUE;

  /*
    normally we don't need to open FK-prelocked tables for RESTRICT,
    MDL is enough. But under LOCK TABLES we have to open everything
  */
  for (TABLE_LIST *tl= *last; tl; tl= tl->next_global)
    tl->open_strategy= TABLE_LIST::OPEN_NORMAL;

  /* We rely on a caller to check that table is going to be changed. */
  DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE);

  return FALSE;
}


bool DML_prelocking_strategy::
handle_table(THD *thd, Query_tables_list *prelocking_ctx,
             TABLE_LIST *table_list, bool *need_prelocking)
{
  /* We rely on a caller to check that table is going to be changed. */
  DBUG_ASSERT(table_list->lock_type >= TL_WRITE_ALLOW_WRITE);

  if (table_list->trg_event_map)
  {
    if (table_list->table->triggers)
    {
      *need_prelocking= TRUE;

      if (table_list->table->triggers->
          add_tables_and_routines_for_triggers(thd, prelocking_ctx, table_list))
        return TRUE;
    }
    if (table_list->table->file->referenced_by_foreign_key())
    {
      return (prepare_fk_prelocking_list(thd, prelocking_ctx, table_list,
                                         need_prelocking,
                                         table_list->trg_event_map));
    }
  }
  else if (table_list->slave_fk_event_map &&
           table_list->table->file->referenced_by_foreign_key())
  {
      return (prepare_fk_prelocking_list(thd, prelocking_ctx,
                                         table_list, need_prelocking,
                                         table_list->slave_fk_event_map));

  }
  return FALSE;
}


static my_bool close_cached_connection_tables_callback(
  TDC_element *element, close_cached_connection_tables_arg *arg)
{
  TABLE_LIST *tmp;

  mysql_mutex_lock(&element->LOCK_table_share);
  /* Ignore if table is not open or does not have a connect_string */
  if (!element->share || !element->share->connect_string.length ||
      !element->ref_count)
    goto end;

  /* Compare the connection string */
  if (arg->connection &&
      (arg->connection->length > element->share->connect_string.length ||
       (arg->connection->length < element->share->connect_string.length &&
        (element->share->connect_string.str[arg->connection->length] != '/' &&
         element->share->connect_string.str[arg->connection->length] != '\\')) ||
       strncasecmp(arg->connection->str, element->share->connect_string.str,
                   arg->connection->length)))
    goto end;

  /* close_cached_tables() only uses these elements */
  if (!(tmp= (TABLE_LIST*) alloc_root(arg->thd->mem_root, sizeof(TABLE_LIST))) ||
      !(tmp->db= strdup_root(arg->thd->mem_root, element->share->db.str)) ||
      !(tmp->table_name= strdup_root(arg->thd->mem_root,
                                     element->share->table_name.str)))
  {
    mysql_mutex_unlock(&element->LOCK_table_share);
    return TRUE;
  }

  tmp->next_local= arg->tables;
  arg->tables= tmp;

end:
  mysql_mutex_unlock(&element->LOCK_table_share);
  return FALSE;
}


bool setup_fields(THD *thd, Ref_ptr_array ref_pointer_array,
                  List<Item> &fields, enum_mark_columns mark_used_columns,
                  List<Item> *sum_func_list, List<Item> *pre_fix,
                  bool allow_sum_func)
{
  Item *item;
  enum_mark_columns save_mark_used_columns= thd->mark_used_columns;
  nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
  List_iterator<Item> it(fields);
  bool save_is_item_list_lookup;
  bool make_pre_fix= (pre_fix && (pre_fix->elements == 0));
  DBUG_ENTER("setup_fields");
  DBUG_PRINT("enter", ("ref_pointer_array: %p", ref_pointer_array.array()));

  thd->mark_used_columns= mark_used_columns;
  DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
  if (allow_sum_func)
    thd->lex->allow_sum_func|=
      (nesting_map)1 << thd->lex->current_select->nest_level;
  thd->where= THD::DEFAULT_WHERE;
  save_is_item_list_lookup= thd->lex->current_select->is_item_list_lookup;
  thd->lex->current_select->is_item_list_lookup= 0;

  /*
    To prevent fail on forward lookup we fill it with zerows,
    then if we got pointer on zero after find_item_in_list we will know
    that it is forward lookup.

    There is other way to solve problem: fill array with pointers to list,
    but it will be slower.

    TODO: remove it when (if) we made one list for allfields and
    ref_pointer_array
  */
  if (!ref_pointer_array.is_null())
  {
    DBUG_ASSERT(ref_pointer_array.size() >= fields.elements);
    memset(ref_pointer_array.array(), 0, sizeof(Item *) * fields.elements);
  }

  /*
    We call set_entry() there (before fix_fields() of the whole list of field
    items) because:
    1) the list of field items has same order as in the query, and the
       Item_func_get_user_var item may go before the Item_func_set_user_var:
          SELECT @a, @a := 10 FROM t;
    2) The entry->update_query_id value controls constantness of
       Item_func_get_user_var items, so in presence of Item_func_set_user_var
       items we have to refresh their entries before fixing of
       Item_func_get_user_var items.
  */
  List_iterator<Item_func_set_user_var> li(thd->lex->set_var_list);
  Item_func_set_user_var *var;
  while ((var= li++))
    var->set_entry(thd, FALSE);

  Ref_ptr_array ref= ref_pointer_array;
  thd->lex->current_select->cur_pos_in_select_list= 0;
  while ((item= it++))
  {
    if (make_pre_fix)
      pre_fix->push_back(item, thd->stmt_arena->mem_root);

    if ((!item->fixed && item->fix_fields(thd, it.ref())) ||
	(item= *(it.ref()))->check_cols(1))
    {
      thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup;
      thd->lex->allow_sum_func= save_allow_sum_func;
      thd->mark_used_columns= save_mark_used_columns;
      DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
      DBUG_RETURN(TRUE); /* purecov: inspected */
    }
    if (!ref.is_null())
    {
      ref[0]= item;
      ref.pop_front();
    }
    /*
      split_sum_func() must be called for Window Function items, see
      Item_window_func::split_sum_func.
    */
    if (sum_func_list &&
         ((item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM) ||
          item->with_window_func))
    {
      item->split_sum_func(thd, ref_pointer_array, *sum_func_list,
                           SPLIT_SUM_SELECT);
    }
    thd->lex->current_select->select_list_tables|= item->used_tables();
    thd->lex->used_tables|= item->used_tables();
    thd->lex->current_select->cur_pos_in_select_list++;
  }
  thd->lex->current_select->is_item_list_lookup= save_is_item_list_lookup;
  thd->lex->current_select->cur_pos_in_select_list= UNDEF_POS;

  thd->lex->allow_sum_func= save_allow_sum_func;
  thd->mark_used_columns= save_mark_used_columns;
  DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
  DBUG_RETURN(MY_TEST(thd->is_error()));
}


  bool safely_trapped_errors()
  {
    /*
      Check for m_handled_errors is here for extra safety.
      It can be useful in situation when call to open_table()
      fails because some error which was suppressed by another
      error handler (e.g. in case of MDL deadlock which we
      decided to solve by back-off and retry).
    */
    return (m_handled_errors && (! m_unhandled_errors));
  }


my_bool mysql_rm_tmp_tables(void)
{
  uint i, idx;
  char	filePath[FN_REFLEN], *tmpdir, filePathCopy[FN_REFLEN];
  MY_DIR *dirp;
  FILEINFO *file;
  TABLE_SHARE share;
  THD *thd;
  DBUG_ENTER("mysql_rm_tmp_tables");

  if (!(thd= new THD(0)))
    DBUG_RETURN(1);
  thd->thread_stack= (char*) &thd;
  thd->store_globals();

  for (i=0; i<=mysql_tmpdir_list.max; i++)
  {
    tmpdir=mysql_tmpdir_list.list[i];
    /* See if the directory exists */
    if (!(dirp = my_dir(tmpdir,MYF(MY_WME | MY_DONT_SORT))))
      continue;

    /* Remove all SQLxxx tables from directory */

    for (idx=0 ; idx < (uint) dirp->number_of_files ; idx++)
    {
      file=dirp->dir_entry+idx;

      if (!strncmp(file->name, tmp_file_prefix, tmp_file_prefix_length))
      {
        char *ext= fn_ext(file->name);
        uint ext_len= strlen(ext);
        uint filePath_len= my_snprintf(filePath, sizeof(filePath),
                                       "%s%c%s", tmpdir, FN_LIBCHAR,
                                       file->name);
        if (!strcmp(reg_ext, ext))
        {
          handler *handler_file= 0;
          /* We should cut file extention before deleting of table */
          memcpy(filePathCopy, filePath, filePath_len - ext_len);
          filePathCopy[filePath_len - ext_len]= 0;
          init_tmp_table_share(thd, &share, "", 0, "", filePathCopy);
          if (!open_table_def(thd, &share) &&
              ((handler_file= get_new_handler(&share, thd->mem_root,
                                              share.db_type()))))
          {
            handler_file->ha_delete_table(filePathCopy);
            delete handler_file;
          }
          free_table_share(&share);
        }
        /*
          File can be already deleted by tmp_table.file->delete_table().
          So we hide error messages which happnes during deleting of these
          files(MYF(0)).
        */
        (void) mysql_file_delete(key_file_misc, filePath, MYF(0));
      }
    }
    my_dirend(dirp);
  }
  delete thd;
  DBUG_RETURN(0);
}


open_tables_check_upgradable_mdl(THD *thd, TABLE_LIST *tables_start,
                                 TABLE_LIST *tables_end, uint flags)
{
  TABLE_LIST *table;

  DBUG_ASSERT(thd->locked_tables_mode);

  for (table= tables_start; table && table != tables_end;
       table= table->next_global)
  {
    /*
      Check below needs to be updated if this function starts
      called for SRO locks.
    */
    DBUG_ASSERT(table->mdl_request.type != MDL_SHARED_READ_ONLY);
    if (table->mdl_request.type < MDL_SHARED_UPGRADABLE ||
        table->open_type == OT_TEMPORARY_ONLY ||
        (table->open_type == OT_TEMPORARY_OR_BASE && is_temporary_table(table)))
    {
      continue;
    }

    /*
      We don't need to do anything about the found TABLE instance as it
      will be handled later in open_tables(), we only need to check that
      an upgradable lock is already acquired. When we enter LOCK TABLES
      mode, SNRW locks are acquired before all other locks. So if under
      LOCK TABLES we find that there is TABLE instance with upgradeable
      lock, all other instances of TABLE for the same table will have the
      same ticket.

      Note that this works OK even for CREATE TABLE statements which
      request X type of metadata lock. This is because under LOCK TABLES
      such statements don't create the table but only check if it exists
      or, in most complex case, only insert into it.
      Thus SNRW lock should be enough.

      Note that find_table_for_mdl_upgrade() will report an error if
      no suitable ticket is found.
    */
    if (!find_table_for_mdl_upgrade(thd, table->db, table->table_name, NULL))
      return TRUE;
  }

  return FALSE;
}


fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Field **ptr,
                                     List<Item> &values, bool ignore_errors,
                                     enum trg_event_type event)
{
  bool result;
  Table_triggers_list *triggers= table->triggers;

  result= fill_record(thd, table, ptr, values, ignore_errors, FALSE);

  if (!result && triggers && *ptr)
    result= triggers->process_triggers(thd, event, TRG_ACTION_BEFORE, TRUE) ||
            not_null_fields_have_null_values(table);
  /*
    Re-calculate virtual fields to cater for cases when base columns are
    updated by the triggers.
  */
  if (!result && triggers && *ptr)
  {
    DBUG_ASSERT(table == (*ptr)->table);
    if (table->vfield)
      result= table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE);
  }
  return result;

}


open_system_table_for_update(THD *thd, TABLE_LIST *one_table)
{
  DBUG_ENTER("open_system_table_for_update");

  TABLE *table= open_ltable(thd, one_table, one_table->lock_type,
                            MYSQL_LOCK_IGNORE_TIMEOUT);
  if (table)
  {
    DBUG_ASSERT(table->s->table_category == TABLE_CATEGORY_SYSTEM);
    table->use_all_columns();
  }

  DBUG_RETURN(table);
}


fill_record(THD *thd, TABLE *table_arg, List<Item> &fields, List<Item> &values,
            bool ignore_errors, bool update)
{
  List_iterator_fast<Item> f(fields),v(values);
  Item *value, *fld;
  Item_field *field;
  bool save_abort_on_warning= thd->abort_on_warning;
  bool save_no_errors= thd->no_errors;
  DBUG_ENTER("fill_record");

  thd->no_errors= ignore_errors;
  /*
    Reset the table->auto_increment_field_not_null as it is valid for
    only one row.
  */
  if (fields.elements)
  {
    /*
      On INSERT or UPDATE fields are checked to be from the same table,
      thus we safely can take table from the first field.
    */
    fld= (Item_field*)f++;
    if (!(field= fld->field_for_view_update()))
    {
      my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name);
      goto err;
    }
    DBUG_ASSERT(field->field->table == table_arg);
    table_arg->auto_increment_field_not_null= FALSE;
    f.rewind();
  }

  while ((fld= f++))
  {
    if (!(field= fld->field_for_view_update()))
    {
      my_error(ER_NONUPDATEABLE_COLUMN, MYF(0), fld->name);
      goto err;
    }
    value=v++;
    Field *rfield= field->field;
    TABLE* table= rfield->table;
    if (table->next_number_field &&
        rfield->field_index ==  table->next_number_field->field_index)
      table->auto_increment_field_not_null= TRUE;
    if (rfield->vcol_info &&
        !value->vcol_assignment_allowed_value() &&
        table->s->table_category != TABLE_CATEGORY_TEMPORARY)
    {
      push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                          ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN,
                          ER_THD(thd, ER_WARNING_NON_DEFAULT_VALUE_FOR_VIRTUAL_COLUMN),
                          rfield->field_name, table->s->table_name.str);
    }
    if (rfield->stored_in_db() &&
        (value->save_in_field(rfield, 0)) < 0 && !ignore_errors)
    {
      my_message(ER_UNKNOWN_ERROR, ER_THD(thd, ER_UNKNOWN_ERROR), MYF(0));
      goto err;
    }
    rfield->set_has_explicit_value();
  }

  if (update)
    table_arg->evaluate_update_default_function();
  else
    if (table_arg->default_field &&
        table_arg->update_default_fields(ignore_errors))
      goto err;

  /* Update virtual fields */
  if (table_arg->vfield &&
      table_arg->update_virtual_fields(table_arg->file, VCOL_UPDATE_FOR_WRITE))
    goto err;
  thd->abort_on_warning= save_abort_on_warning;
  thd->no_errors=        save_no_errors;
  DBUG_RETURN(thd->is_error());
err:
  DBUG_PRINT("error",("got error"));
  thd->abort_on_warning= save_abort_on_warning;
  thd->no_errors=        save_no_errors;
  if (fields.elements)
    table_arg->auto_increment_field_not_null= FALSE;
  DBUG_RETURN(TRUE);
}


static void mark_real_tables_as_free_for_reuse(TABLE_LIST *table_list)
{
  TABLE_LIST *table;
  for (table= table_list; table; table= table->next_global)
    if (!table->placeholder())
    {
      table->table->query_id= 0;
    }
  for (table= table_list; table; table= table->next_global)
    if (!table->placeholder())
    {
      /*
        Detach children of MyISAMMRG tables used in
        sub-statements, they will be reattached at open.
        This has to be done in a separate loop to make sure
        that children have had their query_id cleared.
      */
      table->table->file->extra(HA_EXTRA_DETACH_CHILDREN);
    }
}


Locked_tables_list::init_locked_tables(THD *thd)
{
  DBUG_ASSERT(thd->locked_tables_mode == LTM_NONE);
  DBUG_ASSERT(m_locked_tables == NULL);
  DBUG_ASSERT(m_reopen_array == NULL);
  DBUG_ASSERT(m_locked_tables_count == 0);

  for (TABLE *table= thd->open_tables; table;
       table= table->next, m_locked_tables_count++)
  {
    TABLE_LIST *src_table_list= table->pos_in_table_list;
    char *db, *table_name, *alias;
    size_t db_len=         table->s->db.length;
    size_t table_name_len= table->s->table_name.length;
    size_t alias_len=      table->alias.length();
    TABLE_LIST *dst_table_list;

    if (! multi_alloc_root(&m_locked_tables_root,
                           &dst_table_list, sizeof(*dst_table_list),
                           &db, db_len + 1,
                           &table_name, table_name_len + 1,
                           &alias, alias_len + 1,
                           NullS))
    {
      reset();
      return TRUE;
    }

    memcpy(db,         table->s->db.str, db_len + 1);
    memcpy(table_name, table->s->table_name.str, table_name_len + 1);
    strmake(alias,     table->alias.ptr(), alias_len);
    dst_table_list->init_one_table(db, db_len, table_name, table_name_len,
                                   alias, table->reginfo.lock_type);
    dst_table_list->table= table;
    dst_table_list->mdl_request.ticket= src_table_list->mdl_request.ticket;

    /* Link last into the list of tables */
    *(dst_table_list->prev_global= m_locked_tables_last)= dst_table_list;
    m_locked_tables_last= &dst_table_list->next_global;
    table->pos_in_locked_tables= dst_table_list;
  }
  if (m_locked_tables_count)
  {
    /**
      Allocate an auxiliary array to pass to mysql_lock_tables()
      in reopen_tables(). reopen_tables() is a critical
      path and we don't want to complicate it with extra allocations.
    */
    m_reopen_array= (TABLE_LIST**)alloc_root(&m_locked_tables_root,
                                             sizeof(TABLE_LIST*) *
                                             (m_locked_tables_count+1));
    if (m_reopen_array == NULL)
    {
      reset();
      return TRUE;
    }
  }

  TRANSACT_TRACKER(add_trx_state(thd, TX_LOCKED_TABLES));

  thd->enter_locked_tables_mode(LTM_LOCK_TABLES);

  return FALSE;
}


void setup_defaults(THD *thd, List<Item> &fields, List<Item> &values)
{
  List_iterator<Item> fit(fields);
  List_iterator<Item> vit(values);

  for (Item *value= vit++, *f_item= fit++; value; value= vit++, f_item= fit++)
  {
    value->walk(&Item::enchant_default_with_arg_processor, false, f_item);
  }
}


static void update_field_dependencies(THD *thd, Field *field, TABLE *table)
{
  DBUG_ENTER("update_field_dependencies");
  if (thd->mark_used_columns != MARK_COLUMNS_NONE)
  {
    MY_BITMAP *bitmap;

    /*
      We always want to register the used keys, as the column bitmap may have
      been set for all fields (for example for view).
    */

    table->covering_keys.intersect(field->part_of_key);

    if (field->vcol_info)
      table->mark_virtual_col(field);

    if (thd->mark_used_columns == MARK_COLUMNS_READ)
      bitmap= table->read_set;
    else
      bitmap= table->write_set;

    /*
       The test-and-set mechanism in the bitmap is not reliable during
       multi-UPDATE statements under MARK_COLUMNS_READ mode
       (thd->mark_used_columns == MARK_COLUMNS_READ), as this bitmap contains
       only those columns that are used in the SET clause. I.e they are being
       set here. See multi_update::prepare()
    */
    if (bitmap_fast_test_and_set(bitmap, field->field_index))
    {
      if (thd->mark_used_columns == MARK_COLUMNS_WRITE)
      {
        DBUG_PRINT("warning", ("Found duplicated field"));
        thd->dup_field= field;
      }
      else
      {
        DBUG_PRINT("note", ("Field found before"));
      }
      DBUG_VOID_RETURN;
    }
    if (table->get_fields_in_item_tree)
      field->flags|= GET_FIXED_FIELDS_FLAG;
    table->used_fields++;
  }
  else if (table->get_fields_in_item_tree)
    field->flags|= GET_FIXED_FIELDS_FLAG;
  DBUG_VOID_RETURN;
}


bool JOIN::rollup_process_const_fields()
{
  ORDER *group_tmp;
  Item *item;
  List_iterator<Item> it(all_fields);

  for (group_tmp= group_list; group_tmp; group_tmp= group_tmp->next)
  {
    if (!(*group_tmp->item)->const_item())
      continue;
    while ((item= it++))
    {
      if (*group_tmp->item == item)
      {
        Item* new_item= new (thd->mem_root) Item_func_rollup_const(thd, item);
        if (!new_item)
          return 1;
        new_item->fix_fields(thd, (Item **) 0);
        thd->change_item_tree(it.ref(), new_item);
        for (ORDER *tmp= group_tmp; tmp; tmp= tmp->next)
        {
          if (*tmp->item == item)
            thd->change_item_tree(tmp->item, new_item);
        }
        break;
      }
    }
    it.rewind();
  }
  return 0;
}


int join_read_key2(THD *thd, JOIN_TAB *tab, TABLE *table, TABLE_REF *table_ref)
{
  int error;
  if (!table->file->inited)
  {
    error= table->file->ha_index_init(table_ref->key, tab ? tab->sorted : TRUE);
    if (unlikely(error))
    {
      (void) report_error(table, error);
      return 1;
    }
  }

  /*
    The following is needed when one makes ref (or eq_ref) access from row
    comparisons: one must call row->bring_value() to get the new values.
  */
  if (tab && tab->bush_children)
  {
    TABLE_LIST *emb_sj_nest= tab->bush_children->start->emb_sj_nest;
    emb_sj_nest->sj_subq_pred->left_expr->bring_value();
  }

  /* TODO: Why don't we do "Late NULLs Filtering" here? */

  if (cmp_buffer_with_ref(thd, table, table_ref) ||
      (table->status & (STATUS_GARBAGE | STATUS_NO_PARENT | STATUS_NULL_ROW)))
  {
    if (table_ref->key_err)
    {
      table->status=STATUS_NOT_FOUND;
      return -1;
    }
    /*
      Moving away from the current record. Unlock the row
      in the handler if it did not match the partial WHERE.
    */
    if (tab && tab->ref.has_record && tab->ref.use_count == 0)
    {
      tab->read_record.table->file->unlock_row();
      table_ref->has_record= FALSE;
    }
    error=table->file->ha_index_read_map(table->record[0],
                                  table_ref->key_buff,
                                  make_prev_keypart_map(table_ref->key_parts),
                                  HA_READ_KEY_EXACT);
    if (unlikely(error) &&
        error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
      return report_error(table, error);

    if (likely(!error))
    {
      table_ref->has_record= TRUE;
      table_ref->use_count= 1;
    }
  }
  else if (table->status == 0)
  {
    DBUG_ASSERT(table_ref->has_record);
    table_ref->use_count++;
  }
  table->null_row=0;
  return table->status ? -1 : 0;
}