diff --git a/bbfdmd/src/bbfdmd.c b/bbfdmd/src/bbfdmd.c index 3cc7065f..b8a5efb4 100644 --- a/bbfdmd/src/bbfdmd.c +++ b/bbfdmd/src/bbfdmd.c @@ -367,9 +367,9 @@ static int bbfdm_get_handler(struct ubus_context *ctx, struct ubus_object *obj _ if (is_subprocess_needed) { INFO("Creating subprocess for get method"); - bbfdm_start_deferred(&data, bbfdm_get_value_async); + bbfdm_start_deferred(&data, bbfdm_get_value); } else { - bbfdm_get_value(&data); + bbfdm_get_value(&data, NULL); } free_path_list(&paths_list); @@ -538,20 +538,26 @@ int bbfdm_set_handler(struct ubus_context *ctx, struct ubus_object *obj, INFO("ubus method|%s|, name|%s|, path(%s)", method, obj->name, path); + blob_buf_init(&data.bb, 0); + bbf_init(&data.bbf_ctx); + data.bbf_ctx.in_param = path; - fault = fill_pvlist_set(path, tb[DM_SET_VALUE] ? blobmsg_get_string(tb[DM_SET_VALUE]) : NULL, tb[DM_SET_OBJ_PATH], &pv_list); + fault = fill_pvlist_set(&data, path, tb[DM_SET_VALUE] ? blobmsg_get_string(tb[DM_SET_VALUE]) : NULL, tb[DM_SET_OBJ_PATH], &pv_list); if (fault) { - ERR("Fault in fill pvlist set path |%s|", data.bbf_ctx.in_param); + ERR("Fault in fill pvlist set path |%s| : |%d|", data.bbf_ctx.in_param, fault); + fill_err_code_array(&data, fault); + goto end; + } + + if (list_empty(&pv_list)) { + ERR("Fault in fill pvlist set path |%s| : |list is empty|", data.bbf_ctx.in_param); fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR); goto end; } data.plist = &pv_list; - blob_buf_init(&data.bb, 0); - bbf_init(&data.bbf_ctx); - // no need to process it further since transaction-id is not valid if (data.trans_id && !is_transaction_valid(data.trans_id)) { WARNING("Transaction not started yet"); @@ -703,7 +709,7 @@ int bbfdm_add_handler(struct ubus_context *ctx, struct ubus_object *obj, snprintf(path, PATH_MAX, "%s%s.", (char *)blobmsg_data(tb[DM_ADD_PATH]), data.bbf_ctx.addobj_instance); - fault = fill_pvlist_set(path, NULL, tb[DM_ADD_OBJ_PATH], &pv_list); + fault = fill_pvlist_set(&data, path, NULL, tb[DM_ADD_OBJ_PATH], &pv_list); if (fault) { ERR("Fault in fill pvlist set path |%s|", path); fill_err_code_array(&data, USP_FAULT_INTERNAL_ERROR); diff --git a/bbfdmd/src/common.h b/bbfdmd/src/common.h index 8b6f3083..1e898069 100644 --- a/bbfdmd/src/common.h +++ b/bbfdmd/src/common.h @@ -70,6 +70,8 @@ int get_instance_mode(int instance_mode); #define WARNING(fmt, args...) \ print_warning("[%s:%d] " fmt, __func__, __LINE__, ##args) +int get_resolved_paths(struct dmctx *bbf_ctx, char *qpath, struct list_head *resolved_paths); + // glibc doesn't guarantee a 0 termianted string on strncpy // strncpy with always 0 terminated string static inline void strncpyt(char *dst, const char *src, size_t n) diff --git a/bbfdmd/src/get.c b/bbfdmd/src/get.c index 3ad09c75..98ec6923 100644 --- a/bbfdmd/src/get.c +++ b/bbfdmd/src/get.c @@ -16,11 +16,849 @@ #include -void bbfdm_get_value_async(bbfdm_data_t *data, void *output) +extern char *input_json; + +enum operation { + OPER_EQUAL_EQUAL, + OPER_NOT_EQUAL, + OPER_LESS_THAN_EQUAL, + OPER_GREATER_THAN_EQUAL, + OPER_LESS_THAN, + OPER_GREATER_THAN, +}; + +static const char * const operations[] = { + [OPER_EQUAL_EQUAL] = "==", + [OPER_NOT_EQUAL] = "!=", + [OPER_LESS_THAN_EQUAL] = "<=", + [OPER_GREATER_THAN_EQUAL] = ">=", + [OPER_LESS_THAN] = "<", + [OPER_GREATER_THAN] = ">" +}; + +static bool get_base_path(const char *query_path, char *base_path) { + size_t i, j, qlen; + bool found = false; + char ch; + + if (base_path == NULL) + return false; + + base_path[0] = '\0'; + + if (strncmp(query_path, ROOT_NODE, strlen(ROOT_NODE)) != 0) + return false; + + qlen = DM_STRLEN(query_path); + + for (i = 0; i < qlen; i++) { + switch (query_path[i]) { + case '[': + if (query_path[i - 1] != '.') + return false; + + for (j = i + 1; j < qlen; j++) { + ch = query_path[j]; + if ((ch == '>') || + (ch == '<') || + (ch == '=')) { + found = true; + break; + } + if (query_path[j] == ']') { + i = j; + break; + } + } + break; + case '*': + if (query_path[i - 1] != '.' && + query_path[i + 1] != '.') + return false; + found = true; + break; + } + + if (found) + break; + } + + strncpyt(base_path, query_path, i + 1); + return true; +} + +static bool get_next_param(char *qPath, size_t *pos, char *param) +{ + size_t qlen, i; + bool found; + + if (qPath == NULL || pos == NULL || param == NULL) + return false; + + qlen = DM_STRLEN(qPath); + if (*pos >= qlen || *pos >= MAX_DM_PATH || qlen >= MAX_DM_PATH) + return false; + + param[0] = '\0'; + found = false; + for (i = *pos; i < qlen; i++) { + switch (qPath[i]) { + case '[': + while (i < qlen) { + if (qPath[++i] == ']') + break; + } + if (qPath[++i] != '.') { + ERR("No dot after search parameters"); + return false; + } + // skip the dot + i++; + found = true; + break; + case '.': + i++; + found = true; + break; + } + + if (found) + break; + } + + if (i == qlen) + strncpyt(param, qPath + *pos, i - *pos + 1); + else + strncpyt(param, qPath + *pos, i - *pos); + + *pos = i; + + // removing last . from param + qlen = DM_STRLEN(param); + if (qlen > 0) { + if (param[qlen - 1] == '.') + param[qlen - 1] = '\0'; + } + + if (param[0] == '*') { + size_t param_len = DM_STRLEN(param); + if (param_len > 1) { + ERR("* followed by other characters(%s)", param); + return false; + } + + // * is not followed by . + if (qPath[*pos - 1] != '.') { + ERR("* not followed by dot(%c)", qPath[*pos - 1]); + return false; + } + } + + return true; +} + +static bool is_present_in_datamodel(struct dmctx *bbf_ctx, char *path) +{ + bool found = false; + struct dm_parameter *n; + size_t plen; + + DEBUG("path(%s)", path); + plen = DM_STRLEN(path); + list_for_each_entry(n, &bbf_ctx->list_parameter, list) { + if (strncmp(n->name, path, plen) == 0) { + found = true; + break; + } + } + + return found; +} + +static int seperator(char *token, char *para, enum operation *oper, char *value) +{ + char *ptr; + size_t plen; + bool found; + uint8_t i, op_count; + + // handle ==, !=, <=, >= + if (token == NULL || para == NULL || + oper == NULL || value == NULL) + return USP_FAULT_INTERNAL_ERROR; + + found = false; + op_count = ARRAY_SIZE(operations); + for (i = 0; i < op_count; i++) { + ptr = strstr(token, operations[i]); + if (ptr) { + *oper = i; + plen = (size_t)labs(ptr - token); + ptr += DM_STRLEN(operations[i]); + found = true; + break; + } + } + + if (found) { + strncpyt(para, token, plen + 1); + plen = DM_STRLEN(ptr); + strncpyt(value, ptr, plen + 1); + return 0; + } + + return USP_FAULT_INVALID_PATH_SYNTAX; +} + +static bool get_instance(char *path, size_t start, char *instance) +{ + char *ptr; + size_t plen, path_len; + + if (instance == NULL) + return false; + + path_len = DM_STRLEN(path); + if (path_len <= start) + return false; + + ptr = strchr(path + start, '.'); + + if (ptr == NULL) + return false; + + plen = (size_t)labs(ptr - path) - start; + if (plen > path_len) + return false; + + strncpyt(instance, path + start, plen + 1); + if (strtol(instance, NULL, 10) == 0) { + if (instance[0] != '[') + return false; + } + + return true; +} + +static void handle_special_escape_sequence(char *value, char *buff, size_t buff_len) +{ + size_t i, len, j; + + if (buff == NULL) + return; + + len = DM_STRLEN(value); + j = 0; + for (i = 0; i < len && j < buff_len-1; ++i) { + if (value[i] == '%' && len > i + 2) { + if (value[i + 1] == '2') { + if (value[i + 2] == '5') { + buff[j++] = '%'; + i += 2; + continue; + } else if (value[i + 2] == '2') { + buff[j++] = '"'; + i += 2; + continue; + } + } + } + buff[j++] = value[i]; + } + buff[j] = '\0'; + + DEBUG("value(%s), new_value(%s)", value, buff); +} + +static bool handle_string(char *v1, char *v2, enum operation op, int *fault) +{ + char temp[MAX_DM_VALUE]; + + if (!fault) + return false; + + if (v1 == NULL || v2 == NULL) { + return false; + } + + int v2_len = DM_STRLEN(v2); + if (v2_len == 0) { + *fault = USP_FAULT_INVALID_PATH_SYNTAX; + return false; + } + + if (v2[0] != '"' || v2[v2_len - 1] != '"') { + *fault = USP_FAULT_INVALID_PATH_SYNTAX; + return false; + } + + // Check for %22 and %25 special escape sequences + char buff[MAX_DM_VALUE] = {0}; + handle_special_escape_sequence(v2, buff, MAX_DM_VALUE); + + snprintf(temp, MAX_DM_VALUE, "\"%s\"", v1); + switch (op) { + case OPER_EQUAL_EQUAL: + return !strcmp(temp, buff); + case OPER_NOT_EQUAL: + return !!strcmp(temp, buff); + case OPER_LESS_THAN: + case OPER_GREATER_THAN: + case OPER_LESS_THAN_EQUAL: + case OPER_GREATER_THAN_EQUAL: + *fault = USP_FAULT_INVALID_PATH_SYNTAX; + return false; + } + + return false; +} + +static bool handle_uint(char *v1, char *v2, enum operation op, int *fault) +{ + uint32_t ui1, ui2; + + if (!fault) + return false; + + if (v1 == NULL || v2 == NULL) + return false; + + ui1 = (uint32_t) strtoul(v1, NULL, 10); + ui2 = (uint32_t) strtoul(v2, NULL, 10); + + if ((ui1 == 0 && v1[0] != '0') || + (ui2 == 0 && v2[0] != '0')) { + *fault = USP_FAULT_INVALID_TYPE; + return false; + } + + switch (op) { + case OPER_EQUAL_EQUAL: + return (ui1 == ui2); + case OPER_NOT_EQUAL: + return (ui1 != ui2); + case OPER_LESS_THAN: + return (ui1 < ui2); + case OPER_GREATER_THAN: + return (ui1 > ui2); + case OPER_LESS_THAN_EQUAL: + return (ui1 <= ui2); + case OPER_GREATER_THAN_EQUAL: + return (ui1 >= ui2); + } + + return false; +} + +static bool handle_int(char *v1, char *v2, enum operation op, int *fault) +{ + int32_t i1, i2; + + if (!fault) + return false; + + if (v1 == NULL || v2 == NULL) + return false; + + i1 = (int32_t) strtol(v1, NULL, 10); + i2 = (int32_t) strtol(v2, NULL, 10); + + if ((i1 == 0 && v1[0] != '0') || + (i2 == 0 && v2[0] != '0')) { + *fault = USP_FAULT_INVALID_TYPE; + return false; + } + + switch (op) { + case OPER_EQUAL_EQUAL: + return (i1 == i2); + case OPER_NOT_EQUAL: + return (i1 != i2); + case OPER_LESS_THAN: + return (i1 < i2); + case OPER_GREATER_THAN: + return (i1 > i2); + case OPER_LESS_THAN_EQUAL: + return (i1 <= i2); + case OPER_GREATER_THAN_EQUAL: + return (i1 >= i2); + } + + return false; +} + +static bool handle_unlong(char *v1, char *v2, enum operation op, int *fault) +{ + uint64_t ul1, ul2; + + if (!fault) + return false; + + if (v1 == NULL || v2 == NULL) + return false; + + ul1 = (uint64_t) strtoll(v1, NULL, 10); + ul2 = (uint64_t) strtoll(v2, NULL, 10); + + if ((ul1 == 0 && v1[0] != '0') || + (ul2 == 0 && v2[0] != '0')) { + *fault = USP_FAULT_INVALID_TYPE; + return false; + } + + switch (op) { + case OPER_EQUAL_EQUAL: + return (ul1 == ul2); + case OPER_NOT_EQUAL: + return (ul1 != ul2); + case OPER_LESS_THAN: + return (ul1 < ul2); + case OPER_GREATER_THAN: + return (ul1 > ul2); + case OPER_LESS_THAN_EQUAL: + return (ul1 <= ul2); + case OPER_GREATER_THAN_EQUAL: + return (ul1 >= ul2); + } + + return false; +} + +static bool handle_long(char *v1, char *v2, enum operation op, int *fault) +{ + int64_t l1, l2; + + if (!fault) + return false; + + if (v1 == NULL || v2 == NULL) + return false; + + l1 = (int64_t) strtoll(v1, NULL, 10); + l2 = (int64_t) strtoll(v2, NULL, 10); + + if ((l1 == 0 && v1[0] != '0') || + (l2 == 0 && v2[0] != '0')) { + *fault = USP_FAULT_INVALID_TYPE; + return false; + } + + switch (op) { + case OPER_EQUAL_EQUAL: + return (l1 == l2); + case OPER_NOT_EQUAL: + return (l1 != l2); + case OPER_LESS_THAN: + return (l1 < l2); + case OPER_GREATER_THAN: + return (l1 > l2); + case OPER_LESS_THAN_EQUAL: + return (l1 <= l2); + case OPER_GREATER_THAN_EQUAL: + return (l1 >= l2); + } + + return false; +} + +static bool handle_bool(char *v1, char *v2, enum operation op, int *fault) +{ + bool vb1, vb2; + + if (!fault) + return false; + + if (v1 == NULL || v2 == NULL) + return false; + + vb1 = get_boolean_string(v1); + vb2 = get_boolean_string(v2); + + switch (op) { + case OPER_EQUAL_EQUAL: + return (vb1 == vb2); + case OPER_NOT_EQUAL: + return (vb1 != vb2); + case OPER_LESS_THAN: + case OPER_GREATER_THAN: + case OPER_LESS_THAN_EQUAL: + case OPER_GREATER_THAN_EQUAL: + *fault = USP_FAULT_INVALID_PATH_SYNTAX; + return false; + } + + return false; +} + +static bool handle_time(char *v1, char *v2, enum operation op, const int *fault) +{ + struct tm tm1, tm2; + char *tmp; + time_t t1, t2; + double sec; + + if (!fault) + return false; + + if (v1 == NULL || v2 == NULL) + return false; + + memset(&tm1, 0, sizeof(t1)); + memset(&tm2, 0, sizeof(t2)); + + tmp = strptime(v1, "%Y-%m-%dT%H:%M:%S", &tm1); + if (tmp == NULL) + return USP_FAULT_INVALID_TYPE; + + tmp = strptime(v2, "%Y-%m-%dT%H:%M:%S", &tm2); + if (tmp == NULL) + return USP_FAULT_INVALID_TYPE; + + t1 = timegm(&tm1); + t2 = timegm(&tm2); + + sec = difftime(t1, t2); + switch (op) { + case OPER_EQUAL_EQUAL: + return (sec == 0); + case OPER_NOT_EQUAL: + return (sec != 0); + case OPER_LESS_THAN: + return (sec < 0); + case OPER_GREATER_THAN: + return (sec > 0); + case OPER_LESS_THAN_EQUAL: + return (sec <= 0); + case OPER_GREATER_THAN_EQUAL: + return (sec >= 0); + } + + return false; +} + +static bool handle_hexbin(const char *v1, const char *v2, enum operation op __attribute__((unused)), + int *fault) +{ + if (v1 == NULL || v2 == NULL) + return false; + + *fault = USP_FAULT_INVALID_PATH_SYNTAX; + return false; +} + +static bool check_values(char *val_type, char *val1, char *val2, enum operation oper, int *fault) +{ + bool result = false; + + DEBUG("type(%s), val1(%s), Val2(%s), Oper(%d)", val_type, val1, val2, oper); + switch (get_dm_type(val_type)) { + case DMT_STRING: + result = handle_string(val1, val2, oper, fault); + break; + case DMT_UNINT: + result = handle_uint(val1, val2, oper, fault); + break; + case DMT_INT: + result = handle_int(val1, val2, oper, fault); + break; + case DMT_UNLONG: + result = handle_unlong(val1, val2, oper, fault); + break; + case DMT_LONG: + result = handle_long(val1, val2, oper, fault); + break; + case DMT_BOOL: + result = handle_bool(val1, val2, oper, fault); + break; + case DMT_TIME: + result = handle_time(val1, val2, oper, fault); + break; + case DMT_HEXBIN: + result = handle_hexbin(val1, val2, oper, fault); + break; + } + + return result; +} + +static int search_n_apply(char *bPath, char *para, enum operation oper, char *value, struct list_head *fltrd, struct list_head *resolved_plist) +{ + char temp[MAX_DM_PATH * 2] = {0}; + struct pvNode *pv = NULL; + size_t blen; int fault = 0; + + blen = DM_STRLEN(bPath); + + if (!list_empty(resolved_plist)) { + struct pathNode *iter, *node; + + list_for_each_entry_safe(iter, node, resolved_plist, list) { + bool is_present = false; + + snprintf(temp, sizeof(temp), "%s%s", iter->path, para); + + list_for_each_entry(pv, fltrd, list) { + if (strcmp(pv->param, temp) == 0) { + is_present = true; + break; + } + } + + if (!is_present) { + list_del(&iter->list); + free(iter); + continue; + } + + if (check_values(pv->type, pv->val, value, oper, &fault) == false) { + list_del(&iter->list); + free(iter); + if (fault != 0) + return fault; + } + } + } else { + list_for_each_entry(pv, fltrd, list) { + char inst[MAX_DM_KEY_LEN]; + + if (strncmp(pv->param, bPath, blen) != 0) + continue; + + if (get_instance(pv->param, blen, inst) == false) + continue; + + snprintf(temp, sizeof(temp), "%s%s.%s", bPath, inst, para); + + if (strcmp(pv->param, temp) != 0) + continue; + + if (check_values(pv->type, pv->val, value, oper, &fault)) { + snprintf(temp, sizeof(temp), "%s%s.", bPath, inst); + add_path_list(temp, resolved_plist); // Resolved List + } else { + if (fault) + return fault; + } + } + } + + return fault; +} + +static int solve_all_filters(struct dmctx *bbf_ctx, char *bPath, char *param, struct list_head *resolved_plist) +{ + char *token, *save; + struct dm_parameter *n; + size_t blen; + int ret = 0; + + LIST_HEAD(pv_local); + LIST_HEAD(plist_local); + + INFO("## Basepath(%s), param(%s)", bPath, param); + + // Use shorter list for rest of the operation + blen = DM_STRLEN(bPath); + list_for_each_entry(n, &bbf_ctx->list_parameter, list) { + if (strncmp(n->name, bPath, blen) == 0) { + add_pv_list(n->name, n->data, n->type, &pv_local); + } + } + + token = strtok_r(param, "&&", &save); + while (token) { + enum operation oper; + char para[MAX_DM_KEY_LEN] = {0}; + char value[MAX_DM_VALUE] = {0}; + + ret = seperator(token, para, &oper, value); + if (ret != 0) + break; + + INFO("Filter Para(%s), oper(%d), Val(%s)", para, oper, value); + + if (match(para, "[*]+")) + ret = USP_FAULT_INVALID_TYPE; + else + ret = search_n_apply(bPath, para, oper, value, &pv_local, &plist_local); + + if (ret != 0) + break; + + if (list_empty(&plist_local)) + break; + + token = strtok_r(NULL, "&&", &save); + } + + if (ret == 0) { + struct pathNode *iter; + + list_for_each_entry(iter, &plist_local, list) { + add_path_list(iter->path, resolved_plist); + } + } + + free_path_list(&plist_local); + free_pv_list(&pv_local); + return ret; +} + +static void refresh_path_list(struct list_head *path_list, struct list_head *plist_local) +{ + struct pathNode *iter; + + free_path_list(path_list); + + list_for_each_entry(iter, plist_local, list) { + add_path_list(iter->path, path_list); + } +} + +static int append_all_instances(unsigned int dm_type, char *bPath, struct list_head *plist_local) +{ + char temp[MAX_DM_PATH]; + int fault = 0; + struct dmctx bbf_ctx = { + .in_param = bPath, + .nextlevel = true, + .enable_plugins = input_json ? false : true, + .instance_mode = INSTANCE_MODE_NUMBER, + .dm_type = dm_type + }; + + bbf_sub_init(&bbf_ctx); + + if (0 == (fault = bbfdm_cmd_exec(&bbf_ctx, BBF_INSTANCES))) { + struct dm_parameter *n = NULL; + + list_for_each_entry(n, &bbf_ctx.list_parameter, list) { + temp[0] = '\0'; + + // Add . + snprintf(temp, MAX_DM_PATH, "%s.", n->name); + add_path_list(temp, plist_local); + } + } + + bbf_sub_cleanup(&bbf_ctx); + return fault; +} + +static int resolve_path(struct dmctx *bbf_ctx, char *qPath, size_t pos, struct list_head *resolved_plist) +{ + char temp[MAX_DM_PATH + 5] = {0}; + char param[MAX_DM_PATH] = {0}; + size_t plen; + struct pathNode *ptr; + int fault; + bool check = true; + size_t start; + bool non_leaf = false; + + LIST_HEAD(plist_local); + + start = pos; + size_t qPath_len = DM_STRLEN(qPath); + if (start >= qPath_len) + return 0; + + if (list_empty(resolved_plist)) + return 0; + + INFO("Entry Len :: %d & qPath :: %s", start, qPath); + + if (strchr(qPath+start, '.') != NULL) + non_leaf = true; + + if (get_next_param(qPath, &start, param) == false) + return USP_FAULT_INVALID_PATH_SYNTAX; + + plen = DM_STRLEN(param); + DEBUG("PARAM ::(%s) pos ::(%d)", param, start); + + fault = 0; + list_for_each_entry(ptr, resolved_plist, list) { + + size_t len = DM_STRLEN(ptr->path); + if (len == 0) + continue; + + snprintf(temp, sizeof(temp), "%s%s", ptr->path, (ptr->path[len - 1] != '.') ? "." : ""); + + if (param[0] == '[') { + param[plen-1] = 0; + fault = solve_all_filters(bbf_ctx, temp, param+1, &plist_local); + } else if (param[0] == '*') { + fault = append_all_instances(bbf_ctx->dm_type, temp, &plist_local); + } else { + char buff[MAX_DM_VALUE] = {0}; + if (non_leaf) + snprintf(buff, sizeof(buff), "%s%s.", temp, param); + else + snprintf(buff, sizeof(buff), "%s%s", temp, param); + + check = is_present_in_datamodel(bbf_ctx, buff); + if (check) + add_path_list(buff, &plist_local); + } + + if (fault) { + free_path_list(&plist_local); + return fault; + } + } + + if (check == false && list_empty(&plist_local)) { + free_path_list(&plist_local); + return bbf_fault_map(bbf_ctx->dm_type, FAULT_9005); + } + + refresh_path_list(resolved_plist, &plist_local); + free_path_list(&plist_local); + + return resolve_path(bbf_ctx, qPath, start, resolved_plist); +} + +int get_resolved_paths(struct dmctx *bbf_ctx, char *qpath, struct list_head *resolved_paths) +{ + char bpath[MAX_DM_PATH] = {0}; + int fault = 0; + + if (get_base_path(qpath, bpath)) { + size_t pos = 0; + + pos = strlen(bpath); + INFO("Base Path :: |%s| Pos :: |%d|", bpath, pos); + + bbf_ctx->in_param = bpath; + + fault = bbfdm_cmd_exec(bbf_ctx, BBF_GET_VALUE); + + if (!fault) { + add_path_list(bpath, resolved_paths); + fault = resolve_path(bbf_ctx, qpath, pos, resolved_paths); + } + } else { + INFO("Not able to get base path"); + fault = bbf_fault_map(bbf_ctx->dm_type, FAULT_9005); + } + + if (fault) + WARNING("qpath(%s), fault(%d)", qpath, fault); + + return fault; +} + +void bbfdm_get_value(bbfdm_data_t *data, void *output) +{ struct pathNode *pn; void *array = NULL; + int fault = 0; + + memset(&data->bb, 0, sizeof(struct blob_buf)); bbf_init(&data->bbf_ctx); blob_buf_init(&data->bb, 0); @@ -30,30 +868,29 @@ void bbfdm_get_value_async(bbfdm_data_t *data, void *output) list_for_each_entry(pn, data->plist, list) { bbf_sub_init(&data->bbf_ctx); + LIST_HEAD(resolved_list); - data->bbf_ctx.in_param = pn->path; + if (DM_STRLEN(pn->path) == 0) + snprintf(pn->path, MAX_DM_PATH, ROOT_NODE); + + fault = get_resolved_paths(&data->bbf_ctx, pn->path, &resolved_list); - fault = bbfdm_cmd_exec(&data->bbf_ctx, BBF_GET_VALUE); if (fault) { + data->bbf_ctx.in_param = pn->path; fill_err_code_table(data, fault); } else { - INFO("Preparing result for(%s)", data->bbf_ctx.in_param); + INFO("Preparing result for(%s)", pn->path); + + data->bbf_ctx.in_param = pn->path; if (data->is_raw) { - struct dm_parameter *n = NULL; - - list_for_each_entry(n, &data->bbf_ctx.list_parameter, list) { - void *table = blobmsg_open_table(&data->bb, NULL); - bb_add_string(&data->bb, "path", n->name); - bb_add_string(&data->bb, "data", n->data); - bb_add_string(&data->bb, "type", n->type); - blobmsg_close_table(&data->bb, table); - } + prepare_raw_result(&data->bb, &data->bbf_ctx, &resolved_list); } else { - prepare_pretty_result(data->depth, &data->bb, &data->bbf_ctx); + prepare_pretty_result(data->depth, &data->bb, &data->bbf_ctx, &resolved_list); } } + free_path_list(&resolved_list); bbf_sub_cleanup(&data->bbf_ctx); } @@ -68,69 +905,10 @@ void bbfdm_get_value_async(bbfdm_data_t *data, void *output) if (is_transaction_running() == false) dmuci_commit_bbfdm(); - memcpy(output, data->bb.head, blob_pad_len(data->bb.head)); - - // free - blob_buf_free(&data->bb); - bbf_cleanup(&data->bbf_ctx); -} - -void bbfdm_get_value(bbfdm_data_t *data) -{ - int fault = 0; - struct pathNode *pn; - void *array = NULL; - - memset(&data->bb, 0, sizeof(struct blob_buf)); - - bbf_init(&data->bbf_ctx); - blob_buf_init(&data->bb, 0); - - if (data->is_raw) - array = blobmsg_open_array(&data->bb, "results"); - - list_for_each_entry(pn, data->plist, list) { - bbf_sub_init(&data->bbf_ctx); - - data->bbf_ctx.in_param = pn->path; - - fault = bbfdm_cmd_exec(&data->bbf_ctx, BBF_GET_VALUE); - if (fault) { - fill_err_code_table(data, fault); - } else { - INFO("Preparing result for(%s)", data->bbf_ctx.in_param); - - if (data->is_raw) { - struct dm_parameter *n = NULL; - void *table = NULL; - - list_for_each_entry(n, &data->bbf_ctx.list_parameter, list) { - table = blobmsg_open_table(&data->bb, NULL); - bb_add_string(&data->bb, "path", n->name); - bb_add_string(&data->bb, "data", n->data); - bb_add_string(&data->bb, "type", n->type); - blobmsg_close_table(&data->bb, table); - } - } else { - prepare_pretty_result(data->depth, &data->bb, &data->bbf_ctx); - } - } - - bbf_sub_cleanup(&data->bbf_ctx); - } - - if (data->is_raw) - blobmsg_close_array(&data->bb, array); - - if (!validate_msglen(data)) { - ERR("IPC failed"); - } - - // Apply all bbfdm changes - if (is_transaction_running() == false) - dmuci_commit_bbfdm(); - - ubus_send_reply(data->ctx, data->req, data->bb.head); + if (output) + memcpy(output, data->bb.head, blob_pad_len(data->bb.head)); + else + ubus_send_reply(data->ctx, data->req, data->bb.head); // free blob_buf_free(&data->bb); diff --git a/bbfdmd/src/get.h b/bbfdmd/src/get.h index 2c5fce11..502bb8a9 100644 --- a/bbfdmd/src/get.h +++ b/bbfdmd/src/get.h @@ -31,8 +31,7 @@ enum { __DM_SCHEMA_MAX }; -void bbfdm_get_value(bbfdm_data_t *data); -void bbfdm_get_value_async(bbfdm_data_t *data, void *output); +void bbfdm_get_value(bbfdm_data_t *data, void *output); void bbfdm_get_names(bbfdm_data_t *data); diff --git a/bbfdmd/src/get_helper.c b/bbfdmd/src/get_helper.c index 7c78b90b..6f1b3702 100644 --- a/bbfdmd/src/get_helper.c +++ b/bbfdmd/src/get_helper.c @@ -172,7 +172,7 @@ void free_path_list(struct list_head *plist) void fill_err_code_table(bbfdm_data_t *data, int fault) { void *table = blobmsg_open_table(&data->bb, NULL); - blobmsg_add_string(&data->bb, "path", data->bbf_ctx.in_param); + blobmsg_add_string(&data->bb, "path", data->bbf_ctx.in_param ? data->bbf_ctx.in_param : ""); blobmsg_add_u32(&data->bb, "fault", bbf_fault_map(data->bbf_ctx.dm_type, fault)); bb_add_string(&data->bb, "fault_msg", ""); blobmsg_close_table(&data->bb, table); diff --git a/bbfdmd/src/pretty_print.c b/bbfdmd/src/pretty_print.c index 0b8bc8a5..bb5c5989 100644 --- a/bbfdmd/src/pretty_print.c +++ b/bbfdmd/src/pretty_print.c @@ -389,11 +389,51 @@ void prepare_result_blob(struct blob_buf *bb, struct list_head *pv_list) free_result_list(&result_stack); } -void prepare_pretty_result(uint8_t maxdepth, struct blob_buf *bb, struct dmctx *bbf_ctx) +void prepare_raw_result(struct blob_buf *bb, struct dmctx *bbf_ctx, struct list_head *rslvd) { + struct pathNode *iter = NULL; + struct dm_parameter *n = NULL; + void *table = NULL; + + list_for_each_entry(iter, rslvd, list) { + size_t ilen = DM_STRLEN(iter->path); + if (ilen == 0) + continue; + + list_for_each_entry(n, &bbf_ctx->list_parameter, list) { + if (iter->path[ilen - 1] == DELIM) { + if (!strncmp(n->name, iter->path, ilen)) { + table = blobmsg_open_table(bb, NULL); + bb_add_string(bb, "path", n->name); + bb_add_string(bb, "data", n->data); + bb_add_string(bb, "type", n->type); + blobmsg_close_table(bb, table); + } + } else { + if (!strcmp(n->name, iter->path)) { + table = blobmsg_open_table(bb, NULL); + bb_add_string(bb, "path", n->name); + bb_add_string(bb, "data", n->data); + bb_add_string(bb, "type", n->type); + blobmsg_close_table(bb, table); + } + } + } + } +} + +void prepare_pretty_result(uint8_t maxdepth, struct blob_buf *bb, struct dmctx *bbf_ctx, struct list_head *rslvd) +{ + struct pathNode *iter = NULL; + LIST_HEAD(pv_local); - resulting(maxdepth, bbf_ctx->in_param, bbf_ctx, &pv_local); + DEBUG("################### DATA to PROCESS ##################"); + list_for_each_entry(iter, rslvd, list) { + DEBUG("## %s ##", iter->path); + resulting(maxdepth, iter->path, bbf_ctx, &pv_local); + } + DEBUG("######################################################"); prepare_result_blob(bb, &pv_local); diff --git a/bbfdmd/src/pretty_print.h b/bbfdmd/src/pretty_print.h index 851e401a..a2b15ea7 100644 --- a/bbfdmd/src/pretty_print.h +++ b/bbfdmd/src/pretty_print.h @@ -12,6 +12,7 @@ #define PRETTY_PRINT_H void prepare_result_blob(struct blob_buf *bb, struct list_head *pv_list); -void prepare_pretty_result(uint8_t maxdepth, struct blob_buf *bb, struct dmctx *bbf_ctx); +void prepare_raw_result(struct blob_buf *bb, struct dmctx *bbf_ctx, struct list_head *rslvd); +void prepare_pretty_result(uint8_t maxdepth, struct blob_buf *bb, struct dmctx *bbf_ctx, struct list_head *rslvd); #endif /* PRETTY_PRINT_H */ diff --git a/bbfdmd/src/set.c b/bbfdmd/src/set.c index 4b5a5066..a28acb42 100644 --- a/bbfdmd/src/set.c +++ b/bbfdmd/src/set.c @@ -14,6 +14,8 @@ #include +extern char *input_json; + int bbfdm_set_value(bbfdm_data_t *data) { struct pvNode *pv = NULL; @@ -42,11 +44,42 @@ int bbfdm_set_value(bbfdm_data_t *data) return fault; } -int fill_pvlist_set(char *param_name, char *param_value, struct blob_attr *blob_table, struct list_head *pv_list) +static int set_resolved_paths(unsigned int dm_type, char *path, char *value, struct list_head *pv_list) +{ + int fault = 0; + struct dmctx bbf_ctx = { + .enable_plugins = input_json ? false : true, + .instance_mode = INSTANCE_MODE_NUMBER, + .dm_type = dm_type + }; + + if (!path || !value || !pv_list) + return -1; + + LIST_HEAD(resolved_paths); + bbf_sub_init(&bbf_ctx); + + fault = get_resolved_paths(&bbf_ctx, path, &resolved_paths); + if (!fault) { + struct pathNode *p; + + list_for_each_entry(p, &resolved_paths, list) { + add_pv_list(p->path, value, NULL, pv_list); + } + } + + bbf_sub_cleanup(&bbf_ctx); + free_path_list(&resolved_paths); + + return fault; +} + +int fill_pvlist_set(bbfdm_data_t *data, char *param_name, char *param_value, struct blob_attr *blob_table, struct list_head *pv_list) { struct blob_attr *attr; struct blobmsg_hdr *hdr; char path[MAX_DM_PATH], value[MAX_DM_VALUE]; + int fault = 0; size_t plen = DM_STRLEN(param_name); if (plen == 0) @@ -58,7 +91,9 @@ int fill_pvlist_set(char *param_name, char *param_value, struct blob_attr *blob_ if (param_name[plen - 1] == '.') return USP_FAULT_INVALID_PATH; - add_pv_list(param_name, param_value, NULL, pv_list); + fault = set_resolved_paths(data->bbf_ctx.dm_type, param_name, param_value, pv_list); + if (fault) + return fault; blob__table: @@ -92,7 +127,9 @@ blob__table: } snprintf(path, MAX_DM_PATH, "%s%s", param_name, (char *)hdr->name); - add_pv_list(path, value, NULL, pv_list); + fault = set_resolved_paths(data->bbf_ctx.dm_type, path, value, pv_list); + if (fault) + return fault; } return 0; diff --git a/bbfdmd/src/set.h b/bbfdmd/src/set.h index e41ab198..b2a90616 100644 --- a/bbfdmd/src/set.h +++ b/bbfdmd/src/set.h @@ -14,7 +14,7 @@ enum { __DM_SET_MAX, }; -int fill_pvlist_set(char *param_name, char *param_value, struct blob_attr *blob_table, struct list_head *pv_list); +int fill_pvlist_set(bbfdm_data_t *data, char *param_name, char *param_value, struct blob_attr *blob_table, struct list_head *pv_list); int bbfdm_set_value(bbfdm_data_t *data); #endif /* SET_H */ diff --git a/docs/guide/bbfdm_ubus_methods.md b/docs/guide/bbfdm_ubus_methods.md index 1258776b..a125bbd5 100644 --- a/docs/guide/bbfdm_ubus_methods.md +++ b/docs/guide/bbfdm_ubus_methods.md @@ -163,7 +163,6 @@ root@iopsys:~# ubus call bbfdm get '{"path":"Device.IP.Diagnostics.", "optional" } } } - root@iopsys:~# root@iopsys:~# ubus call bbfdm get '{"path":"Device.Users."}' { @@ -511,7 +510,134 @@ root@iopsys:~# ubus call bbfdm get '{"path":"Device.Users.", "optional": {"forma } ] } - +root@iopsys:~# +root@iopsys:~# ubus call bbfdm get '{"path":"Device.WiFi.SSID.*.SSID"}' +{ + "SSID": [ + { + "SSID": "iopsysWrt-44D43771B120" + }, + { + "SSID": "MAP-44D43771B120-BH-5GHz" + }, + { + "SSID": "iopsysWrt-44D43771B120" + }, + { + "SSID": "MAP-44D43771B120-BH-2.4GHz" + } + ] +} +root@iopsys:~# +root@iopsys:~# ubus call bbfdm get '{"path":"Device.WiFi.SSID.*.SSID", "optional":{"format":"raw"}}' +{ + "results": [ + { + "path": "Device.WiFi.SSID.1.SSID", + "data": "iopsysWrt-44D43771B120", + "type": "xsd:string" + }, + { + "path": "Device.WiFi.SSID.2.SSID", + "data": "MAP-44D43771B120-BH-5GHz", + "type": "xsd:string" + }, + { + "path": "Device.WiFi.SSID.3.SSID", + "data": "iopsysWrt-44D43771B120", + "type": "xsd:string" + }, + { + "path": "Device.WiFi.SSID.4.SSID", + "data": "MAP-44D43771B120-BH-2.4GHz", + "type": "xsd:string" + } + ] +} +root@iopsys:~# +root@iopsys:~# ubus call bbfdm get '{"path":"Device.WiFi.SSID.[BSSID==\"be:d4:37:71:b1:28\"].SSID"}' +{ + "SSID": [ + { + "SSID": "MAP-44D43771B120-BH-5GHz" + } + ] +} +root@iopsys:~# +root@iopsys:~# ubus call bbfdm get '{"path":"Device.IP.Interface.[Status==\"Up\"].IPv4Address.[AddressingType==\"DHCP\"].IPAddress"}' +{ + "Interface": [ + { + "IPv4Address": [ + { + "IPAddress": "10.100.1.201" + } + ] + } + ] +} +root@iopsys:~# +root@iopsys:~# ubus call bbfdm get '{"path":"Device.IP.Interface.[Status==\"Up\"].IPv4Address.[AddressingType==\"DHCP\"&&Status==\"Up\"]."}' +{ + "Interface": [ + { + "IPv4Address": [ + { + "Enable": true, + "Status": "Enabled", + "Alias": "cpe-1", + "IPAddress": "10.100.1.201", + "SubnetMask": "255.255.255.0", + "AddressingType": "DHCP" + } + ] + } + ] +} +root@iopsys:~# +root@iopsys:~# ubus call bbfdm get '{"path":"Device.IP.Interface.[Type==\"Normal\"&&Stats.PacketsSent<=500].IPv4Address.[AddressingType==\"DHCP\"].IPAddress"}' +{ + "Interface": [ + { + "IPv4Address": [ + { + "IPAddress": "10.100.1.201" + } + ] + } + ] +} +root@iopsys:~# +root@iopsys:~# ubus call bbfdm get '{"path": "Device.Firewall.Chain.1.Rule.[Description==\"Allow-Ping\"]."}' +{ + "Rule": [ + { + "Enable": true, + "Status": "Enabled", + "Order": 3, + "Alias": "cpe-3", + "Description": "Allow-Ping", + "Target": "Accept", + "Log": false, + "CreationDate": "0001-01-01T00:00:00Z", + "ExpiryDate": "9999-12-31T23:59:59Z", + "SourceInterface": "Device.IP.Interface.2", + "SourceAllInterfaces": false, + "DestInterface": "", + "DestAllInterfaces": false, + "IPVersion": 4, + "DestIP": "", + "DestMask": "", + "SourceIP": "", + "SourceMask": "", + "Protocol": 1, + "DestPort": -1, + "DestPortRangeMax": -1, + "SourcePort": -1, + "SourcePortRangeMax": -1 + } + ] +} ``` - For more info on the `bbfdm` ubus API see [link](../api/ubus/bbfdm.md#get) @@ -606,6 +732,15 @@ root@iopsys:~# ubus call bbfdm get '{"paths":["Device.WiFi.SSID.1.Enable", "Devi "Enable": false, "SSID": "test-2g" } +root@iopsys:~# ubus call bbfdm set '{"path": "Device.Firewall.Chain.1.Rule.[Description==\"Allow-Ping\"].", "obj_path": {"Target": "Accept"}}' +{ + "results": [ + { + "path": "Device.Firewall.Chain.1.Rule.3.Target", + "data": "1" + } + ] +} ``` - For more info on the `bbfdm` ubus API see [link](../api/ubus/bbfdm.md#set) diff --git a/gitlab-ci/bbfdmd-functional-test.sh b/gitlab-ci/bbfdmd-functional-test.sh index 7aa79de9..28e74f2b 100755 --- a/gitlab-ci/bbfdmd-functional-test.sh +++ b/gitlab-ci/bbfdmd-functional-test.sh @@ -18,7 +18,7 @@ supervisorctl status all # debug logging echo "Checking ubus status [$(date '+%d/%m/%Y %H:%M:%S')]" ubus list -ubus -v list bbf +ubus -v list bbfdm echo "Checking system resources" free -h diff --git a/gitlab-ci/iopsys-supervisord.conf b/gitlab-ci/iopsys-supervisord.conf index 7082399e..9ed06c2a 100755 --- a/gitlab-ci/iopsys-supervisord.conf +++ b/gitlab-ci/iopsys-supervisord.conf @@ -1,41 +1,17 @@ [program:ubusd] -autorestart=false -numprocs_start=0 -startretries=0 -priority=1 command=/bin/bash -c "/usr/sbin/ubusd" [program:rpcd] -autorestart=false -numprocs_start=1 -startretries=0 -priority=2 command=/bin/bash -c "/usr/sbin/rpcd" [program:bbfdmd] -autorestart=false -numprocs_start=2 -startretries=0 -priority=3 command=/bin/bash -c "/usr/bin/valgrind --xml=yes --xml-file=/tmp/memory-report.xml --leak-check=full --show-reachable=yes --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=1 --track-origins=yes --leak-resolution=high --show-error-list=yes --child-silent-after-fork=yes /usr/sbin/bbfdmd" [program:bbfdm_dataelementsd] -autorestart=false -numprocs_start=3 -startretries=0 -priority=4 command=/bin/bash -c "/usr/bin/valgrind --xml=yes --xml-file=/tmp/memory-report-dataelements.xml --leak-check=full --show-reachable=yes --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=1 --track-origins=yes --leak-resolution=high --show-error-list=yes --child-silent-after-fork=yes /usr/sbin/bbfdmd -m /tmp/wifi_dataelements.json" [program:bbfdm_bulkdatad] -autorestart=false -numprocs_start=4 -startretries=0 -priority=5 command=/bin/bash -c "/usr/bin/valgrind --xml=yes --xml-file=/tmp/memory-report-bulkdata.xml --leak-check=full --show-reachable=yes --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=1 --track-origins=yes --leak-resolution=high --show-error-list=yes --child-silent-after-fork=yes /usr/sbin/bbfdmd -m /etc/bulkdata/input.json" [program:bbfdm_periodicstatsd] -autorestart=false -numprocs_start=5 -startretries=0 -priority=6 command=/bin/bash -c "/usr/bin/valgrind --xml=yes --xml-file=/tmp/memory-report-periodicstats.xml --leak-check=full --show-reachable=yes --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=1 --track-origins=yes --leak-resolution=high --show-error-list=yes --child-silent-after-fork=yes /usr/sbin/bbfdmd -m /etc/periodicstats/input.json" diff --git a/test/python/validate_invalid_transaction_id.py b/test/python/validate_invalid_transaction_id.py index cb2da7d0..eb00510f 100755 --- a/test/python/validate_invalid_transaction_id.py +++ b/test/python/validate_invalid_transaction_id.py @@ -19,21 +19,21 @@ if sock.exists (): else: assert ubus.connect() -fault_wrong_transaction("set", {"path":"Device.Users.User.1.Username", "value":"abc", "optional":{"format":"raw", "transaction_id":1234}}, 7003) -fault_wrong_transaction("set", {"path":"Device.Users.User.1.Username", "value":"abc", "optional":{"format":"raw", "proto":"usp", "transaction_id":1234}}, 7003) -fault_wrong_transaction("set", {"path":"Device.Users.User.1.Username", "value":"abc", "optional":{"format":"raw", "proto":"cwmp", "transaction_id":1234}}, 9002) +fault_wrong_transaction("set", {"path":"Device.WiFi.SSID.1.SSID", "value":"abc", "optional":{"format":"raw", "transaction_id":1234}}, 7003) +fault_wrong_transaction("set", {"path":"Device.WiFi.SSID.1.SSID", "value":"abc", "optional":{"format":"raw", "proto":"usp", "transaction_id":1234}}, 7003) +fault_wrong_transaction("set", {"path":"Device.WiFi.SSID.1.SSID", "value":"abc", "optional":{"format":"raw", "proto":"cwmp", "transaction_id":1234}}, 9002) -fault_wrong_transaction("set", {"path":"Device.Users.User.1.", "obj_path":{"Username":"abc"}, "optional":{"format":"raw", "transaction_id":1234}}, 7003) -fault_wrong_transaction("set", {"path":"Device.Users.User.1.", "obj_path":{"Username":"abc"}, "optional":{"format":"raw", "transaction_id":1234, "proto":"usp"}}, 7003) -fault_wrong_transaction("set", {"path":"Device.Users.User.1.", "obj_path":{"Username":"abc"}, "optional":{"format":"raw", "transaction_id":1234, "proto":"cwmp"}}, 9002) +fault_wrong_transaction("set", {"path":"Device.WiFi.SSID.1.", "obj_path":{"SSID":"abc"}, "optional":{"format":"raw", "transaction_id":1234}}, 7003) +fault_wrong_transaction("set", {"path":"Device.WiFi.SSID.1.", "obj_path":{"SSID":"abc"}, "optional":{"format":"raw", "transaction_id":1234, "proto":"usp"}}, 7003) +fault_wrong_transaction("set", {"path":"Device.WiFi.SSID.1.", "obj_path":{"SSID":"abc"}, "optional":{"format":"raw", "transaction_id":1234, "proto":"cwmp"}}, 9002) -fault_wrong_transaction("add", {"path":"Device.Users.User.", "optional":{"format":"raw", "transaction_id":1234}}, 7003) -fault_wrong_transaction("add", {"path":"Device.Users.User.", "optional":{"format":"raw", "transaction_id":1234, "proto":"usp"}}, 7003) -fault_wrong_transaction("add", {"path":"Device.Users.User.", "optional":{"format":"raw", "transaction_id":1234, "proto":"cwmp"}}, 9002) +fault_wrong_transaction("add", {"path":"Device.WiFi.SSID.", "optional":{"format":"raw", "transaction_id":1234}}, 7003) +fault_wrong_transaction("add", {"path":"Device.WiFi.SSID.", "optional":{"format":"raw", "transaction_id":1234, "proto":"usp"}}, 7003) +fault_wrong_transaction("add", {"path":"Device.WiFi.SSID.", "optional":{"format":"raw", "transaction_id":1234, "proto":"cwmp"}}, 9002) -fault_wrong_transaction("del", {"path":"Device.Users.User.1", "optional":{"format":"raw", "transaction_id":1234}}, 7003) -fault_wrong_transaction("del", {"path":"Device.Users.User.1", "optional":{"format":"raw", "transaction_id":1234, "proto":"usp"}}, 7003) -fault_wrong_transaction("del", {"path":"Device.Users.User.1", "optional":{"format":"raw", "transaction_id":1234, "proto":"cwmp"}}, 9002) +fault_wrong_transaction("del", {"path":"Device.WiFi.SSID.1", "optional":{"format":"raw", "transaction_id":1234}}, 7003) +fault_wrong_transaction("del", {"path":"Device.WiFi.SSID.1", "optional":{"format":"raw", "transaction_id":1234, "proto":"usp"}}, 7003) +fault_wrong_transaction("del", {"path":"Device.WiFi.SSID.1", "optional":{"format":"raw", "transaction_id":1234, "proto":"cwmp"}}, 9002) ubus.disconnect()