diff --git a/Dockerfile b/Dockerfile index 1afef4006852e144be2dd606a4a91d04f9f838fc..c98345ed3b7b44969160a20588ca2e02e677db7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -170,6 +170,10 @@ EXPOSE 22 RUN mkdir -p /var/log/supervisor COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +# Prepare JSON Schemas +RUN mkdir -p /usr/share/rpcd/schemas +#COPY cmocka_test/files/usr/share/rpcd/schemas/* /usr/share/rpcd/schemas/ + # Start entrypoint COPY entrypoint.sh /usr/local/bin/entrypoint.sh ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/src/json-validator.cpp b/src/json-validator.cpp index d7533e40335f6d393960785f1d6e080d2d74f1be..88278a0b61aadfb726f49b7ab597f2d5410e39b8 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -29,16 +29,37 @@ using nlohmann::json_schema::json_validator; bool initialized; +static void loader(const json_uri &uri, json &schema) +{ + struct schema_definition *s_def; + char *str; + + s_def = schema_get_definitions_schema(uri.url().c_str()); + if (!s_def) + return; + + str = blobmsg_format_json(s_def->definitions, true); + if (!str) + return; + + schema = json::parse(str); + + free(str); +} + bool json_object_validate_schema(json obj, json sch) { /* json-parse the schema */ - json_validator validator; // create validator + dbg("validation object -- %s\n", obj.dump().c_str()); + dbg("validation schema -- %s\n", sch.dump().c_str()); + + json_validator validator(loader); try { validator.set_root_schema(sch); // insert root-schema } catch (const std::exception &e) { - std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n"; + //std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n"; return 0; } @@ -48,7 +69,7 @@ bool json_object_validate_schema(json obj, json sch) void error(const nlohmann::json::json_pointer &ptr, const json &instance, const std::string &message) override { nlohmann::json_schema::basic_error_handler::error(ptr, instance, message); - std::cerr << "ERROR: '" << ptr << "' - '" << instance << "': " << message << "\n"; + //std::cerr << "ERROR: '" << ptr << "' - '" << instance << "': " << message << "\n"; } }; @@ -65,7 +86,7 @@ int schema_validator_destroy(void) if (!initialized) return 0; - schema_flush_objects(); + schema_destroy(); initialized = false; return 0; } @@ -75,7 +96,7 @@ int schema_validator_init(void) if (initialized) return 0; - schema_setup_json(); + schema_setup(); initialized = true; return 0; } @@ -181,8 +202,6 @@ bool schema_validator_validate_jobj(struct json_object *j_object, const char *ob obj = json::parse(json_object_get_string(j_object)); - dbg("validation object -- %s\n", obj.dump().c_str()); - dbg("validation schema -- %s\n", sch.dump().c_str()); rv = json_object_validate_schema(obj, sch); out_definitions: diff --git a/src/schema.cpp b/src/schema.cpp index 23e7b6d5207d8aa5da9050f99195cc8fde8c225b..f7846e123b469c3dcceea137c9c106bccaedc07e 100644 --- a/src/schema.cpp +++ b/src/schema.cpp @@ -65,7 +65,7 @@ static void schema_flush_object(struct schema_object *s_object) return; } -void schema_flush_objects(void) +static void schema_flush_objects(void) { struct schema_object *s_object, *tmp; @@ -79,6 +79,39 @@ void schema_flush_objects(void) return; } +static void schema_flush_definition(struct schema_definition *s_def) +{ + dbg("cleaning object %s\n", (char *)s_def->avl.key); + + free((void *)s_def->avl.key); + if (s_def->definitions) + free(s_def->definitions); + + free(s_def); + + return; +} + +static void schema_flush_definitions(void) +{ + struct schema_definition *s_def, *tmp; + + dbg("cleaning all schema objects\n"); + + avl_for_each_element_safe(&s_ctx.schema_definitions, s_def, avl, tmp) { + avl_delete(&s_ctx.schema_objects, &s_def->avl); + schema_flush_definition(s_def); + } + + return; +} + +void schema_destroy(void) +{ + schema_flush_definitions(); + schema_flush_objects(); +} + static void schema_parse_object(struct schema_object *schema_object, struct blob_attr *object) { struct blob_attr *method, *properties; @@ -90,7 +123,7 @@ static void schema_parse_object(struct schema_object *schema_object, struct blob continue; dbg("%s %d method name = %s\n", __func__, __LINE__, blobmsg_name(object)); - s_method = (struct schema_method *)calloc(1, sizeof(*s_method)); + s_method = (struct schema_method *)calloc(1, sizeof(struct schema_method)); if (!s_method) continue; @@ -116,29 +149,76 @@ static void schema_parse_object(struct schema_object *schema_object, struct blob } static void -schema_setup_file(const char *path) +schema_setup_definitions(const char *path) +{ + struct blob_buf def; + struct blob_attr *root; + int rem, len; + struct schema_definition *s_def; + + memset(&def, 0, sizeof(struct blob_buf)); + blob_buf_init(&def, 0); + + if (!blobmsg_add_json_from_file(&def, path)) { + dbg("Failed to parse %s\n", path); + goto out; + } + + s_def = (struct schema_definition *) calloc(1, sizeof(struct schema_definition)); + if (!s_def) + goto out; + + len = blob_raw_len(def.head); + + s_def->definitions = (struct blob_attr *) calloc(1, sizeof(struct blob_attr) + len); + memcpy(s_def->definitions, def.head, len); + + /* find the key properties of root */ + blob_for_each_attr(root, def.head, rem) { + if (!strncmp(blobmsg_name(root), "$id", 4)) { + s_def->avl.key = strdup(blobmsg_get_string(root)); + if (!s_def->avl.key) + goto out_flush; + } + } + + if (!s_def->avl.key) + goto out_flush; + + avl_insert(&s_ctx.schema_definitions, &s_def->avl); + +out: + blob_buf_free(&def); + return; +out_flush: + schema_flush_definition(s_def); + blob_buf_free(&def); +} + +static void +schema_setup_documents(const char *path) { - struct blob_buf acl; + struct blob_buf doc; struct blob_attr *root, *object; int rem, rem2; struct schema_object *schema_object; - memset(&acl, 0, sizeof(acl)); - blob_buf_init(&acl, 0); + memset(&doc, 0, sizeof(struct blob_buf)); + blob_buf_init(&doc, 0); - if (!blobmsg_add_json_from_file(&acl, path)) { + if (!blobmsg_add_json_from_file(&doc, path)) { dbg("Failed to parse %s\n", path); goto out; } - schema_object = (struct schema_object *) calloc(1, sizeof(*schema_object)); + schema_object = (struct schema_object *) calloc(1, sizeof(struct schema_object)); if (!schema_object) goto out; avl_init(&schema_object->schema_methods, avl_strcmp, false, NULL); /* find the key properties of root */ - blob_for_each_attr(root, acl.head, rem) { + blob_for_each_attr(root, doc.head, rem) { if (!strncmp(blobmsg_name(root), "regex", 6)) { schema_object->regex = blobmsg_get_bool(root); dbg("%s: regex enabled %d\n", __func__, schema_object->regex); @@ -166,7 +246,6 @@ schema_setup_file(const char *path) continue; schema_parse_object(schema_object, object); - dbg("added schema %s\n", schema_object->object_name); } } @@ -187,11 +266,11 @@ schema_setup_file(const char *path) avl_insert(&s_ctx.schema_objects, &schema_object->avl); out: - blob_buf_free(&acl); + blob_buf_free(&doc); return; out_flush: schema_flush_object(schema_object); - blob_buf_free(&acl); + blob_buf_free(&doc); } struct schema_method *schema_get_method_schema(const char *object, const char *method) @@ -213,15 +292,17 @@ struct schema_method *schema_get_method_schema(const char *object, const char *m return NULL; } -struct blob_attr *schema_get_definitions_schema(const char *object) +struct schema_definition *schema_get_definitions_schema(const char *id) { - struct schema_object *s_object; + struct schema_definition *s_def; - s_object = schema_get_object_schema(object); - if (!s_object) - return NULL; + avl_for_each_element(&s_ctx.schema_definitions, s_def, avl) { + dbg("%s: definition = %s, avl.key = %s\n", __func__, id, (char *)s_def->avl.key); + if (!strncmp(id, (char *) s_def->avl.key, DEFINITION_NAME_MAX_LEN)) + return s_def; + } - return s_object->definitions; + return NULL; } struct schema_object *schema_get_object_schema(const char *object) @@ -245,19 +326,33 @@ struct schema_object *schema_get_object_schema(const char *object) } void -schema_setup_json(void) +schema_setup(void) { unsigned int i; glob_t gl; + /* setup docs */ + if (glob(JSON_SCHEMA_DIR "/*.json", 0, NULL, &gl)) + return; + avl_init(&s_ctx.schema_objects, avl_strcmp, false, NULL); - if (glob(JSON_SCHEMA_DIR "/*.json", 0, NULL, &gl)) + for (i = 0; i < gl.gl_pathc; i++) { + dbg("doc path = %s\n", gl.gl_pathv[i]); + schema_setup_documents(gl.gl_pathv[i]); + } + + globfree(&gl); + + /* setup definitions */ + if (glob(JSON_SCHEMA_DIR "/definitions/*.json", 0, NULL, &gl)) return; + avl_init(&s_ctx.schema_definitions, avl_strcmp, false, NULL); + for (i = 0; i < gl.gl_pathc; i++) { - dbg("path = %s\n", gl.gl_pathv[i]); - schema_setup_file(gl.gl_pathv[i]); + dbg("def path = %s\n", gl.gl_pathv[i]); + schema_setup_definitions(gl.gl_pathv[i]); } globfree(&gl); diff --git a/src/schema.h b/src/schema.h index 2201b45afe8ba02a0a4ad798964ae2c426a3ed62..ed0f699f494d96dbf9c6d8e28566cdec63079b97 100644 --- a/src/schema.h +++ b/src/schema.h @@ -8,14 +8,14 @@ extern "C" #define METHOD_NAME_MAX_LEN 64 #define OBJECT_NAME_MAX_LEN 64 +#define DEFINITION_NAME_MAX_LEN 128 struct schema_context { struct avl_tree schema_objects; + struct avl_tree schema_definitions; }; struct schema_method { - char method_name[METHOD_NAME_MAX_LEN]; - struct blob_attr *b_input; struct blob_attr *b_output; @@ -23,8 +23,6 @@ struct schema_method { }; struct schema_object { - char object_name[OBJECT_NAME_MAX_LEN]; - struct blob_attr *definitions; bool regex; @@ -34,11 +32,17 @@ struct schema_object { struct avl_node avl; }; +struct schema_definition { + struct blob_attr *definitions; + + struct avl_node avl; +}; + struct schema_method *schema_get_method_schema(const char *object, const char *method); -struct blob_attr *schema_get_definitions_schema(const char *object); +struct schema_definition *schema_get_definitions_schema(const char *object); struct schema_object *schema_get_object_schema(const char *object); -void schema_setup_json(); -void schema_flush_objects(); +void schema_setup(void); +void schema_destroy(void); #ifdef __cplusplus } diff --git a/test/schema_test.c b/test/schema_test.c index 8ddd9934167cfea3a47f9477a69adf3ea9f8d3ad..d0eeacac62b0e6fa7878bfb654084c49b2d5ba4a 100644 --- a/test/schema_test.c +++ b/test/schema_test.c @@ -215,6 +215,30 @@ static void test_validate_output_document_blob(void **state) schema_validator_destroy(); } +static void test_validate_definitions_file(void **state) +{ + (void *) state; + + schema_validator_init(); + + validate_doc_blob("/opt/work/test/files/definition/doc.json", "wifi", "status", true, SCHEMA_OUTPUT_CALL); + validate_doc_blob("/opt/work/test/files/definition/doc.json", "wifi", "status", true, SCHEMA_OUTPUT_CALL); + + schema_validator_destroy(); +} + +static void test_validate_definitions_mixed(void **state) +{ + (void *) state; + + schema_validator_init(); + + validate_doc_blob("/opt/work/test/files/definition/doc.json", "wifi", "status", true, SCHEMA_OUTPUT_CALL); + validate_doc_blob("/opt/work/test/files/definition/doc.json", "wifi", "status", true, SCHEMA_OUTPUT_CALL); + + schema_validator_destroy(); +} + static int create_setup(void** state) { printf("copy schemas\n"); cp("/usr/share/rpcd/schemas/wifi.json", "/opt/work/ubus/wifi.json"); @@ -235,6 +259,37 @@ static int create_teardown(void** state) { return 0; } +static int definition_setup(void** state) { + printf("copy schemas\n"); + cp("/usr/share/rpcd/schemas/wifi.json", "/opt/work/test/files/definition/schema.json"); + cp("/usr/share/rpcd/schemas/definitions/definitions.json", "/opt/work/test/files/definition/definitions.json"); + printf("finished copying schemas\n"); + return 0; +} + +static int definition_teardown(void** state) { + printf("removing schemas\n"); + remove("/usr/share/rpcd/schemas/wifi.json"); + remove("/usr/share/rpcd/schemas/definition.json"); + return 0; +} + + +static int mixed_setup(void** state) { + printf("copy schemas\n"); + cp("/usr/share/rpcd/schemas/wifi.json", "/opt/work/test/files/definition/mixed_schema.json"); + cp("/usr/share/rpcd/schemas/definitions/definitions.json", "/opt/work/test/files/definition/definitions.json"); + printf("finished copying schemas\n"); + return 0; +} + +static int mixed_teardown(void** state) { + printf("removing schemas\n"); + remove("/usr/share/rpcd/schemas/wifi.json"); + remove("/usr/share/rpcd/schemas/definition.json"); + return 0; +} + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_validator_create_nothing), @@ -244,6 +299,8 @@ int main(void) { cmocka_unit_test_setup_teardown(test_validate_output_document_jobj, create_setup, create_teardown), cmocka_unit_test_setup_teardown(test_validate_input_document_blob, create_setup, create_teardown), cmocka_unit_test_setup_teardown(test_validate_output_document_blob, create_setup, create_teardown), + cmocka_unit_test_setup_teardown(test_validate_definitions_file, definition_setup, definition_teardown), + cmocka_unit_test_setup_teardown(test_validate_definitions_mixed, definition_setup, definition_teardown) }; return cmocka_run_group_tests(tests, NULL, NULL);