/* * Copyright (C) 2018 Konsulko Group * Author Pantelis Antoniou * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct state { const char *url; int port; const char *token; const char *api; char *uri; bool interactive; bool debug; bool noexit; sd_event *loop; struct afb_wsj1 *wsj1; const char *proto; bool hangup; }; struct cmd { const char *verb; int (*call)(struct state *s, const struct cmd *c, int argc, char *argv[]); void (*reply)(void *closure, struct afb_wsj1_msg *msg); }; /* print usage of the program */ /* declaration of functions */ static void on_wsj1_hangup(void *closure, struct afb_wsj1 *wsj1); static void on_wsj1_call(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg); static void on_wsj1_event(void *closure, const char *event, struct afb_wsj1_msg *msg); /* the callback interface for wsj1 */ static struct afb_wsj1_itf wsj1_itf = { .on_hangup = on_wsj1_hangup, .on_call = on_wsj1_call, .on_event = on_wsj1_event }; static void on_reply(struct state *s, const char *verb, struct afb_wsj1_msg *msg) { printf("ON-REPLY %s: %s: %s\n%s\n", s->api, verb, afb_wsj1_msg_is_reply_ok(msg) ? "OK" : "ERROR", json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg), JSON_C_TO_STRING_PRETTY)); fflush(stdout); /* if non interactive terminate */ if (!s->interactive) { if (!s->noexit) sd_event_exit(s->loop, afb_wsj1_msg_is_reply_ok(msg) ? 0 : EXIT_FAILURE); else printf("Ctrl-C to exit\n"); } } static int do_call(struct state *s, const struct cmd *c, json_object *jparams) { printf("CALL %s(%s)\n", c->verb, jparams ? json_object_to_json_string(jparams) : ""); return afb_wsj1_call_j(s->wsj1, s->api, c->verb, jparams, c->reply, s); } static int call_void(struct state *s, const struct cmd *c, int argc, char *argv[]) { return do_call(s, c, NULL); } static void on_ping_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "ping", msg); } static void on_subscribe_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "subscribe", msg); } static void on_unsubscribe_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "unsubscribe", msg); } static int call_subscribe_unsubscribe(struct state *s, const struct cmd *c, int argc, char *argv[]) { json_object *jobj; /* must give event name */ if (argc < 1) return -1; jobj = json_object_new_object(); json_object_object_add(jobj, "value", json_object_new_string(argv[0])); return do_call(s, c, jobj); } static void on_state_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "state", msg); } static void on_reset_counters_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "reset_counters", msg); } static void on_technologies_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "technologies", msg); } static void on_services_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "services", msg); } static void on_enable_technology_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "enable_technology", msg); } static void on_disable_technology_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "disable_technology", msg); } static int call_technology_arg(struct state *s, const struct cmd *c, int argc, char *argv[]) { json_object *jobj; if (argc < 1) return -1; jobj = json_object_new_object(); json_object_object_add(jobj, "technology", json_object_new_string(argv[0])); return do_call(s, c, jobj); } static void on_scan_services_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "scan_services", msg); } static void on_offline_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "offline", msg); } static int call_offline(struct state *s, const struct cmd *c, int argc, char *argv[]) { json_object *jobj; /* with no arguments it's a getter */ if (argc >= 1) { jobj = json_object_new_object(); json_object_object_add(jobj, "value", json_object_new_string(argv[0])); } else jobj = NULL; return do_call(s, c, jobj); } static void on_connect_service_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "connect_service", msg); } static int call_service_arg(struct state *s, const struct cmd *c, int argc, char *argv[]) { json_object *jobj; if (argc < 1) return -1; jobj = json_object_new_object(); json_object_object_add(jobj, "service", json_object_new_string(argv[0])); return do_call(s, c, jobj); } static void on_disconnect_service_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "disconnect_service", msg); } static void on_remove_service_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "remove_service", msg); } static void on_move_service_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "move_service", msg); } static int call_move_service(struct state *s, const struct cmd *c, int argc, char *argv[]) { json_object *jobj; /* 3 arguments and the middle must be before or after */ if (argc < 3 || (strcmp(argv[1], "before") && strcmp(argv[1], "after"))) return -1; jobj = json_object_new_object(); json_object_object_add(jobj, "service", json_object_new_string(argv[0])); json_object_object_add(jobj, !strcmp(argv[1], "before") ? "before_service" : "after_service", json_object_new_string(argv[2])); return do_call(s, c, jobj); } static void on_set_property_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "set_property", msg); } static void on_get_property_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "get_property", msg); } static bool get_property_obj(json_object *jparent, int argc, char *argv[]) { json_object *jobj, *jobj2; int i, j, nest; for (i = 0; i < argc; i++) { if ((i < (argc - 1) && !strcmp(argv[i+1], "{"))) { nest = 1; for (j = i + 2; j < argc; j++) { if (!strcmp(argv[j], "{")) nest++; else if (!strcmp(argv[j], "}")) nest--; if (!nest) break; } if (j >= argc) return false; jobj = json_object_new_object(); jobj2 = json_object_new_array(); get_property_obj(jobj2, j - i - 2, argv + i + 2); json_object_object_add(jobj, argv[i], jobj2); i = j; } else jobj = json_object_new_string(argv[i]); json_object_array_add(jparent, jobj); } return true; } struct json_object *get_property_value(const char *str) { char *le, *de; long long ll; double d; if (!strcmp(str, "null")) return NULL; if (!strcmp(str, "true")) return json_object_new_boolean(true); if (!strcmp(str, "false")) return json_object_new_boolean(false); /* try both double and long and see which one is furthest */ ll = strtoll(str, &le, 0); d = strtod(str, &de); if (de > le) { while (isspace(*de)) de++; if (!*de) return json_object_new_double(d); } else { while (isspace(*le)) le++; if (!*le) return json_object_new_int64((int64_t)ll); } /* everything else is a string */ return json_object_new_string(str); } /* how many args is a single json object value */ static int next_span(int pos, int argc, char *argv[]) { const char *left, *right; int j, nest; if (pos >= argc) return 0; if (!strcmp(argv[pos], "{") || !strcmp(argv[pos], "[")) { if (!strcmp(argv[pos], "{")) { left = "{"; right = "}"; } else { left = "["; right = "]"; } nest = 1; for (j = pos + 1; nest > 0 && j < argc; j++) { if (!strcmp(argv[j], left)) nest++; else if (!strcmp(argv[j], right)) nest--; } if (nest && j >= argc) { fprintf(stderr, "nesting error\n"); return -1; } return j - pos; } return 1; } bool add_property_value(json_object *jparent, const char *key, int argc, char *argv[]) { json_object *jobj; const char *key2; int i, span; if (!strcmp(argv[0], "[")) { assert(!strcmp(argv[argc - 1], "]")); argc -= 2; argv++; jobj = json_object_new_array(); for (i = 0; i < argc; ) { span = next_span(i, argc, argv); if (span < 0) { fprintf(stderr, "bad nesting\n"); return NULL; } if (!add_property_value(jobj, NULL, span, argv + i)) { fprintf(stderr, "error adding array\n"); return false; } i += span; } } else if (!strcmp(argv[0], "{")) { assert(!strcmp(argv[argc - 1], "}")); argc -= 2; argv++; jobj = json_object_new_object(); for (i = 0; i < argc; ) { key2 = argv[i]; span = next_span(i + 1, argc, argv); if (span < 0) { fprintf(stderr, "bad nesting\n"); return NULL; } if (!add_property_value(jobj, key2, span, argv + i + 1)) { fprintf(stderr, "error adding object\n"); return false; } i += 1 + span; } } else { jobj = get_property_value(argv[0]); } if (key) json_object_object_add(jparent, key, jobj); else json_object_array_add(jparent, jobj); return true; } bool set_property_obj(json_object *jparent, int argc, char *argv[]) { int i, span; const char *key; for (i = 0; i < argc; ) { key = argv[i]; if (!strcmp(key, "{") || !strcmp(key, "[")) { fprintf(stderr, "Bad object start {\n"); return false; } i++; if (i > (argc - 1)) { fprintf(stderr, "out of arguments on key %s\n", key); return false; } span = next_span(i, argc, argv); if (span < 0) { fprintf(stderr, "bad nesting\n"); return false; } if (!add_property_value(jparent, key, span, argv + i)) { fprintf(stderr, "error adding property\n"); return false; } i += span; } return true; } static int call_get_set_property(struct state *s, const struct cmd *c, int argc, char *argv[]) { json_object *jobj, *jprop = NULL; const char *type; int min_argc; if (argc < 1) return -1; type = argv[0]; /* global or technology or service */ if (strcmp(type, "global") && strcmp(type, "technology") && strcmp(type, "service")) { fprintf(stderr, "unknown property type %s\n", type); return -1; } /* minimum is get global */ min_argc = 1; /* technology or service have extra argument */ if (!strcmp(type, "technology") || !strcmp(type, "service")) min_argc++; /* set property must have an argument */ min_argc += !strcmp(c->verb, "set_property") ? 2 : 0; if (argc < min_argc) { fprintf(stderr, "not enough arguments\n"); return -1; } jobj = json_object_new_object(); argc--; argv++; if (!strcmp(type, "technology")) { json_object_object_add(jobj, "technology", json_object_new_string(argv[0])); argc--; argv++; } else if (!strcmp(type, "service")) { json_object_object_add(jobj, "service", json_object_new_string(argv[0])); argc--; argv++; } /* get is array, set is object */ if (!strcmp(c->verb, "get_property")) { if (argc > 0) { jprop = json_object_new_array(); get_property_obj(jprop, argc, argv); } } else { jprop = json_object_new_object(); set_property_obj(jprop, argc, argv); } if (jprop) json_object_object_add(jobj, "properties", jprop); return do_call(s, c, jobj); } static void on_agent_response_reply(void *closure, struct afb_wsj1_msg *msg) { on_reply(closure, "agent_response", msg); } static int call_agent_response(struct state *s, const struct cmd *c, int argc, char *argv[]) { json_object *jresp = NULL, *jprop = NULL; const char *method; const char *propname; int id; if (argc < 3) return -1; method = *argv++; argc--; id = (int)strtol(*argv++, NULL, 10); argc--; if (id <= 0) { fprintf(stderr, "bad agent response id %d\n", id); return -1; } propname = NULL; if (!strcmp(method, "request-input")) { propname = "fields"; } else { fprintf(stderr, "unknown agent response method '%s'\n", method); return -1; } jresp = json_object_new_object(); json_object_object_add(jresp, "method", json_object_new_string(method)); json_object_object_add(jresp, "id", json_object_new_int(id)); if (argc > 0) { jprop = json_object_new_object(); set_property_obj(jprop, argc, argv); json_object_object_add(jresp, propname, jprop); } return do_call(s, c, jresp); } static const struct cmd cmds[] = { { .verb = "ping", .call = call_void, .reply = on_ping_reply, }, { .verb = "subscribe", .call = call_subscribe_unsubscribe, .reply = on_subscribe_reply, }, { .verb = "unsubscribe", .call = call_subscribe_unsubscribe, .reply = on_unsubscribe_reply, }, { .verb = "state", .call = call_void, .reply = on_state_reply, }, { .verb = "reset_counters", .call = call_service_arg, .reply = on_reset_counters_reply, }, { .verb = "technologies", .call = call_void, .reply = on_technologies_reply, }, { .verb = "services", .call = call_void, .reply = on_services_reply, }, { .verb = "enable_technology", .call = call_technology_arg, .reply = on_enable_technology_reply, }, { .verb = "disable_technology", .call = call_technology_arg, .reply = on_disable_technology_reply, }, { .verb = "scan_services", .call = call_technology_arg, .reply = on_scan_services_reply, }, { .verb = "offline", .call = call_offline, .reply = on_offline_reply, }, { .verb = "connect_service", .call = call_service_arg, .reply = on_connect_service_reply, }, { .verb = "disconnect_service", .call = call_service_arg, .reply = on_disconnect_service_reply, }, { .verb = "remove_service", .call = call_service_arg, .reply = on_remove_service_reply, }, { .verb = "move_service", .call = call_move_service, .reply = on_move_service_reply, }, { .verb = "set_property", .call = call_get_set_property, .reply = on_set_property_reply, }, { .verb = "get_property", .call = call_get_set_property, .reply = on_get_property_reply, }, { .verb = "agent_response", .call = call_agent_response, .reply = on_agent_response_reply, }, { } }; static int call_cmd(struct state *s, int argc, char *argv[]) { const struct cmd *c; const char *verb; /* first argument is verb */ if (argc < 1 || !argv[0]) { if (!s->interactive) fprintf(stderr, "No verb given\n"); goto out_err; } verb = argv[0]; argv++; argc--; for (c = cmds; c->verb; c++) { if (!strcmp(verb, c->verb)) return c->call(s, c, argc, argv); } if (!s->interactive) fprintf(stderr, "Unknown API verb \"%s\"\n", verb); out_err: if (!s->interactive) sd_event_exit(s->loop, EXIT_FAILURE); return -1; } static struct option opts[] = { { "url", required_argument, 0, 'u' }, { "port", required_argument, 0, 'p' }, { "token", required_argument, 0, 't' }, { "api", required_argument, 0, 'a' }, { "noexit", no_argument, 0, 'x' }, { "debug", no_argument, 0, 'd' }, { "help", no_argument, 0, 'h' }, { } }; #define DEFAULT_URL "ws://localhost" #define DEFAULT_PORT 1234 #define DEFAULT_TOKEN "HELLO" #define DEFAULT_API "network-manager" #define DEFAULT_DEBUG true static void usage(int status, const char *arg0) __attribute__((__noreturn__)); static void usage(int status, const char *arg0) { const char *name = strrchr(arg0, '/'); FILE *outf = status ? stderr : stdout; name = name ? name + 1 : arg0; fprintf(outf, "usage: %s [arguments]\n", name); fprintf(outf, " options are:\n"); fprintf(outf, " -u, --url=X URL to connect to (default %s)\n", DEFAULT_URL); fprintf(outf, " -p, --port=X Port to use for connection (default %d)\n", DEFAULT_PORT); fprintf(outf, " -t, --token=X Token to use (default %s)\n", DEFAULT_TOKEN); fprintf(outf, " -a, --api=X API to use (default %s)\n", DEFAULT_API); fprintf(outf, " -x, --noexit Do not exit in non-interactive mode (events)\n"); fprintf(outf, " -d, --debug Enable debug printouts\n"); fprintf(outf, " -h, -?, --help Display this help\n"); exit(status); } /* entry function */ int main(int argc, char **argv) { int rc, ret, cc = 0, option_index; const char *name; struct state state, *s = &state; memset(s, 0, sizeof(*s)); s->url = DEFAULT_URL; s->port = DEFAULT_PORT; s->token = DEFAULT_TOKEN; s->api = DEFAULT_API; s->debug = DEFAULT_DEBUG; s->interactive = false; s->noexit = false; while ((cc = getopt_long(argc, argv, "u:p:t:a:xdh?", opts, &option_index)) != -1) { if (cc == 0 && option_index >= 0) { name = opts[option_index].name; if (!name) continue; /* we don't have long options without short ones */ usage(EXIT_FAILURE, argv[0]); } switch (cc) { case 'u': s->url = optarg; break; case 'p': s->port = atoi(optarg); break; case 't': s->token = optarg; break; case 'a': s->api = optarg; break; case 'x': s->noexit = true; break; case 'd': s->debug = true; break; case 'h': case '?': usage(0, argv[0]); break; } } /* no arguments left, interactive mode */ if (optind >= argc) { printf("optind=%d\n", optind); printf("argc=%d\n", argc); s->interactive = true; } ret = EXIT_FAILURE; rc = asprintf(&s->uri, "%s:%d/api?token=%s", s->url, s->port, s->token); if (rc < 0) { fprintf(stderr, "Unable to build URI\n"); goto out; } if (s->debug) { fprintf(stderr, "URI: \"%s\"\n", s->uri); fprintf(stderr, "API: \"%s\"\n", s->api); fprintf(stderr, "interactive: %s\n", s->interactive ? "true" : "false"); fprintf(stderr, "noexit: %s\n", s->noexit ? "true" : "false"); } /* get the default event loop */ rc = sd_event_default(&s->loop); if (rc < 0) { fprintf(stderr, "connection to default event loop failed: %s\n", strerror(-rc)); goto out_no_event; } /* connect the websocket wsj1 to the uri given by the first argument */ s->wsj1 = afb_ws_client_connect_wsj1(s->loop, s->uri, &wsj1_itf, s); if (s->wsj1 == NULL) { fprintf(stderr, "connection to %s failed: %m\n", argv[1]); goto out_no_wsj1; } if (!s->interactive) { ret = call_cmd(s, argc - optind, argv + optind); if (ret < 0) { fprintf(stderr, "command failed\n"); sd_event_exit(s->loop, EXIT_FAILURE); } } else { fprintf(stderr, "interactive mode not yet supported\n"); sd_event_exit(s->loop, EXIT_FAILURE); } ret = sd_event_loop(s->loop); /* cleanup */ afb_wsj1_unref(s->wsj1); out_no_wsj1: sd_event_unref(s->loop); out_no_event: free(s->uri); out: return ret; } /* called when wsj1 hangsup */ static void on_wsj1_hangup(void *closure, struct afb_wsj1 *wsj1) { struct state *s = closure; printf("ON-HANGUP\n"); fflush(stdout); s->hangup = true; sd_event_exit(s->loop, 0); } /* called when wsj1 receives a method invocation */ static void on_wsj1_call(void *closure, const char *api, const char *verb, struct afb_wsj1_msg *msg) { int rc; printf("ON-CALL %s/%s:\n%s\n", api, verb, json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg), JSON_C_TO_STRING_PRETTY)); fflush(stdout); rc = afb_wsj1_reply_error_s(msg, "\"unimplemented\"", NULL); if (rc < 0) fprintf(stderr, "replying failed: %m\n"); } /* called when wsj1 receives an event */ static void on_wsj1_event(void *closure, const char *event, struct afb_wsj1_msg *msg) { printf("ON-EVENT %s:\n%s\n", event, json_object_to_json_string_ext(afb_wsj1_msg_object_j(msg), JSON_C_TO_STRING_PRETTY)); fflush(stdout); }