diff --git a/src/brayns/core/rendering/Camera.cpp b/src/brayns/core/enginev2/Camera.cpp similarity index 100% rename from src/brayns/core/rendering/Camera.cpp rename to src/brayns/core/enginev2/Camera.cpp diff --git a/src/brayns/core/rendering/Camera.h b/src/brayns/core/enginev2/Camera.h similarity index 100% rename from src/brayns/core/rendering/Camera.h rename to src/brayns/core/enginev2/Camera.h diff --git a/src/brayns/core/rendering/Data.h b/src/brayns/core/enginev2/Data.h similarity index 100% rename from src/brayns/core/rendering/Data.h rename to src/brayns/core/enginev2/Data.h diff --git a/src/brayns/core/rendering/Device.cpp b/src/brayns/core/enginev2/Device.cpp similarity index 100% rename from src/brayns/core/rendering/Device.cpp rename to src/brayns/core/enginev2/Device.cpp diff --git a/src/brayns/core/rendering/Device.h b/src/brayns/core/enginev2/Device.h similarity index 100% rename from src/brayns/core/rendering/Device.h rename to src/brayns/core/enginev2/Device.h diff --git a/src/brayns/core/rendering/GraphicsApi.cpp b/src/brayns/core/enginev2/Engine.cpp similarity index 88% rename from src/brayns/core/rendering/GraphicsApi.cpp rename to src/brayns/core/enginev2/Engine.cpp index c7e9a2d9a..34e89ddd3 100644 --- a/src/brayns/core/rendering/GraphicsApi.cpp +++ b/src/brayns/core/enginev2/Engine.cpp @@ -19,23 +19,23 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "GraphicsApi.h" +#include "Engine.h" #include namespace brayns::experimental { -GraphicsApi::Loader::~Loader() +Engine::Loader::~Loader() { ospShutdown(); } -GraphicsApi::GraphicsApi(std::unique_ptr loader): +Engine::Engine(std::unique_ptr loader): _loader(std::move(loader)) { } -Device GraphicsApi::createDevice(Logger &logger) +Device Engine::createDevice(Logger &logger) { auto currentDevice = ospGetCurrentDevice(); if (currentDevice != nullptr) @@ -71,7 +71,7 @@ Device GraphicsApi::createDevice(Logger &logger) return Device(device); } -GraphicsApi loadGraphicsApi() +Engine loadEngine() { auto error = ospLoadModule("cpu"); @@ -81,8 +81,8 @@ GraphicsApi loadGraphicsApi() throw std::runtime_error(message); } - auto loader = std::make_unique(); + auto loader = std::make_unique(); - return GraphicsApi(std::move(loader)); + return Engine(std::move(loader)); } } diff --git a/src/brayns/core/rendering/GraphicsApi.h b/src/brayns/core/enginev2/Engine.h similarity index 93% rename from src/brayns/core/rendering/GraphicsApi.h rename to src/brayns/core/enginev2/Engine.h index 495f6d175..cc938ea1a 100644 --- a/src/brayns/core/rendering/GraphicsApi.h +++ b/src/brayns/core/enginev2/Engine.h @@ -29,7 +29,7 @@ namespace brayns::experimental { -class GraphicsApi +class Engine { public: class Loader @@ -44,7 +44,7 @@ class GraphicsApi Loader &operator=(Loader &&other) = delete; }; - explicit GraphicsApi(std::unique_ptr loader); + explicit Engine(std::unique_ptr loader); Device createDevice(Logger &logger); @@ -52,5 +52,5 @@ class GraphicsApi std::unique_ptr _loader; }; -GraphicsApi loadGraphicsApi(); +Engine loadEngine(); } diff --git a/src/brayns/core/rendering/Framebuffer.cpp b/src/brayns/core/enginev2/Framebuffer.cpp similarity index 100% rename from src/brayns/core/rendering/Framebuffer.cpp rename to src/brayns/core/enginev2/Framebuffer.cpp diff --git a/src/brayns/core/rendering/Framebuffer.h b/src/brayns/core/enginev2/Framebuffer.h similarity index 100% rename from src/brayns/core/rendering/Framebuffer.h rename to src/brayns/core/enginev2/Framebuffer.h diff --git a/src/brayns/core/rendering/Geometry.cpp b/src/brayns/core/enginev2/Geometry.cpp similarity index 100% rename from src/brayns/core/rendering/Geometry.cpp rename to src/brayns/core/enginev2/Geometry.cpp diff --git a/src/brayns/core/rendering/Geometry.h b/src/brayns/core/enginev2/Geometry.h similarity index 100% rename from src/brayns/core/rendering/Geometry.h rename to src/brayns/core/enginev2/Geometry.h diff --git a/src/brayns/core/rendering/Light.cpp b/src/brayns/core/enginev2/Light.cpp similarity index 100% rename from src/brayns/core/rendering/Light.cpp rename to src/brayns/core/enginev2/Light.cpp diff --git a/src/brayns/core/rendering/Light.h b/src/brayns/core/enginev2/Light.h similarity index 100% rename from src/brayns/core/rendering/Light.h rename to src/brayns/core/enginev2/Light.h diff --git a/src/brayns/core/rendering/Managed.h b/src/brayns/core/enginev2/Managed.h similarity index 100% rename from src/brayns/core/rendering/Managed.h rename to src/brayns/core/enginev2/Managed.h diff --git a/src/brayns/core/rendering/Material.cpp b/src/brayns/core/enginev2/Material.cpp similarity index 100% rename from src/brayns/core/rendering/Material.cpp rename to src/brayns/core/enginev2/Material.cpp diff --git a/src/brayns/core/rendering/Material.h b/src/brayns/core/enginev2/Material.h similarity index 100% rename from src/brayns/core/rendering/Material.h rename to src/brayns/core/enginev2/Material.h diff --git a/src/brayns/core/rendering/Render.cpp b/src/brayns/core/enginev2/Render.cpp similarity index 100% rename from src/brayns/core/rendering/Render.cpp rename to src/brayns/core/enginev2/Render.cpp diff --git a/src/brayns/core/rendering/Render.h b/src/brayns/core/enginev2/Render.h similarity index 100% rename from src/brayns/core/rendering/Render.h rename to src/brayns/core/enginev2/Render.h diff --git a/src/brayns/core/rendering/Renderer.cpp b/src/brayns/core/enginev2/Renderer.cpp similarity index 100% rename from src/brayns/core/rendering/Renderer.cpp rename to src/brayns/core/enginev2/Renderer.cpp diff --git a/src/brayns/core/rendering/Renderer.h b/src/brayns/core/enginev2/Renderer.h similarity index 100% rename from src/brayns/core/rendering/Renderer.h rename to src/brayns/core/enginev2/Renderer.h diff --git a/src/brayns/core/rendering/Volume.cpp b/src/brayns/core/enginev2/Volume.cpp similarity index 100% rename from src/brayns/core/rendering/Volume.cpp rename to src/brayns/core/enginev2/Volume.cpp diff --git a/src/brayns/core/rendering/Volume.h b/src/brayns/core/enginev2/Volume.h similarity index 100% rename from src/brayns/core/rendering/Volume.h rename to src/brayns/core/enginev2/Volume.h diff --git a/src/brayns/core/rendering/World.cpp b/src/brayns/core/enginev2/World.cpp similarity index 100% rename from src/brayns/core/rendering/World.cpp rename to src/brayns/core/enginev2/World.cpp diff --git a/src/brayns/core/rendering/World.h b/src/brayns/core/enginev2/World.h similarity index 100% rename from src/brayns/core/rendering/World.h rename to src/brayns/core/enginev2/World.h diff --git a/src/brayns/core/jsonrpc/Errors.cpp b/src/brayns/core/jsonrpc/Errors.cpp new file mode 100644 index 000000000..3007343b6 --- /dev/null +++ b/src/brayns/core/jsonrpc/Errors.cpp @@ -0,0 +1,94 @@ +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Errors.h" + +namespace brayns::experimental +{ +template<> +struct JsonReflector +{ + static JsonValue serialize(const JsonSchemaError &value) + { + auto object = createJsonObject(); + + auto path = toString(value.path); + object->set("path", path); + + auto error = toString(value.error); + object->set("error", error); + + return object; + } +}; + +JsonRpcException::JsonRpcException(int code, const std::string &message, const JsonValue &data): + std::runtime_error(message), + _code(code), + _data(data) +{ +} + +int JsonRpcException::getCode() const +{ + return _code; +} + +const JsonValue &JsonRpcException::getData() const +{ + return _data; +} + +ParseError::ParseError(const std::string &message): + JsonRpcException(-32700, message) +{ +} + +InvalidRequest::InvalidRequest(const std::string &message): + JsonRpcException(-32600, message) +{ +} + +InvalidRequest::InvalidRequest(const std::string &message, const std::vector &errors): + JsonRpcException(-32600, message, serializeToJson(errors)) +{ +} + +MethodNotFound::MethodNotFound(const std::string &method): + JsonRpcException(-32601, fmt::format("Method not found: '{}'", method)) +{ +} + +InvalidParams::InvalidParams(const std::string &message): + JsonRpcException(-32602, message) +{ +} + +InvalidParams::InvalidParams(const std::string &message, const std::vector &errors): + JsonRpcException(-32602, message, serializeToJson(errors)) +{ +} + +InternalError::InternalError(const std::string &message): + JsonRpcException(-32603, message) +{ +} +} // namespace brayns diff --git a/src/brayns/core/jsonrpc/Errors.h b/src/brayns/core/jsonrpc/Errors.h new file mode 100644 index 000000000..489d0e149 --- /dev/null +++ b/src/brayns/core/jsonrpc/Errors.h @@ -0,0 +1,74 @@ +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include + +#include + +namespace brayns::experimental +{ +class JsonRpcException : public std::runtime_error +{ +public: + explicit JsonRpcException(int code, const std::string &message, const JsonValue &data = {}); + + int getCode() const; + const JsonValue &getData() const; + +private: + int _code = 0; + JsonValue _data; +}; + +class ParseError : public JsonRpcException +{ +public: + explicit ParseError(const std::string &message); +}; + +class InvalidRequest : public JsonRpcException +{ +public: + explicit InvalidRequest(const std::string &message); + explicit InvalidRequest(const std::string &message, const std::vector &errors); +}; + +class MethodNotFound : public JsonRpcException +{ +public: + explicit MethodNotFound(const std::string &method); +}; + +class InvalidParams : public JsonRpcException +{ +public: + explicit InvalidParams(const std::string &message); + explicit InvalidParams(const std::string &message, const std::vector &errors); +}; + +class InternalError : public JsonRpcException +{ +public: + explicit InternalError(const std::string &message); +}; +} diff --git a/src/brayns/core/jsonrpc/Messages.h b/src/brayns/core/jsonrpc/Messages.h new file mode 100644 index 000000000..41c3ca63a --- /dev/null +++ b/src/brayns/core/jsonrpc/Messages.h @@ -0,0 +1,147 @@ +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include + +namespace brayns::experimental +{ +using JsonRpcId = std::variant; + +struct JsonRpcRequest +{ + JsonRpcId id; + std::string method; + JsonValue params; + std::string binary = {}; +}; + +template<> +struct JsonObjectReflector +{ + static auto reflect() + { + auto builder = JsonObjectInfoBuilder(); + builder.constant("jsonrpc", "2.0"); + builder.field("id", [](auto &object) { return &object.id; }).required(false); + builder.field("method", [](auto &object) { return &object.method; }); + builder.field("params", [](auto &object) { return &object.params; }).required(false); + return builder.build(); + } +}; + +struct JsonRpcResponse +{ + JsonRpcId id; + JsonValue result; + std::string binary = {}; +}; + +template<> +struct JsonObjectReflector +{ + static auto reflect() + { + auto builder = JsonObjectInfoBuilder(); + builder.constant("jsonrpc", "2.0"); + builder.field("id", [](auto &object) { return &object.id; }); + builder.field("result", [](auto &object) { return &object.result; }); + return builder.build(); + } +}; + +struct JsonRpcError +{ + int code; + std::string message; + JsonValue data; +}; + +template<> +struct JsonObjectReflector +{ + static auto reflect() + { + auto builder = JsonObjectInfoBuilder(); + builder.field("code", [](auto &object) { return &object.code; }); + builder.field("message", [](auto &object) { return &object.message; }); + builder.field("data", [](auto &object) { return &object.data; }).required(false); + return builder.build(); + } +}; + +struct JsonRpcErrorResponse +{ + JsonRpcId id; + JsonRpcError error; +}; + +template<> +struct JsonObjectReflector +{ + static auto reflect() + { + auto builder = JsonObjectInfoBuilder(); + builder.constant("jsonrpc", "2.0"); + builder.field("id", [](auto &object) { return &object.id; }); + builder.field("error", [](auto &object) { return &object.error; }); + return builder.build(); + } +}; + +struct JsonRpcProgress +{ + JsonRpcId id; + std::string operation; + float amount; +}; + +template<> +struct JsonObjectReflector +{ + static auto reflect() + { + auto builder = JsonObjectInfoBuilder(); + builder.field("id", [](auto &object) { return &object.id; }); + builder.field("operation", [](auto &object) { return &object.operation; }); + builder.field("amount", [](auto &object) { return &object.amount; }); + return builder.build(); + } +}; + +struct JsonRpcNotification +{ + JsonRpcProgress params; +}; + +template<> +struct JsonObjectReflector +{ + static auto reflect() + { + auto builder = JsonObjectInfoBuilder(); + builder.constant("jsonrpc", "2.0"); + builder.field("params", [](auto &object) { return &object.params; }); + return builder.build(); + } +}; +} diff --git a/src/brayns/core/jsonrpc/Parser.cpp b/src/brayns/core/jsonrpc/Parser.cpp new file mode 100644 index 000000000..b46c833fd --- /dev/null +++ b/src/brayns/core/jsonrpc/Parser.cpp @@ -0,0 +1,100 @@ +/* Copyright (c) 2015-2024, EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "Parser.h" + +#include + +#include + +#include "Errors.h" + +namespace brayns::experimental +{ +JsonRpcRequest parseJsonRpcRequest(const std::string &data) +{ + auto json = JsonValue(); + + try + { + json = parseJson(data); + } + catch (const JsonException &e) + { + throw ParseError(e.what()); + } + + const auto &schema = getJsonSchema(); + + auto errors = validate(json, schema); + + if (!errors.empty()) + { + throw InvalidRequest("Invalid request schema", errors); + } + + return deserializeAs(json); +} + +JsonRpcRequest parseBinaryJsonRpcRequest(std::string_view data) +{ + if (data.size() < 4) + { + throw ParseError("Invalid binary: expected at least 4 bytes header."); + } + + auto textSize = extractBytesAs(data, std::endian::little); + + if (data.size() < textSize) + { + throw ParseError("Invalid binary: text size is bigger than total size"); + } + + auto text = extractBytes(data, textSize); + + auto request = parseJsonRpcRequest(std::string(text)); + + request.binary = std::string(data); + + return request; +} + +std::string composeAsText(const JsonRpcResponse &response) +{ + return stringifyToJson(response); +} + +std::string composeAsBinary(const JsonRpcResponse &response) +{ + auto text = composeAsText(response); + + auto textSize = text.size(); + + if (textSize > std::numeric_limits::max()) + { + throw InternalError("Text size does not fit in 4 bytes"); + } + + auto header = composeBytes(static_cast(textSize), std::endian::little); + + return header + text + response.binary; +} +} diff --git a/src/brayns/core/jsonrpc/Parser.h b/src/brayns/core/jsonrpc/Parser.h new file mode 100644 index 000000000..f39c304d0 --- /dev/null +++ b/src/brayns/core/jsonrpc/Parser.h @@ -0,0 +1,35 @@ +/* Copyright (c) 2015-2024, EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include +#include + +#include "Messages.h" + +namespace brayns::experimental +{ +JsonRpcRequest parseJsonRpcRequest(const std::string &data); +JsonRpcRequest parseBinaryJsonRpcRequest(std::string_view data); +std::string composeAsText(const JsonRpcResponse &response); +std::string composeAsBinary(const JsonRpcResponse &response); +} diff --git a/src/brayns/core/jsonv2/JsonReflector.h b/src/brayns/core/jsonv2/JsonReflector.h index d4bdcb724..9117c91df 100644 --- a/src/brayns/core/jsonv2/JsonReflector.h +++ b/src/brayns/core/jsonv2/JsonReflector.h @@ -51,9 +51,10 @@ struct JsonReflector }; template -JsonSchema getJsonSchema() +const JsonSchema &getJsonSchema() { - return JsonReflector::getSchema(); + static const auto schema = JsonReflector::getSchema(); + return schema; } template @@ -76,7 +77,7 @@ std::string stringifyToJson(const T &value) } template -T parseJson(const std::string &data) +T parseJsonAs(const std::string &data) { auto json = parseJson(data); return deserializeAs(json); diff --git a/src/brayns/core/jsonv2/JsonSchema.h b/src/brayns/core/jsonv2/JsonSchema.h index 1f4c43de4..57e352f95 100644 --- a/src/brayns/core/jsonv2/JsonSchema.h +++ b/src/brayns/core/jsonv2/JsonSchema.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include diff --git a/src/brayns/core/jsonv2/types/Objects.h b/src/brayns/core/jsonv2/types/Objects.h index 33882e2a0..257b7e756 100644 --- a/src/brayns/core/jsonv2/types/Objects.h +++ b/src/brayns/core/jsonv2/types/Objects.h @@ -82,10 +82,9 @@ class JsonObjectInfo for (const auto &field : _fields) { - auto jsonItem = object.get(field.name); - - if (!jsonItem.isEmpty()) + if (object.has(field.name)) { + auto jsonItem = object.get(field.name); field.deserialize(jsonItem, value); continue; } @@ -155,6 +154,12 @@ class JsonFieldBuilder return *this; } + JsonFieldBuilder required(bool value) + { + _field->schema.required = value; + return *this; + } + JsonFieldBuilder minimum(std::optional value) { _field->schema.minimum = value; @@ -218,6 +223,26 @@ class JsonObjectInfoBuilder return JsonFieldBuilder(field); } + JsonFieldBuilder constant(std::string name, const std::string &value) + { + auto &field = _fields.emplace_back(); + + field.name = std::move(name); + field.schema = getJsonSchema(); + field.schema.constant = value; + field.serialize = [=](const auto &) { return value; }; + field.deserialize = [=](const auto &json, auto &) + { + auto item = deserializeAs(json); + if (item != value) + { + throw JsonException("Invalid const"); + } + }; + + return JsonFieldBuilder(field); + } + JsonObjectInfo build() { return JsonObjectInfo(std::exchange(_fields, {})); diff --git a/src/brayns/core/utils/Binary.h b/src/brayns/core/utils/Binary.h new file mode 100644 index 000000000..189acff47 --- /dev/null +++ b/src/brayns/core/utils/Binary.h @@ -0,0 +1,111 @@ +/* Copyright (c) 2015-2024 EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * Responsible Author: adrien.fleury@epfl.ch + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace brayns::experimental +{ +static_assert(std::endian::native == std::endian::little || std::endian::native == std::endian::big); + +template +std::string_view asBytes(const T &value) +{ + auto data = reinterpret_cast(&value); + return {data, data + sizeof(T)}; +} + +template +std::span asBytes(T &value) +{ + auto data = reinterpret_cast(&value); + return {data, data + sizeof(T)}; +} + +template +T swapBytes(T value) +{ + auto bytes = asBytes(value); + std::ranges::reverse(bytes); + return value; +} + +inline std::string_view extractBytes(std::string_view &bytes, std::size_t count) +{ + auto extracted = bytes.substr(0, count); + bytes.remove_prefix(count); + return extracted; +} + +template +T extractBytesAs(std::string_view &bytes, std::endian bytesEndian) +{ + static constexpr auto valueSize = sizeof(T); + + assert(bytes.size() >= valueSize); + + auto extracted = extractBytes(bytes, valueSize); + + T value; + std::memcpy(&value, extracted.data(), valueSize); + + if (bytesEndian != std::endian::native) + { + return swapBytes(value); + } + + return value; +} + +template +T parseBytesAs(std::string_view bytes, std::endian bytesEndian) +{ + return extractBytesAs(bytes, bytesEndian); +} + +template +void composeBytes(T value, std::endian outputEndian, std::string &output) +{ + auto bytes = asBytes(value); + + if (outputEndian != std::endian::native) + { + std::ranges::reverse(bytes); + } + + output.append(bytes.data(), bytes.size()); +} + +template +std::string composeBytes(T value, std::endian outputEndian) +{ + auto bytes = std::string(); + composeBytes(value, outputEndian, bytes); + return bytes; +} +} diff --git a/src/brayns/core/utils/EnumReflector.h b/src/brayns/core/utils/EnumReflector.h index d24a0f7ab..9037631bb 100644 --- a/src/brayns/core/utils/EnumReflector.h +++ b/src/brayns/core/utils/EnumReflector.h @@ -56,23 +56,23 @@ class EnumInfo return _fields; } - const EnumField *findByName(std::string_view name) const + const EnumField *findFieldByName(std::string_view name) const { auto sameName = [&](const auto &field) { return field.name == name; }; auto i = std::ranges::find_if(_fields, sameName); return i == _fields.end() ? nullptr : &*i; } - const EnumField *findByValue(T value) const + const EnumField *findFieldByValue(T value) const { auto sameValue = [&](const auto &field) { return field.value == value; }; auto i = std::ranges::find_if(_fields, sameValue); return i == _fields.end() ? nullptr : &*i; } - const EnumField &getByName(std::string_view name) const + const EnumField &getFieldByName(std::string_view name) const { - const auto *field = findByName(name); + const auto *field = findFieldByName(name); if (field) { return *field; @@ -80,9 +80,9 @@ class EnumInfo throw std::invalid_argument(fmt::format("Invalid enum name: '{}'", name)); } - const EnumField &getByValue(T value) const + const EnumField &getFieldByValue(T value) const { - const auto *field = findByValue(value); + const auto *field = findFieldByValue(value); if (field) { return *field; @@ -118,7 +118,7 @@ template const std::string &getEnumName(T value) { const auto &info = reflectEnum(); - const auto &field = info.getByValue(value); + const auto &field = info.getFieldByValue(value); return field.name; } @@ -126,7 +126,7 @@ template T getEnumValue(std::string_view name) { const auto &info = reflectEnum(); - const auto &field = info.getByName(name); + const auto &field = info.getFieldByName(name); return field.value; } diff --git a/tests/core/rendering/TestRender.cpp b/tests/core/enginev2/TestRender.cpp similarity index 97% rename from tests/core/rendering/TestRender.cpp rename to tests/core/enginev2/TestRender.cpp index fe031307e..43cbe827c 100644 --- a/tests/core/rendering/TestRender.cpp +++ b/tests/core/enginev2/TestRender.cpp @@ -25,7 +25,7 @@ #include -#include +#include using namespace brayns; using namespace brayns::experimental; @@ -38,9 +38,9 @@ TEST_CASE("Render") auto handler = [&](const auto &record) { error = record.message; }; auto logger = Logger("Test", level, handler); - auto api = loadGraphicsApi(); + auto engine = loadEngine(); - auto device = api.createDevice(logger); + auto device = engine.createDevice(logger); auto width = 480; auto height = 360; diff --git a/tests/core/jsonrpc/TestRpc.cpp b/tests/core/jsonrpc/TestRpc.cpp new file mode 100644 index 000000000..8a5511255 --- /dev/null +++ b/tests/core/jsonrpc/TestRpc.cpp @@ -0,0 +1,103 @@ +/* Copyright (c) 2015-2024, EPFL/Blue Brain Project + * All rights reserved. Do not distribute without permission. + * + * This file is part of Brayns + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License version 3.0 as published + * by the Free Software Foundation. + * + * This library 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include +#include +#include +#include + +using namespace brayns::experimental; + +TEST_CASE("JsonRpcParser") +{ + auto json = std::string(R"({ + "jsonrpc": "2.0", + "id": 1, + "method": "test", + "params": 123 + })"); + + SUBCASE("Text") + { + auto request = parseJsonRpcRequest(json); + + CHECK_EQ(std::get(request.id), 1); + CHECK_EQ(request.method, "test"); + CHECK_EQ(request.params, 123); + CHECK_EQ(request.binary, ""); + } + SUBCASE("Binary") + { + auto jsonSize = static_cast(json.size()); + auto header = composeBytes(jsonSize, std::endian::little); + auto data = header + json + "binary"; + + auto request = parseBinaryJsonRpcRequest(data); + + CHECK_EQ(std::get(request.id), 1); + CHECK_EQ(request.method, "test"); + CHECK_EQ(request.params, 123); + CHECK_EQ(request.binary, "binary"); + } + SUBCASE("Invalid JSON") + { + auto data = "{\"test"; + CHECK_THROWS_AS(parseJsonRpcRequest(data), ParseError); + } + SUBCASE("Invalid schema") + { + auto data = R"({ + "id": 1, + "method": "test", + "params": 123 + })"; + CHECK_THROWS_AS(parseJsonRpcRequest(data), InvalidRequest); + } + SUBCASE("Invalid JSON-RPC version") + { + auto data = R"({ + "jsonrpc": "1.0", + "id": 1, + "method": "test", + "params": 123 + })"; + CHECK_THROWS_AS(parseJsonRpcRequest(data), InvalidRequest); + } + SUBCASE("No methods") + { + auto data = R"({ + "jsonrpc": "2.0", + "id": 1, + "params": 123 + })"; + CHECK_THROWS_AS(parseJsonRpcRequest(data), InvalidRequest); + } + SUBCASE("Binary without size") + { + auto data = "123"; + CHECK_THROWS_AS(parseBinaryJsonRpcRequest(data), ParseError); + } + SUBCASE("Binary size bigger than frame size") + { + auto data = std::string("\x10\x00\x00\x00", 4) + "{}"; + CHECK_THROWS_AS(parseBinaryJsonRpcRequest(data), ParseError); + } +} diff --git a/tests/core/jsonv2/TestJsonReflection.cpp b/tests/core/jsonv2/TestJsonReflection.cpp index 8c71f4604..9118ea6e8 100644 --- a/tests/core/jsonv2/TestJsonReflection.cpp +++ b/tests/core/jsonv2/TestJsonReflection.cpp @@ -22,8 +22,9 @@ #include -using namespace brayns; using namespace brayns::experimental; +using brayns::Quaternion; +using brayns::Vector3; namespace brayns::experimental { @@ -53,7 +54,7 @@ struct Internal template<> struct JsonObjectReflector { - static JsonObjectInfo reflect() + static auto reflect() { auto builder = JsonObjectInfoBuilder(); builder.field("value", [](auto &object) { return &object.value; }); @@ -76,9 +77,10 @@ struct SomeObject template<> struct JsonObjectReflector { - static JsonObjectInfo reflect() + static auto reflect() { auto builder = JsonObjectInfoBuilder(); + builder.constant("constant", "test"); builder.field("required", [](auto &object) { return &object.required; }); builder.field("bounded", [](auto &object) { return &object.bounded; }).minimum(1).maximum(3); builder.field("description", [](auto &object) { return &object.description; }).description("Test"); @@ -170,7 +172,7 @@ TEST_CASE("JsonReflection") CHECK_EQ( getJsonSchema>(), JsonSchema{.type = JsonType::Array, .items = {JsonSchema{.type = JsonType::String}}}); - CHECK_EQ(parseJson>("[1,2,3]"), std::vector{1, 2, 3}); + CHECK_EQ(parseJsonAs>("[1,2,3]"), std::vector{1, 2, 3}); CHECK_EQ(stringifyToJson(std::vector{1, 2, 3}), "[1,2,3]"); } SUBCASE("Math") @@ -193,17 +195,17 @@ TEST_CASE("JsonReflection") .maxItems = 4, }); - CHECK_EQ(parseJson("[1,2,3]"), Vector3(1, 2, 3)); - CHECK_EQ(parseJson("[1,2,3,4]"), Quaternion(4, 1, 2, 3)); + CHECK_EQ(parseJsonAs("[1,2,3]"), Vector3(1, 2, 3)); + CHECK_EQ(parseJsonAs("[1,2,3,4]"), Quaternion(4, 1, 2, 3)); CHECK_EQ(stringifyToJson(Vector3(1, 2, 3)), "[1,2,3]"); CHECK_EQ(stringifyToJson(Quaternion(4, 1, 2, 3)), "[1,2,3,4]"); - CHECK_THROWS_AS(parseJson("[1,2,3,4]"), JsonException); - CHECK_THROWS_AS(parseJson("[1,2,3,4,5]"), JsonException); + CHECK_THROWS_AS(parseJsonAs("[1,2,3,4]"), JsonException); + CHECK_THROWS_AS(parseJsonAs("[1,2,3,4,5]"), JsonException); - CHECK_THROWS_AS(parseJson("[1,2]"), JsonException); - CHECK_THROWS_AS(parseJson("[1,2]"), JsonException); + CHECK_THROWS_AS(parseJsonAs("[1,2]"), JsonException); + CHECK_THROWS_AS(parseJsonAs("[1,2]"), JsonException); } SUBCASE("Map") { @@ -219,10 +221,10 @@ TEST_CASE("JsonReflection") auto map = Map{{"test1", 1}, {"test2", 2}}; auto json = R"({"test1":1,"test2":2})"; - CHECK_EQ(parseJson(json), map); + CHECK_EQ(parseJsonAs(json), map); CHECK_EQ(stringifyToJson(map), json); - CHECK_THROWS_AS(parseJson(R"({"invalid":2.5})"), JsonException); + CHECK_THROWS_AS(parseJsonAs(R"({"invalid":2.5})"), JsonException); } SUBCASE("Variant") { @@ -255,12 +257,13 @@ TEST_CASE("JsonReflection") } SUBCASE("Object") { - auto schema = getJsonSchema(); + const auto &schema = getJsonSchema(); CHECK_EQ(schema.type, JsonType::Object); const auto &properties = schema.properties; + CHECK_EQ(properties.at("constant"), JsonSchema{.type = JsonType::String, .constant = "test"}); CHECK_EQ(properties.at("required"), getJsonSchema()); CHECK_EQ(properties.at("bounded"), JsonSchema{.type = JsonType::Integer, .minimum = 1, .maximum = 3}); CHECK_EQ(properties.at("description"), JsonSchema{.description = "Test", .type = JsonType::Boolean}); @@ -288,6 +291,7 @@ TEST_CASE("JsonReflection") internal->set("value", 2); auto object = createJsonObject(); + object->set("constant", "test"); object->set("required", true); object->set("bounded", 2); object->set("description", true); @@ -310,8 +314,6 @@ TEST_CASE("JsonReflection") object->set("default", "test"); - auto backToJson = serializeToJson(test); - - CHECK_EQ(backToJson, json); + CHECK_EQ(stringifyToJson(test), stringifyToJson(json)); } } diff --git a/tests/core/jsonv2/TestJsonSchema.cpp b/tests/core/jsonv2/TestJsonSchema.cpp index 1fad74076..bb09207a2 100644 --- a/tests/core/jsonv2/TestJsonSchema.cpp +++ b/tests/core/jsonv2/TestJsonSchema.cpp @@ -22,7 +22,6 @@ #include -using namespace brayns; using namespace brayns::experimental; TEST_CASE("JsonSchema")