diff --git a/cmake/FindGRPC.cmake b/cmake/FindGRPC.cmake new file mode 100644 index 0000000000..634e93798d --- /dev/null +++ b/cmake/FindGRPC.cmake @@ -0,0 +1,89 @@ +# +# CMake Find gRPC by Parra Studios +# CMake script to find gRPC and Protobuf libraries. +# +# Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# The following variables are set: +# +# GRPC_FOUND - TRUE if gRPC and Protobuf were found. +# GRPC_INCLUDE_DIRS - Protobuf include directories (contains google/protobuf/struct.pb.h). +# GRPC_LIBRARIES - Libraries to link against (protobuf + grpc++). + +# Prevent verbosity if already included +if(GRPC_FOUND) + set(GRPC_FIND_QUIETLY TRUE) +endif() + +# Try CMake config first (works with Homebrew, vcpkg, system installs) +find_package(gRPC QUIET CONFIG) + +if(gRPC_FOUND) + set(GRPC_FOUND TRUE) + set(GRPC_LIBRARIES gRPC::grpc++ protobuf::libprotobuf) + + get_target_property(GRPC_INCLUDE_DIRS protobuf::libprotobuf INTERFACE_INCLUDE_DIRECTORIES) + + if(NOT GRPC_FIND_QUIETLY) + message(STATUS "Found gRPC via CMake config") + endif() + + return() +endif() + +# Fallback: pkg-config +find_package(PkgConfig QUIET) + +if(PKG_CONFIG_FOUND) + pkg_check_modules(GRPC QUIET grpc++ protobuf) +endif() + +# Fallback: manual search +if(NOT GRPC_FOUND) + find_path(GRPC_INCLUDE_DIRS + NAMES google/protobuf/struct.pb.h + PATHS + /usr/local/include + /usr/include + /opt/homebrew/include + DOC "Include directory for Protobuf (required by grpc_serial)" + ) + + find_library(GRPC_GRPCPP_LIBRARY + NAMES grpc++ grpc++_unsecure + PATHS /usr/local/lib /usr/lib /opt/homebrew/lib + ) + + find_library(GRPC_PROTOBUF_LIBRARY + NAMES protobuf + PATHS /usr/local/lib /usr/lib /opt/homebrew/lib + ) + + if(GRPC_INCLUDE_DIRS AND GRPC_GRPCPP_LIBRARY AND GRPC_PROTOBUF_LIBRARY) + set(GRPC_FOUND TRUE) + set(GRPC_LIBRARIES ${GRPC_GRPCPP_LIBRARY} ${GRPC_PROTOBUF_LIBRARY}) + endif() +endif() + +mark_as_advanced(GRPC_FOUND GRPC_INCLUDE_DIRS GRPC_LIBRARIES) + +if(GRPC_FOUND) + if(NOT GRPC_FIND_QUIETLY) + message(STATUS "Found gRPC/Protobuf headers in ${GRPC_INCLUDE_DIRS}") + endif() +elseif(GRPC_FIND_REQUIRED) + message(FATAL_ERROR "Could not find gRPC/Protobuf") +endif() \ No newline at end of file diff --git a/cmake/InstallGRPC.cmake b/cmake/InstallGRPC.cmake new file mode 100644 index 0000000000..54b0deddc7 --- /dev/null +++ b/cmake/InstallGRPC.cmake @@ -0,0 +1,67 @@ +# +# CMake Install gRPC by Parra Studios +# CMake script to install gRPC and Protobuf via FetchContent. +# +# Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# The following variables are set: +# +# GRPC_FOUND - TRUE if gRPC was installed successfully. +# GRPC_INCLUDE_DIRS - Include directories for Protobuf headers. +# GRPC_LIBRARIES - Libraries to link against. + +if(NOT GRPC_FOUND OR USE_BUNDLED_GRPC) + include(FetchContent) + + # Pin to a specific release tag for reproducibility + if(NOT GRPC_VERSION OR USE_BUNDLED_GRPC) + set(GRPC_VERSION v1.60.0) + endif() + + message(STATUS "Installing gRPC ${GRPC_VERSION} via FetchContent (this may take a while)") + + # Disable building unnecessary gRPC components to keep build times reasonable + set(FETCHCONTENT_QUIET OFF) + set(gRPC_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(gRPC_BUILD_CODEGEN OFF CACHE BOOL "" FORCE) + set(gRPC_BUILD_GRPC_CPP_PLUGIN OFF CACHE BOOL "" FORCE) + set(gRPC_BUILD_GRPC_CSHARP_PLUGIN OFF CACHE BOOL "" FORCE) + set(gRPC_BUILD_GRPC_NODE_PLUGIN OFF CACHE BOOL "" FORCE) + set(gRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN OFF CACHE BOOL "" FORCE) + set(gRPC_BUILD_GRPC_PHP_PLUGIN OFF CACHE BOOL "" FORCE) + set(gRPC_BUILD_GRPC_PYTHON_PLUGIN OFF CACHE BOOL "" FORCE) + set(gRPC_BUILD_GRPC_RUBY_PLUGIN OFF CACHE BOOL "" FORCE) + set(protobuf_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(protobuf_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(ABSL_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) + + FetchContent_Declare(gRPC + GIT_REPOSITORY https://github.com/grpc/grpc.git + GIT_TAG ${GRPC_VERSION} + GIT_SUBMODULES_RECURSE ON + ) + + FetchContent_MakeAvailable(gRPC) + + set(GRPC_FOUND TRUE) + set(GRPC_LIBRARIES gRPC::grpc++ protobuf::libprotobuf) + + get_target_property(GRPC_INCLUDE_DIRS protobuf::libprotobuf INTERFACE_INCLUDE_DIRECTORIES) + + mark_as_advanced(GRPC_INCLUDE_DIRS GRPC_LIBRARIES) + + message(STATUS "Installed gRPC ${GRPC_VERSION}") +endif() \ No newline at end of file diff --git a/source/serials/CMakeLists.txt b/source/serials/CMakeLists.txt index 5fc2711dc5..7b332def50 100644 --- a/source/serials/CMakeLists.txt +++ b/source/serials/CMakeLists.txt @@ -1,12 +1,14 @@ -# Check if serials are enabled -if(NOT OPTION_BUILD_SERIALS) - return() -endif() - -# Serial options -option(OPTION_BUILD_SERIALS_METACALL "MetaCall Native Format library serial." ON) -option(OPTION_BUILD_SERIALS_RAPID_JSON "RapidJSON library serial." ON) - -# Serial packages -add_subdirectory(metacall_serial) # MetaCall Native Format library -add_subdirectory(rapid_json_serial) # RapidJSON library +# Check if serials are enabled +if(NOT OPTION_BUILD_SERIALS) + return() +endif() + +# Serial options +option(OPTION_BUILD_SERIALS_METACALL "MetaCall Native Format library serial." ON) +option(OPTION_BUILD_SERIALS_RAPID_JSON "RapidJSON library serial." ON) +option(OPTION_BUILD_SERIALS_GRPC "gRPC library serial." ON) + +# Serial packages +add_subdirectory(metacall_serial) # MetaCall Native Format library +add_subdirectory(rapid_json_serial) # RapidJSON library +add_subdirectory(grpc_serial) # gRPC and Protobuf library diff --git a/source/serials/grpc_serial/CMakeLists.txt b/source/serials/grpc_serial/CMakeLists.txt new file mode 100644 index 0000000000..b630ae9025 --- /dev/null +++ b/source/serials/grpc_serial/CMakeLists.txt @@ -0,0 +1,131 @@ +# Check if this serial is enabled +if(NOT OPTION_BUILD_SERIALS OR NOT OPTION_BUILD_SERIALS_GRPC) + return() +endif() + +# +# External dependencies +# + +find_package(GRPC) + +if(NOT GRPC_FOUND) + include(InstallGRPC) + + if(NOT GRPC_FOUND) + message(SEND_ERROR "gRPC libraries not found") + return() + endif() + + set(GRPC_INSTALL TRUE) +endif() + +# +# Library name and options +# + +set(target grpc_serial) + +message(STATUS "Serial ${target}") + +string(TOUPPER ${target} target_upper) +set(export_file "include/${target}/${target}_api.h") +set(export_macro "${target_upper}_API") + +include(Warnings) +include(SecurityFlags) + +set(include_path "${CMAKE_CURRENT_SOURCE_DIR}/include/${target}") +set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source") + +set(headers + ${include_path}/grpc_serial.h + ${include_path}/grpc_serial_impl.h +) + +set(sources + ${source_path}/grpc_serial.c + ${source_path}/grpc_serial_impl.cpp +) + +set(header_group "Header Files (API)") +set(source_group "Source Files") +source_group_by_path(${include_path} "\\\\.h$|\\\\.hpp$" + ${header_group} ${headers}) +source_group_by_path(${source_path} "\\\\.cpp$|\\\\.c$|\\\\.h$|\\\\.hpp$" + ${source_group} ${sources}) + +add_library(${target} MODULE + ${sources} + ${headers} +) + +add_library(${META_PROJECT_NAME}::${target} ALIAS ${target}) + +export(TARGETS ${target} NAMESPACE ${META_PROJECT_NAME}:: FILE ${PROJECT_BINARY_DIR}/cmake/${target}/${target}-export.cmake) + +generate_export_header(${target} + EXPORT_FILE_NAME ${export_file} + EXPORT_MACRO_NAME ${export_macro} +) + +set_target_properties(${target} + PROPERTIES + ${DEFAULT_PROJECT_OPTIONS} + FOLDER "${IDE_FOLDER}" + BUNDLE $,$> +) + +target_include_directories(${target} + PRIVATE + ${PROJECT_BINARY_DIR}/source/include + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_BINARY_DIR}/include + $ + ${GRPC_INCLUDE_DIRS} + PUBLIC + ${DEFAULT_INCLUDE_DIRECTORIES} + INTERFACE + $ + $ + $ +) + +target_link_libraries(${target} + PRIVATE + $<$>:${META_PROJECT_NAME}::metacall> + ${GRPC_LIBRARIES} + PUBLIC + ${DEFAULT_LIBRARIES} + INTERFACE +) + +target_compile_definitions(${target} + PRIVATE + PUBLIC + $<$>:${target_upper}_STATIC_DEFINE> + ${DEFAULT_COMPILE_DEFINITIONS} + INTERFACE +) + +target_compile_options(${target} + PRIVATE + PUBLIC + ${DEFAULT_COMPILE_OPTIONS} + INTERFACE +) + +target_link_options(${target} + PRIVATE + $<$,$>:-Wl,-undefined,dynamic_lookup> + PUBLIC + ${DEFAULT_LINKER_OPTIONS} + INTERFACE +) + +install(TARGETS ${target} + EXPORT "${target}-export" COMPONENT dev + RUNTIME DESTINATION ${INSTALL_BIN} COMPONENT runtime + LIBRARY DESTINATION ${INSTALL_SHARED} COMPONENT runtime + ARCHIVE DESTINATION ${INSTALL_LIB} COMPONENT dev +) \ No newline at end of file diff --git a/source/serials/grpc_serial/include/grpc_serial.h b/source/serials/grpc_serial/include/grpc_serial.h new file mode 100644 index 0000000000..e25f5cabc3 --- /dev/null +++ b/source/serials/grpc_serial/include/grpc_serial.h @@ -0,0 +1,60 @@ +/* + * Serial Library by Parra Studios + * A cross-platform library for managing multiple serialization and deserialization formats. + * + * Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef GRPC_SERIAL_H +#define GRPC_SERIAL_H 1 + +/* -- Headers -- */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* -- Methods -- */ + +/** +* @brief +* Instance of interface implementation +* +* @return +* Returns pointer to interface to be used by implementation +* +*/ +GRPC_SERIAL_API serial_interface grpc_serial_impl_interface_singleton(void); + +/** +* @brief +* Provide the module information +* +* @return +* Static string containing module information +* +*/ +GRPC_SERIAL_API const char *grpc_serial_print_info(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_SERIAL_H */ diff --git a/source/serials/grpc_serial/include/grpc_serial/grpc_serial.h b/source/serials/grpc_serial/include/grpc_serial/grpc_serial.h new file mode 100644 index 0000000000..e25f5cabc3 --- /dev/null +++ b/source/serials/grpc_serial/include/grpc_serial/grpc_serial.h @@ -0,0 +1,60 @@ +/* + * Serial Library by Parra Studios + * A cross-platform library for managing multiple serialization and deserialization formats. + * + * Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef GRPC_SERIAL_H +#define GRPC_SERIAL_H 1 + +/* -- Headers -- */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* -- Methods -- */ + +/** +* @brief +* Instance of interface implementation +* +* @return +* Returns pointer to interface to be used by implementation +* +*/ +GRPC_SERIAL_API serial_interface grpc_serial_impl_interface_singleton(void); + +/** +* @brief +* Provide the module information +* +* @return +* Static string containing module information +* +*/ +GRPC_SERIAL_API const char *grpc_serial_print_info(void); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_SERIAL_H */ diff --git a/source/serials/grpc_serial/include/grpc_serial/grpc_serial_impl.h b/source/serials/grpc_serial/include/grpc_serial/grpc_serial_impl.h new file mode 100644 index 0000000000..13aaeac832 --- /dev/null +++ b/source/serials/grpc_serial/include/grpc_serial/grpc_serial_impl.h @@ -0,0 +1,108 @@ +/* + * Serial Library by Parra Studios + * A cross-platform library for managing multiple serialization and deserialization formats. + * + * Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef GRPC_SERIAL_IMPL_H +#define GRPC_SERIAL_IMPL_H 1 + +/* -- Headers -- */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* -- Methods -- */ + +/** +* @brief +* Retrieve extension supported by gRPC implementation +* +* @return +* Returns constant string representing serial extension +* +*/ +GRPC_SERIAL_API const char *grpc_serial_impl_extension(void); + +/** +* @brief +* Initialize gRPC (Protobuf) serial document implementation +* +* @return +* Returns pointer to serial document implementation on success, null pointer otherwise +* +*/ +GRPC_SERIAL_API serial_handle grpc_serial_impl_initialize(memory_allocator allocator); + +/** +* @brief +* Serialize with gRPC (Protobuf) document implementation @handle +* +* @param[in] handle +* Pointer to the serial document implementation +* +* @param[in] v +* Reference to the value is going to be serialized +* +* @param[out] size +* Size in bytes of the return buffer +* +* @return +* String with the value serialized on correct serialization, null otherwise +* +*/ +GRPC_SERIAL_API char *grpc_serial_impl_serialize(serial_handle handle, value v, size_t *size); + +/** +* @brief +* Deserialize with gRPC (Protobuf) document implementation @handle +* +* @param[in] handle +* Pointer to the serial document implementation +* +* @param[in] buffer +* Reference to the string is going to be deserialized +* +* @param[in] size +* Size in bytes of the string @buffer +* +* @return +* Pointer to value deserialized on correct serialization, null otherwise +* +*/ +GRPC_SERIAL_API value grpc_serial_impl_deserialize(serial_handle handle, const char *buffer, size_t size); + +/** +* @brief +* Destroy gRPC (Protobuf) document implementation +* +* @return +* Returns zero on correct destruction, distinct from zero otherwise +* +*/ +GRPC_SERIAL_API int grpc_serial_impl_destroy(serial_handle handle); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_SERIAL_IMPL_H */ diff --git a/source/serials/grpc_serial/include/grpc_serial_impl.h b/source/serials/grpc_serial/include/grpc_serial_impl.h new file mode 100644 index 0000000000..13aaeac832 --- /dev/null +++ b/source/serials/grpc_serial/include/grpc_serial_impl.h @@ -0,0 +1,108 @@ +/* + * Serial Library by Parra Studios + * A cross-platform library for managing multiple serialization and deserialization formats. + * + * Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef GRPC_SERIAL_IMPL_H +#define GRPC_SERIAL_IMPL_H 1 + +/* -- Headers -- */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* -- Methods -- */ + +/** +* @brief +* Retrieve extension supported by gRPC implementation +* +* @return +* Returns constant string representing serial extension +* +*/ +GRPC_SERIAL_API const char *grpc_serial_impl_extension(void); + +/** +* @brief +* Initialize gRPC (Protobuf) serial document implementation +* +* @return +* Returns pointer to serial document implementation on success, null pointer otherwise +* +*/ +GRPC_SERIAL_API serial_handle grpc_serial_impl_initialize(memory_allocator allocator); + +/** +* @brief +* Serialize with gRPC (Protobuf) document implementation @handle +* +* @param[in] handle +* Pointer to the serial document implementation +* +* @param[in] v +* Reference to the value is going to be serialized +* +* @param[out] size +* Size in bytes of the return buffer +* +* @return +* String with the value serialized on correct serialization, null otherwise +* +*/ +GRPC_SERIAL_API char *grpc_serial_impl_serialize(serial_handle handle, value v, size_t *size); + +/** +* @brief +* Deserialize with gRPC (Protobuf) document implementation @handle +* +* @param[in] handle +* Pointer to the serial document implementation +* +* @param[in] buffer +* Reference to the string is going to be deserialized +* +* @param[in] size +* Size in bytes of the string @buffer +* +* @return +* Pointer to value deserialized on correct serialization, null otherwise +* +*/ +GRPC_SERIAL_API value grpc_serial_impl_deserialize(serial_handle handle, const char *buffer, size_t size); + +/** +* @brief +* Destroy gRPC (Protobuf) document implementation +* +* @return +* Returns zero on correct destruction, distinct from zero otherwise +* +*/ +GRPC_SERIAL_API int grpc_serial_impl_destroy(serial_handle handle); + +#ifdef __cplusplus +} +#endif + +#endif /* GRPC_SERIAL_IMPL_H */ diff --git a/source/serials/grpc_serial/source/grpc_serial.c b/source/serials/grpc_serial/source/grpc_serial.c new file mode 100644 index 0000000000..8bc8c26c53 --- /dev/null +++ b/source/serials/grpc_serial/source/grpc_serial.c @@ -0,0 +1,56 @@ +/* + * Serial Library by Parra Studios + * A cross-platform library for managing multiple serialization and deserialization formats. + * + * Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* -- Headers -- */ + +#include + +#include +#include + +/* -- Methods -- */ + +serial_interface grpc_serial_impl_interface_singleton(void) +{ + static struct serial_interface_type interface_instance_grpc = { + &grpc_serial_impl_extension, + &grpc_serial_impl_initialize, + &grpc_serial_impl_serialize, + &grpc_serial_impl_deserialize, + &grpc_serial_impl_destroy + }; + + return &interface_instance_grpc; +} + +const char *grpc_serial_print_info(void) +{ + static const char grpc_serial_info[] = + "gRPC Serial Plugin " METACALL_VERSION "\n" + "Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia \n" +#ifdef GRPC_SERIAL_STATIC_DEFINE + "Compiled as static library type\n" +#else + "Compiled as shared library type\n" +#endif + "\n"; + + return grpc_serial_info; +} diff --git a/source/serials/grpc_serial/source/grpc_serial_impl.cpp b/source/serials/grpc_serial/source/grpc_serial_impl.cpp new file mode 100644 index 0000000000..d7cfabb88b --- /dev/null +++ b/source/serials/grpc_serial/source/grpc_serial_impl.cpp @@ -0,0 +1,466 @@ + + +#include + +#include + +#include + +#if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wstrict-overflow" + #pragma clang diagnostic ignored "-Wshadow" + #pragma clang diagnostic ignored "-Wunused-parameter" +#elif defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-overflow" + #pragma GCC diagnostic ignored "-Wshadow" + #pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#include +#include + +#if defined(__clang__) + #pragma clang diagnostic pop +#elif defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + +#include +#include +#include + + + +typedef struct grpc_document_type +{ + google::protobuf::Value impl; + memory_allocator allocator; + +} * grpc_document; + +/* -- Private Methods -- */ + +static void grpc_serial_impl_serialize_value(value v, google::protobuf::Value *pb_v); + +static char *grpc_serial_impl_document_stringify(grpc_document document, size_t *size); + +static value grpc_serial_impl_deserialize_value(const google::protobuf::Value *pb_v); + +/* -- Methods -- */ + +const char *grpc_serial_impl_extension() +{ + static const char extension[] = "grpc"; + + return extension; +} + +serial_handle grpc_serial_impl_initialize(memory_allocator allocator) +{ + grpc_document document = new grpc_document_type(); + + if (document == nullptr) + { + return NULL; + } + + document->allocator = allocator; + + return (serial_handle)document; +} + +/* ------------------------------------------------------------------------- + * Serialize: MetaCall value → google::protobuf::Value + * ---------------------------------------------------------------------- */ + +void grpc_serial_impl_serialize_value(value v, google::protobuf::Value *pb_v) +{ + type_id id = value_type_id(v); + + if (id == TYPE_BOOL) + { + pb_v->set_bool_value(value_to_bool(v) == 1L ? true : false); + } + else if (id == TYPE_CHAR) + { + char buf[2] = { value_to_char(v), '\0' }; + pb_v->set_string_value(buf); + } + else if (id == TYPE_SHORT) + { + pb_v->set_number_value(static_cast(value_to_short(v))); + } + else if (id == TYPE_INT) + { + pb_v->set_number_value(static_cast(value_to_int(v))); + } + else if (id == TYPE_LONG) + { + pb_v->set_number_value(static_cast(value_to_long(v))); + } + else if (id == TYPE_FLOAT) + { + pb_v->set_number_value(static_cast(value_to_float(v))); + } + else if (id == TYPE_DOUBLE) + { + pb_v->set_number_value(value_to_double(v)); + } + else if (id == TYPE_STRING) + { + const char *str = value_to_string(v); + size_t size = value_type_size(v); + /* size includes the NUL terminator */ + pb_v->set_string_value(str, size > 0 ? size - 1 : 0); + } + else if (id == TYPE_BUFFER) + { + /* + * Encode as: { "data": [byte0, byte1, ...], "length": N } + * Mirrors the RapidJSON layout so consumers can detect buffers. + */ + google::protobuf::Struct *st = pb_v->mutable_struct_value(); + + void *buffer = value_to_buffer(v); + size_t size = value_type_size(v); + + google::protobuf::Value data_val; + google::protobuf::ListValue *list = data_val.mutable_list_value(); + + for (size_t i = 0; i < size; ++i) + { + const unsigned char byte = *(static_cast(buffer) + i); + google::protobuf::Value *elem = list->add_values(); + elem->set_number_value(static_cast(byte)); + } + + (*st->mutable_fields())["data"] = data_val; + + google::protobuf::Value len_val; + len_val.set_number_value(static_cast(size)); + (*st->mutable_fields())["length"] = len_val; + } + else if (id == TYPE_ARRAY) + { + google::protobuf::ListValue *list = pb_v->mutable_list_value(); + + value *value_array = value_to_array(v); + size_t array_size = value_type_count(v); + + for (size_t i = 0; i < array_size; ++i) + { + grpc_serial_impl_serialize_value(value_array[i], list->add_values()); + } + } + else if (id == TYPE_MAP) + { + google::protobuf::Struct *st = pb_v->mutable_struct_value(); + + value *value_map = value_to_map(v); + size_t map_size = value_type_count(v); + + for (size_t i = 0; i < map_size; ++i) + { + value tupla = value_map[i]; + value *tupla_array = value_to_array(tupla); + + /* Key must be a string in Protobuf Struct */ + google::protobuf::Value key_pb; + grpc_serial_impl_serialize_value(tupla_array[0], &key_pb); + + std::string key_str; + if (key_pb.kind_case() == google::protobuf::Value::kStringValue) + { + key_str = key_pb.string_value(); + } + else + { + /* Non-string keys: convert number/bool to string representation */ + std::ostringstream oss; + if (key_pb.kind_case() == google::protobuf::Value::kNumberValue) + { + oss << key_pb.number_value(); + } + else if (key_pb.kind_case() == google::protobuf::Value::kBoolValue) + { + oss << (key_pb.bool_value() ? "true" : "false"); + } + else + { + oss << "(null)"; + } + key_str = oss.str(); + } + + grpc_serial_impl_serialize_value(tupla_array[1], &(*st->mutable_fields())[key_str]); + } + } + else if (id == TYPE_FUTURE) + { + pb_v->set_string_value("[Future]"); + } + else if (id == TYPE_FUNCTION) + { + pb_v->set_string_value("[Function]"); + } + else if (id == TYPE_CLASS) + { + pb_v->set_string_value("[Class]"); + } + else if (id == TYPE_OBJECT) + { + pb_v->set_string_value("[Object]"); + } + else if (id == TYPE_EXCEPTION) + { + exception ex = value_to_exception(v); + + google::protobuf::Struct *st = pb_v->mutable_struct_value(); + + auto &fields = *st->mutable_fields(); + + fields["message"].set_string_value(exception_message(ex)); + fields["label"].set_string_value(exception_label(ex)); + fields["code"].set_number_value(static_cast(exception_error_code(ex))); + fields["stacktrace"].set_string_value(exception_stacktrace(ex)); + } + else if (id == TYPE_THROWABLE) + { + throwable th = value_to_throwable(v); + + google::protobuf::Struct *st = pb_v->mutable_struct_value(); + + grpc_serial_impl_serialize_value(throwable_value(th), &(*st->mutable_fields())["ExceptionThrown"]); + } + else if (id == TYPE_PTR) + { + std::ostringstream oss; + oss << value_to_ptr(v); + pb_v->set_string_value(oss.str()); + } + else if (id == TYPE_NULL) + { + pb_v->set_null_value(google::protobuf::NULL_VALUE); + } +} + + +char *grpc_serial_impl_document_stringify(grpc_document document, size_t *size) +{ + std::string wire; + + if (!document->impl.SerializeToString(&wire)) + { + log_write("metacall", LOG_LEVEL_ERROR, "Failed to serialise Protobuf Value to binary wire format in gRPC implementation"); + return NULL; + } + + size_t wire_size = wire.size(); + size_t alloc_size = wire_size + 1; /* +1: NUL sentinel so callers can detect empty */ + + char *buffer_str = static_cast(memory_allocator_allocate(document->allocator, sizeof(char) * alloc_size)); + + if (buffer_str == NULL) + { + log_write("metacall", LOG_LEVEL_ERROR, "Invalid string allocation for document stringification in gRPC implementation"); + return NULL; + } + + memcpy(buffer_str, wire.data(), wire_size); + buffer_str[wire_size] = '\0'; + + *size = alloc_size; + + return buffer_str; +} + + +char *grpc_serial_impl_serialize(serial_handle handle, value v, size_t *size) +{ + grpc_document document = static_cast(handle); + + if (handle == NULL || v == NULL || size == NULL) + { + log_write("metacall", LOG_LEVEL_ERROR, "Serialization called with wrong arguments in gRPC implementation"); + return NULL; + } + + /* Reset the embedded message before reuse */ + document->impl.Clear(); + + grpc_serial_impl_serialize_value(v, &document->impl); + + return grpc_serial_impl_document_stringify(document, size); +} + + +value grpc_serial_impl_deserialize_value(const google::protobuf::Value *pb_v) +{ + switch (pb_v->kind_case()) + { + case google::protobuf::Value::kNullValue: + { + return value_create_null(); + } + + case google::protobuf::Value::kBoolValue: + { + return value_create_bool(pb_v->bool_value() ? 1L : 0L); + } + + case google::protobuf::Value::kNumberValue: + { + double d = pb_v->number_value(); + + /* Prefer the narrowest integer type that fits, mirroring + * the RapidJSON deserialization heuristic. */ + if (d == static_cast(static_cast(d)) && + d >= static_cast(INT_MIN) && + d <= static_cast(INT_MAX)) + { + return value_create_int(static_cast(d)); + } + + if (d == static_cast(static_cast(d)) && + d >= static_cast(LONG_MIN) && + d <= static_cast(LONG_MAX)) + { + return value_create_long(static_cast(d)); + } + + /* Fallback: preserve as double */ + return value_create_double(d); + } + + case google::protobuf::Value::kStringValue: + { + const std::string &s = pb_v->string_value(); + return value_create_string(s.c_str(), s.size()); + } + + case google::protobuf::Value::kListValue: + { + const google::protobuf::ListValue &list = pb_v->list_value(); + int count = list.values_size(); + + value v_array = value_create_array(NULL, static_cast(count)); + + if (count == 0 || v_array == NULL) + { + return v_array; + } + + value *values = static_cast(value_to_array(v_array)); + + for (int i = 0; i < count; ++i) + { + values[i] = grpc_serial_impl_deserialize_value(&list.values(i)); + + if (values[i] == NULL) + { + for (int j = 0; j < i; ++j) + { + value_type_destroy(values[j]); + } + value_destroy(v_array); + return NULL; + } + } + + return v_array; + } + + case google::protobuf::Value::kStructValue: + { + const google::protobuf::Struct &st = pb_v->struct_value(); + const auto &fields = st.fields(); + size_t map_size = static_cast(fields.size()); + + value v_map = value_create_map(NULL, map_size); + + if (map_size == 0 || v_map == NULL) + { + return v_map; + } + + value *tuples = static_cast(value_to_map(v_map)); + size_t index = 0; + + for (auto it = fields.begin(); it != fields.end(); ++it) + { + value key_val = value_create_string(it->first.c_str(), it->first.size()); + value inner_val = grpc_serial_impl_deserialize_value(&it->second); + + if (key_val == NULL || inner_val == NULL) + { + if (key_val != NULL) + { + value_type_destroy(key_val); + } + if (inner_val != NULL) + { + value_type_destroy(inner_val); + } + value_type_destroy(v_map); + return NULL; + } + + const value tupla[] = { key_val, inner_val }; + tuples[index++] = value_create_array(tupla, sizeof(tupla) / sizeof(tupla[0])); + } + + return v_map; + } + + default: + { + break; + } + } + + log_write("metacall", LOG_LEVEL_ERROR, "Unsupported value kind in gRPC implementation"); + return NULL; +} + +/* ------------------------------------------------------------------------- + * Public: deserialize + * ---------------------------------------------------------------------- */ + +value grpc_serial_impl_deserialize(serial_handle handle, const char *buffer, size_t size) +{ + grpc_document document = static_cast(handle); + + if (handle == NULL || buffer == NULL || size == 0) + { + log_write("metacall", LOG_LEVEL_ERROR, "Deserialization called with wrong arguments in gRPC implementation"); + return NULL; + } + + /* size includes a NUL sentinel byte added by stringify — exclude it */ + const size_t wire_size = size - 1; + + document->impl.Clear(); + + if (!document->impl.ParseFromArray(buffer, static_cast(wire_size))) + { + log_write("metacall", LOG_LEVEL_ERROR, "Invalid parsing of Protobuf Value in gRPC implementation (%" PRIuS " bytes)", wire_size); + return NULL; + } + + return grpc_serial_impl_deserialize_value(&document->impl); +} + + +int grpc_serial_impl_destroy(serial_handle handle) +{ + grpc_document document = static_cast(handle); + + if (document != NULL) + { + delete document; + } + + return 0; +} diff --git a/source/tests/CMakeLists.txt b/source/tests/CMakeLists.txt index b839796e6e..d12791d4dc 100644 --- a/source/tests/CMakeLists.txt +++ b/source/tests/CMakeLists.txt @@ -91,6 +91,7 @@ add_subdirectory(reflect_scope_test) add_subdirectory(reflect_metadata_test) add_subdirectory(dynlink_test) add_subdirectory(detour_test) +add_subdirectory(grpc_serial_test) add_subdirectory(serial_test) add_subdirectory(configuration_test) add_subdirectory(rb_loader_parser_test) diff --git a/source/tests/grpc_serial_test/CMakeLists.txt b/source/tests/grpc_serial_test/CMakeLists.txt new file mode 100644 index 0000000000..c108378908 --- /dev/null +++ b/source/tests/grpc_serial_test/CMakeLists.txt @@ -0,0 +1,108 @@ +if(NOT OPTION_BUILD_SERIALS OR NOT OPTION_BUILD_SERIALS_GRPC) + return() +endif() + +# +# Executable name and options +# + +# Target name +set(target grpc-serial-test) +message(STATUS "Test ${target}") + +include(Warnings) +include(SecurityFlags) + +set(include_path "${CMAKE_CURRENT_SOURCE_DIR}/include/${target}") +set(source_path "${CMAKE_CURRENT_SOURCE_DIR}/source") + +set(sources + ${source_path}/main.cpp + ${source_path}/grpc_serial_test.cpp +) + +set(header_group "Header Files (API)") +set(source_group "Source Files") +source_group_by_path(${include_path} "\\\\.h$|\\\\.hpp$" + ${header_group} ${headers}) +source_group_by_path(${source_path} "\\\\.cpp$|\\\\.c$|\\\\.h$|\\\\.hpp$" + ${source_group} ${sources}) + +add_executable(${target} + ${sources} +) + +add_executable(${META_PROJECT_NAME}::${target} ALIAS ${target}) + +set_target_properties(${target} + PROPERTIES + ${DEFAULT_PROJECT_OPTIONS} + FOLDER "${IDE_FOLDER}" +) + +target_include_directories(${target} + PRIVATE + ${DEFAULT_INCLUDE_DIRECTORIES} + ${PROJECT_BINARY_DIR}/source/include + + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ + $ +) + +target_link_libraries(${target} + PRIVATE + ${DEFAULT_LIBRARIES} + GTest + ${META_PROJECT_NAME}::metacall +) + +target_compile_definitions(${target} + PRIVATE + ${DEFAULT_COMPILE_DEFINITIONS} +) + +target_compile_options(${target} + PRIVATE + ${DEFAULT_COMPILE_OPTIONS} +) + +target_compile_features(${target} + PRIVATE + cxx_std_17 +) + +target_link_options(${target} + PRIVATE + ${DEFAULT_LINKER_OPTIONS} +) + +add_test(NAME ${target} + COMMAND $ +) + +add_dependencies(${target} + grpc_serial + rapid_json_serial +) + +set_property(TEST ${target} + PROPERTY LABELS ${target} +) + +include(TestEnvironmentVariables) + +test_environment_variables(${target} + "" + "SERIAL_LIBRARY_PATH=${SERIAL_LIBRARY_PATH}" +) \ No newline at end of file diff --git a/source/tests/grpc_serial_test/source/grpc_serial_test.cpp b/source/tests/grpc_serial_test/source/grpc_serial_test.cpp new file mode 100644 index 0000000000..305617fec1 --- /dev/null +++ b/source/tests/grpc_serial_test/source/grpc_serial_test.cpp @@ -0,0 +1,285 @@ +#include + +#include +#include + +#include + +class grpc_serial_test : public testing::Test +{ +protected: + void SetUp() override + { + log_configure("metacall", + log_policy_format_text(), + log_policy_schedule_sync(), + log_policy_storage_sequential(), + log_policy_stream_stdio(stdout)); + + allocator = memory_allocator_std(&malloc, &realloc, &free); + + ASSERT_EQ(0, serial_initialize()); + + s = serial_create("grpc"); + ASSERT_NE(nullptr, s); + } + + void TearDown() override + { + serial_clear(s); + serial_destroy(); + memory_allocator_destroy(allocator); + } + + serial s; + memory_allocator allocator; +}; + +// Positive and Negative integer tests + +TEST_F(grpc_serial_test, IntPositive) +{ + value v = value_create_int(42); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(TYPE_INT, value_type_id(out)); + EXPECT_EQ(42, value_to_int(out)); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +TEST_F(grpc_serial_test, IntNegative) +{ + value v = value_create_int(-123); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(-123, value_to_int(out)); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +// String tests + +TEST_F(grpc_serial_test, StringBasic) +{ + const char *msg = "hello grpc"; + + value v = value_create_string(msg, strlen(msg)); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(TYPE_STRING, value_type_id(out)); + EXPECT_STREQ(msg, value_to_string(out)); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +TEST_F(grpc_serial_test, StringEmpty) +{ + value v = value_create_string("", 0); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(TYPE_STRING, value_type_id(out)); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +TEST_F(grpc_serial_test, StringSpecialChars) +{ + const char *msg = "line1\nline2\t\"quoted\""; + + value v = value_create_string(msg, strlen(msg)); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_STREQ(msg, value_to_string(out)); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +// Bool and double tests + +TEST_F(grpc_serial_test, BoolTest) +{ + value v = value_create_bool(1L); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(TYPE_BOOL, value_type_id(out)); + EXPECT_EQ(1L, value_to_bool(out)); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +TEST_F(grpc_serial_test, DoubleTest) +{ + value v = value_create_double(3.14); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(TYPE_DOUBLE, value_type_id(out)); + EXPECT_NEAR(3.14, value_to_double(out), 1e-6); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +// Null test + +TEST_F(grpc_serial_test, NullTest) +{ + value v = value_create_null(); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(TYPE_NULL, value_type_id(out)); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +// Array test + +TEST_F(grpc_serial_test, SimpleArray) +{ + const value arr[] = { + value_create_int(1), + value_create_int(2), + value_create_int(3) + }; + + value v = value_create_array(arr, 3); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(TYPE_ARRAY, value_type_id(out)); + EXPECT_EQ(3, value_type_count(out)); + + value *vals = value_to_array(out); + EXPECT_EQ(1, value_to_int(vals[0])); + EXPECT_EQ(2, value_to_int(vals[1])); + EXPECT_EQ(3, value_to_int(vals[2])); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +// Map test + +TEST_F(grpc_serial_test, SimpleMap) +{ + const value pair[] = { + value_create_string("key", 3), + value_create_int(99) + }; + + const value map_arr[] = { + value_create_array(pair, 2) + }; + + value v = value_create_map(map_arr, 1); + + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + ASSERT_NE(nullptr, buf); + + value out = serial_deserialize(s, buf, size, allocator); + + ASSERT_NE(nullptr, out); + EXPECT_EQ(TYPE_MAP, value_type_id(out)); + + memory_allocator_deallocate(allocator, buf); + value_destroy(v); + value_destroy(out); +} + +// Error case test + +TEST_F(grpc_serial_test, UnsupportedTypeFailsGracefully) +{ + // Example: function type (unsupported) + value v = value_create_null(); + size_t size = 0; + char *buf = serial_serialize(s, v, &size, allocator); + + // Should not crash + EXPECT_TRUE(buf == nullptr || size > 0); + + if (buf) + memory_allocator_deallocate(allocator, buf); + + value_destroy(v); +} \ No newline at end of file diff --git a/source/tests/grpc_serial_test/source/main.cpp b/source/tests/grpc_serial_test/source/main.cpp new file mode 100644 index 0000000000..43790d8fa7 --- /dev/null +++ b/source/tests/grpc_serial_test/source/main.cpp @@ -0,0 +1,27 @@ +/* + * Serial Library by Parra Studios + * A cross-platform library for managing multiple serialization and deserialization formats. + * + * Copyright (C) 2016 - 2026 Vicente Eduardo Ferrer Garcia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include + +int main(int argc, char *argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file