From 44936606047e653ff1550b64e87a2a23a8786eb0 Mon Sep 17 00:00:00 2001 From: Andrew Gunnerson Date: Sun, 30 Jul 2017 22:14:22 -0400 Subject: [PATCH] libmbdevice: Port to rapidjson and add schema validation Signed-off-by: Andrew Gunnerson --- .../nativelib/LibMbDevice.java | 45 +- devicesgen/CMakeLists.txt | 3 +- devicesgen/devicesgen.cpp | 257 ++--- hosttools/CMakeLists.txt | 1 + libmbdevice/CMakeLists.txt | 78 +- libmbdevice/include/mbdevice/capi/json.h | 18 +- libmbdevice/include/mbdevice/json.h | 20 +- libmbdevice/include/mbdevice/schema.h | 105 +++ libmbdevice/schemas/device.json | 264 ++++++ libmbdevice/schemas/device_list.json | 10 + libmbdevice/schemas2cpp.cpp | 356 +++++++ libmbdevice/src/capi/json.cpp | 20 +- libmbdevice/src/json.cpp | 878 ++++++------------ libmbdevice/src/schema.cpp | 48 + libmbdevice/tests/test_json.cpp | 84 +- 15 files changed, 1374 insertions(+), 813 deletions(-) create mode 100644 libmbdevice/include/mbdevice/schema.h create mode 100644 libmbdevice/schemas/device.json create mode 100644 libmbdevice/schemas/device_list.json create mode 100644 libmbdevice/schemas2cpp.cpp create mode 100644 libmbdevice/src/schema.cpp diff --git a/Android_GUI/src/com/github/chenxiaolong/dualbootpatcher/nativelib/LibMbDevice.java b/Android_GUI/src/com/github/chenxiaolong/dualbootpatcher/nativelib/LibMbDevice.java index e1b992ab4..29276af4f 100644 --- a/Android_GUI/src/com/github/chenxiaolong/dualbootpatcher/nativelib/LibMbDevice.java +++ b/Android_GUI/src/com/github/chenxiaolong/dualbootpatcher/nativelib/LibMbDevice.java @@ -23,6 +23,7 @@ import com.github.chenxiaolong.dualbootpatcher.nativelib.LibMbDevice.CWrapper.CDevice; import com.github.chenxiaolong.dualbootpatcher.nativelib.LibMbDevice.CWrapper.CJsonError; +import com.sun.jna.IntegerType; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.PointerType; @@ -52,6 +53,16 @@ public CDevice(Pointer p) { public static class CJsonError extends PointerType {} + public class SizeT extends IntegerType { + public SizeT() { + this(0); + } + + public SizeT(long value) { + super(Native.SIZE_T_SIZE, value, true); + } + } + // BEGIN: device.h static native CDevice mb_device_new(); @@ -159,11 +170,11 @@ public static class CJsonError extends PointerType {} static native void mb_device_json_error_free(CJsonError error); static native /* uint16_t */ short mb_device_json_error_type(CJsonError error); - static native int mb_device_json_error_line(CJsonError error); - static native int mb_device_json_error_column(CJsonError error); - static native /* char * */ Pointer mb_device_json_error_context(CJsonError error); - static native /* char * */ Pointer mb_device_json_error_expected_type(CJsonError error); - static native /* char * */ Pointer mb_device_json_error_actual_type(CJsonError error); + static native /* size_t */ SizeT mb_device_json_error_offset(CJsonError error); + static native /* char * */ Pointer mb_device_json_error_message(CJsonError error); + static native /* char * */ Pointer mb_device_json_error_schema_uri(CJsonError error); + static native /* char * */ Pointer mb_device_json_error_schema_keyword(CJsonError error); + static native /* char * */ Pointer mb_device_json_error_document_uri(CJsonError error); static native CDevice mb_device_new_from_json(String json, CJsonError error); @@ -236,32 +247,36 @@ public short type() { return CWrapper.mb_device_json_error_type(mCJsonError); } - public int line() { - return CWrapper.mb_device_json_error_line(mCJsonError); + public long offset() { + return CWrapper.mb_device_json_error_offset(mCJsonError).longValue(); } - public int column() { - return CWrapper.mb_device_json_error_column(mCJsonError); + public String message() { + Pointer p = CWrapper.mb_device_json_error_message(mCJsonError); + if (p == null) { + return null; + } + return LibC.getStringAndFree(p); } - public String context() { - Pointer p = CWrapper.mb_device_json_error_context(mCJsonError); + public String schemaUri() { + Pointer p = CWrapper.mb_device_json_error_schema_uri(mCJsonError); if (p == null) { return null; } return LibC.getStringAndFree(p); } - public String expectedType() { - Pointer p = CWrapper.mb_device_json_error_expected_type(mCJsonError); + public String schemaKeyword() { + Pointer p = CWrapper.mb_device_json_error_schema_keyword(mCJsonError); if (p == null) { return null; } return LibC.getStringAndFree(p); } - public String actualType() { - Pointer p = CWrapper.mb_device_json_error_actual_type(mCJsonError); + public String documentUri() { + Pointer p = CWrapper.mb_device_json_error_document_uri(mCJsonError); if (p == null) { return null; } diff --git a/devicesgen/CMakeLists.txt b/devicesgen/CMakeLists.txt index f67fe6f4f..a86ace043 100644 --- a/devicesgen/CMakeLists.txt +++ b/devicesgen/CMakeLists.txt @@ -4,7 +4,6 @@ if(${MBP_BUILD_TARGET} STREQUAL hosttools) target_include_directories( devicesgen PRIVATE - ${MBP_JANSSON_INCLUDES} ${MBP_YAML_CPP_INCLUDES} ) @@ -26,8 +25,8 @@ if(${MBP_BUILD_TARGET} STREQUAL hosttools) target_link_libraries( devicesgen mbdevice-shared - ${MBP_JANSSON_LIBRARIES} ${MBP_YAML_CPP_LIBRARIES} + rapidjson ) set_target_properties( diff --git a/devicesgen/devicesgen.cpp b/devicesgen/devicesgen.cpp index d043a4723..b810f19fe 100644 --- a/devicesgen/devicesgen.cpp +++ b/devicesgen/devicesgen.cpp @@ -23,63 +23,64 @@ #include -#include +#include +#include +#include #include #include "mbdevice/json.h" +#include "mbdevice/schema.h" -using ScopedJsonT = std::unique_ptr; -using ScopedCharArray = std::unique_ptr; +using namespace rapidjson; using namespace mb::device; -static void * xmalloc(size_t size) -{ - void *result = malloc(size); - if (!result) { - abort(); - } - return result; -} - -static json_t * yaml_node_to_json_node(const YAML::Node &yaml_node) +template +static Value yaml_node_to_json(const YAML::Node &yaml_node, Allocator &alloc) { switch (yaml_node.Type()) { case YAML::NodeType::Null: - return json_object(); + return Value(kNullType); - case YAML::NodeType::Scalar: + case YAML::NodeType::Scalar: { + Value value; try { - return json_integer(yaml_node.as()); + value.SetInt64(yaml_node.as()); + return value; } catch (const YAML::BadConversion &e) {} try { - return json_real(yaml_node.as()); + value.SetDouble(yaml_node.as()); + return value; } catch (const YAML::BadConversion &e) {} try { - return json_boolean(yaml_node.as()); + value.SetBool(yaml_node.as()); + return value; } catch (const YAML::BadConversion &e) {} try { - return json_string(yaml_node.as().c_str()); + value.SetString(yaml_node.as(), alloc); + return value; } catch (const YAML::BadConversion &e) {} throw std::runtime_error("Cannot convert scalar value to known type"); + } case YAML::NodeType::Sequence: { - json_t *array = json_array(); + Value array(kArrayType); for (auto const &item : yaml_node) { - json_array_append_new(array, yaml_node_to_json_node(item)); + array.PushBack(yaml_node_to_json(item, alloc), alloc); } return array; } case YAML::NodeType::Map: { - json_t *object = json_object(); + Value object(kObjectType); for (auto const &item : yaml_node) { - json_object_set_new(object, item.first.as().c_str(), - yaml_node_to_json_node(item.second)); + Value key; + key.SetString(item.first.as(), alloc); + object.AddMember(key, yaml_node_to_json(item.second, alloc), alloc); } return object; @@ -91,114 +92,76 @@ static json_t * yaml_node_to_json_node(const YAML::Node &yaml_node) } } -static void print_json_error(const std::string &path, const JsonError &error) +template +static bool convert_files(int argc, char *argv[], Value &value, + Allocator &alloc) { - fprintf(stderr, "%s: Error: ", path.c_str()); - - switch (error.type) { - case JsonErrorType::ParseError: - fprintf(stderr, "Failed to parse generated JSON\n"); - break; - case JsonErrorType::MismatchedType: - fprintf(stderr, "Expected %s, but found %s at %s\n", - error.expected_type.c_str(), error.actual_type.c_str(), - error.context.c_str()); - break; - case JsonErrorType::UnknownKey: - fprintf(stderr, "Unknown key at %s\n", error.context.c_str()); - break; - case JsonErrorType::UnknownValue: - fprintf(stderr, "Unknown value at %s\n", error.context.c_str()); - break; - default: - fprintf(stderr, "Unknown error\n"); - break; - } -} + value.SetArray(); -static void print_validation_error(const std::string &path, - const std::string &id, - ValidateFlags flags) -{ - fprintf(stderr, "%s: [%s] Error during validation (0x%" PRIx64 "):\n", - path.c_str(), id.empty() ? "unknown" : id.c_str(), - static_cast(flags)); - - struct { - ValidateFlag flag; - const char *msg; - } mappings[] = { - { ValidateFlag::MissingId, "Missing device ID" }, - { ValidateFlag::MissingCodenames, "Missing device codenames" }, - { ValidateFlag::MissingName, "Missing device name" }, - { ValidateFlag::MissingArchitecture, "Missing device architecture" }, - { ValidateFlag::MissingSystemBlockDevs, "Missing system block device paths" }, - { ValidateFlag::MissingCacheBlockDevs, "Missing cache block device paths" }, - { ValidateFlag::MissingDataBlockDevs, "Missing data block device paths" }, - { ValidateFlag::MissingBootBlockDevs, "Missing boot block device paths" }, - { ValidateFlag::MissingRecoveryBlockDevs, "Missing recovery block device paths" }, - { ValidateFlag::MissingBootUiTheme, "Missing Boot UI theme" }, - { ValidateFlag::MissingBootUiGraphicsBackends, "Missing Boot UI graphics backends" }, - { ValidateFlag::InvalidArchitecture, "Invalid device architecture" }, - { ValidateFlag::InvalidFlags, "Invalid device flags" }, - { ValidateFlag::InvalidBootUiFlags, "Invalid Boot UI flags" }, - { static_cast(0), nullptr }, - }; + for (int i = 0; i < argc; ++i) { + try { + YAML::Node root = YAML::LoadFile(argv[i]); - for (auto it = mappings; it->msg; ++it) { - if (flags & it->flag) { - fprintf(stderr, "- %s\n", it->msg); - flags &= ~ValidateFlags(it->flag); + if (root.Type() != YAML::NodeType::Sequence) { + fprintf(stderr, "%s: Root is not an array\n", argv[i]); + return false; + } + + for (auto const &item : root) { + value.PushBack(yaml_node_to_json(item, alloc), alloc); + } + } catch (const std::exception &e) { + fprintf(stderr, "%s: Failed to convert file: %s\n", + argv[i], e.what()); + return false; } } - if (flags) { - fprintf(stderr, "- Unknown remaining flags (0x%" PRIx64 ")", - static_cast(flags)); - } + return true; } -static bool validate(const std::string &path, const std::string &json, - bool is_array) +template +static bool validate_and_write(Document &d, const SchemaDocument &sd, + Writer &writer) { - JsonError error; + GenericSchemaValidator sv(sd, writer); + if (!d.Accept(sv)) { + if (!sv.IsValid()) { + StringBuffer sb; + fprintf(stderr, "Schema validation failed:\n"); + sv.GetInvalidSchemaPointer().StringifyUriFragment(sb); + fprintf(stderr, "- Schema URI: %s\n", sb.GetString()); + sb.Clear(); + fprintf(stderr, "- Schema keyword: %s\n", sv.GetInvalidSchemaKeyword()); + sv.GetInvalidDocumentPointer().StringifyUriFragment(sb); + fprintf(stderr, "- Document URI: %s\n", sb.GetString()); + + // Try to dump parent + std::string ref{sb.GetString(), sb.GetSize()}; + auto pos = ref.rfind("/"); + if (pos != std::string::npos) { + ref.erase(pos); + } - if (is_array) { - std::vector devices; + if (Value *value = Pointer(ref).Get(d)) { + fprintf(stderr, "Parent of offending JSON value:\n"); - if (!device_list_from_json(json, devices, error)) { - print_json_error(path, error); - return false; - } + char write_buf[65536]; + FileWriteStream os(stderr, write_buf, sizeof(write_buf)); + PrettyWriter writer(os); - bool failed = false; + if (!value->Accept(writer)) { + fprintf(stderr, "Failed to write JSON snippet\n"); + } - for (auto const &device : devices) { - auto flags = device.validate(); - if (flags) { - print_validation_error(path, device.id(), flags); - failed = true; + fputc('\n', stderr); } + } else { + fprintf(stderr, "Failed to write JSON\n"); } - if (failed) { - return false; - } - } else { - Device device; - - if (!device_from_json(json, device, error)) { - print_json_error(path, error); - return false; - } - - auto flags = device.validate(); - if (flags) { - print_validation_error(path, device.id(), flags); - return false; - } + return false; } - return true; } @@ -257,38 +220,6 @@ int main(int argc, char *argv[]) } } - json_set_alloc_funcs(&xmalloc, &free); - - ScopedJsonT json_root(json_array(), &json_decref); - - for (int i = optind; i < argc; ++i) { - try { - YAML::Node root = YAML::LoadFile(argv[i]); - ScopedJsonT node(yaml_node_to_json_node(root), json_decref); - ScopedCharArray output(json_dumps(node.get(), JSON_COMPACT), &free); - - bool valid = validate(argv[i], output.get(), json_is_array(node)); - if (!valid) { - return EXIT_FAILURE; - } - - if (json_is_array(node)) { - size_t index; - json_t *elem; - - json_array_foreach(node.get(), index, elem) { - json_array_append(json_root.get(), elem); - } - } else { - json_array_append_new(json_root.get(), node.release()); - } - } catch (const std::exception &e) { - fprintf(stderr, "%s: Failed to convert file: %s\n", - argv[i], e.what()); - return EXIT_FAILURE; - } - } - FILE *fp = stdout; if (output_file) { @@ -300,16 +231,30 @@ int main(int argc, char *argv[]) } } - ScopedCharArray output(nullptr, &free); - if (styled) { - output.reset(json_dumps(json_root.get(), - JSON_INDENT(4) | JSON_SORT_KEYS)); - } else { - output.reset(json_dumps(json_root.get(), JSON_COMPACT)); + char write_buf[65536]; + FileWriteStream os(fp, write_buf, sizeof(write_buf)); + bool ret; + + DeviceSchemaProvider<> sp; + const SchemaDocument *sd = sp.GetSchema("device_list.json"); + if (!sd) { + assert(false); + return EXIT_FAILURE; } - if (fputs(output.get(), fp) == EOF) { - fprintf(stderr, "Failed to write JSON: %s\n", strerror(errno)); + Document d; + auto &alloc = d.GetAllocator(); + + if (!convert_files(argc - optind, argv + optind, d, alloc)) { + return EXIT_FAILURE; + } + + if (styled) { + PrettyWriter writer(os); + ret = validate_and_write(d, *sd, writer); + } else { + Writer writer(os); + ret = validate_and_write(d, *sd, writer); } if (output_file) { @@ -320,5 +265,5 @@ int main(int argc, char *argv[]) } } - return EXIT_SUCCESS; + return ret ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/hosttools/CMakeLists.txt b/hosttools/CMakeLists.txt index 378bcf07f..2de437ea9 100644 --- a/hosttools/CMakeLists.txt +++ b/hosttools/CMakeLists.txt @@ -32,4 +32,5 @@ if(MBP_TOP_LEVEL_BUILD) set(SIGNTOOL_COMMAND "${CMAKE_CURRENT_BINARY_DIR}/result/bin/signtool" PARENT_SCOPE) set(DEVICESGEN_COMMAND "${CMAKE_CURRENT_BINARY_DIR}/result/bin/devicesgen" PARENT_SCOPE) + set(SCHEMAS2CPP_COMMAND "${CMAKE_CURRENT_BINARY_DIR}/result/bin/schemas2cpp" PARENT_SCOPE) endif() diff --git a/libmbdevice/CMakeLists.txt b/libmbdevice/CMakeLists.txt index 563c07ca9..f2fef5016 100644 --- a/libmbdevice/CMakeLists.txt +++ b/libmbdevice/CMakeLists.txt @@ -1,8 +1,12 @@ +set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") + set(MBDEVICE_SOURCES src/device.cpp src/json.cpp + src/schema.cpp src/capi/device.cpp src/capi/json.cpp + ${generated_dir}/schemas_gen.cpp ) set(MBDEVICE_TESTS_SOURCES @@ -14,6 +18,35 @@ set(MBDEVICE_TESTS_SOURCES tests/test_json.cpp ) +set(MBDEVICE_SCHEMAS + schemas/device.json + schemas/device_list.json +) + +make_directory("${generated_dir}") + +# libmbdevice needs to be built for hosttools target too +if(${MBP_BUILD_TARGET} STREQUAL hosttools) + set(schemas2cpp_deps schemas2cpp) + set(schemas2cpp_command $) +else() + set(schemas2cpp_deps hosttools) + set(schemas2cpp_command ${SCHEMAS2CPP_COMMAND}) +endif() + +# Convert schemas to C++ strings +add_custom_command( + OUTPUT "${generated_dir}/schemas_gen.cpp" + "${generated_dir}/schemas_gen.h" + COMMAND "${schemas2cpp_command}" + ${MBDEVICE_SCHEMAS} + -o "${generated_dir}/schemas_gen.cpp" + DEPENDS ${schemas2cpp_deps} ${MBDEVICE_SCHEMAS} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating code for JSON schemas" + VERBATIM +) + add_definitions(-DMBDEVICE_BUILD) set(variants) @@ -44,8 +77,7 @@ foreach(variant ${variants}) PUBLIC include PRIVATE - ${CMAKE_CURRENT_BINARY_DIR}/include - ${MBP_JANSSON_INCLUDES} + ${CMAKE_CURRENT_BINARY_DIR}/generated ) # Only build static library if needed @@ -83,7 +115,7 @@ foreach(variant ${variants}) target_link_libraries( ${lib_target} PUBLIC mbcommon-${variant} - PRIVATE ${MBP_JANSSON_LIBRARIES} + PRIVATE rapidjson ) # Install shared library @@ -129,3 +161,43 @@ if(variants AND MBP_ENABLE_TESTS) COMMAND mbdevice_tests ) endif() + +# Build schemas2cpp +if(${MBP_BUILD_TARGET} STREQUAL hosttools) + add_executable(schemas2cpp schemas2cpp.cpp) + + set_target_properties( + schemas2cpp + PROPERTIES + POSITION_INDEPENDENT_CODE 1 + ) + + if(NOT MSVC) + set_target_properties( + schemas2cpp + PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED 1 + ) + endif() + + target_link_libraries( + schemas2cpp + PRIVATE + mbcommon-shared # Headers only + rapidjson + ) + + set_target_properties( + schemas2cpp + PROPERTIES + BUILD_WITH_INSTALL_RPATH OFF + INSTALL_RPATH "\$ORIGIN/../lib" + ) + + install( + TARGETS schemas2cpp + RUNTIME DESTINATION "${BIN_INSTALL_DIR}/" + COMPONENT Applications + ) +endif() diff --git a/libmbdevice/include/mbdevice/capi/json.h b/libmbdevice/include/mbdevice/capi/json.h index 9b933f490..ada560585 100644 --- a/libmbdevice/include/mbdevice/capi/json.h +++ b/libmbdevice/include/mbdevice/capi/json.h @@ -21,12 +21,12 @@ #include "mbdevice/capi/device.h" +#include + MB_BEGIN_C_DECLS -#define MB_DEVICE_JSON_PARSE_ERROR (1u) -#define MB_DEVICE_JSON_MISMATCHED_TYPE (2u) -#define MB_DEVICE_JSON_UNKNOWN_KEY (3u) -#define MB_DEVICE_JSON_UNKNOWN_VALUE (4u) +#define MB_DEVICE_JSON_PARSE_ERROR (1u) +#define MB_DEVICE_JSON_SCHEMA_VALIDATION_FAILURE (2u) struct CJsonError; typedef CJsonError CJsonError; @@ -36,11 +36,11 @@ MB_EXPORT CJsonError * mb_device_json_error_new(); MB_EXPORT void mb_device_json_error_free(CJsonError *error); MB_EXPORT uint16_t mb_device_json_error_type(const CJsonError *error); -MB_EXPORT int mb_device_json_error_line(const CJsonError *error); -MB_EXPORT int mb_device_json_error_column(const CJsonError *error); -MB_EXPORT char * mb_device_json_error_context(const CJsonError *error); -MB_EXPORT char * mb_device_json_error_expected_type(const CJsonError *error); -MB_EXPORT char * mb_device_json_error_actual_type(const CJsonError *error); +MB_EXPORT size_t mb_device_json_error_offset(const CJsonError *error); +MB_EXPORT char * mb_device_json_error_message(const CJsonError *error); +MB_EXPORT char * mb_device_json_error_schema_uri(const CJsonError *error); +MB_EXPORT char * mb_device_json_error_schema_keyword(const CJsonError *error); +MB_EXPORT char * mb_device_json_error_document_uri(const CJsonError *error); MB_EXPORT CDevice * mb_device_new_from_json(const char *json, CJsonError *error); diff --git a/libmbdevice/include/mbdevice/json.h b/libmbdevice/include/mbdevice/json.h index 5c2200111..39d5736d4 100644 --- a/libmbdevice/include/mbdevice/json.h +++ b/libmbdevice/include/mbdevice/json.h @@ -28,26 +28,22 @@ namespace device enum class JsonErrorType : uint16_t { - // Use |line| and |column| fields + // Use |offset| and |message| fields ParseError = 1, - // Use |context|, |expected_type|, and |actual_type| fields - MismatchedType, - // Use |context| field - UnknownKey, - // Use |context] field - UnknownValue, + // Use |schema_uri|, |schema_keyword|, and |document_uri| fields + SchemaValidationFailure, }; struct JsonError { JsonErrorType type; - int line; - int column; + size_t offset; + std::string message; - std::string context; - std::string expected_type; - std::string actual_type; + std::string schema_uri; + std::string schema_keyword; + std::string document_uri; }; MB_EXPORT bool device_from_json(const std::string &json, Device &device, diff --git a/libmbdevice/include/mbdevice/schema.h b/libmbdevice/include/mbdevice/schema.h new file mode 100644 index 000000000..7b8ddb7e3 --- /dev/null +++ b/libmbdevice/include/mbdevice/schema.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 Andrew Gunnerson + * + * This file is part of DualBootPatcher + * + * DualBootPatcher is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DualBootPatcher is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DualBootPatcher. If not, see . + */ + +#pragma once + +#include "mbcommon/common.h" + +#include +#include +#include + +#include + +namespace mb +{ +namespace device +{ + +MB_EXPORT const char * find_schema(const std::string &uri); + +template +class DeviceSchemaProvider + : public rapidjson::IGenericRemoteSchemaDocumentProvider +{ +public: + DeviceSchemaProvider() + { + } + + virtual ~DeviceSchemaProvider() + { + } + + MB_DISABLE_COPY_CONSTRUCT_AND_ASSIGN(DeviceSchemaProvider) + MB_DISABLE_MOVE_CONSTRUCT_AND_ASSIGN(DeviceSchemaProvider) + + virtual const SchemaDocumentType * + GetRemoteDocument(const char *uri, rapidjson::SizeType length) + { + using SchemaDocItem = typename decltype(_schema_docs)::value_type; + + // Find cached SchemaDocument + { + auto it = std::find_if(_schema_docs.begin(), _schema_docs.end(), + [uri, length](const SchemaDocItem &item) { + return item.first.size() == length + && memcmp(item.first.data(), uri, length); + }); + if (it != _schema_docs.end()) { + return it->second.get(); + } + } + + // Cache new SchemaDocument + { + std::string name{uri, length}; + const char *schema = find_schema(name); + if (!schema) { + return nullptr; + } + + using DocumentType = rapidjson::GenericDocument< + typename SchemaDocumentType::EncodingType>; + DocumentType d; + if (d.Parse(schema).HasParseError()) { + assert(false); + return nullptr; + } + + std::unique_ptr ptr( + new SchemaDocumentType(d, this)); + + _schema_docs.emplace_back(std::move(name), std::move(ptr)); + + return _schema_docs.back().second.get(); + } + } + + const SchemaDocumentType * GetSchema(const std::string &uri) + { + return GetRemoteDocument(uri.c_str(), uri.size()); + } + +private: + std::vector>> _schema_docs; +}; + +} +} diff --git a/libmbdevice/schemas/device.json b/libmbdevice/schemas/device.json new file mode 100644 index 000000000..217bd888a --- /dev/null +++ b/libmbdevice/schemas/device.json @@ -0,0 +1,264 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://dbp.noobdev.io/schemas/device.json", + "definitions": { + "dev_path": { + "type": "string", + "pattern": "^/dev/[^/]+(/[^/]+)*$" + }, + "dev_path_list": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/definitions/dev_path" + } + }, + "sysfs_path": { + "type": "string", + "pattern": "^/sys/[^/]+(/[^/]+)*$" + }, + "absolute_path": { + "type": "string", + "pattern": "^/[^/]+(/[^/]+)*$" + } + }, + "title": "Device definition", + "description": "Contains all necessary information about a device to allow it to work with DualBootPatcher", + "type": "object", + "properties": { + "name": { + "title": "Human readable device name", + "type": "string", + "minLength": 1 + }, + "id": { + "title": "Device ID", + "description": "The device ID can be any string unique to the set of supported devices. It can be any non-empty string, but should match the device's codename or model if possible.", + "type": "string", + "minLength": 1 + }, + "codenames": { + "title": "List of device codenames", + "description": "The codename is the value of 'ro.product.device' or 'ro.build.product' in the /system/build.prop file. WARNING: All of the devices listed MUST have the same partition table. Otherwise, there's a chance of hard-bricking the device.", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1 + } + }, + "architecture": { + "title": "Device architecture", + "description": "Target Android ABI. Binaries compiled for the selected ABI will be used during flashing and normal operation of the app.", + "type": "string", + "enum": [ + "armeabi-v7a", + "arm64-v8a", + "x86", + "x86_64" + ] + }, + "flags": { + "title": "List of device flags", + "description": "The device flags control DualBootPatcher behavior for the device.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "HAS_COMBINED_BOOT_AND_RECOVERY", + "FSTAB_SKIP_SDCARD0" + ] + } + }, + "block_devs": { + "title": "Block device paths", + "type": "object", + "properties": { + "base_dirs": { + "title": "Base directories", + "description": "This is a list of the 'by-name' directories in /dev/ that contain the partition block devices.", + "$ref": "#/definitions/dev_path_list" + }, + "system": { + "title": "System partition block device paths", + "type": "array", + "$ref": "#/definitions/dev_path_list" + }, + "cache": { + "title": "Cache partition block device paths", + "type": "array", + "$ref": "#/definitions/dev_path_list" + }, + "data": { + "title": "Data partition block device paths", + "type": "array", + "$ref": "#/definitions/dev_path_list" + }, + "boot": { + "title": "Boot partition block device paths", + "type": "array", + "$ref": "#/definitions/dev_path_list" + }, + "recovery": { + "title": "Recovery partition block device paths", + "type": "array", + "$ref": "#/definitions/dev_path_list" + }, + "extra": { + "title": "Extra block device paths", + "description": "This is a list of block devices that will be available during the flashing process. Any block devices not listed here cannot be touched by a ROM's installer.", + "$ref": "#/definitions/dev_path_list" + } + }, + "required": [ + "system", + "cache", + "data", + "boot" + ], + "additionalProperties": false + }, + "boot_ui": { + "title": "Boot UI", + "type": "object", + "properties": { + "supported": { + "title": "Whether Boot UI is supported", + "type": "boolean" + }, + "flags": { + "title": "List of TWRP flags", + "description": "These flags correspond to the TWRP flags with the same name.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "TW_TOUCHSCREEN_SWAP_XY", + "TW_TOUCHSCREEN_FLIP_X", + "TW_TOUCHSCREEN_FLIP_Y", + "TW_GRAPHICS_FORCE_USE_LINELENGTH", + "TW_SCREEN_BLANK_ON_BOOT", + "TW_BOARD_HAS_FLIPPED_SCREEN", + "TW_IGNORE_MAJOR_AXIS_0", + "TW_IGNORE_MT_POSITION_0", + "TW_IGNORE_ABS_MT_TRACKING_ID", + "TW_NEW_ION_HEAP", + "TW_NO_SCREEN_BLANK", + "TW_NO_SCREEN_TIMEOUT", + "TW_ROUND_SCREEN", + "TW_NO_CPU_TEMP", + "TW_QCOM_RTC_FIX", + "TW_HAS_DOWNLOAD_MODE", + "TW_PREFER_LCD_BACKLIGHT" + ] + } + }, + "pixel_format": { + "title": "TWRP pixel format", + "type": "string", + "enum": [ + "DEFAULT", + "ABGR_8888", + "RGBX_8888", + "BGRA_8888", + "RGBA_8888" + ] + }, + "force_pixel_format": { + "title": "TWRP force pixel format", + "type": "string", + "enum": [ + "NONE", + "RGB_565" + ] + }, + "overscan_percent": { + "title": "TWRP overscan percentage", + "type": "integer" + }, + "default_x_offset": { + "title": "TWRP default X offset", + "type": "integer" + }, + "default_y_offset": { + "title": "TWRP default Y offset", + "type": "integer" + }, + "brightness_path": { + "title": "TWRP brightness sysfs path", + "$ref": "#/definitions/sysfs_path" + }, + "secondary_brightness_path": { + "title": "TWRP secondary brightness sysfs path", + "$ref": "#/definitions/sysfs_path" + }, + "max_brightness": { + "title": "TWRP max brightness", + "type": "integer", + "minimum": 0 + }, + "default_brightness": { + "title": "TWRP default brightness", + "type": "integer", + "minimum": 0 + }, + "battery_path": { + "title": "TWRP battery sysfs path", + "$ref": "#/definitions/sysfs_path" + }, + "cpu_temp_path": { + "title": "TWRP CPU temperature sys path", + "$ref": "#/definitions/sysfs_path" + }, + "input_blacklist": { + "title": "TWRP input device blacklist", + "type": "string", + "minLength": 1 + }, + "input_whitelist": { + "title": "TWRP input device whitelist", + "type": "string", + "minLength": 1 + }, + "graphics_backends": { + "title": "TWRP graphics backend list", + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "overlay_msm_old", + "drm", + "fbdev" + ] + } + }, + "theme": { + "title": "TWRP theme", + "type": "string", + "enum": [ + "portrait_hdpi" + ] + } + }, + "required": [ + "graphics_backends", + "theme" + ], + "additionalProperties": false + } + }, + "required": [ + "name", + "id", + "codenames", + "architecture", + "block_devs" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/libmbdevice/schemas/device_list.json b/libmbdevice/schemas/device_list.json new file mode 100644 index 000000000..cdd869f4a --- /dev/null +++ b/libmbdevice/schemas/device_list.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://dbp.noobdev.io/schemas/device_list.json", + "definitions": { + }, + "type": "array", + "items": { + "$ref": "device.json#" + } +} \ No newline at end of file diff --git a/libmbdevice/schemas2cpp.cpp b/libmbdevice/schemas2cpp.cpp new file mode 100644 index 000000000..7ceb4138d --- /dev/null +++ b/libmbdevice/schemas2cpp.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2017 Andrew Gunnerson + * + * This file is part of DualBootPatcher + * + * DualBootPatcher is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DualBootPatcher is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DualBootPatcher. If not, see . + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "mbcommon/common.h" +#include "mbcommon/string.h" + +using namespace rapidjson; + +template +class UnneededKeyFilter +{ +public: + UnneededKeyFilter(OutputHandler &out) + : _out(out), _filter_depth(), _filter_key_count() + { + } + + MB_DISABLE_COPY_CONSTRUCT_AND_ASSIGN(UnneededKeyFilter) + MB_DISABLE_MOVE_CONSTRUCT_AND_ASSIGN(UnneededKeyFilter) + + bool Null() + { + return _filter_depth > 0 ? EndValue() : _out.Null(); + } + + bool Bool(bool b) + { + return _filter_depth > 0 ? EndValue() : _out.Bool(b); + } + + bool Int(int i) + { + return _filter_depth > 0 ? EndValue() : _out.Int(i); + } + + bool Uint(unsigned u) + { + return _filter_depth > 0 ? EndValue() : _out.Uint(u); + } + + bool Int64(int64_t i) + { + return _filter_depth > 0 ? EndValue() : _out.Int64(i); + } + + bool Uint64(uint64_t u) + { + return _filter_depth > 0 ? EndValue() : _out.Uint64(u); + } + + bool Double(double d) + { + return _filter_depth > 0 ? EndValue() : _out.Double(d); + } + + bool RawNumber(const char *str, SizeType length, bool copy) + { + return _filter_depth > 0 ? EndValue() + : _out.RawNumber(str, length, copy); + } + + bool String(const char *str, SizeType length, bool copy) + { + return _filter_depth > 0 ? EndValue() : _out.String(str, length, copy); + } + + bool StartObject() + { + if (_filter_depth > 0) { + ++_filter_depth; + return true; + } else { + _filter_key_count.push(0); + return _out.StartObject(); + } + } + + bool Key(const char *str, SizeType length, bool copy) + { + if (_filter_depth > 0) { + return true; + } else if ((length == 5 && memcmp(str, "title", 5) == 0) + || (length == 11 && memcmp(str, "description", 11) == 0)) { + _filter_depth = 1; + return true; + } else { + ++_filter_key_count.top(); + return _out.Key(str, length, copy); + } + } + + bool EndObject(SizeType member_count) + { + (void) member_count; + + if (_filter_depth > 0) { + --_filter_depth; + return EndValue(); + } else { + member_count = _filter_key_count.top(); + _filter_key_count.pop(); + return _out.EndObject(member_count); + } + } + + bool StartArray() + { + if (_filter_depth > 0) { + _filter_depth++; + return true; + } else { + return _out.StartArray(); + } + } + + bool EndArray(SizeType element_count) + { + if (_filter_depth > 0) { + --_filter_depth; + return EndValue(); + } else { + return _out.EndArray(element_count); + } + } + +private: + bool EndValue() + { + if (_filter_depth == 1) { + _filter_depth = 0; + } + return true; + } + + OutputHandler &_out; + unsigned _filter_depth; + std::stack _filter_key_count; +}; + +static std::string base_name(const std::string &path) +{ + auto slash = path.find_last_of("/\\"); + if (slash != std::string::npos) { + return path.substr(slash + 1); + } else { + return path; + } +} + +static void escape_print(FILE *fp, const std::string &str) +{ + static const char digits[] = "0123456789abcdef"; + + for (char c : str) { + if (c == '\\') { + fputs("\\\\", fp); + } else if (c == '"') { + fputs("\\\"", fp); + } else if (isprint(c)) { + fputc(c, fp); + } else if (c == '\a') { + fputs("\\a", fp); + } else if (c == '\b') { + fputs("\\b", fp); + } else if (c == '\f') { + fputs("\\f", fp); + } else if (c == '\n') { + fputs("\\n", fp); + } else if (c == '\r') { + fputs("\\r", fp); + } else if (c == '\t') { + fputs("\\t", fp); + } else if (c == '\v') { + fputs("\\v", fp); + } else { + unsigned char uc = static_cast(c); + fputs("\\x", fp); + fputc(digits[(uc >> 4) & 0xf], fp); + fputc(digits[uc & 0xf], fp); + } + } +} + +static void usage(FILE *stream) +{ + fprintf(stream, R"raw(\ +Usage: schemas2cpp [OPTION]... [FILE]... + +Options: + -o, --output Output cpp file path + -h, --help Display this help message + +The -o option specifies the output path for the cpp file. A corresponding header +file will also be written. If the cpp output path ends in ".cpp", the header +will be written to the same path except ".cpp" is replaced with ".h". Otherwise, +".h" is simply appended to the path. +)raw"); +} + +int main(int argc, char *argv[]) +{ + int opt; + + static const char short_options[] = "o:h"; + + static struct option long_options[] = { + {"output", required_argument, 0, 'o'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int long_index = 0; + + const char *output_path = nullptr; + + while ((opt = getopt_long(argc, argv, short_options, + long_options, &long_index)) != -1) { + switch (opt) { + case 'o': + output_path = optarg; + break; + + case 'h': + usage(stdout); + return EXIT_SUCCESS; + + default: + usage(stderr); + return EXIT_FAILURE; + } + } + + if (!output_path) { + fprintf(stderr, "No output path specified.\n"); + return EXIT_FAILURE; + } + + using ScopedFILE = std::unique_ptr; + + char read_buf[65536]; + StringBuffer sb; + + std::vector> results; + + for (int i = optind; i < argc; ++i) { + ScopedFILE fp(fopen(argv[i], "r"), &fclose); + if (!fp) { + fprintf(stderr, "%s: Failed to open for reading: %s\n", + argv[i], strerror(errno)); + return EXIT_FAILURE; + } + + sb.Clear(); + + Reader reader; + FileReadStream is(fp.get(), read_buf, sizeof(read_buf)); + + Writer writer(sb); + UnneededKeyFilter> filter(writer); + + if (!reader.Parse(is, filter)) { + fprintf(stderr, "Error at offset %zu: %s\n", + reader.GetErrorOffset(), + GetParseError_En(reader.GetParseErrorCode())); + return EXIT_FAILURE; + } + + results.emplace_back(base_name(argv[i]), + std::string{sb.GetString(), sb.GetSize()}); + } + + // Write cpp file + { + ScopedFILE fp(fopen(output_path, "w"), &fclose); + if (!fp) { + fprintf(stderr, "%s: Failed to open for writing: %s\n", + output_path, strerror(errno)); + return EXIT_FAILURE; + } + + fprintf(fp.get(), + "#include \n" + "#include \n" + "std::array, %zu> g_schemas{{\n", + results.size()); + + for (auto const &r : results) { + fprintf(fp.get(), " { \""); + escape_print(fp.get(), r.first); + fprintf(fp.get(), "\", \""); + escape_print(fp.get(), r.second); + fprintf(fp.get(), "\" },\n"); + } + + fprintf(fp.get(), "}};\n"); + } + + // Write header file + { + std::string hpp_path{output_path}; + if (mb::ends_with(hpp_path, ".cpp")) { + hpp_path.resize(hpp_path.size() - 4); + } + hpp_path += ".h"; + + ScopedFILE fp(fopen(hpp_path.c_str(), "w"), &fclose); + if (!fp) { + fprintf(stderr, "%s: Failed to open for writing: %s\n", + hpp_path.c_str(), strerror(errno)); + return EXIT_FAILURE; + } + + fprintf(fp.get(), + "#pragma once\n" + "#include \n" + "#include \n" + "extern std::array, %zu> g_schemas;\n", + results.size()); + } + + return EXIT_SUCCESS; +} diff --git a/libmbdevice/src/capi/json.cpp b/libmbdevice/src/capi/json.cpp index a86e81736..d945c58e5 100644 --- a/libmbdevice/src/capi/json.cpp +++ b/libmbdevice/src/capi/json.cpp @@ -60,34 +60,34 @@ uint16_t mb_device_json_error_type(const CJsonError *error) return static_cast::type>(je->type); } -int mb_device_json_error_line(const CJsonError *error) +size_t mb_device_json_error_offset(const CJsonError *error) { JE_CCAST(error); - return je->line; + return je->offset; } -int mb_device_json_error_column(const CJsonError *error) +char * mb_device_json_error_message(const CJsonError *error) { JE_CCAST(error); - return je->column; + return mb::capi_str_to_cstr(je->message); } -char * mb_device_json_error_context(const CJsonError *error) +char * mb_device_json_error_schema_uri(const CJsonError *error) { JE_CCAST(error); - return capi_str_to_cstr(je->context); + return mb::capi_str_to_cstr(je->schema_uri); } -char * mb_device_json_error_expected_type(const CJsonError *error) +char * mb_device_json_error_schema_keyword(const CJsonError *error) { JE_CCAST(error); - return capi_str_to_cstr(je->expected_type); + return mb::capi_str_to_cstr(je->schema_keyword); } -char * mb_device_json_error_actual_type(const CJsonError *error) +char * mb_device_json_error_document_uri(const CJsonError *error) { JE_CCAST(error); - return capi_str_to_cstr(je->actual_type); + return mb::capi_str_to_cstr(je->document_uri); } CDevice * mb_device_new_from_json(const char *json, CJsonError *error) diff --git a/libmbdevice/src/json.cpp b/libmbdevice/src/json.cpp index f1c6282ce..791d85aa5 100644 --- a/libmbdevice/src/json.cpp +++ b/libmbdevice/src/json.cpp @@ -19,22 +19,26 @@ #include "mbdevice/json.h" +#include #include -#include +#include +#include +#include +#include +#include #include "mbcommon/string.h" +#include "mbdevice/schema.h" -#define JSON_BOOLEAN JSON_TRUE +using namespace rapidjson; namespace mb { namespace device { -using ScopedJsonT = std::unique_ptr; - using DeviceFlagMapping = std::pair; using TwFlagMapping = std::pair; using TwPixelFormatMapping = std::pair; @@ -78,189 +82,47 @@ static constexpr std::array g_tw_force_pxfmt_mappi { "RGB_565", TwForcePixelFormat::Rgb565 }, }}; -static std::string json_type_to_string(json_type type) -{ - switch (type) { - case JSON_OBJECT: - return "object"; - case JSON_ARRAY: - return "array"; - case JSON_STRING: - return "string"; - case JSON_INTEGER: - return "integer"; - case JSON_REAL: - return "real"; - case JSON_TRUE: - case JSON_FALSE: - return "boolean"; - case JSON_NULL: - return "null"; - default: - return {}; - } -} - -static void json_error_set_parse_error(JsonError &error, int line, int column) +static void json_error_set_parse_error(JsonError &error, size_t offset, + std::string message) { error.type = JsonErrorType::ParseError; - error.line = line; - error.column = column; + error.offset = offset; + error.message = std::move(message); } -static void json_error_set_mismatched_type(JsonError &error, - std::string context, - json_type actual_type, - json_type expected_type) +static void json_error_set_schema_validation_failure(JsonError &error, + std::string schema_uri, + std::string schema_keyword, + std::string document_uri) { - error.type = JsonErrorType::MismatchedType; - - if (context.empty()) { - error.context = "."; - } else { - error.context = std::move(context); - } - - error.actual_type = json_type_to_string(actual_type); - error.expected_type = json_type_to_string(expected_type); + error.type = JsonErrorType::SchemaValidationFailure; + error.schema_uri = std::move(schema_uri); + error.schema_keyword = std::move(schema_keyword); + error.document_uri = std::move(document_uri); } -static void json_error_set_unknown_key(JsonError &error, std::string context) +static inline std::string get_string(const Value &node) { - error.type = JsonErrorType::UnknownKey; - - if (context.empty()) { - error.context = "."; - } else { - error.context = std::move(context); - } + return {node.GetString(), node.GetStringLength()}; } -static void json_error_set_unknown_value(JsonError &error, std::string context) +static inline std::vector get_string_array(const Value &node) { - error.type = JsonErrorType::UnknownValue; - - if (context.empty()) { - error.context = "."; - } else { - error.context = std::move(context); - } -} - -static inline bool device_set_boolean(void (Device::*setter)(bool), - Device &device, json_t *value, - const std::string &context, - JsonError &error) -{ - if (!json_is_boolean(value)) { - json_error_set_mismatched_type( - error, context, value->type, JSON_BOOLEAN); - return false; - } - - (device.*setter)(json_boolean_value(value)); - return true; -} - -static inline bool device_set_int(void (Device::*setter)(int), - Device &device, json_t *value, - const std::string &context, - JsonError &error) -{ - if (!json_is_integer(value)) { - json_error_set_mismatched_type( - error, context, value->type, JSON_INTEGER); - return false; - } - - (device.*setter)(json_integer_value(value)); - return true; -} - -static inline bool device_set_string(void (Device::*setter)(std::string), - Device &device, json_t *value, - const std::string &context, - JsonError &error) -{ - if (!json_is_string(value)) { - json_error_set_mismatched_type( - error, context, value->type, JSON_STRING); - return false; - } - - (device.*setter)(json_string_value(value)); - return true; -} - -static inline bool device_set_string_array(void (Device::*setter)(std::vector), - Device &device, json_t *node, - const std::string &context, - JsonError &error) -{ - std::string subcontext; - size_t index; - json_t *value; std::vector array; - if (!json_is_array(node)) { - json_error_set_mismatched_type( - error, context, node->type, JSON_ARRAY); - return false; - } - - json_array_foreach(node, index, value) { -#ifdef __ANDROID__ - subcontext = format("%s[%" MB_PRIzu "]", context.c_str(), index); -#else - subcontext = context; - subcontext += "["; - subcontext += std::to_string(index); - subcontext += "]"; -#endif - - if (!json_is_string(value)) { - json_error_set_mismatched_type( - error, subcontext, value->type, JSON_STRING); - return false; - } - - array.push_back(json_string_value(value)); + for (auto const &item : node.GetArray()) { + array.push_back(get_string(item)); } - (device.*setter)(std::move(array)); - return true; + return array; } -static bool process_device_flags(Device &device, json_t *node, - const std::string &context, JsonError &error) +static void process_device_flags(Device &device, const Value &node) { - std::string subcontext; - size_t index; - json_t *value; DeviceFlags flags = 0; - if (!json_is_array(node)) { - json_error_set_mismatched_type(error, context, node->type, JSON_ARRAY); - return false; - } - - json_array_foreach(node, index, value) { -#ifdef __ANDROID__ - subcontext = mb::format("%s[%" MB_PRIzu "]", context.c_str(), index); -#else - subcontext = context; - subcontext += "["; - subcontext += std::to_string(index); - subcontext += "]"; -#endif - - if (!json_is_string(value)) { - json_error_set_mismatched_type( - error, subcontext, value->type, JSON_STRING); - return false; - } - - const std::string &str = json_string_value(value); + for (auto const &item : node.GetArray()) { + auto const &str = get_string(item); DeviceFlags old_flags = flags; for (auto const &item : g_device_flag_mappings) { @@ -270,46 +132,18 @@ static bool process_device_flags(Device &device, json_t *node, } } - if (flags == old_flags) { - json_error_set_unknown_value(error, subcontext); - return false; - } + assert(flags != old_flags); } device.set_flags(flags); - return true; } -static bool process_boot_ui_flags(Device &device, json_t *node, - const std::string &context, JsonError &error) +static void process_boot_ui_flags(Device &device, const Value &node) { - std::string subcontext; - size_t index; - json_t *value; TwFlags flags = 0; - if (!json_is_array(node)) { - json_error_set_mismatched_type(error, context, node->type, JSON_ARRAY); - return false; - } - - json_array_foreach(node, index, value) { -#ifdef __ANDROID__ - subcontext = mb::format("%s[%" MB_PRIzu "]", context.c_str(), index); -#else - subcontext = context; - subcontext += "["; - subcontext += std::to_string(index); - subcontext += "]"; -#endif - - if (!json_is_string(value)) { - json_error_set_mismatched_type( - error, subcontext, value->type, JSON_STRING); - return false; - } - - const std::string &str = json_string_value(value); + for (auto const &item : node.GetArray()) { + auto const &str = get_string(item); TwFlags old_flags = flags; for (auto const &item : g_tw_flag_mappings) { @@ -319,456 +153,357 @@ static bool process_boot_ui_flags(Device &device, json_t *node, } } - if (flags == old_flags) { - json_error_set_unknown_value(error, subcontext); - return false; - } + assert(flags != old_flags); } device.set_tw_flags(flags); - return true; } -static bool process_boot_ui_pixel_format(Device &device, json_t *node, - const std::string &context, - JsonError &error) +static void process_boot_ui_pixel_format(Device &device, const Value &node) { - if (!json_is_string(node)) { - json_error_set_mismatched_type(error, context, node->type, JSON_STRING); - return false; - } - - std::string str = json_string_value(node); + auto const &str = get_string(node); for (auto const &item : g_tw_pxfmt_mappings) { if (str == item.first) { device.set_tw_pixel_format(item.second); - return true; + return; } } - json_error_set_unknown_value(error, context); - return false; + assert(false); } -static bool process_boot_ui_force_pixel_format(Device &device, json_t *node, - const std::string &context, - JsonError &error) +static void process_boot_ui_force_pixel_format(Device &device, const Value &node) { - if (!json_is_string(node)) { - json_error_set_mismatched_type(error, context, node->type, JSON_STRING); - return false; - } - - std::string str = json_string_value(node); + auto const &str = get_string(node); for (auto const &item : g_tw_force_pxfmt_mappings) { if (str == item.first) { device.set_tw_force_pixel_format(item.second); - return true; + return; } } - json_error_set_unknown_value(error, context); - return false; + assert(false); } -static bool process_boot_ui(Device &device, json_t *node, - const std::string &context, - JsonError &error) +static void process_boot_ui(Device &device, const Value &node) { - bool ret; - std::string subcontext; - const char *key; - json_t *value; - - if (!json_is_object(node)) { - json_error_set_mismatched_type( - error, context, node->type, JSON_OBJECT); - return false; - } - - json_object_foreach(node, key, value) { - subcontext = context; - subcontext += "."; - subcontext += key; - - if (strcmp(key, "supported") == 0) { - ret = device_set_boolean(&Device::set_tw_supported, - device, value, subcontext, error); - } else if (strcmp(key, "flags") == 0) { - ret = process_boot_ui_flags(device, value, subcontext, error); - } else if (strcmp(key, "pixel_format") == 0) { - ret = process_boot_ui_pixel_format(device, value, subcontext, - error); - } else if (strcmp(key, "force_pixel_format") == 0) { - ret = process_boot_ui_force_pixel_format(device, value, subcontext, - error); - } else if (strcmp(key, "overscan_percent") == 0) { - ret = device_set_int(&Device::set_tw_overscan_percent, - device, value, subcontext, error); - } else if (strcmp(key, "default_x_offset") == 0) { - ret = device_set_int(&Device::set_tw_default_x_offset, - device, value, subcontext, error); - } else if (strcmp(key, "default_y_offset") == 0) { - ret = device_set_int(&Device::set_tw_default_y_offset, - device, value, subcontext, error); - } else if (strcmp(key, "brightness_path") == 0) { - ret = device_set_string(&Device::set_tw_brightness_path, - device, value, subcontext, error); - } else if (strcmp(key, "secondary_brightness_path") == 0) { - ret = device_set_string(&Device::set_tw_secondary_brightness_path, - device, value, subcontext, error); - } else if (strcmp(key, "max_brightness") == 0) { - ret = device_set_int(&Device::set_tw_max_brightness, - device, value, subcontext, error); - } else if (strcmp(key, "default_brightness") == 0) { - ret = device_set_int(&Device::set_tw_default_brightness, - device, value, subcontext, error); - } else if (strcmp(key, "battery_path") == 0) { - ret = device_set_string(&Device::set_tw_battery_path, - device, value, subcontext, error); - } else if (strcmp(key, "cpu_temp_path") == 0) { - ret = device_set_string(&Device::set_tw_cpu_temp_path, - device, value, subcontext, error); - } else if (strcmp(key, "input_blacklist") == 0) { - ret = device_set_string(&Device::set_tw_input_blacklist, - device, value, subcontext, error); - } else if (strcmp(key, "input_whitelist") == 0) { - ret = device_set_string(&Device::set_tw_input_whitelist, - device, value, subcontext, error); - } else if (strcmp(key, "graphics_backends") == 0) { - ret = device_set_string_array(&Device::set_tw_graphics_backends, - device, value, subcontext, error); - } else if (strcmp(key, "theme") == 0) { - ret = device_set_string(&Device::set_tw_theme, - device, value, subcontext, error); + for (auto const &item : node.GetObject()) { + auto const &key = get_string(item.name); + + if (key == "supported") { + device.set_tw_supported(item.value.GetBool()); + } else if (key == "flags") { + process_boot_ui_flags(device, item.value); + } else if (key == "pixel_format") { + process_boot_ui_pixel_format(device, item.value); + } else if (key == "force_pixel_format") { + process_boot_ui_force_pixel_format(device, item.value); + } else if (key == "overscan_percent") { + device.set_tw_overscan_percent(item.value.GetInt()); + } else if (key == "default_x_offset") { + device.set_tw_default_x_offset(item.value.GetInt()); + } else if (key == "default_y_offset") { + device.set_tw_default_y_offset(item.value.GetInt()); + } else if (key == "brightness_path") { + device.set_tw_brightness_path(get_string(item.value)); + } else if (key == "secondary_brightness_path") { + device.set_tw_secondary_brightness_path(get_string(item.value)); + } else if (key == "max_brightness") { + device.set_tw_max_brightness(item.value.GetInt()); + } else if (key == "default_brightness") { + device.set_tw_default_brightness(item.value.GetInt()); + } else if (key == "battery_path") { + device.set_tw_battery_path(get_string(item.value)); + } else if (key == "cpu_temp_path") { + device.set_tw_cpu_temp_path(get_string(item.value)); + } else if (key == "input_blacklist") { + device.set_tw_input_blacklist(get_string(item.value)); + } else if (key == "input_whitelist") { + device.set_tw_input_whitelist(get_string(item.value)); + } else if (key == "graphics_backends") { + device.set_tw_graphics_backends(get_string_array(item.value)); + } else if (key == "theme") { + device.set_tw_theme(get_string(item.value)); } else { - json_error_set_unknown_key(error, subcontext); - ret = false; - } - - if (!ret) { - break; + assert(false); } } - - return ret; } -static bool process_block_devs(Device &device, json_t *node, - const std::string &context, - JsonError &error) +static void process_block_devs(Device &device, const Value &node) { - bool ret; - std::string subcontext; - const char *key; - json_t *value; - - if (!json_is_object(node)) { - json_error_set_mismatched_type( - error, context, node->type, JSON_OBJECT); - return false; - } - - json_object_foreach(node, key, value) { - subcontext = context; - subcontext += "."; - subcontext += key; - - if (strcmp(key, "base_dirs") == 0) { - ret = device_set_string_array(&Device::set_block_dev_base_dirs, - device, value, subcontext, error); - } else if (strcmp(key, "system") == 0) { - ret = device_set_string_array(&Device::set_system_block_devs, - device, value, subcontext, error); - } else if (strcmp(key, "cache") == 0) { - ret = device_set_string_array(&Device::set_cache_block_devs, - device, value, subcontext, error); - } else if (strcmp(key, "data") == 0) { - ret = device_set_string_array(&Device::set_data_block_devs, - device, value, subcontext, error); - } else if (strcmp(key, "boot") == 0) { - ret = device_set_string_array(&Device::set_boot_block_devs, - device, value, subcontext, error); - } else if (strcmp(key, "recovery") == 0) { - ret = device_set_string_array(&Device::set_recovery_block_devs, - device, value, subcontext, error); - } else if (strcmp(key, "extra") == 0) { - ret = device_set_string_array(&Device::set_extra_block_devs, - device, value, subcontext, error); + for (auto const &item : node.GetObject()) { + auto const &key = get_string(item.name); + + if (key == "base_dirs") { + device.set_block_dev_base_dirs(get_string_array(item.value)); + } else if (key == "system") { + device.set_system_block_devs(get_string_array(item.value)); + } else if (key == "cache") { + device.set_cache_block_devs(get_string_array(item.value)); + } else if (key == "data") { + device.set_data_block_devs(get_string_array(item.value)); + } else if (key == "boot") { + device.set_boot_block_devs(get_string_array(item.value)); + } else if (key == "recovery") { + device.set_recovery_block_devs(get_string_array(item.value)); + } else if (key == "extra") { + device.set_extra_block_devs(get_string_array(item.value)); } else { - json_error_set_unknown_key(error, subcontext); - ret = false; - } - - if (!ret) { - break; + assert(false); } } - - return ret; } -static bool process_device(Device &device, json_t *node, - const std::string &context, - JsonError &error) +static void process_device(Device &device, const Value &node) { - bool ret; - std::string subcontext; - const char *key; - json_t *value; - - if (!json_is_object(node)) { - json_error_set_mismatched_type( - error, context, node->type, JSON_OBJECT); - return false; - } - - json_object_foreach(node, key, value) { - subcontext = context; - subcontext += "."; - subcontext += key; - - if (strcmp(key, "name") == 0) { - ret = device_set_string(&Device::set_name, - device, value, subcontext, error); - } else if (strcmp(key, "id") == 0) { - ret = device_set_string(&Device::set_id, - device, value, subcontext, error); - } else if (strcmp(key, "codenames") == 0) { - ret = device_set_string_array(&Device::set_codenames, - device, value, subcontext, error); - } else if (strcmp(key, "architecture") == 0) { - ret = device_set_string(&Device::set_architecture, - device, value, subcontext, error); - } else if (strcmp(key, "flags") == 0) { - ret = process_device_flags(device, value, subcontext, error); - } else if (strcmp(key, "block_devs") == 0) { - ret = process_block_devs(device, value, subcontext, error); - } else if (strcmp(key, "boot_ui") == 0) { - ret = process_boot_ui(device, value, subcontext, error); + for (auto const &item : node.GetObject()) { + auto const &key = get_string(item.name); + + if (key == "name") { + device.set_name(get_string(item.value)); + } else if (key == "id") { + device.set_id(get_string(item.value)); + } else if (key == "codenames") { + device.set_codenames(get_string_array(item.value)); + } else if (key == "architecture") { + device.set_architecture(get_string(item.value)); + } else if (key == "flags") { + process_device_flags(device, item.value); + } else if (key == "block_devs") { + process_block_devs(device, item.value); + } else if (key == "boot_ui") { + process_boot_ui(device, item.value); } else { - json_error_set_unknown_key(error, subcontext); - ret = false; - } - - if (!ret) { - break; + assert(false); } } - - return ret; } bool device_from_json(const std::string &json, Device &device, JsonError &error) { - json_error_t json_error; + DeviceSchemaProvider<> sp; + const SchemaDocument *sd = sp.GetSchema("device.json"); + if (!sd) { + assert(false); + return false; + } + + Document d; + StringStream is(json.c_str()); + SchemaValidatingReader> reader(is, *sd); + d.Populate(reader); - ScopedJsonT root(json_loads(json.c_str(), 0, &json_error), &json_decref); - if (!root) { - json_error_set_parse_error(error, json_error.line, json_error.column); + const ParseResult &result = reader.GetParseResult(); + if (!result) { + if (!reader.IsValid()) { + StringBuffer sb; + reader.GetInvalidSchemaPointer().StringifyUriFragment(sb); + std::string schema_uri{sb.GetString(), sb.GetLength()}; + sb.Clear(); + reader.GetInvalidDocumentPointer().StringifyUriFragment(sb); + std::string document_uri{sb.GetString(), sb.GetLength()}; + + json_error_set_schema_validation_failure( + error, std::move(schema_uri), + reader.GetInvalidSchemaKeyword(), std::move(document_uri)); + } else { + json_error_set_parse_error(error, result.Offset(), + GetParseError_En(result.Code())); + } return false; } device = Device(); - - return process_device(device, root.get(), "", error); + process_device(device, d); + return true; } bool device_list_from_json(const std::string &json, std::vector &devices, JsonError &error) { - std::vector result; - json_t *elem; - size_t index; - json_error_t json_error; - std::string context; - - ScopedJsonT root(json_loads(json.c_str(), 0, &json_error), &json_decref); - if (!root) { - json_error_set_parse_error(error, json_error.line, json_error.column); + DeviceSchemaProvider<> sp; + const SchemaDocument *sd = sp.GetSchema("device_list.json"); + if (!sd) { + assert(false); return false; } - if (!json_is_array(root)) { - json_error_set_mismatched_type(error, "", root->type, JSON_ARRAY); + Document d; + StringStream is(json.c_str()); + SchemaValidatingReader> reader(is, *sd); + d.Populate(reader); + + const ParseResult &result = reader.GetParseResult(); + if (!result) { + if (!reader.IsValid()) { + StringBuffer sb; + reader.GetInvalidSchemaPointer().StringifyUriFragment(sb); + std::string schema_uri{sb.GetString(), sb.GetLength()}; + sb.Clear(); + reader.GetInvalidDocumentPointer().StringifyUriFragment(sb); + std::string document_uri{sb.GetString(), sb.GetLength()}; + + json_error_set_schema_validation_failure( + error, std::move(schema_uri), + reader.GetInvalidSchemaKeyword(), std::move(document_uri)); + } else { + json_error_set_parse_error(error, result.Offset(), + GetParseError_En(result.Code())); + } return false; } - json_array_foreach(root.get(), index, elem) { -#ifdef __ANDROID__ - context = mb::format("[%" MB_PRIzu "]", index); -#else - context = "["; - context += std::to_string(index); - context += "]"; -#endif + std::vector array; + for (auto const &item : d.GetArray()) { Device device; - - if (!process_device(device, elem, context, error)) { - return false; - } - - result.push_back(std::move(device)); + process_device(device, item); + array.push_back(std::move(device)); } - devices.swap(result); + devices.swap(array); return true; } -static json_t * json_string_array(const std::vector &array) -{ - ScopedJsonT j_array(json_array(), &json_decref); - if (!j_array) { - return nullptr; - } - - for (auto const &item : array) { - if (json_array_append_new(j_array.get(), - json_string(item.c_str())) < 0) { - return nullptr; - } - } - - return j_array.release(); -} - bool device_to_json(const Device &device, std::string &json) { - ScopedJsonT root(json_object(), &json_decref); - if (!root) { - return false; - } + Document d; + d.SetObject(); + + auto &alloc = d.GetAllocator(); auto const &id = device.id(); - if (!id.empty() && json_object_set_new( - root.get(), "id", json_string(id.c_str())) < 0) { - return false; + if (!id.empty()) { + d.AddMember("id", id, alloc); } auto const &codenames = device.codenames(); - if (!codenames.empty() && json_object_set_new( - root.get(), "codenames", json_string_array(codenames)) < 0) { - return false; + if (!codenames.empty()) { + Value array(kArrayType); + for (auto const &c : codenames) { + array.PushBack(StringRef(c), alloc); + } + d.AddMember("codenames", array, alloc); } auto const &name = device.name(); - if (!name.empty() && json_object_set_new( - root.get(), "name", json_string(name.c_str())) < 0) { - return false; + if (!name.empty()) { + d.AddMember("name", name, alloc); } auto const &architecture = device.architecture(); - if (!architecture.empty() && json_object_set_new( - root.get(), "architecture", json_string(architecture.c_str())) < 0) { - return false; + if (!architecture.empty()) { + d.AddMember("architecture", architecture, alloc); } auto const flags = device.flags(); if (flags) { - json_t *array = json_array(); - - if (json_object_set_new(root.get(), "flags", array) < 0) { - return false; - } - + Value array(kArrayType); for (auto const &item : g_device_flag_mappings) { - if ((flags & item.second) && json_array_append_new( - array, json_string(item.first)) < 0) { - return false; + if (flags & item.second) { + array.PushBack(StringRef(item.first), alloc); } } + d.AddMember("flags", array, alloc); } /* Block devs */ - json_t *block_devs = json_object(); - - if (json_object_set_new(root.get(), "block_devs", block_devs) < 0) { - return false; - } + Value block_devs(kObjectType); auto const &base_dirs = device.block_dev_base_dirs(); - if (!base_dirs.empty() && json_object_set_new( - block_devs, "base_dirs", json_string_array(base_dirs)) < 0) { - return false; + if (!base_dirs.empty()) { + Value array(kArrayType); + for (auto const &p : base_dirs) { + array.PushBack(StringRef(p), d.GetAllocator()); + } + block_devs.AddMember("base_dirs", array, alloc); } auto const &system_devs = device.system_block_devs(); - if (!system_devs.empty() && json_object_set_new( - block_devs, "system", json_string_array(system_devs)) < 0) { - return false; + if (!system_devs.empty()) { + Value array(kArrayType); + for (auto const &p : system_devs) { + array.PushBack(StringRef(p), d.GetAllocator()); + } + block_devs.AddMember("system", array, alloc); } auto const &cache_devs = device.cache_block_devs(); - if (!cache_devs.empty() && json_object_set_new( - block_devs, "cache", json_string_array(cache_devs)) < 0) { - return false; + if (!cache_devs.empty()) { + Value array(kArrayType); + for (auto const &p : cache_devs) { + array.PushBack(StringRef(p), d.GetAllocator()); + } + block_devs.AddMember("cache", array, alloc); } auto const &data_devs = device.data_block_devs(); - if (!data_devs.empty() && json_object_set_new( - block_devs, "data", json_string_array(data_devs)) < 0) { - return false; + if (!data_devs.empty()) { + Value array(kArrayType); + for (auto const &p : data_devs) { + array.PushBack(StringRef(p), d.GetAllocator()); + } + block_devs.AddMember("data", array, alloc); } auto const &boot_devs = device.boot_block_devs(); - if (!boot_devs.empty() && json_object_set_new( - block_devs, "boot", json_string_array(boot_devs)) < 0) { - return false; + if (!boot_devs.empty()) { + Value array(kArrayType); + for (auto const &p : boot_devs) { + array.PushBack(StringRef(p), d.GetAllocator()); + } + block_devs.AddMember("boot", array, alloc); } auto const &recovery_devs = device.recovery_block_devs(); - if (!recovery_devs.empty() && json_object_set_new( - block_devs, "recovery", json_string_array(recovery_devs)) < 0) { - return false; + if (!recovery_devs.empty()) { + Value array(kArrayType); + for (auto const &p : recovery_devs) { + array.PushBack(StringRef(p), d.GetAllocator()); + } + block_devs.AddMember("recovery", array, alloc); } auto const &extra_devs = device.extra_block_devs(); - if (!extra_devs.empty() && json_object_set_new( - block_devs, "extra", json_string_array(extra_devs)) < 0) { - return false; + if (!extra_devs.empty()) { + Value array(kArrayType); + for (auto const &p : extra_devs) { + array.PushBack(StringRef(p), d.GetAllocator()); + } + block_devs.AddMember("extra", array, alloc); } - /* Boot UI */ - - json_t *boot_ui = json_object(); - - if (json_object_set_new(root.get(), "boot_ui", boot_ui) < 0) { - return false; + if (!block_devs.ObjectEmpty()) { + d.AddMember("block_devs", block_devs, alloc); } - if (device.tw_supported() && json_object_set_new( - boot_ui, "supported", json_true()) < 0) { - return false; + /* Boot UI */ + Value boot_ui(kObjectType); + + if (device.tw_supported()) { + boot_ui.AddMember("supported", true, alloc); } auto const tw_flags = device.tw_flags(); if (tw_flags) { - json_t *array = json_array(); - - if (json_object_set_new(boot_ui, "flags", array) < 0) { - return false; - } - + Value array(kArrayType); for (auto const &item : g_tw_flag_mappings) { - if ((tw_flags & item.second) && json_array_append_new( - array, json_string(item.first)) < 0) { - return false; + if (tw_flags & item.second) { + array.PushBack(StringRef(item.first), d.GetAllocator()); } } + boot_ui.AddMember("flags", array, alloc); } auto const pixel_format = device.tw_pixel_format(); if (pixel_format != TwPixelFormat::Default) { for (auto const &item : g_tw_pxfmt_mappings) { if (pixel_format == item.second) { - if (json_object_set_new(boot_ui, "pixel_format", - json_string(item.first)) < 0) { - return false; - } + boot_ui.AddMember("pixel_format", StringRef(item.first), alloc); break; } } @@ -778,114 +513,105 @@ bool device_to_json(const Device &device, std::string &json) if (force_pixel_format != TwForcePixelFormat::None) { for (auto const &item : g_tw_force_pxfmt_mappings) { if (force_pixel_format == item.second) { - if (json_object_set_new(boot_ui, "force_pixel_format", - json_string(item.first)) < 0) { - return false; - } + boot_ui.AddMember("force_pixel_format", StringRef(item.first), + alloc); break; } } } auto const overscan_percent = device.tw_overscan_percent(); - if (overscan_percent != 0 && json_object_set_new( - boot_ui, "overscan_percent", json_integer(overscan_percent)) < 0) { - return false; + if (overscan_percent != 0) { + boot_ui.AddMember("overscan_percent", overscan_percent, alloc); } auto const default_x_offset = device.tw_default_x_offset(); - if (default_x_offset != 0 && json_object_set_new( - boot_ui, "default_x_offset", json_integer(default_x_offset)) < 0) { - return false; + if (default_x_offset != 0) { + boot_ui.AddMember("default_x_offset", default_x_offset, alloc); } auto const default_y_offset = device.tw_default_y_offset(); - if (default_y_offset != 0 && json_object_set_new( - boot_ui, "default_y_offset", json_integer(default_y_offset)) < 0) { - return false; + if (default_y_offset != 0) { + boot_ui.AddMember("default_y_offset", default_y_offset, alloc); } auto const &brightness_path = device.tw_brightness_path(); - if (!brightness_path.empty() && json_object_set_new( - boot_ui, "brightness_path", - json_string(brightness_path.c_str())) < 0) { - return false; + if (!brightness_path.empty()) { + boot_ui.AddMember("brightness_path", brightness_path, alloc); } auto const &secondary_brightness_path = device.tw_secondary_brightness_path(); - if (!secondary_brightness_path.empty() && json_object_set_new( - boot_ui, "secondary_brightness_path", - json_string(secondary_brightness_path.c_str())) < 0) { - return false; + if (!secondary_brightness_path.empty()) { + boot_ui.AddMember("secondary_brightness_path", + secondary_brightness_path, alloc); } auto const max_brightness = device.tw_max_brightness(); - if (max_brightness != -1 && json_object_set_new( - boot_ui, "max_brightness", json_integer(max_brightness)) < 0) { - return false; + if (max_brightness != -1) { + boot_ui.AddMember("max_brightness", max_brightness, alloc); } auto const default_brightness = device.tw_default_brightness(); - if (default_brightness != -1 && json_object_set_new( - boot_ui, "default_brightness", - json_integer(default_brightness)) < 0) { - return false; + if (default_brightness != -1) { + boot_ui.AddMember("default_brightness", default_brightness, alloc); } auto const &battery_path = device.tw_battery_path(); - if (!battery_path.empty() && json_object_set_new( - boot_ui, "battery_path", json_string(battery_path.c_str())) < 0) { - return false; + if (!battery_path.empty()) { + boot_ui.AddMember("battery_path", battery_path, alloc); } auto const &cpu_temp_path = device.tw_cpu_temp_path(); - if (!cpu_temp_path.empty() && json_object_set_new( - boot_ui, "cpu_temp_path", json_string(cpu_temp_path.c_str())) < 0) { - return false; + if (!cpu_temp_path.empty()) { + boot_ui.AddMember("cpu_temp_path", cpu_temp_path, alloc); } auto const &input_blacklist = device.tw_input_blacklist(); - if (!input_blacklist.empty() && json_object_set_new( - boot_ui, "input_blacklist", - json_string(input_blacklist.c_str())) < 0) { - return false; + if (!input_blacklist.empty()) { + boot_ui.AddMember("input_blacklist", input_blacklist, alloc); } auto const &input_whitelist = device.tw_input_whitelist(); - if (!input_whitelist.empty() && json_object_set_new( - boot_ui, "input_whitelist", - json_string(input_whitelist.c_str())) < 0) { - return false; + if (!input_whitelist.empty()) { + boot_ui.AddMember("input_whitelist", input_whitelist, alloc); } auto const &graphics_backends = device.tw_graphics_backends(); - if (!graphics_backends.empty() && json_object_set_new( - boot_ui, "graphics_backends", - json_string_array(graphics_backends)) < 0) { - return false; + if (!graphics_backends.empty()) { + Value array(kArrayType); + for (auto const &b : graphics_backends) { + array.PushBack(StringRef(b), d.GetAllocator()); + } + boot_ui.AddMember("graphics_backends", array, alloc); } auto const &theme = device.tw_theme(); - if (!theme.empty() && json_object_set_new( - boot_ui, "theme", json_string(theme.c_str())) < 0) { - return false; + if (!theme.empty()) { + boot_ui.AddMember("theme", theme, alloc); } - if (json_object_size(boot_ui) == 0) { - json_object_del(root.get(), "boot_ui"); + if (!boot_ui.ObjectEmpty()) { + d.AddMember("boot_ui", boot_ui, alloc); } - std::unique_ptr result( - json_dumps(root.get(), 0), &free); - if (!result) { + StringBuffer sb; + Writer writer(sb); + DeviceSchemaProvider<> sp; + const SchemaDocument *sd = sp.GetSchema("device.json"); + if (!sd) { + assert(false); return false; } + GenericSchemaValidator sv(*sd, writer); - json = result.get(); + if (!d.Accept(sv)) { + return false; + } + + json = {sb.GetString(), sb.GetSize()}; return true; } - } } diff --git a/libmbdevice/src/schema.cpp b/libmbdevice/src/schema.cpp new file mode 100644 index 000000000..5838aa37c --- /dev/null +++ b/libmbdevice/src/schema.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 Andrew Gunnerson + * + * This file is part of DualBootPatcher + * + * DualBootPatcher is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * DualBootPatcher is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with DualBootPatcher. If not, see . + */ + +#include "mbdevice/schema.h" + +#include + +#include + +#include "schemas_gen.h" + +using namespace rapidjson; + +namespace mb +{ +namespace device +{ + +const char * find_schema(const std::string &uri) +{ + using SchemaItem = decltype(g_schemas)::value_type; + + auto it = std::find_if(g_schemas.begin(), g_schemas.end(), + [&uri](const SchemaItem &item) { + return uri == item.first; + }); + + return it == g_schemas.end() ? nullptr : it->second; +} + +} +} diff --git a/libmbdevice/tests/test_json.cpp b/libmbdevice/tests/test_json.cpp index 51e1256fd..4d6550aa5 100644 --- a/libmbdevice/tests/test_json.cpp +++ b/libmbdevice/tests/test_json.cpp @@ -174,10 +174,28 @@ static constexpr char sample_malformed[] = R"json( static constexpr char sample_multiple[] = R"json( [ { - "id": "test1" + "name": "test1", + "id": "test1", + "codenames": ["test1"], + "architecture": "armeabi-v7a", + "block_devs": { + "system": ["/dev/blah"], + "cache": ["/dev/blah"], + "data": ["/dev/blah"], + "boot": ["/dev/blah"] + } }, { - "id": "test2" + "name": "test2", + "id": "test2", + "codenames": ["test2"], + "architecture": "arm64-v8a", + "block_devs": { + "system": ["/dev/blah"], + "cache": ["/dev/blah"], + "data": ["/dev/blah"], + "boot": ["/dev/blah"] + } } ] )json"; @@ -289,8 +307,10 @@ TEST(JsonTest, LoadInvalidKey) Device device; JsonError error; ASSERT_FALSE(device_from_json(sample_invalid_key, device, error)); - ASSERT_EQ(error.type, JsonErrorType::UnknownKey); - ASSERT_EQ(error.context, ".foo"); + ASSERT_EQ(error.type, JsonErrorType::SchemaValidationFailure); + ASSERT_EQ(error.schema_uri, "#"); + ASSERT_EQ(error.schema_keyword, "additionalProperties"); + ASSERT_EQ(error.document_uri, "#/foo"); } TEST(JsonTest, LoadInvalidValue) @@ -298,26 +318,34 @@ TEST(JsonTest, LoadInvalidValue) Device d1; JsonError e1; ASSERT_FALSE(device_from_json(sample_invalid_device_flags, d1, e1)); - ASSERT_EQ(e1.type, JsonErrorType::UnknownValue); - ASSERT_EQ(e1.context, ".flags[0]"); + ASSERT_EQ(e1.type, JsonErrorType::SchemaValidationFailure); + ASSERT_EQ(e1.schema_uri, "#/properties/flags/items"); + ASSERT_EQ(e1.schema_keyword, "enum"); + ASSERT_EQ(e1.document_uri, "#/flags/0"); Device d2; JsonError e2; ASSERT_FALSE(device_from_json(sample_invalid_tw_flags, d2, e2)); - ASSERT_EQ(e2.type, JsonErrorType::UnknownValue); - ASSERT_EQ(e2.context, ".boot_ui.flags[0]"); + ASSERT_EQ(e2.type, JsonErrorType::SchemaValidationFailure); + ASSERT_EQ(e2.schema_uri, "#/properties/boot_ui/properties/flags/items"); + ASSERT_EQ(e2.schema_keyword, "enum"); + ASSERT_EQ(e2.document_uri, "#/boot_ui/flags/0"); Device d3; JsonError e3; ASSERT_FALSE(device_from_json(sample_invalid_tw_pixel_format, d3, e3)); - ASSERT_EQ(e3.type, JsonErrorType::UnknownValue); - ASSERT_EQ(e3.context, ".boot_ui.pixel_format"); + ASSERT_EQ(e3.type, JsonErrorType::SchemaValidationFailure); + ASSERT_EQ(e3.schema_uri, "#/properties/boot_ui/properties/pixel_format"); + ASSERT_EQ(e3.schema_keyword, "enum"); + ASSERT_EQ(e3.document_uri, "#/boot_ui/pixel_format"); Device d4; JsonError e4; ASSERT_FALSE(device_from_json(sample_invalid_tw_force_pixel_format, d4, e4)); - ASSERT_EQ(e4.type, JsonErrorType::UnknownValue); - ASSERT_EQ(e4.context, ".boot_ui.force_pixel_format"); + ASSERT_EQ(e4.type, JsonErrorType::SchemaValidationFailure); + ASSERT_EQ(e4.schema_uri, "#/properties/boot_ui/properties/force_pixel_format"); + ASSERT_EQ(e4.schema_keyword, "enum"); + ASSERT_EQ(e4.document_uri, "#/boot_ui/force_pixel_format"); } TEST(JsonTest, LoadInvalidType) @@ -325,18 +353,18 @@ TEST(JsonTest, LoadInvalidType) Device d1; JsonError e1; ASSERT_FALSE(device_from_json(sample_invalid_root, d1, e1)); - ASSERT_EQ(e1.type, JsonErrorType::MismatchedType); - ASSERT_EQ(e1.context, "."); - ASSERT_EQ(e1.actual_type, "array"); - ASSERT_EQ(e1.expected_type, "object"); + ASSERT_EQ(e1.type, JsonErrorType::SchemaValidationFailure); + ASSERT_EQ(e1.schema_uri, "#"); + ASSERT_EQ(e1.schema_keyword, "type"); + ASSERT_EQ(e1.document_uri, "#"); Device d2; JsonError e2; ASSERT_FALSE(device_from_json(sample_invalid_type, d2, e2)); - ASSERT_EQ(e2.type, JsonErrorType::MismatchedType); - ASSERT_EQ(e2.context, ".boot_ui"); - ASSERT_EQ(e2.actual_type, "string"); - ASSERT_EQ(e2.expected_type, "object"); + ASSERT_EQ(e2.type, JsonErrorType::SchemaValidationFailure); + ASSERT_EQ(e2.schema_uri, "#/properties/boot_ui"); + ASSERT_EQ(e2.schema_keyword, "type"); + ASSERT_EQ(e2.document_uri, "#/boot_ui"); } TEST(JsonTest, LoadMalformed) @@ -356,10 +384,10 @@ TEST(JsonTest, LoadMultiple) std::vector d2; JsonError e2; ASSERT_FALSE(device_list_from_json(sample_complete, d2, e2)); - ASSERT_EQ(e2.type, JsonErrorType::MismatchedType); - ASSERT_EQ(e2.context, "."); - ASSERT_EQ(e2.actual_type, "object"); - ASSERT_EQ(e2.expected_type, "array"); + ASSERT_EQ(e2.type, JsonErrorType::SchemaValidationFailure); + ASSERT_EQ(e2.schema_uri, "#"); + ASSERT_EQ(e2.schema_keyword, "type"); + ASSERT_EQ(e2.document_uri, "#"); } TEST(JsonTest, CreateJson) @@ -382,10 +410,6 @@ TEST(JsonTest, CheckCapiFlagsEqual) { ASSERT_EQ(TO_U(JsonErrorType, ParseError), MB_DEVICE_JSON_PARSE_ERROR); - ASSERT_EQ(TO_U(JsonErrorType, MismatchedType), - MB_DEVICE_JSON_MISMATCHED_TYPE); - ASSERT_EQ(TO_U(JsonErrorType, UnknownKey), - MB_DEVICE_JSON_UNKNOWN_KEY); - ASSERT_EQ(TO_U(JsonErrorType, UnknownValue), - MB_DEVICE_JSON_UNKNOWN_VALUE); + ASSERT_EQ(TO_U(JsonErrorType, SchemaValidationFailure), + MB_DEVICE_JSON_SCHEMA_VALIDATION_FAILURE); }