add 'command type' introspective handler
[openocd.git] / src / helper / command.c
index 9cc996c226fbe563ad79a87b95d771574f53bc3c..23175baf2cda31aab5ee563076a2145731b05b42 100644 (file)
@@ -44,6 +44,9 @@
 #include "jim-eventloop.h"
 
 
+/* nice short description of source file */
+#define __THIS__FILE__ "command.c"
+
 Jim_Interp *interp = NULL;
 
 static int run_command(struct command_context *context,
@@ -56,6 +59,34 @@ static void tcl_output(void *privData, const char *file, unsigned line,
        Jim_AppendString(interp, tclOutput, string, strlen(string));
 }
 
+static Jim_Obj *command_log_capture_start(Jim_Interp *interp)
+{
+       /* capture log output and return it. A garbage collect can
+        * happen, so we need a reference count to this object */
+       Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
+       if (NULL == tclOutput)
+               return NULL;
+       Jim_IncrRefCount(tclOutput);
+       log_add_callback(tcl_output, tclOutput);
+       return tclOutput;
+}
+
+static void command_log_capture_finish(Jim_Interp *interp, Jim_Obj *tclOutput)
+{
+       log_remove_callback(tcl_output, tclOutput);
+       Jim_SetResult(interp, tclOutput);
+       Jim_DecrRefCount(interp, tclOutput);
+}
+
+static int command_retval_set(Jim_Interp *interp, int retval)
+{
+       int *return_retval = Jim_GetAssocData(interp, "retval");
+       if (return_retval != NULL)
+               *return_retval = retval;
+
+       return (retval == ERROR_OK) ? JIM_OK : JIM_ERR;
+}
+
 extern struct command_context *global_cmd_ctx;
 
 void script_debug(Jim_Interp *interp, const char *name,
@@ -108,72 +139,61 @@ static const char **script_command_args_alloc(
        return words;
 }
 
-static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+static struct command_context *current_command_context(void)
 {
-       /* the private data is stashed in the interp structure */
-       struct command *c;
-       struct command_context *context;
-       int retval;
-
-       /* DANGER!!!! be careful what we invoke here, since interp->cmdPrivData might
-        * get overwritten by running other Jim commands! Treat it as an
-        * emphemeral global variable that is used in lieu of an argument
-        * to the fn and fish it out manually.
-        */
-       c = interp->cmdPrivData;
-       if (c == NULL)
+       /* grab the command context from the associated data */
+       struct command_context *cmd_ctx = Jim_GetAssocData(interp, "context");
+       if (NULL == cmd_ctx)
        {
-               LOG_ERROR("BUG: interp->cmdPrivData == NULL");
-               return JIM_ERR;
+               /* Tcl can invoke commands directly instead of via command_run_line(). This would
+                * happen when the Jim Tcl interpreter is provided by eCos.
+                */
+               cmd_ctx = global_cmd_ctx;
        }
+       return cmd_ctx;
+}
+
+static int script_command_run(Jim_Interp *interp,
+               int argc, Jim_Obj *const *argv, struct command *c, bool capture)
+{
        target_call_timer_callbacks_now();
        LOG_USER_N("%s", ""); /* Keep GDB connection alive*/
 
-       script_debug(interp, c->name, argc, argv);
-
        unsigned nwords;
        const char **words = script_command_args_alloc(argc, argv, &nwords);
        if (NULL == words)
                return JIM_ERR;
 
-       /* grab the command context from the associated data */
-       context = Jim_GetAssocData(interp, "context");
-       if (context == NULL)
-       {
-               /* Tcl can invoke commands directly instead of via command_run_line(). This would
-                * happen when the Jim Tcl interpreter is provided by eCos.
-                */
-               context = global_cmd_ctx;
-       }
+       Jim_Obj *tclOutput = NULL;
+       if (capture)
+               tclOutput = command_log_capture_start(interp);
 
-       /* capture log output and return it */
-       Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
-       /* a garbage collect can happen, so we need a reference count to this object */
-       Jim_IncrRefCount(tclOutput);
-
-       log_add_callback(tcl_output, tclOutput);
+       struct command_context *cmd_ctx = current_command_context();
+       int retval = run_command(cmd_ctx, c, (const char **)words, nwords);
 
-       retval = run_command(context, c, (const char **)words, nwords);
-
-       log_remove_callback(tcl_output, tclOutput);
-
-       /* We dump output into this local variable */
-       Jim_SetResult(interp, tclOutput);
-       Jim_DecrRefCount(interp, tclOutput);
+       if (capture)
+               command_log_capture_finish(interp, tclOutput);
 
        script_command_args_free(words, nwords);
+       return command_retval_set(interp, retval);
+}
 
-       int *return_retval = Jim_GetAssocData(interp, "retval");
-       if (return_retval != NULL)
-       {
-               *return_retval = retval;
-       }
+static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+       /* the private data is stashed in the interp structure */
 
-       return (retval == ERROR_OK)?JIM_OK:JIM_ERR;
+       struct command *c = interp->cmdPrivData;
+       assert(c);
+       script_debug(interp, c->name, argc, argv);
+       return script_command_run(interp, argc, argv, c, true);
 }
 
-/* nice short description of source file */
-#define __THIS__FILE__ "command.c"
+static struct command *command_root(struct command *c)
+{
+       while (NULL != c->parent)
+               c = c->parent;
+       return c;
+}
 
 /**
  * Find a command by name from a list of commands.
@@ -189,6 +209,16 @@ static struct command *command_find(struct command *head, const char *name)
        }
        return NULL;
 }
+struct command *command_find_in_context(struct command_context *cmd_ctx,
+               const char *name)
+{
+       return command_find(cmd_ctx->commands, name);
+}
+struct command *command_find_in_parent(struct command *parent,
+               const char *name)
+{
+       return command_find(parent->children, name);
+}
 
 /**
  * Add the command into the linked list, sorted by name.
@@ -224,23 +254,23 @@ static struct command **command_list_for_parent(
 }
 
 static struct command *command_new(struct command_context *cmd_ctx,
-               struct command *parent, const char *name,
-               command_handler_t handler, enum command_mode mode,
-               const char *help, const char *usage)
+               struct command *parent, const struct command_registration *cr)
 {
-       assert(name);
+       assert(cr->name);
 
        struct command *c = malloc(sizeof(struct command));
        memset(c, 0, sizeof(struct command));
 
-       c->name = strdup(name);
-       if (help)
-               c->help = strdup(help);
-       if (usage)
-               c->usage = strdup(usage);
+       c->name = strdup(cr->name);
+       if (cr->help)
+               c->help = strdup(cr->help);
+       if (cr->usage)
+               c->usage = strdup(cr->usage);
        c->parent = parent;
-       c->handler = handler;
-       c->mode = mode;
+       c->handler = cr->handler;
+       c->jim_handler = cr->jim_handler;
+       c->jim_handler_data = cr->jim_handler_data;
+       c->mode = cr->mode;
 
        command_add_child(command_list_for_parent(cmd_ctx, parent), c);
 
@@ -273,19 +303,22 @@ static int register_command_handler(struct command *c)
        if (NULL == full_name)
                return retval;
 
-       const char *ocd_name = alloc_printf("ocd_%s", full_name);
-       if (NULL == full_name)
-               goto free_full_name;
+       if (NULL != c->handler)
+       {
+               const char *ocd_name = alloc_printf("ocd_%s", full_name);
+               if (NULL == full_name)
+                       goto free_full_name;
 
-       Jim_CreateCommand(interp, ocd_name, script_command, c, NULL);
-       free((void *)ocd_name);
+               Jim_CreateCommand(interp, ocd_name, script_command, c, NULL);
+               free((void *)ocd_name);
+       }
 
        /* we now need to add an overrideable proc */
        const char *override_name = alloc_printf("proc %s {args} {"
                        "if {[catch {eval ocd_%s $args}] == 0} "
                        "{return \"\"} else {return -code error}}",
                        full_name, full_name);
-       if (NULL == full_name)
+       if (NULL == override_name)
                goto free_full_name;
 
        Jim_Eval_Named(interp, override_name, __THIS__FILE__, __LINE__);
@@ -314,35 +347,59 @@ struct command* register_command(struct command_context *context,
                return c;
        }
 
-       c = command_new(context, parent, name, cr->handler, cr->mode, cr->help, cr->usage);
-       /* if allocation failed or it is a placeholder (no handler), we're done */
-       if (NULL == c || NULL == c->handler)
-               return c;
+       c = command_new(context, parent, cr);
+       if (NULL == c)
+               return NULL;
 
-       int retval = register_command_handler(c);
-       if (ERROR_OK != retval)
+       if (NULL != c->handler)
        {
-               unregister_command(context, parent, name);
-               c = NULL;
+               int retval = register_command_handler(command_root(c));
+               if (ERROR_OK != retval)
+               {
+                       unregister_command(context, parent, name);
+                       return NULL;
+               }
        }
+
+       if (NULL != cr->jim_handler && NULL == parent)
+               Jim_CreateCommand(interp, cr->name, cr->jim_handler, cr->jim_handler_data, NULL);
+
        return c;
 }
 
 int register_commands(struct command_context *cmd_ctx, struct command *parent,
                const struct command_registration *cmds)
 {
+       int retval = ERROR_OK;
        unsigned i;
-       for (i = 0; cmds[i].name; i++)
+       for (i = 0; cmds[i].name || cmds[i].chain; i++)
        {
-               struct command *c = register_command(cmd_ctx, parent, cmds + i);
-               if (NULL != c)
-                       continue;
+               const struct command_registration *cr = cmds + i;
 
+               struct command *c = NULL;
+               if (NULL != cr->name)
+               {
+                       c = register_command(cmd_ctx, parent, cr);
+                       if (NULL == c)
+                       {
+                               retval = ERROR_FAIL;
+                               break;
+                       }
+               }
+               if (NULL != cr->chain)
+               {
+                       struct command *p = c ? : parent;
+                       retval = register_commands(cmd_ctx, p, cr->chain);
+                       if (ERROR_OK != retval)
+                               break;
+               }
+       }
+       if (ERROR_OK != retval)
+       {
                for (unsigned j = 0; j < i; j++)
                        unregister_command(cmd_ctx, parent, cmds[j].name);
-               return ERROR_FAIL;
        }
-       return ERROR_OK;
+       return retval;
 }
 
 int unregister_all_commands(struct command_context *context,
@@ -728,23 +785,13 @@ static int jim_capture(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 {
        if (argc != 2)
                return JIM_ERR;
-       int retcode;
-       const char *str = Jim_GetString(argv[1], NULL);
-
-       /* capture log output and return it */
-       Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
-       /* a garbage collect can happen, so we need a reference count to this object */
-       Jim_IncrRefCount(tclOutput);
 
-       log_add_callback(tcl_output, tclOutput);
-
-       retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
+       Jim_Obj *tclOutput = command_log_capture_start(interp);
 
-       log_remove_callback(tcl_output, tclOutput);
+       const char *str = Jim_GetString(argv[1], NULL);
+       int retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
 
-       /* We dump output into this local variable */
-       Jim_SetResult(interp, tclOutput);
-       Jim_DecrRefCount(interp, tclOutput);
+       command_log_capture_finish(interp, tclOutput);
 
        return retcode;
 }
@@ -755,6 +802,8 @@ static COMMAND_HELPER(command_help_find, struct command *head,
        if (0 == CMD_ARGC)
                return ERROR_INVALID_ARGUMENTS;
        *out = command_find(head, CMD_ARGV[0]);
+       if (NULL == *out && strncmp(CMD_ARGV[0], "ocd_", 4) == 0)
+               *out = command_find(head, CMD_ARGV[0] + 4);
        if (NULL == *out)
                return ERROR_INVALID_ARGUMENTS;
        if (--CMD_ARGC == 0)
@@ -773,20 +822,48 @@ static COMMAND_HELPER(command_help_show_list, struct command *head, unsigned n,
                CALL_COMMAND_HANDLER(command_help_show, c, n, show_help);
        return ERROR_OK;
 }
+
+#define HELP_LINE_WIDTH(_n) (int)(76 - (2 * _n))
+
+static void command_help_show_indent(unsigned n)
+{
+       for (unsigned i = 0; i < n; i++)
+               LOG_USER_N("  ");
+}
+static void command_help_show_wrap(const char *str, unsigned n, unsigned n2)
+{
+       const char *cp = str, *last = str;
+       while (*cp)
+       {
+               const char *next = last;
+               do {
+                       cp = next;
+                       do {
+                               next++;
+                       } while (*next != ' ' && *next != '\t' && *next != '\0');
+               } while ((next - last < HELP_LINE_WIDTH(n)) && *next != '\0');
+               if (next - last < HELP_LINE_WIDTH(n))
+                       cp = next;
+               command_help_show_indent(n);
+               LOG_USER_N("%.*s", (int)(cp - last), last);
+               LOG_USER_N("\n");
+               last = cp + 1;
+               n = n2;
+       }
+}
 static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
                bool show_help)
 {
-       const char *usage = c->usage ? : "";
-       const char *help = "";
-       const char *sep = "";
-       if (show_help && c->help)
-       {
-               help = c->help ? : "";
-               sep = c->usage ? " | " : "";
+       command_help_show_indent(n);
+       LOG_USER_N("%s", command_name(c, ' '));
+       if (c->usage) {
+               LOG_USER_N(" ");
+               command_help_show_wrap(c->usage, 0, n + 5);
        }
-       command_run_linef(CMD_CTX, "cmd_help {%s} {%s%s%s} %d",
-                       command_name(c, ' '), usage, sep, help, n);
-
+       else
+               LOG_USER_N("\n");
+       if (show_help && c->help)
+               command_help_show_wrap(c->help, n + 3, n + 3);
        if (++n >= 2)
                return ERROR_OK;
 
@@ -795,32 +872,106 @@ static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
 }
 COMMAND_HANDLER(handle_help_command)
 {
+       bool full = strcmp(CMD_NAME, "help") == 0;
+
        struct command *c = CMD_CTX->commands;
 
        if (0 == CMD_ARGC)
-               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, true);
+               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, full);
 
        int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
        if (ERROR_OK != retval)
                return retval;
 
-       return CALL_COMMAND_HANDLER(command_help_show, c, 0, true);
+       return CALL_COMMAND_HANDLER(command_help_show, c, 0, full);
 }
 
-COMMAND_HANDLER(handle_usage_command)
+static int command_unknown_find(unsigned argc, Jim_Obj *const *argv,
+               struct command *head, struct command **out, bool top_level)
 {
-       struct command *c = CMD_CTX->commands;
+       if (0 == argc)
+               return argc;
+       const char *cmd_name = Jim_GetString(argv[0], NULL);
+       struct command *c = command_find(head, cmd_name);
+       if (NULL == c && top_level && strncmp(cmd_name, "ocd_", 4) == 0)
+               c = command_find(head, cmd_name + 4);
+       if (NULL == c)
+               return argc;
+       *out = c;
+       return command_unknown_find(--argc, ++argv, (*out)->children, out, false);
+}
 
-       if (0 == CMD_ARGC)
-               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, false);
+static int command_unknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+       const char *cmd_name = Jim_GetString(argv[0], NULL);
+       script_debug(interp, cmd_name, argc - 1, argv + 1);
+
+       struct command_context *cmd_ctx = current_command_context();
+       struct command *c = cmd_ctx->commands;
+       int remaining = command_unknown_find(argc - 1, argv + 1, c, &c, true);
+       // if nothing could be consumed, then it's really an unknown command
+       if (remaining == argc - 1)
+       {
+               const char *cmd = Jim_GetString(argv[1], NULL);
+               LOG_ERROR("Unknown command:\n  %s", cmd);
+               return JIM_OK;
+       }
 
-       int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
-       if (ERROR_OK != retval)
-               return retval;
+       bool found = true;
+       Jim_Obj *const *start;
+       unsigned count;
+       if (c->handler || c->jim_handler)
+       {
+               // include the command name in the list
+               count = remaining + 1;
+               start = argv + (argc - remaining - 1);
+       }
+       else
+       {
+               c = command_find(cmd_ctx->commands, "help");
+               if (NULL == c)
+               {
+                       LOG_ERROR("unknown command, but help is missing too");
+                       return JIM_ERR;
+               }
+               count = argc - remaining;
+               start = argv;
+               found = false;
+       }
+       // pass the command through to the intended handler
+       if (c->jim_handler)
+       {
+               interp->cmdPrivData = c->jim_handler_data;
+               return (*c->jim_handler)(interp, count, start);
+       }
 
-       return CALL_COMMAND_HANDLER(command_help_show, c, 0, false);
+       return script_command_run(interp, count, start, c, found);
 }
 
+static int jim_command_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+       if (1 == argc)
+               return JIM_ERR;
+
+       struct command_context *cmd_ctx = current_command_context();
+       struct command *c = cmd_ctx->commands;
+       int remaining = command_unknown_find(argc - 1, argv + 1, c, &c, true);
+       // if nothing could be consumed, then it's an unknown command
+       if (remaining == argc - 1)
+       {
+               Jim_SetResultString(interp, "unknown", -1);
+               return JIM_OK;
+       }
+
+       if (c->jim_handler)
+               Jim_SetResultString(interp, "native", -1);
+       else if (c->handler)
+               Jim_SetResultString(interp, "simple", -1);
+       else
+               Jim_SetResultString(interp, "group", -1);
+
+       return JIM_OK;
+}
 
 int help_add_command(struct command_context *cmd_ctx, struct command *parent,
                const char *cmd_name, const char *help_text, const char *usage)
@@ -843,8 +994,9 @@ int help_add_command(struct command_context *cmd_ctx, struct command *parent,
                        return ERROR_FAIL;
                }
                LOG_DEBUG("added '%s' help text", cmd_name);
+               return ERROR_OK;
        }
-       else
+       if (help_text)
        {
                bool replaced = false;
                if (nc->help)
@@ -853,12 +1005,25 @@ int help_add_command(struct command_context *cmd_ctx, struct command *parent,
                        replaced = true;
                }
                nc->help = strdup(help_text);
-
                if (replaced)
                        LOG_INFO("replaced existing '%s' help", cmd_name);
                else
                        LOG_DEBUG("added '%s' help text", cmd_name);
        }
+       if (usage)
+       {
+               bool replaced = false;
+               if (nc->usage)
+               {
+                       free((void *)nc->usage);
+                       replaced = true;
+               }
+               nc->usage = strdup(usage);
+               if (replaced)
+                       LOG_INFO("replaced existing '%s' usage", cmd_name);
+               else
+                       LOG_DEBUG("added '%s' usage text", cmd_name);
+       }
        return ERROR_OK;
 }
 
@@ -871,7 +1036,14 @@ COMMAND_HANDLER(handle_help_add_command)
        }
 
        // save help text and remove it from argument list
-       const char *help_text = CMD_ARGV[--CMD_ARGC];
+       const char *str = CMD_ARGV[--CMD_ARGC];
+       const char *help = !strcmp(CMD_NAME, "add_help_text") ? str : NULL;
+       const char *usage = !strcmp(CMD_NAME, "add_usage_text") ? str : NULL;
+       if (!help && !usage)
+       {
+               LOG_ERROR("command name '%s' is unknown", CMD_NAME);
+               return ERROR_INVALID_ARGUMENTS;
+       }
        // likewise for the leaf command name
        const char *cmd_name = CMD_ARGV[--CMD_ARGC];
 
@@ -883,7 +1055,7 @@ COMMAND_HANDLER(handle_help_add_command)
                if (ERROR_OK != retval)
                        return retval;
        }
-       return help_add_command(CMD_CTX, c, cmd_name, help_text, NULL);
+       return help_add_command(CMD_CTX, c, cmd_name, help, usage);
 }
 
 /* sleep command sleeps for <n> miliseconds
@@ -922,6 +1094,18 @@ COMMAND_HANDLER(handle_sleep_command)
        return ERROR_OK;
 }
 
+static const struct command_registration command_subcommand_handlers[] = {
+       {
+               .name = "type",
+               .mode = COMMAND_ANY,
+               .jim_handler = &jim_command_type,
+               .usage = "<name> ...",
+               .help = "Returns the type of built-in command:"
+                       "'native', 'simple', 'group', or 'unknown'",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
 static const struct command_registration command_builtin_handlers[] = {
        {
                .name = "add_help_text",
@@ -930,6 +1114,13 @@ static const struct command_registration command_builtin_handlers[] = {
                .help = "add new command help text",
                .usage = "<command> [...] <help_text>]",
        },
+       {
+               .name = "add_usage_text",
+               .handler = &handle_help_add_command,
+               .mode = COMMAND_ANY,
+               .help = "add new command usage text",
+               .usage = "<command> [...] <usage_text>]",
+       },
        {
                .name = "sleep",
                .handler = &handle_sleep_command,
@@ -942,15 +1133,21 @@ static const struct command_registration command_builtin_handlers[] = {
                .name = "help",
                .handler = &handle_help_command,
                .mode = COMMAND_ANY,
-               .help = "show built-in command help",
-               .usage = "[<command_name> ...]",
+               .help = "show full command help",
+               .usage = "[<command> ...]",
        },
        {
                .name = "usage",
-               .handler = &handle_usage_command,
+               .handler = &handle_help_command,
                .mode = COMMAND_ANY,
-               .help = "show command usage",
-               .usage = "[<command_name> ...]",
+               .help = "show basic command usage",
+               .usage = "[<command> ...]",
+       },
+       {
+               .name = "command",
+               .mode= COMMAND_ANY,
+               .help = "core command group (introspection)",
+               .chain = command_subcommand_handlers,
        },
        COMMAND_REGISTRATION_DONE
 };
@@ -1000,6 +1197,7 @@ struct command_context* command_init(const char *startup_tcl)
        Jim_SetGlobalVariableStr(interp, "ocd_HOSTOS",
                        Jim_NewStringObj(interp, HostOs , strlen(HostOs)));
 
+       Jim_CreateCommand(interp, "unknown", &command_unknown, NULL, NULL);
        Jim_CreateCommand(interp, "ocd_find", jim_find, NULL, NULL);
        Jim_CreateCommand(interp, "echo", jim_echo, NULL, NULL);
        Jim_CreateCommand(interp, "capture", jim_capture, NULL, NULL);
@@ -1054,18 +1252,6 @@ void process_jim_events(void)
 #endif
 }
 
-void register_jim(struct command_context *cmd_ctx, const char *name,
-               Jim_CmdProc cmd, const char *help)
-{
-       Jim_CreateCommand(interp, name, cmd, NULL, NULL);
-
-       Jim_Obj *cmd_list = Jim_NewListObj(interp, NULL, 0);
-       Jim_ListAppendElement(interp, cmd_list,
-                       Jim_NewStringObj(interp, name, -1));
-
-       help_add_command(cmd_ctx, NULL, name, help, NULL);
-}
-
 #define DEFINE_PARSE_NUM_TYPE(name, type, func, min, max) \
        int parse##name(const char *str, type *ul) \
        { \

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)