From 02e90de6a414a5dbb02cc15e153909d9becabb1f Mon Sep 17 00:00:00 2001 From: Mark Pizzolato Date: Tue, 14 Oct 2014 10:49:24 -0700 Subject: [PATCH] SCP: Added EXPECT and SEND commands to react to data from and inject data into the simulated console port (and other MUX ports) Ideas based on Dave Bryan's console halt efforts. sim> SEND {:line} {DELAY=n,}"string" Where is the name of the device pointed to by the TMXR structure. If :line isn't specified, then the console device is implicitly being referenced. Delay is optional and once set persists for subsequent SEND operations to the same device. Delay defaults to 1000. The DELAY value is a minimum number of instructions which must execute before the next character in the provided string will be injected to the console port. The DELAY value has effect between the characters delivered as well. "string" requires quotes and within the quoted string, common C escape character syntax is available (\r\r\t, etc.). Each device (console, and each line in each mux) has a separate value for DELAY. An arbitrary number of 'expect' conditions can be defined. The command syntax is: sim> EXPECT {:line} {[cnt]} "matchstring" {actioncommand {; actioncommand ...}} Where is the name of the device pointed to by the TMXR structure. If :line isn't specified, then the console device is implicitly being referenced. "matchstring" requires quotes and within the quoted string, common C escape character syntax is available (\r\r\t, etc.). The quotes used can be single or double quotes, but the closing quote must match the opening quote. The match string might be extended to allow the use of perl style regular expressions in the "matchstring" when a -R switch is specified on the command line. sim> EXPECT "Enter Color: " SEND "Red\r"; g A specific 'expect' condition can be removed with: sim> NOEXPECT {:line} "matchstring" All 'expect' conditions can be removed with: sim> NOEXPECT {:line} 'expect' conditions can be examined with: sim> SHOW EXPECT {:line} Expect rules are one-shots (i.e. they disappear once a match has occurred) unless they are explicitly described as persistent with the -P switch. The -C switch is available when defining expect rules. The effect of a rule defined with the -C flag is that when an expect match occurs for that rule, ALL rules are cleared for that device (console or :line). --- scp.c | 914 +++++++++++++++++++++++++++++++++++++++++++++++--- scp.h | 18 +- sim_console.c | 44 ++- sim_console.h | 6 + sim_defs.h | 40 ++- sim_tmxr.c | 66 +++- sim_tmxr.h | 4 + 7 files changed, 1030 insertions(+), 62 deletions(-) diff --git a/scp.c b/scp.c index f2cc4909..7d678bae 100644 --- a/scp.c +++ b/scp.c @@ -375,6 +375,8 @@ t_stat show_version (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) t_stat show_default (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); t_stat show_break (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); t_stat show_on (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); +t_stat sim_show_send (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); +t_stat sim_show_expect (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); t_stat show_device (FILE *st, DEVICE *dptr, int32 flag); t_stat show_unit (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag); t_stat show_all_mods (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flg, int32 *toks); @@ -400,7 +402,7 @@ FILE *stdnul; SCHTAB *get_search (char *cptr, int32 radix, SCHTAB *schptr); int32 test_search (t_value val, SCHTAB *schptr); -static const char *get_glyph_gen (const char *iptr, char *optr, char mchar, t_bool uc, t_bool quote); +static const char *get_glyph_gen (const char *iptr, char *optr, char mchar, t_bool uc, t_bool quote, char escape_char); int32 get_switches (char *cptr); char *get_sim_sw (char *cptr); t_stat get_aval (t_addr addr, DEVICE *dptr, UNIT *uptr); @@ -438,6 +440,7 @@ t_stat dep_addr (int32 flag, char *cptr, t_addr addr, DEVICE *dptr, UNIT *uptr, int32 dfltinc); void fprint_fields (FILE *stream, t_value before, t_value after, BITFIELD* bitdefs); t_stat step_svc (UNIT *ptr); +t_stat expect_svc (UNIT *ptr); t_stat shift_args (char *do_arg[], size_t arg_count); t_stat set_on (int32 flag, char *cptr); t_stat set_verify (int32 flag, char *cptr); @@ -507,6 +510,7 @@ static t_stat sim_last_cmd_stat; /* Command Status */ static SCHTAB sim_stab; static UNIT sim_step_unit = { UDATA (&step_svc, 0, 0) }; +static UNIT sim_expect_unit = { UDATA (&expect_svc, 0, 0) }; #if defined USE_INT64 static const char *sim_si64 = "64b data"; #else @@ -572,6 +576,7 @@ const struct scp_error { {"AFAIL", "Assertion failed"}, {"INVREM", "Invalid remote console command"}, {"NOTATT", "Not attached"}, + {"EXPECT", "Expect matched"}, }; const size_t size_map[] = { sizeof (int8), @@ -1112,6 +1117,8 @@ static const char simh_help[] = #define HLP_SHOW_MULTIPLEXER "*Commands SHOW" #define HLP_SHOW_CLOCKS "*Commands SHOW" #define HLP_SHOW_ON "*Commands SHOW" +#define HLP_SHOW_SEND "*Commands SHOW" +#define HLP_SHOW_EXPECT "*Commands SHOW" #define HLP_HELP "*Commands HELP" /***************** 80 character line width template *************************/ "2HELP\n" @@ -1364,6 +1371,80 @@ ASSERT failure have several different actions: " If there is no argument, ECHO prints a blank line on the console. This\n" " may be used to provide spacing in the console display or log.\n" /***************** 80 character line width template *************************/ +#define HLP_SEND "*Commands Executing_Command_Files Injecting_Console_Input" + /***************** 80 character line width template *************************/ + "3Injecting Console Input\n" + " The SEND command provides a way to insert input into the console device of\n" + " a simulated system as if it was entered by a user.\n\n" + "++SEND {delay=nn,}\"\" send input string into console\n\n" + " The string argument must be delimited by quote characters. Quotes may\n" + " be either single or double but the opening and closing quote characters\n" + " must match. Data in the string may contain escaped character strings.\n\n" + " The SEND command can also insert input into any serial device on a\n" + " simulated system as if it was entered by a user.\n\n" + "++SEND :line {delay=nn,}\"\"\n\n" + "4Delay\n" + " Specifies a positive integer representing a minimal instruction delay\n" + " before and between characdters being sent. The value specified in a delay\n" + " argument persists across SEND commands. The delay parameter can be set by\n" + " itself with SEND DELAY=n\n" + "4Escaping String Data\n" + " The following character escapes are explicitly supported:\n" + " ++\\r Sends the ASCII Carriage Return character (Decimal value 13)\n" + " ++\\n Sends the ASCII Linefeed character (Decimal value 10)\n" + " ++\\f Sends the ASCII Formfeed character (Decimal value 12)\n" + " ++\\t Sends the ASCII Horizontal Tab character (Decimal value 9)\n" + " ++\\v Sends the ASCII Vertical Tab character (Decimal value 11)\n" + " ++\\b Sends the ASCII Backspace character (Decimal value 8)\n" + " ++\\\\ Sends the ASCII Backslash character (Decimal value 92)\n" + " ++\\' Sends the ASCII Single Quote character (Decimal value 39)\n" + " ++\\\" Sends the ASCII Double Quote character (Decimal value 34)\n" + " ++\\? Sends the ASCII Question Mark character (Decimal value 63)\n" + " ++\\e Sends the ASCII Escape character (Decimal value 27)\n" + " as well as octal character values of the form:\n" + " ++\\n{n{n}} where each n is an octal digit (0-7)\n" + " and hext character values of the form:\n" + " ++\\xh{h} where each h is a hex digit (0-9A-Fa-f)\n" + /***************** 80 character line width template *************************/ +#define HLP_EXPECT "*Commands Executing_Command_Files Reacting_To_Console_Output" + /***************** 80 character line width template *************************/ + "3Reacting To Console Output\n" + " The EXPECT command provides a way to stop execution and take actions\n" + " when specific output has been generated by the simulated system.\n" + " a simulated system as if it was entered by a user.\n\n" + "++EXPECT {dev:line} \"\" {actioncommand {; actioncommand}...}\n\n" + " The string argument must be delimited by quote characters. Quotes may\n" + " be either single or double but the opening and closing quote characters\n" + " must match. Data in the string may contain escaped character strings.\n" + /***************** 80 character line width template *************************/ + "4Switches\n" + " Switches can be used to influence the behavior of EXPECT rules\n\n" + "5-p\n" + " EXPECT rules default to be one shot activities. That is a rule is\n" + " automatically removed when it matches unless it is designated as a\n" + " persistent rule by using a -p switch when the rule is defined.\n" + "5-c\n" + " If an expect rule is defined with the -c switch, it will cause all\n" + " pending expect rules on the current device to be cleared when the rule\n" + " matches data in the device output stream.\n" + "4Escaping String Data\n" + " The following character escapes are explicitly supported:\n" + " ++\\r Sends the ASCII Carriage Return character (Decimal value 13)\n" + " ++\\n Sends the ASCII Linefeed character (Decimal value 10)\n" + " ++\\f Sends the ASCII Formfeed character (Decimal value 12)\n" + " ++\\t Sends the ASCII Horizontal Tab character (Decimal value 9)\n" + " ++\\v Sends the ASCII Vertical Tab character (Decimal value 11)\n" + " ++\\b Sends the ASCII Backspace character (Decimal value 8)\n" + " ++\\\\ Sends the ASCII Backslash character (Decimal value 92)\n" + " ++\\' Sends the ASCII Single Quote character (Decimal value 39)\n" + " ++\\\" Sends the ASCII Double Quote character (Decimal value 34)\n" + " ++\\? Sends the ASCII Question Mark character (Decimal value 63)\n" + " ++\\e Sends the ASCII Escape character (Decimal value 27)\n" + " as well as octal character values of the form:\n" + " ++\\n{n{n}} where each n is an octal digit (0-7)\n" + " and hext character values of the form:\n" + " ++\\xh{h} where each h is a hex digit (0-9A-Fa-f)\n" + /***************** 80 character line width template *************************/ #define HLP_ASSERT "*Commands Executing_Command_Files Testing_Simulator_State" "3Testing Simulator State\n" " The ASSERT command tests a simulator state condition and halts command\n" @@ -1459,6 +1540,9 @@ static CTAB cmd_table[] = { { "IGNORE", &noop_cmd, 0, HLP_IGNORE }, { "ECHO", &echo_cmd, 0, HLP_ECHO }, { "ASSERT", &assert_cmd, 0, HLP_ASSERT }, + { "SEND", &send_cmd, 0, HLP_SEND }, + { "EXPECT", &expect_cmd, 1, HLP_EXPECT }, + { "NOEXPECT", &expect_cmd, 0, HLP_EXPECT }, { "!", &spawn_cmd, 0, HLP_SPAWN }, { "HELP", &help_cmd, 0, HLP_HELP }, { NULL, NULL, 0 } @@ -1536,6 +1620,8 @@ static SHTAB show_glob_tab[] = { { "MULTIPLEXER", &tmxr_show_open_devices, 0, HLP_SHOW_MULTIPLEXER }, { "MUX", &tmxr_show_open_devices, 0, HLP_SHOW_MULTIPLEXER }, { "CLOCKS", &sim_show_timers, 0, HLP_SHOW_CLOCKS }, + { "SEND", &sim_show_send, 0, HLP_SHOW_SEND }, + { "EXPECT", &sim_show_expect, 0, HLP_SHOW_EXPECT }, { "ON", &show_on, 0, HLP_SHOW_ON }, { NULL, NULL, 0 } }; @@ -1755,7 +1841,7 @@ char gbuf[CBUFSIZE]; if ((!cptr) || (*cptr == '\0')) return SCPE_ARG; -cptr = get_glyph_nc (cptr, gbuf, '"'); /* get quote delimted token */ +cptr = get_glyph_nc (cptr, gbuf, '"'); /* get quote delimited token */ if (gbuf[0] == '\0') { /* Token started with quote */ gbuf[sizeof (gbuf)-1] = '\0'; strncpy (gbuf, cptr, sizeof (gbuf)-1); @@ -2472,11 +2558,8 @@ do { } if (*cptr == 0) /* ignore blank */ continue; - if (echo) { /* echo if -v */ - printf("%s> %s\n", do_position(), cptr); - if (sim_log) - fprintf (sim_log, "%s> %s\n", do_position(), cptr); - } + if (echo) /* echo if -v */ + sim_printf("%s> %s\n", do_position(), cptr); if (*cptr == ':') /* ignore label */ continue; cptr = get_glyph (cptr, gbuf, 0); /* get command glyph */ @@ -2528,20 +2611,15 @@ do { if (!echo && !sim_quiet && /* report if not echoing */ !stat_nomessage && /* and not suppressing messages */ !(cmdp && cmdp->message)) { /* and not handling them specially */ - printf("%s> %s\n", do_position(), ocptr); - if (sim_log) - fprintf (sim_log, "%s> %s\n", do_position(), ocptr); + sim_printf("%s> %s\n", do_position(), ocptr); } } if (!stat_nomessage) { /* report error if not suppressed */ if (cmdp && cmdp->message) /* special message handler */ cmdp->message ((!echo && !sim_quiet) ? ocptr : NULL, stat); else - if (stat >= SCPE_BASE) { /* report error if not suppressed */ - printf ("%s\n", sim_error_text (stat)); - if (sim_log) - fprintf (sim_log, "%s\n", sim_error_text (stat)); - } + if (stat >= SCPE_BASE) /* report error if not suppressed */ + sim_printf ("%s\n", sim_error_text (stat)); } if (staying && (sim_on_check[sim_do_depth]) && @@ -2903,6 +2981,141 @@ if (test_search (val, &sim_stab)) /* test condition */ return SCPE_AFAIL; /* condition fails */ } +/* Send command + + Syntax: SEND {Delay=n},"string-to-send" + + Delay - is a positive integer representing a minimal instruction delay + before and between characters being sent. The value specified + in a delay argument persists across SEND commands. The delay + parameter can be set by itself with SEND DELAY=n,"" + String - must be quoted. Quotes may be either single or double but the + opening anc closing quote characters must match. Within quotes + C style character escapes are allowed. + The following character escapes are explicitly supported: + \r Sends the ASCII Carriage Return character (Decimal value 13) + \n Sends the ASCII Linefeed character (Decimal value 10) + \f Sends the ASCII Formfeed character (Decimal value 12) + \t Sends the ASCII Horizontal Tab character (Decimal value 9) + \v Sends the ASCII Vertical Tab character (Decimal value 11) + \b Sends the ASCII Backspace character (Decimal value 8) + \\ Sends the ASCII Backslash character (Decimal value 92) + \' Sends the ASCII Single Quote character (Decimal value 39) + \" Sends the ASCII Double Quote character (Decimal value 34) + \? Sends the ASCII Question Mark character (Decimal value 63) + \e Sends the ASCII Escape character (Decimal value 27) + as well as octal character values of the form: + \n{n{n}} where each n is an octal digit (0-7) + and hext character values of the form: + \xh{h} where each h is a hex digit (0-9A-Fa-f) + */ + +t_stat send_cmd (int32 flag, char *cptr) +{ +char gbuf[CBUFSIZE], *gptr = gbuf, *tptr; +uint8 dbuf[CBUFSIZE], *dptr = dbuf; +uint32 dsize = 0; +uint32 delay = 0; +t_stat r; +SEND *snd; + +tptr = get_glyph (cptr, gbuf, ','); +if (isalpha(gbuf[0]) && (strchr (gbuf, ':'))) { + r = tmxr_locate_line_send (gbuf, &snd); + if (r != SCPE_OK) + return r; + tptr = get_glyph (tptr, gbuf, ','); + } +else + snd = sim_cons_get_send (); + +if ((!strncmp(gbuf, "DELAY=", 6)) && (gbuf[6])) { + delay = (uint32)get_uint (&gbuf[6], 10, 10000000, &r); + if (r != SCPE_OK) + return SCPE_ARG; + cptr = tptr; + } +if (*cptr) { + if ((*cptr != '"') && (*cptr != '\'')) + return SCPE_ARG; /* String must be quote delimited */ + cptr = get_glyph_quoted (cptr, gbuf, 0); + if (*cptr != '\0') + return SCPE_2MARG; /* No more arguments */ + + if (SCPE_OK != sim_decode_quoted_string (gbuf, dbuf, &dsize)) + return SCPE_ARG; + } +if ((dsize == 0) && (delay == 0)) + return SCPE_2FARG; +return sim_send_input (snd, dbuf, dsize, delay); +} + +t_stat sim_show_send (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) +{ +char gbuf[CBUFSIZE], *tptr; +t_stat r; +SEND *snd; + +tptr = get_glyph (cptr, gbuf, ','); +if (isalpha(gbuf[0]) && (strchr (gbuf, ':'))) { + r = tmxr_locate_line_send (gbuf, &snd); + if (r != SCPE_OK) + return r; + cptr = tptr; + } +else + snd = sim_cons_get_send (); +if (*cptr) + return SCPE_2MARG; +return sim_show_send_input (st, snd); +} + +t_stat expect_cmd (int32 flag, char *cptr) +{ +char gbuf[CBUFSIZE], *tptr; +t_stat r; +EXPECT *exp; + +tptr = get_glyph (cptr, gbuf, ','); +if (isalpha(gbuf[0]) && (strchr (gbuf, ':'))) { + r = tmxr_locate_line_expect (gbuf, &exp); + if (r != SCPE_OK) + return r; + cptr = tptr; + } +else + exp = sim_cons_get_expect (); +if (flag) + return sim_set_expect (exp, cptr); +else + return sim_set_noexpect (exp, cptr); +} + +t_stat sim_show_expect (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) +{ +char gbuf[CBUFSIZE], *tptr; +t_stat r; +EXPECT *exp; + +tptr = get_glyph (cptr, gbuf, ','); +if (isalpha(gbuf[0]) && (strchr (gbuf, ':'))) { + r = tmxr_locate_line_expect (gbuf, &exp); + if (r != SCPE_OK) + return r; + cptr = tptr; + } +else + exp = sim_cons_get_expect (); +if (*cptr && (*cptr != '"') && (*cptr != '\'')) + return SCPE_ARG; /* String must be quote delimited */ +tptr = get_glyph_quoted (cptr, gbuf, 0); +if (*tptr != '\0') + return SCPE_2MARG; /* No more arguments */ +if (*cptr && (cptr[strlen(cptr)-1] != '"') && (cptr[strlen(cptr)-1] != '\'')) + return SCPE_ARG; /* String must be quote delimited */ +return sim_exp_show (st, exp, gbuf); +} + /* Goto command */ @@ -2933,9 +3146,7 @@ while (1) { sim_brk_clract (); /* goto defangs current actions */ sim_do_echo = saved_do_echo; /* restore echo mode */ if (sim_do_echo) /* echo if -v */ - printf("%s> %s\n", do_position(), cbuf); - if (sim_do_echo && sim_log) - fprintf (sim_log, "%s> %s\n", do_position(), cbuf); + sim_printf("%s> %s\n", do_position(), cbuf); return SCPE_OK; } } @@ -3804,12 +4015,17 @@ else { for (uptr = sim_clock_queue; uptr != QUEUE_LIST_END; uptr = uptr->next) { if (uptr == &sim_step_unit) fprintf (st, " Step timer"); - else if ((dptr = find_dev_from_unit (uptr)) != NULL) { - fprintf (st, " %s", sim_dname (dptr)); - if (dptr->numunits > 1) - fprintf (st, " unit %d", (int32) (uptr - dptr->units)); - } - else fprintf (st, " Unknown"); + else + if (uptr == &sim_expect_unit) + fprintf (st, " Expect fired"); + else + if ((dptr = find_dev_from_unit (uptr)) != NULL) { + fprintf (st, " %s", sim_dname (dptr)); + if (dptr->numunits > 1) + fprintf (st, " unit %d", (int32) (uptr - dptr->units)); + } + else + fprintf (st, " Unknown"); fprintf (st, " at %d\n", accum + uptr->time); accum = accum + uptr->time; } @@ -5261,9 +5477,7 @@ else if ((flag == RU_STEP) || static t_bool not_implemented_message = FALSE; if ((!not_implemented_message) && (flag == RU_NEXT)) { - printf ("This simulator does not have subroutine call detection.\nPerforming a STEP instead\n"); - if (sim_log) - fprintf (sim_log, "This simulator does not have subroutine call detection.\nPerforming a STEP instead\n"); + sim_printf ("This simulator does not have subroutine call detection.\nPerforming a STEP instead\n"); not_implemented_message = TRUE; flag = RU_STEP; } @@ -5453,11 +5667,8 @@ run_cmd_message (const char *unechoed_cmdline, t_stat r) #if defined (VMS) printf ("\n"); #endif -if (unechoed_cmdline) { - printf("%s> %s\n", do_position(), unechoed_cmdline); - if (sim_log) - fprintf (sim_log, "%s> %s\n", do_position(), unechoed_cmdline); - } +if (unechoed_cmdline) + sim_printf("%s> %s\n", do_position(), unechoed_cmdline); fprint_stopped (stdout, r); /* print msg */ if (sim_log) /* log if enabled */ fprint_stopped (sim_log, r); @@ -5531,6 +5742,14 @@ t_stat step_svc (UNIT *uptr) return SCPE_STEP; } +/* Unit service to facilitate expect matching to stop simulation. + Return expect SCP code, will cause simulation to stop */ + +t_stat expect_svc (UNIT *uptr) +{ +return SCPE_EXPECT | (sim_do_echo ? 0 : SCPE_NOMESSAGE); +} + /* Cancel scheduled step service */ t_stat sim_cancel_step (void) @@ -6273,9 +6492,7 @@ while (isspace (*cptr)) /* trim leading spc */ cptr++; if (*cptr == ';') { /* ignore comment */ if (sim_do_echo) /* echo comments if -v */ - printf("%s> %s\n", do_position(), cptr); - if (sim_do_echo && sim_log) - fprintf (sim_log, "%s> %s\n", do_position(), cptr); + sim_printf("%s> %s\n", do_position(), cptr); *cptr = 0; } @@ -6293,26 +6510,36 @@ return cptr; get_glyph_gen get next glyph (general case) Inputs: - iptr = pointer to input string - optr = pointer to output string - mchar = optional end of glyph character - uc = TRUE for convert to upper case (_gen only) - quote = TRUE to allow quote enclosing values (_gen only) + iptr = pointer to input string + optr = pointer to output string + mchar = optional end of glyph character + uc = TRUE for convert to upper case (_gen only) + quote = TRUE to allow quote enclosing values (_gen only) + escape_char = optional escape character within quoted strings (_gen only) + Outputs - result = pointer to next character in input string + result = pointer to next character in input string */ -static const char *get_glyph_gen (const char *iptr, char *optr, char mchar, t_bool uc, t_bool quote) +static const char *get_glyph_gen (const char *iptr, char *optr, char mchar, t_bool uc, t_bool quote, char escape_char) { t_bool quoting = FALSE; +t_bool escaping = FALSE; char quote_char = 0; while ((*iptr != 0) && ((quote && quoting) || ((isspace (*iptr) == 0) && (*iptr != mchar)))) { if (quote) { if (quoting) { - if (*iptr == quote_char) - quoting = FALSE; + if (!escaping) { + if (*iptr == escape_char) + escaping = TRUE; + else + if (*iptr == quote_char) + quoting = FALSE; + } + else + escaping = FALSE; } else { if ((*iptr == '"') || (*iptr == '\'')) { @@ -6336,17 +6563,17 @@ return iptr; char *get_glyph (const char *iptr, char *optr, char mchar) { -return (char *)get_glyph_gen (iptr, optr, mchar, TRUE, FALSE); +return (char *)get_glyph_gen (iptr, optr, mchar, TRUE, FALSE, 0); } char *get_glyph_nc (const char *iptr, char *optr, char mchar) { -return (char *)get_glyph_gen (iptr, optr, mchar, FALSE, FALSE); +return (char *)get_glyph_gen (iptr, optr, mchar, FALSE, FALSE, 0); } char *get_glyph_quoted (const char *iptr, char *optr, char mchar) { -return (char *)get_glyph_gen (iptr, optr, mchar, FALSE, TRUE); +return (char *)get_glyph_gen (iptr, optr, mchar, FALSE, TRUE, '\\'); } /* Trim trailing spaces from a string @@ -6400,7 +6627,7 @@ return FALSE; val = value */ -t_value get_uint (char *cptr, uint32 radix, t_value max, t_stat *status) +t_value get_uint (const char *cptr, uint32 radix, t_value max, t_stat *status) { t_value val; char *tptr; @@ -6472,6 +6699,236 @@ if (term && (*tptr++ != term)) return tptr; } +/* sim_decode_quoted_string + + Inputs: + iptr = pointer to input string + optr = pointer to output buffer + the output buffer must be allocated by the caller + and to avoid overrunat it must be at least as big + as the input string. + + Outputs + result = status of decode SCPE_OK when good, SCPE_ARG otherwise + osize = size of the data in the optr buffer + + The input string must be quoted. Quotes may be either single or + double but the opening anc closing quote characters must match. + Within quotes C style character escapes are allowed. + + The following character escapes are explicitly supported: + \r ASCII Carriage Return character (Decimal value 13) + \n ASCII Linefeed character (Decimal value 10) + \f ASCII Formfeed character (Decimal value 12) + \t ASCII Horizontal Tab character (Decimal value 9) + \v ASCII Vertical Tab character (Decimal value 11) + \b ASCII Backspace character (Decimal value 8) + \\ ASCII Backslash character (Decimal value 92) + \' ASCII Single Quote character (Decimal value 39) + \" ASCII Double Quote character (Decimal value 34) + \? ASCII Question Mark character (Decimal value 63) + \e ASCII Escape character (Decimal value 27) + as well as octal character values of the form: + \n{n{n}} where each n is an octal digit (0-7) + and hext character values of the form: + \xh{h} where each h is a hex digit (0-9A-Fa-f) + +*/ + +t_stat sim_decode_quoted_string (const char *iptr, uint8 *optr, uint32 *osize) +{ +char quote_char; +uint8 *ostart = optr; + +*osize = 0; +if ((strlen(iptr) == 1) || + ((iptr[strlen(iptr)-1] != '"') && (iptr[strlen(iptr)-1] != '\''))) + return SCPE_ARG; /* String must be quote delimited */ +quote_char = *iptr++; /* Save quote character */ +while (iptr[1]) { /* Skip trailing quote */ + if (*iptr != '\\') { + if (*iptr == quote_char) + return SCPE_ARG; /* Imbedded quotes must be escaped */ + *(optr++) = (uint8)(*(iptr++)); + continue; + } + ++iptr; /* Skip backslash */ + switch (*iptr) { + case 'r': /* ASCII Carriage Return character (Decimal value 13) */ + *(optr++) = 13; ++iptr; + break; + case 'n': /* ASCII Linefeed character (Decimal value 10) */ + *(optr++) = 10; ++iptr; + break; + case 'f': /* ASCII Formfeed character (Decimal value 12) */ + *(optr++) = 12; ++iptr; + break; + case 't': /* ASCII Horizontal Tab character (Decimal value 9) */ + *(optr++) = 9; ++iptr; + break; + case 'v': /* ASCII Vertical Tab character (Decimal value 11) */ + *(optr++) = 11; ++iptr; + break; + case 'b': /* ASCII Backspace character (Decimal value 8) */ + *(optr++) = 8; ++iptr; + break; + case '\\': /* ASCII Backslash character (Decimal value 92) */ + *(optr++) = 92; ++iptr; + break; + case 'e': /* ASCII Escape character (Decimal value 27) */ + *(optr++) = 27; ++iptr; + break; + case '\'': /* ASCII Single Quote character (Decimal value 39) */ + *(optr++) = 39; ++iptr; + break; + case '"': /* ASCII Double Quote character (Decimal value 34) */ + *(optr++) = 34; ++iptr; + break; + case '?': /* ASCII Question Mark character (Decimal value 63) */ + *(optr++) = 63; ++iptr; + break; + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': + *optr = *(iptr++) - '0'; + if ((*iptr >= '0') && (*iptr <= '7')) + *optr = ((*optr)<<3) + (*(iptr++) - '0'); + if ((*iptr >= '0') && (*iptr <= '7')) + *optr = ((*optr)<<3) + (*(iptr++) - '0'); + ++optr; + break; + case 'x': + if (1) { + static char *hex_digits = "0123456789ABCDEF"; + const char *c; + + ++iptr; + *optr = 0; + c = strchr (hex_digits, toupper(*iptr)); + if (c) { + *optr = ((*optr)<<4) + (uint8)(c-hex_digits); + ++iptr; + } + c = strchr (hex_digits, toupper(*iptr)); + if (c) { + *optr = ((*optr)<<4) + (uint8)(c-hex_digits); + ++iptr; + } + ++optr; + } + break; + default: + return SCPE_ARG; /* Invalid escape */ + } + } +*optr = '\0'; +*osize = (uint32)(optr-ostart); +return SCPE_OK; +} + +/* sim_decode_quoted_string + + Inputs: + iptr = pointer to input string + optr = pointer to output buffer + the output buffer must be allocated by the caller + and to avoid overrunat it must be at least as big + as the input string. + + Outputs + result = status of decode SCPE_OK when good, SCPE_ARG otherwise + osize = size of the data in the optr buffer + + The input string must be quoted. Quotes may be either single or + double but the opening anc closing quote characters must match. + Within quotes C style character escapes are allowed. + + The following character escapes are explicitly supported: + \r ASCII Carriage Return character (Decimal value 13) + \n ASCII Linefeed character (Decimal value 10) + \f ASCII Formfeed character (Decimal value 12) + \t ASCII Horizontal Tab character (Decimal value 9) + \v ASCII Vertical Tab character (Decimal value 11) + \b ASCII Backspace character (Decimal value 8) + \\ ASCII Backslash character (Decimal value 92) + \' ASCII Single Quote character (Decimal value 39) + \" ASCII Double Quote character (Decimal value 34) + \? ASCII Question Mark character (Decimal value 63) + \e ASCII Escape character (Decimal value 27) + as well as octal character values of the form: + \n{n{n}} where each n is an octal digit (0-7) + and hext character values of the form: + \xh{h} where each h is a hex digit (0-9A-Fa-f) + +*/ + +char *sim_encode_quoted_string (const uint8 *iptr, uint32 size) +{ +uint32 i; +t_bool double_quote_found = FALSE; +t_bool single_quote_found = FALSE; +char quote = '"'; +char *tptr, *optr; + +optr = malloc (4*size + 3); +if (optr == NULL) + return NULL; +tptr = optr; +for (i=0; ityp |= sw; /* set type */ bp->cnt = ncnt; /* set count */ if ((!(sw & BRK_TYP_DYN_ALL)) && /* Not Dynamic and */ @@ -7762,6 +8219,363 @@ if (spc < SIM_BKPT_N_SPC) { return; } +/* Expect package. This code provides a mechanism to stop and control simulator + execution based on traffic coming out of simulated ports and as well as a means + to inject data into those ports. It can conceptually viewed as a string + breakpoint package. + + Expect rules are stored in tables associated with each port which can use this + facility. An expect rule consists of a four entry structure: + + match the expect match string + size the number of bytes in the match string + cnt number of iterations before match is declared + action command string to be executed when match occurs + + An expect rule is contained in an expect match context structure. + + rules the match rules + size the count of match rules + buf the buffer of output data which has produced + buf_ins the buffer insertion point for the next output data + buf_size the buffer size + + The package contains the following public routines: + + sim_set_expect expect command parser and intializer + sim_set_noexpect noexpect command parser + sim_exp_init initialize an expect context + sim_exp_set set or add an expect rule + sim_exp_clr clear or delete an expect rule + sim_exp_clrall clear all expect rules + sim_exp_show show an expect rule + sim_exp_showall show all expect rules + sim_exp_check test for rule match +*/ + +/* Initialize an expect context. */ + +t_stat sim_exp_init (EXPECT *exp) +{ +memset (exp, 0, sizeof(*exp)); +return SCPE_OK; +} + +/* Set expect */ + +t_stat sim_set_expect (EXPECT *exp, char *cptr) +{ +char gbuf[CBUFSIZE], *c1ptr; +int32 cnt = 0; + +if ((cptr == NULL) || (*cptr == 0)) + return SCPE_2FARG; +if (*cptr == '[') { + cnt = (int32) strtotv (cptr + 1, &c1ptr, 10); + if ((cptr == c1ptr) || (*c1ptr != ']')) + return SCPE_ARG; + cptr = c1ptr + 1; + while (isspace(*cptr)) + ++cptr; + } +if ((*cptr != '"') && (*cptr != '\'')) + return SCPE_ARG; /* Expect string must be quote delimited */ +cptr = get_glyph_quoted (cptr, gbuf, 0); +return sim_exp_set (exp, gbuf, cnt, sim_switches, cptr); +} + +/* Clear expect */ + +t_stat sim_set_noexpect (EXPECT *exp, char *cptr) +{ +if (!cptr || !*cptr) + return sim_exp_clrall (exp); /* clear all rules */ +return sim_exp_clr (exp, cptr); /* clear one rule */ +} + +/* Search for an expect rule in an expect context */ + +EXPTAB *sim_exp_fnd (EXPECT *exp, const char *match) +{ +int32 i; +uint8 *match_buf; +uint32 match_size; + +if (!exp->rules) + return NULL; +match_buf = (uint8 *)malloc (strlen (match) + 1); +if (!match_buf) + return NULL; +if (SCPE_OK != sim_decode_quoted_string (match, match_buf, &match_size)) { + free (match_buf); + return NULL; + } +for (i=0; isize; i++) + if ((match_size == exp->rules[i].size) && + (0 == memcmp (exp->rules[i].match, match_buf, match_size))) { + free (match_buf); + return &exp->rules[i]; + } +free (match_buf); +return NULL; +} + +/* Add an expecct rule */ + +EXPTAB *sim_exp_new (EXPECT *exp, const char *match) +{ +uint8 *match_buf; +uint32 match_size; +EXPTAB *ep; +int32 i; + +ep = sim_exp_fnd (exp, match); +if (ep) + return ep; +match_buf = (uint8 *)malloc (strlen (match) + 1); +if (!match_buf) + return NULL; +if (SCPE_OK != sim_decode_quoted_string (match, match_buf, &match_size)) { + free (match_buf); + return NULL; + } +exp->rules = (EXPTAB *) realloc (exp->rules, sizeof (*exp->rules)*(exp->size + 1)); +ep = &exp->rules[exp->size]; +exp->size += 1; +memset (ep, 0, sizeof(*ep)); +ep->match = match_buf; +ep->size = match_size; +/* Make sure that the production buffer is large enough to detect a match for all rules */ +for (i=0; isize; i++) { + if (exp->rules[i].size > exp->buf_size) { + free (exp->buf); + exp->buf = (uint8 *)calloc (exp->rules[i].size, sizeof(*exp->buf)); + exp->buf_size = exp->rules[i].size; + exp->buf_ins = 0; + } + } +return ep; +} + +/* Set a expect rule */ + +t_stat sim_exp_set (EXPECT *exp, const char *match, int32 cnt, int32 switches, char *act) +{ +EXPTAB *ep; +uint8 *match_buf; +uint32 match_size; + +/* Validate the match string */ +match_buf = (uint8 *)malloc (strlen (match) + 1); +if (!match_buf) + return SCPE_MEM; +if (SCPE_OK != sim_decode_quoted_string (match, match_buf, &match_size)) { + free (match_buf); + return SCPE_ARG; + } +free (match_buf); +ep = sim_exp_fnd (exp, match); /* present? */ +if (!ep) /* no, allocate */ + ep = sim_exp_new (exp, match); +if (!ep) /* still no? mem err */ + return SCPE_MEM; +ep->cnt = cnt; /* set proceed count */ +ep->switches = switches; /* set switches */ +if (ep->act) { /* replace old action? */ + free (ep->act); /* deallocate */ + ep->act = NULL; /* now no action */ + } +if (act) while (isspace(*act)) ++act; /* skip leading spaces in action string */ +if ((act != NULL) && (*act != 0)) { /* new action? */ + char *newp = (char *) calloc (strlen (act)+1, sizeof (*act)); /* alloc buf */ + if (newp == NULL) /* mem err? */ + return SCPE_MEM; + strcpy (newp, act); /* copy action */ + ep->act = newp; /* set pointer */ + } +return SCPE_OK; +} + +/* Clear (delete) an expect rule */ + +t_stat sim_exp_clr_tab (EXPECT *exp, EXPTAB *ep) +{ +int32 i; + +if (!ep) /* not there? ok */ + return SCPE_OK; +free (ep->match); /* deallocate match string */ +free (ep->act); /* deallocate action */ +for (i=ep-exp->rules; isize; i++) /* shuffle up remaining rules */ + exp->rules[i] = exp->rules[i+1]; +exp->size -= 1; /* decrement count */ +if (exp->size == 0) { /* No rules left? */ + free (exp->rules); + exp->rules = NULL; + } +return SCPE_OK; +} + +t_stat sim_exp_clr (EXPECT *exp, const char *match) +{ +return sim_exp_clr_tab (exp, sim_exp_fnd (exp, match)); +} + +/* Clear all expect rules */ + +t_stat sim_exp_clrall (EXPECT *exp) +{ +int32 i; + +for (i=0; isize; i++) { + free (exp->rules[i].match); /* deallocate match string */ + free (exp->rules[i].act); /* deallocate action */ + } +free (exp->rules); +exp->rules = NULL; +exp->size = 0; +free (exp->buf); +exp->buf = NULL; +exp->buf_size = 0; +return SCPE_OK; +} + +/* Show an expect rule */ + +t_stat sim_exp_show_tab (FILE *st, EXPECT *exp, EXPTAB *ep) +{ +if (!ep) + return SCPE_OK; +fprintf (st, "EXPECT "); +fprint_buffer_string (st, ep->match, ep->size); +if (ep->cnt > 0) + fprintf (st, " [%d]", ep->cnt); +if (ep->act) + fprintf (st, " %s", ep->act); +fprintf (st, "\n"); +return SCPE_OK; +} + +t_stat sim_exp_show (FILE *st, EXPECT *exp, const char *match) +{ +EXPTAB *ep = sim_exp_fnd (exp, match); + +if (!*match) + return sim_exp_showall (st, exp); +if (!ep) + return SCPE_ARG; +return sim_exp_show_tab (st, exp, ep); +} + +/* Show all expect rules */ + +t_stat sim_exp_showall (FILE *st, EXPECT *exp) +{ +int32 i; + +for (i=0; i < exp->size; i++) + sim_exp_show_tab (st, exp, &exp->rules[i]); +return SCPE_OK; +} + +/* Test for expect match */ + +t_stat sim_exp_check (EXPECT *exp, uint8 data) +{ +int32 i; +EXPTAB *ep; + +if ((!exp) || (!exp->rules)) /* Anying to check? */ + return SCPE_OK; + +exp->buf[exp->buf_ins++] = data; /* Save new data */ + +for (i=0; i < exp->size; i++) { + ep = &exp->rules[i]; + if (exp->buf_ins < ep->size) { + if (memcmp (exp->buf, &ep->match[ep->size-exp->buf_ins], exp->buf_ins)) + continue; + if (memcmp (&exp->buf[exp->buf_size-(ep->size-exp->buf_ins)], ep->match, ep->size-exp->buf_ins)) + continue; + break; + } + else { + if (memcmp (&exp->buf[exp->buf_ins-ep->size], ep->match, ep->size)) + continue; + break; + } + } +if (exp->buf_ins == exp->buf_size) /* At end of match buffer? */ + exp->buf_ins = 0; /* wrap around to beginning */ +if (i != exp->size) { /* Found? */ + if (ep->cnt > 0) + ep->cnt -= 1; + else { + sim_brk_setact (ep->act); /* set up actions */ + if (!(ep->switches & EXP_TYP_PERSIST)) /* One shot expect rule? */ + sim_exp_clr_tab (exp, ep); /* delete it */ + if (ep->switches & EXP_TYP_CLEARALL) /* One shot expect rule? */ + sim_exp_clrall (exp); /* delete all rules */ + sim_activate (&sim_expect_unit, 0); /* schedule simulation stop asap */ + } + } +return SCPE_OK; +} + +/* Queue input data for sending */ + +t_stat sim_send_input (SEND *snd, uint8 *data, size_t size, uint32 delay) +{ +if (snd->extoff != 0) { + if ((snd->insoff-snd->extoff) > 0) + memmove(snd->buffer, snd->buffer+snd->extoff, snd->insoff-snd->extoff); + snd->insoff -= snd->extoff; + snd->extoff -= 0; + } +if (snd->insoff+size > snd->bufsize) { + snd->bufsize = snd->insoff+size; + snd->buffer = realloc(snd->buffer, snd->bufsize); + } +memcpy(snd->buffer+snd->insoff, data, size); +snd->insoff += size; +if (delay) + snd->delay = delay; +snd->next_time = sim_gtime() + snd->delay; +return SCPE_OK; +} + +/* Display console Queued input data status */ + +t_stat sim_show_send_input (FILE *st, SEND *snd) +{ +if (snd->extoff < snd->insoff) { + fprintf (st, "%d bytes of pending input Data:\n ", snd->insoff-snd->extoff); + fprint_buffer_string (st, snd->buffer+snd->extoff, snd->insoff-snd->extoff); + fprintf (st, "\n"); + } +else + fprintf (st, "No Pending Input Data\n"); +fprintf (st, "Pending Input Delay=%d instructions per character\n", snd->delay); +return SCPE_OK; +} + +/* Poll for Queued input data */ + +t_bool sim_send_poll_data (SEND *snd, t_stat *stat) +{ +if (snd && (snd->extoff < snd->insoff)) { /* pending input characters available? */ + if (sim_gtime() < snd->next_time) /* too soon? */ + *stat = SCPE_OK; + else { + *stat = snd->buffer[snd->extoff++] | SCPE_KFLAG;/* get one */ + snd->next_time = sim_gtime() + snd->delay; + } + return TRUE; + } +return FALSE; +} + + /* Message Text */ const char *sim_error_text (t_stat stat) diff --git a/scp.h b/scp.h index 83a57379..dd93744f 100644 --- a/scp.h +++ b/scp.h @@ -82,6 +82,8 @@ t_stat call_cmd (int32 flag, char *ptr); t_stat on_cmd (int32 flag, char *ptr); t_stat noop_cmd (int32 flag, char *ptr); t_stat assert_cmd (int32 flag, char *ptr); +t_stat send_cmd (int32 flag, char *ptr); +t_stat expect_cmd (int32 flag, char *ptr); t_stat help_cmd (int32 flag, char *ptr); t_stat spawn_cmd (int32 flag, char *ptr); t_stat echo_cmd (int32 flag, char *ptr); @@ -114,9 +116,12 @@ char *get_sim_opt (int32 opt, char *cptr, t_stat *st); char *get_glyph (const char *iptr, char *optr, char mchar); char *get_glyph_nc (const char *iptr, char *optr, char mchar); char *get_glyph_quoted (const char *iptr, char *optr, char mchar); -t_value get_uint (char *cptr, uint32 radix, t_value max, t_stat *status); +t_value get_uint (const char *cptr, uint32 radix, t_value max, t_stat *status); char *get_range (DEVICE *dptr, char *cptr, t_addr *lo, t_addr *hi, uint32 rdx, t_addr max, char term); +t_stat sim_decode_quoted_string (const char *iptr, uint8 *optr, uint32 *osize); +char *sim_encode_quoted_string (const uint8 *iptr, uint32 size); +void fprint_buffer_string (FILE *st, const uint8 *buf, uint32 size); t_value strtotv (const char *cptr, char **endptr, uint32 radix); t_stat fprint_val (FILE *stream, t_value val, uint32 rdx, uint32 wid, uint32 fmt); t_stat sim_print_val (t_value val, uint32 radix, uint32 width, uint32 format); @@ -140,6 +145,17 @@ uint32 sim_brk_test (t_addr bloc, uint32 btyp); void sim_brk_clrspc (uint32 spc); char *sim_brk_clract (void); void sim_brk_setact (const char *action); +t_stat sim_send_input (SEND *snd, uint8 *data, size_t size, uint32 delay); +t_stat sim_show_send_input (FILE *st, SEND *snd); +t_bool sim_send_poll_data (SEND *snd, t_stat *stat); +t_stat sim_set_expect (EXPECT *exp, char *cptr); +t_stat sim_set_noexpect (EXPECT *exp, char *cptr); +t_stat sim_exp_set (EXPECT *exp, const char *match, int32 cnt, int32 switches, char *act); +t_stat sim_exp_clr (EXPECT *exp, const char *match); +t_stat sim_exp_clrall (EXPECT *exp); +t_stat sim_exp_show (FILE *st, EXPECT *exp, const char *match); +t_stat sim_exp_showall (FILE *st, EXPECT *exp); +t_stat sim_exp_check (EXPECT *exp, uint8 data); char *match_ext (char *fnam, char *ext); t_stat show_version (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); t_stat set_dev_debug (DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); diff --git a/sim_console.c b/sim_console.c index 7363a08b..8f86d1ed 100644 --- a/sim_console.c +++ b/sim_console.c @@ -103,7 +103,10 @@ sim_show_cons_log show console log sim_tt_inpcvt convert input character per mode sim_tt_outcvt convert output character per mode - + sim_cons_get_send get console send structure address + sim_cons_get_expect get console expect structure address + sim_show_cons_send_input show pending input data + sim_show_cons_expect show expect rules and state sim_ttinit called once to get initial terminal state sim_ttrun called to put terminal into run state sim_ttcmd called to return terminal to command state @@ -160,6 +163,9 @@ int32 sim_del_char = '\b'; /* delete character */ #else int32 sim_del_char = 0177; #endif +SEND sim_con_send = {SEND_DEFAULT_DELAY}; +EXPECT sim_con_expect; + static t_stat sim_con_poll_svc (UNIT *uptr); /* console connection poll routine */ static t_stat sim_con_reset (DEVICE *dptr); /* console reset routine */ UNIT sim_con_unit = { UDATA (&sim_con_poll_svc, 0, 0) }; /* console connection unit */ @@ -247,6 +253,8 @@ static SHTAB show_con_tab[] = { { "TELNET", &sim_show_telnet, 0 }, { "DEBUG", &sim_show_cons_debug, 0 }, { "BUFFERED", &sim_show_cons_buff, 0 }, + { "EXPECT", &sim_show_cons_expect, 0 }, + { "INPUT", &sim_show_cons_send_input, 0 }, { NULL, NULL, 0 } }; @@ -295,7 +303,7 @@ while (*cptr != 0) { /* do all mods */ *cvptr++ = 0; get_glyph (gbuf, gbuf, 0); /* modifier to UC */ if ((ctptr = find_ctab (set_con_tab, gbuf))) { /* match? */ - r = ctptr->action (ctptr->arg, cvptr); /* do the rest */ + r = ctptr->action (ctptr->arg, cvptr); /* do the rest */ if (r != SCPE_OK) return r; } @@ -1369,6 +1377,13 @@ if (sim_con_ldsc.serport == 0) /* ignore if already closed */ return tmxr_close_master (&sim_con_tmxr); /* close master socket */ } +/* Show the console expect rules and state */ + +t_stat sim_show_cons_expect (FILE *st, DEVICE *dunused, UNIT *uunused, int32 flag, char *cptr) +{ +return sim_exp_show (st, &sim_con_expect, cptr); +} + /* Log File Open/Close/Show Support */ /* Open log file */ @@ -1511,12 +1526,35 @@ for (i = 0; i < sec; i++) { /* loop */ return SCPE_TTMO; /* timed out */ } +/* Get Send object address for console */ + +SEND *sim_cons_get_send (void) +{ +return &sim_con_send; +} + +/* Get Expect object address for console */ + +EXPECT *sim_cons_get_expect (void) +{ +return &sim_con_expect; +} + +/* Display console Queued input data status */ + +t_stat sim_show_cons_send_input (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr) +{ +return sim_show_send_input (st, &sim_con_send); +} + /* Poll for character */ t_stat sim_poll_kbd (void) { int32 c; +if (sim_send_poll_data (&sim_con_send, &c)) /* injected input characters available? */ + return c; c = sim_os_poll_kbd (); /* get character */ if ((c == SCPE_STOP) || /* ^E or not Telnet? */ ((sim_con_tmxr.master == 0) && /* and not serial? */ @@ -1540,6 +1578,7 @@ return SCPE_OK; t_stat sim_putchar (int32 c) { +sim_exp_check (&sim_con_expect, c); if ((sim_con_tmxr.master == 0) && /* not Telnet? */ (sim_con_ldsc.serport == 0)) { /* and not serial port */ if (sim_log) /* log file? */ @@ -1563,6 +1602,7 @@ t_stat sim_putchar_s (int32 c) { t_stat r; +sim_exp_check (&sim_con_expect, c); if ((sim_con_tmxr.master == 0) && /* not Telnet? */ (sim_con_ldsc.serport == 0)) { /* and not serial port */ if (sim_log) /* log file? */ diff --git a/sim_console.h b/sim_console.h index a23068a3..03c95d1f 100644 --- a/sim_console.h +++ b/sim_console.h @@ -68,6 +68,8 @@ t_stat sim_set_cons_unbuff (int32 flg, char *cptr); t_stat sim_set_cons_log (int32 flg, char *cptr); t_stat sim_set_cons_nolog (int32 flg, char *cptr); t_stat sim_set_deboff (int32 flag, char *cptr); +t_stat sim_set_cons_expect (int32 flg, char *cptr); +t_stat sim_set_cons_noexpect (int32 flg, char *cptr); t_stat sim_debug_flush (void); t_stat sim_set_pchar (int32 flag, char *cptr); t_stat sim_show_console (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); @@ -80,10 +82,14 @@ t_stat sim_show_pchar (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cpt t_stat sim_show_cons_buff (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); t_stat sim_show_cons_log (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); t_stat sim_show_cons_debug (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); +t_stat sim_show_cons_expect (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); t_stat sim_check_console (int32 sec); t_stat sim_open_logfile (char *filename, t_bool binary, FILE **pf, FILEREF **pref); t_stat sim_close_logfile (FILEREF **pref); const char *sim_logfile_name (FILE *st, FILEREF *ref); +SEND *sim_cons_get_send (void); +EXPECT *sim_cons_get_expect (void); +t_stat sim_show_cons_send_input (FILE *st, DEVICE *dptr, UNIT *uptr, int32 flag, char *cptr); t_stat sim_poll_kbd (void); t_stat sim_putchar (int32 c); t_stat sim_putchar_s (int32 c); diff --git a/sim_defs.h b/sim_defs.h index c3f500d8..123c4910 100644 --- a/sim_defs.h +++ b/sim_defs.h @@ -313,8 +313,9 @@ typedef uint32 t_addr; #define SCPE_AFAIL (SCPE_BASE + 42) /* assert failed */ #define SCPE_INVREM (SCPE_BASE + 43) /* invalid remote console command */ #define SCPE_NOTATT (SCPE_BASE + 44) /* not attached */ +#define SCPE_EXPECT (SCPE_BASE + 45) /* expect matched */ -#define SCPE_MAX_ERR (SCPE_BASE + 45) /* Maximum SCPE Error Value */ +#define SCPE_MAX_ERR (SCPE_BASE + 46) /* Maximum SCPE Error Value */ #define SCPE_KFLAG 0x1000 /* tti data flag */ #define SCPE_BREAK 0x2000 /* tti break flag */ #define SCPE_NOMESSAGE 0x10000000 /* message display supression flag */ @@ -632,6 +633,40 @@ struct sim_brktab { char *act; /* action string */ }; +/* Expect rule */ + +struct sim_exptab { + uint8 *match; /* match string */ + uint32 size; /* match string size */ + int32 cnt; /* proceed count */ + int32 switches; /* flags */ +#define EXP_TYP_PERSIST (SWMASK ('P')) /* rule persists after match, default is once a rule matches, it is removed */ +#define EXP_TYP_CLEARALL (SWMASK ('C')) /* clear all rules after matching this rule, default is to once a rule matches, it is removed */ + char *act; /* action string */ + }; + +/* Expect Context */ + +struct sim_expect { + struct sim_exptab *rules; /* match rules */ + int32 size; /* count of match rules */ + uint8 *buf; /* buffer of output data which has produced */ + uint32 buf_ins; /* buffer insertion point for the next output data */ + uint32 buf_size; /* buffer size */ + }; + +/* Send Context */ + +struct sim_send { + uint32 delay; /* instruction delay before/between sent data */ +#define SEND_DEFAULT_DELAY 1000 /* default delay instruction count */ + double next_time; /* execution time when next data can be sent */ + uint8 *buffer; /* buffer */ + size_t bufsize; /* buffer size */ + int32 insoff; /* insert offset */ + int32 extoff; /* extra offset */ + }; + /* Debug table */ struct sim_debtab { @@ -745,6 +780,9 @@ typedef struct sim_shtab SHTAB; typedef struct sim_mtab MTAB; typedef struct sim_schtab SCHTAB; typedef struct sim_brktab BRKTAB; +typedef struct sim_exptab EXPTAB; +typedef struct sim_expect EXPECT; +typedef struct sim_send SEND; typedef struct sim_debtab DEBTAB; typedef struct sim_fileref FILEREF; typedef struct sim_bitfield BITFIELD; diff --git a/sim_tmxr.c b/sim_tmxr.c index da76ba20..3869dd45 100644 --- a/sim_tmxr.c +++ b/sim_tmxr.c @@ -1530,15 +1530,17 @@ uint32 tmp; tmxr_debug_trace_line (lp, "tmxr_getc_ln()"); if (lp->conn && lp->rcve) { /* conn & enb? */ - j = lp->rxbpi - lp->rxbpr; /* # input chrs */ - if (j) { /* any? */ - tmp = lp->rxb[lp->rxbpr]; /* get char */ - val = TMXR_VALID | (tmp & 0377); /* valid + chr */ - if (lp->rbr[lp->rxbpr]) { /* break? */ - lp->rbr[lp->rxbpr] = 0; /* clear status */ - val = val | SCPE_BREAK; /* indicate to caller */ + if (!sim_send_poll_data (&lp->send, &val)) { /* injected input characters available? */ + j = lp->rxbpi - lp->rxbpr; /* # input chrs */ + if (j) { /* any? */ + tmp = lp->rxb[lp->rxbpr]; /* get char */ + val = TMXR_VALID | (tmp & 0377); /* valid + chr */ + if (lp->rbr[lp->rxbpr]) { /* break? */ + lp->rbr[lp->rxbpr] = 0; /* clear status */ + val = val | SCPE_BREAK; /* indicate to caller */ + } + lp->rxbpr = lp->rxbpr + 1; /* adv pointer */ } - lp->rxbpr = lp->rxbpr + 1; /* adv pointer */ } } /* end if conn */ if (lp->rxbpi == lp->rxbpr) /* empty? zero ptrs */ @@ -1816,6 +1818,7 @@ if ((lp->txbfd && !lp->notelnet) || (TXBUF_AVAIL(lp) > 1)) {/* room for char (+ lp->xmte = 0; /* disable line */ if (lp->txlog) /* log if available */ fputc (chr, lp->txlog); + sim_exp_check (&lp->expect, chr); /* process expect rules as needed */ return SCPE_OK; /* char sent */ } ++lp->txdrp; lp->xmte = 0; /* no room, dsbl line */ @@ -3198,6 +3201,9 @@ for (i=0; ilines; i++) + if (0 == mux->ldsc[i].send.delay) + mux->ldsc[i].send.delay = SEND_DEFAULT_DELAY; } #if defined(SIM_ASYNCH_IO) && defined(SIM_ASYNCH_MUX) pthread_mutex_unlock (&sim_tmxr_poll_lock); @@ -3226,6 +3232,46 @@ pthread_mutex_unlock (&sim_tmxr_poll_lock); #endif } +static t_stat _tmxr_locate_line_send_expect (const char *cptr, SEND **snd, EXPECT **exp) +{ +char gbuf[CBUFSIZE]; +DEVICE *dptr; +int i; +t_stat r; + +if (snd) + *snd = NULL; +if (exp) + *exp = NULL; +cptr = get_glyph(cptr, gbuf, ':'); +dptr = find_dev (gbuf); /* device match? */ +if (!dptr) + return SCPE_ARG; + +for (i=0; idptr == dptr) { + int line = (int)get_uint (cptr, 10, tmxr_open_devices[i]->lines, &r); + if (r != SCPE_OK) + return r; + if (snd) + *snd = &tmxr_open_devices[i]->ldsc[line].send; + if (exp) + *exp = &tmxr_open_devices[i]->ldsc[line].expect; + return SCPE_OK; + } +return SCPE_ARG; +} + +t_stat tmxr_locate_line_send (const char *cptr, SEND **snd) +{ +return _tmxr_locate_line_send_expect (cptr, snd, NULL); +} + +t_stat tmxr_locate_line_expect (const char *cptr, EXPECT **exp) +{ +return _tmxr_locate_line_send_expect (cptr, NULL, exp); +} + t_stat tmxr_change_async (void) { #if defined(SIM_ASYNCH_IO) @@ -3812,6 +3858,10 @@ if (lp->modem_control) { if ((lp->serport == 0) && (lp->sock) && (!lp->datagram)) fprintf (st, " %s\n", (lp->notelnet) ? "Telnet disabled (RAW data)" : "Telnet protocol"); +if (lp->send.buffer) + sim_show_send_input (st, &lp->send); +if (lp->expect.buf) + sim_exp_showall (st, &lp->expect); if (lp->txlog) fprintf (st, " Logging to %s\n", lp->txlogname); return; diff --git a/sim_tmxr.h b/sim_tmxr.h index 208f539f..30188753 100644 --- a/sim_tmxr.h +++ b/sim_tmxr.h @@ -161,6 +161,8 @@ struct tmln { UNIT *uptr; /* input polling unit (default to mp->uptr) */ UNIT *o_uptr; /* output polling unit (default to lp->uptr)*/ DEVICE *dptr; /* line specific device */ + EXPECT expect; /* Expect rules */ + SEND send; /* Send input state */ }; struct tmxr { @@ -238,6 +240,8 @@ t_stat tmxr_activate (UNIT *uptr, int32 interval); t_stat tmxr_activate_after (UNIT *uptr, int32 usecs_walltime); t_stat tmxr_clock_coschedule (UNIT *uptr, int32 interval); t_stat tmxr_change_async (void); +t_stat tmxr_locate_line_send (const char *dev_line, SEND **snd); +t_stat tmxr_locate_line_expect (const char *dev_line, EXPECT **exp); t_stat tmxr_startup (void); t_stat tmxr_shutdown (void); t_stat tmxr_start_poll (void);