diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 000000000..44f9b03f4 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,2 @@ +src/Debug/debug.proto +src/Debug/nanopb/** diff --git a/.github/actions/build-wdcli/action.yml b/.github/actions/build-wdcli/action.yml new file mode 100644 index 000000000..049da0cc3 --- /dev/null +++ b/.github/actions/build-wdcli/action.yml @@ -0,0 +1,25 @@ +name: Build WARDuino CLI +description: Configure and build the WARDuino emulator CLI. + +inputs: + build-directory: + description: Directory for CMake build artifacts. + required: false + default: build-emu + +outputs: + wdcli: + description: Absolute path to the built CLI executable. + value: ${{ steps.build.outputs.wdcli }} + +runs: + using: composite + steps: + - id: build + shell: bash + run: | + cmake -S "$GITHUB_WORKSPACE" -B "$INPUT_BUILD_DIRECTORY" -D BUILD_EMULATOR=ON + cmake --build "$INPUT_BUILD_DIRECTORY" + wdcli="$(cd "$INPUT_BUILD_DIRECTORY" && pwd)/wdcli" + echo "wdcli=$wdcli" >> "$GITHUB_OUTPUT" + echo "EMULATOR=$wdcli" >> "$GITHUB_ENV" diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 1825cff8d..a5c19e66a 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -33,12 +33,8 @@ jobs: with: submodules: 'recursive' - - name: Create build folder - run: mkdir build-emu - - name: Build WARDuino CLI - run: cmake .. -D BUILD_EMULATOR=ON ; cmake --build . - working-directory: build-emu + uses: ./.github/actions/build-wdcli compile-with-arduino: name: (Arduino) Compile on ${{matrix.board.platform-name}} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8994a650b..cd8062a66 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,11 +44,8 @@ jobs: with: node-version: 20 - - name: Build warduino cli - run: | - cmake . -D BUILD_EMULATOR=ON - cmake --build . - echo "EMULATOR=$(realpath ./wdcli)" >> $GITHUB_ENV + - name: Build WARDuino CLI + uses: ./.github/actions/build-wdcli - name: Get WABT commit ID working-directory: lib/wabt @@ -99,11 +96,8 @@ jobs: with: node-version: 20 - - name: Build warduino cli - run: | - cmake . -D BUILD_EMULATOR=ON - cmake --build . - echo "EMULATOR=$(realpath ./wdcli)" >> $GITHUB_ENV + - name: Build WARDuino CLI + uses: ./.github/actions/build-wdcli - name: Get WABT commit ID working-directory: lib/wabt @@ -152,11 +146,8 @@ jobs: with: node-version: 20 - - name: Build warduino cli - run: | - cmake . -D BUILD_EMULATOR=ON - cmake --build . - echo "EMULATOR=$(realpath ./wdcli)" >> $GITHUB_ENV + - name: Build WARDuino CLI + uses: ./.github/actions/build-wdcli - name: Get WABT commit ID working-directory: lib/wabt diff --git a/.gitignore b/.gitignore index 0e6a68346..6cf8eb2fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ .idea/ +.air/ .vscode/ .ccls +.cache + *.bin *.ipch *.o @@ -11,6 +14,10 @@ build/ cmake-build-* build* +# local actions +!/.github/actions/build-wdcli/ +!/.github/actions/build-wdcli/action.yml + # CMake CMakeLists.txt.user CMakeCache.txt @@ -35,3 +42,4 @@ core venv *.wasm + diff --git a/CMakeLists.txt b/CMakeLists.txt index e560ba033..94a195126 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,10 +36,21 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") set(WARDUINO_VERSION_STRING "${PROJECT_VERSION}") configure_file(src/config.h.in include/warduino/config.h) +# Use the checked-in nanopb runtime and schema output on every platform. +add_library(proto STATIC + src/Debug/nanopb/debug.pb.c + src/Debug/nanopb/pb_common.c + src/Debug/nanopb/pb_decode.c + src/Debug/nanopb/pb_encode.c +) +target_include_directories(proto PUBLIC + ${PROJECT_SOURCE_DIR}/src/Debug + ${PROJECT_SOURCE_DIR}/src/Debug/nanopb +) + # Build the emulator version of WARDuino if (BUILD_EMULATOR) set(EXTERNAL_LIB_HEADERS lib/json/single_include) - find_package(Threads REQUIRED) set(SOURCE_FILES @@ -71,7 +82,7 @@ if (BUILD_EMULATOR) # WARDuino CLI add_executable(wdcli platforms/CLI-Emulator/main.cpp ${SOURCE_FILES}) - target_link_libraries(wdcli PRIVATE Threads::Threads) + target_link_libraries(wdcli PRIVATE Threads::Threads proto) target_include_directories(wdcli PRIVATE ${EXTERNAL_LIB_HEADERS} "${PROJECT_BINARY_DIR}/include") endif (BUILD_EMULATOR) @@ -122,7 +133,7 @@ if (BUILD_UNITTEST) get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE) message(DEBUG "Add executable for " ${TEST_FILE}) add_executable(${TEST_NAME} ${TEST_FILE} ${SOURCE_FILES} ${SHARED_SRC}) - target_link_libraries(${TEST_NAME} PRIVATE doctest::doctest) + target_link_libraries(${TEST_NAME} PRIVATE doctest::doctest proto) target_include_directories(${TEST_NAME} PRIVATE ${EXTERNAL_LIB_HEADERS} "${PROJECT_BINARY_DIR}/include") add_test(${TEST_NAME} ${TEST_NAME}) endforeach () diff --git a/justfile b/justfile index 166c9bd04..7612ff23b 100644 --- a/justfile +++ b/justfile @@ -164,6 +164,16 @@ all: WABT="../../lib/wabt/build/" npm run tests:all +## Nanopb + +[group('codegen')] +[doc('Regenerate vendored nanopb sources from the debugger schema')] +generate-nanopb: _nanopb + mkdir -p src/Debug/nanopb + protoc -I src/Debug --nanopb_out=src/Debug/nanopb src/Debug/debug.proto + perl -0pi -e 's{#include }{#include "pb.h"}' src/Debug/nanopb/debug.pb.h + + ## QoL / Maintenance [group('maintenance')] diff --git a/lib/FindNanopb.cmake b/lib/FindNanopb.cmake new file mode 100644 index 000000000..9def7833d --- /dev/null +++ b/lib/FindNanopb.cmake @@ -0,0 +1,482 @@ +# This is an example script for use with CMake projects for locating and configuring +# the nanopb library. +# +# The following variables can be set and are optional: +# +# +# PROTOBUF_SRC_ROOT_FOLDER - When compiling with MSVC, if this cache variable is set +# the protobuf-default VS project build locations +# (vsprojects/Debug & vsprojects/Release) will be searched +# for libraries and binaries. +# +# NANOPB_IMPORT_DIRS - List of additional directories to be searched for +# imported .proto files. +# +# NANOPB_OPTIONS - List of options passed to nanopb. +# +# Nanopb_FIND_COMPONENTS - List of options to append to NANOPB_OPTIONS without the +# leading '--'. This should not manually be set, but allows +# passing options to nanopb via find_package. For example, +# 'find_package(Nanopb REQUIRED COMPONENTS cpp-descriptors)' +# is equivalent to setting NANOPB_OPTIONS to --cpp-descriptors. +# +# NANOPB_DEPENDS - List of files to be used as dependencies +# for the generated source and header files. These +# files are not directly passed as options to +# nanopb but rather their directories. +# +# NANOPB_GENERATE_CPP_APPEND_PATH - By default -I will be passed to protoc +# for each directory where a proto file is referenced. +# This causes all output files to go directly +# under build directory, instead of mirroring +# relative paths of source directories. +# Set to FALSE if you want to disable this behaviour. +# PROTOC_OPTIONS - Pass options to protoc executable +# +# Defines the following variables: +# +# NANOPB_FOUND - Found the nanopb library (source&header files, generator tool, protoc compiler tool) +# NANOPB_INCLUDE_DIRS - Include directories for Google Protocol Buffers +# +# The following cache variables are also available to set or use: +# PROTOBUF_PROTOC_EXECUTABLE - The protoc compiler +# NANOPB_GENERATOR_SOURCE_DIR - The nanopb generator source +# +# ==================================================================== +# +# NANOPB_GENERATE_CPP (public function) +# NANOPB_GENERATE_CPP(SRCS HDRS [RELPATH ] +# ...) +# SRCS = Variable to define with autogenerated source files +# HDRS = Variable to define with autogenerated header files +# NANOPB_GENERATE_CPP(TARGET TGT [RELPATH ] +# ...) +# TGT = Name of the static library to create with the autogenerated files +# +# If you want to use relative paths in your import statements use the RELPATH +# option. The argument to RELPATH should be the directory that all the +# imports will be relative to. +# When RELPATH is not specified then all proto files can be imported without +# a path. +# +# +# ==================================================================== +# Example using modern targets: +# +# set(NANOPB_SRC_ROOT_FOLDER "/path/to/nanopb") +# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${NANOPB_SRC_ROOT_FOLDER}/extra) +# find_package( Nanopb REQUIRED ) +# +# NANOPB_GENERATE_CPP(TARGET proto foo.proto) +# +# add_executable(bar bar.cc) +# target_link_libraries(bar proto) +# +# Example with RELPATH: +# Assume we have a layout like: +# .../CMakeLists.txt +# .../bar.cc +# .../proto/ +# .../proto/foo.proto (Which contains: import "sub/bar.proto"; ) +# .../proto/sub/bar.proto +# Everything would be the same as the previous example, but the call to +# NANOPB_GENERATE_CPP would change to: +# +# NANOPB_GENERATE_CPP(TARGET proto RELPATH proto +# proto/foo.proto proto/sub/bar.proto) +# +# Example using traditional variables: +# +# set(NANOPB_SRC_ROOT_FOLDER "/path/to/nanopb") +# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${NANOPB_SRC_ROOT_FOLDER}/extra) +# find_package( Nanopb REQUIRED ) +# include_directories(${NANOPB_INCLUDE_DIRS}) +# +# NANOPB_GENERATE_CPP(PROTO_SRCS PROTO_HDRS foo.proto) +# +# include_directories(${CMAKE_CURRENT_BINARY_DIR}) +# add_executable(bar bar.cc ${PROTO_SRCS} ${PROTO_HDRS}) +# +# ==================================================================== + +#============================================================================= +# Copyright 2009 Kitware, Inc. +# Copyright 2009-2011 Philip Lowman +# Copyright 2008 Esben Mose Hansen, Ange Optimization ApS +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the names of Kitware, Inc., the Insight Software Consortium, +# nor the names of their contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +#============================================================================= +# +# Changes +# 2013.01.31 - Pavlo Ilin - used Modules/FindProtobuf.cmake from cmake 2.8.10 to +# write FindNanopb.cmake +# +#============================================================================= + + +function(NANOPB_GENERATE_CPP) + cmake_parse_arguments(NANOPB_GENERATE_CPP "" "RELPATH;TARGET" "" ${ARGN}) + if(NANOPB_GENERATE_CPP_TARGET) + set(SRCS NANOPB_TARGET_SRCS) + set(HDRS NANOPB_TARGET_HDRS) + else() + list(GET NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS 0 SRCS) + list(GET NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS 1 HDRS) + list(REMOVE_AT NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS 0 1) + endif() + if(NOT NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS) + return() + endif() + set(NANOPB_OPTIONS_DIRS) + + if(MSVC) + set(CUSTOM_COMMAND_PREFIX call) + endif() + + if(NANOPB_GENERATE_CPP_RELPATH) + get_filename_component(NANOPB_GENERATE_CPP_RELPATH ${NANOPB_GENERATE_CPP_RELPATH} ABSOLUTE) + list(APPEND _nanopb_include_path "-I${NANOPB_GENERATE_CPP_RELPATH}") + list(APPEND NANOPB_OPTIONS_DIRS ${NANOPB_GENERATE_CPP_RELPATH}) + endif() + + if(NANOPB_GENERATE_CPP_APPEND_PATH) + # Create an include path for each file specified + foreach(FIL ${NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(ABS_PATH ${ABS_FIL} PATH) + list(APPEND _nanopb_include_path "-I${ABS_PATH}") + endforeach() + else() + list(APPEND _nanopb_include_path "-I${CMAKE_CURRENT_SOURCE_DIR}") + endif() + + if(DEFINED NANOPB_IMPORT_DIRS) + foreach(DIR ${NANOPB_IMPORT_DIRS}) + get_filename_component(ABS_PATH ${DIR} ABSOLUTE) + list(APPEND _nanopb_include_path "-I${ABS_PATH}") + endforeach() + endif() + + list(REMOVE_DUPLICATES _nanopb_include_path) + + set(GENERATOR_PATH ${CMAKE_CURRENT_BINARY_DIR}/nanopb/generator) + + set(NANOPB_GENERATOR_EXECUTABLE ${GENERATOR_PATH}/nanopb_generator.py) + if(NOT NANOPB_GENERATOR_PLUGIN) + if (CMAKE_HOST_WIN32) + set(NANOPB_GENERATOR_PLUGIN ${GENERATOR_PATH}/protoc-gen-nanopb.bat) + else() + set(NANOPB_GENERATOR_PLUGIN ${GENERATOR_PATH}/protoc-gen-nanopb) + endif() + endif() + + set(GENERATOR_CORE_DIR ${GENERATOR_PATH}/proto) + set(GENERATOR_CORE_SRC + ${GENERATOR_CORE_DIR}/nanopb.proto) + + # Set extensions according to NANOPB_OPTIONS + string(REGEX MATCH "--extension=[^ ]+" _gen_ext "${NANOPB_OPTIONS}") + string(REGEX MATCH "--header-extension=[^ ]+" _gen_hdr_ext + "${NANOPB_OPTIONS}") + string(REGEX MATCH "--source-extension=[^ ]+" _gen_src_ext + "${NANOPB_OPTIONS}") + if(_gen_ext) + string(REPLACE "--extension=" "" GEN_EXTENSION "${_gen_ext}") + else() + set(GEN_EXTENSION ".pb") + endif() + if(_gen_hdr_ext) + string(REPLACE "--header-extension=" "" GEN_HDR_EXTENSION "${_gen_hdr_ext}") + else() + set(GEN_HDR_EXTENSION ".h") + endif() + if(_gen_src_ext) + string(REPLACE "--source-extension=" "" GEN_SRC_EXTENSION "${_gen_src_ext}") + else() + set(GEN_SRC_EXTENSION ".c") + endif() + + # Treat the source directory as immutable. + # + # Copy the generator directory to the build directory before + # compiling python and proto files. Fixes issues when using the + # same build directory with different python/protobuf versions + # as the binary build directory is discarded across builds. + # + # Notice: copy_directory does not copy the content if the directory already exists. + # We therefore append '/' to specify that we want to copy the content of the folder. See #847 + # + add_custom_command( + OUTPUT ${NANOPB_GENERATOR_EXECUTABLE} ${GENERATOR_CORE_SRC} + COMMAND ${CMAKE_COMMAND} -E copy_directory + ARGS ${NANOPB_GENERATOR_SOURCE_DIR}/ ${GENERATOR_PATH} + VERBATIM) + + set(GENERATOR_CORE_PYTHON_SRC) + foreach(FIL ${GENERATOR_CORE_SRC}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WE) + + set(output "${GENERATOR_CORE_DIR}/${FIL_WE}_pb2.py") + set(GENERATOR_CORE_PYTHON_SRC ${GENERATOR_CORE_PYTHON_SRC} ${output}) + add_custom_command( + OUTPUT ${output} + COMMAND ${CUSTOM_COMMAND_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS -I${GENERATOR_PATH}/proto + --python_out=${GENERATOR_CORE_DIR} ${ABS_FIL} + DEPENDS ${ABS_FIL} + VERBATIM) + endforeach() + + foreach(FIL ${NANOPB_GENERATE_CPP_UNPARSED_ARGUMENTS}) + get_filename_component(ABS_FIL ${FIL} ABSOLUTE) + get_filename_component(FIL_WE ${FIL} NAME_WLE) + get_filename_component(FIL_DIR ${ABS_FIL} PATH) + set(FIL_PATH_REL) + if(NANOPB_GENERATE_CPP_RELPATH) + # Check that the file is under the given "RELPATH" + string(FIND ${ABS_FIL} ${NANOPB_GENERATE_CPP_RELPATH} LOC) + if (${LOC} EQUAL 0) + string(REPLACE "${NANOPB_GENERATE_CPP_RELPATH}/" "" FIL_REL ${ABS_FIL}) + get_filename_component(FIL_PATH_REL ${FIL_REL} PATH) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}) + endif() + endif() + if(NOT FIL_PATH_REL) + set(FIL_PATH_REL ".") + endif() + + list(APPEND ${SRCS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}${GEN_EXTENSION}${GEN_SRC_EXTENSION}") + list(APPEND ${HDRS} "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}${GEN_EXTENSION}${GEN_HDR_EXTENSION}") + + get_filename_component(ABS_OPT_IN_FIL ${FIL_DIR}/${FIL_WE}.options.in ABSOLUTE) + if(EXISTS ${ABS_OPT_IN_FIL}) + set(ABS_OPT_FIL "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}.options") + configure_file(${ABS_OPT_IN_FIL} ${ABS_OPT_FIL}) + else() + get_filename_component(ABS_OPT_FIL ${FIL_DIR}/${FIL_WE}.options ABSOLUTE) + endif() + + # If there an options file in the same working directory, set it as a dependency + if(EXISTS ${ABS_OPT_FIL}) + # Get directory as lookups for dependency options fail if an options + # file is used. The options is still set as a dependency of the + # generated source and header. + get_filename_component(options_dir ${ABS_OPT_FIL} DIRECTORY) + list(APPEND NANOPB_OPTIONS_DIRS ${options_dir}) + else() + set(ABS_OPT_FIL) + endif() + + # If the dependencies are options files, we need to pass the directories + # as arguments to nanopb + foreach(depends_file ${NANOPB_DEPENDS}) + get_filename_component(ext ${depends_file} EXT) + if(ext STREQUAL ".options") + get_filename_component(depends_dir ${depends_file} DIRECTORY) + list(APPEND NANOPB_OPTIONS_DIRS ${depends_dir}) + endif() + endforeach() + + if(NANOPB_OPTIONS_DIRS) + list(REMOVE_DUPLICATES NANOPB_OPTIONS_DIRS) + endif() + + set(NANOPB_PLUGIN_OPTIONS) + foreach(options_path ${NANOPB_OPTIONS_DIRS}) + set(NANOPB_PLUGIN_OPTIONS "${NANOPB_PLUGIN_OPTIONS} -I${options_path}") + endforeach() + + # Remove leading space before the first -I directive + string(STRIP "${NANOPB_PLUGIN_OPTIONS}" NANOPB_PLUGIN_OPTIONS) + + if(NANOPB_OPTIONS) + set(NANOPB_PLUGIN_OPTIONS "${NANOPB_PLUGIN_OPTIONS} ${NANOPB_OPTIONS}") + endif() + + # based on the version of protoc it might be necessary to add "/${FIL_PATH_REL}" currently dealt with in #516 + set(NANOPB_OUT "${CMAKE_CURRENT_BINARY_DIR}") + + # We need to pass the path to the option files to the nanopb plugin. There are two ways to do it. + # - An older hacky one using ':' as option separator in protoc args preventing the ':' to be used in path. + # - Or a newer one, using --nanopb_opt which requires a version of protoc >= 3.6 + # Since nanopb 0.4.6, --nanopb_opt is the default. + if(DEFINED NANOPB_PROTOC_OLDER_THAN_3_6_0) + set(NANOPB_OPT_STRING "--nanopb_out=${NANOPB_PLUGIN_OPTIONS}:${NANOPB_OUT}") + else() + set(NANOPB_OPT_STRING "--nanopb_opt=${NANOPB_PLUGIN_OPTIONS}" "--nanopb_out=${NANOPB_OUT}") + endif() + + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}${GEN_EXTENSION}${GEN_SRC_EXTENSION}" + "${CMAKE_CURRENT_BINARY_DIR}/${FIL_PATH_REL}/${FIL_WE}${GEN_EXTENSION}${GEN_HDR_EXTENSION}" + COMMAND ${CUSTOM_COMMAND_PREFIX} ${PROTOBUF_PROTOC_EXECUTABLE} + ARGS ${_nanopb_include_path} -I${GENERATOR_PATH} + -I${GENERATOR_CORE_DIR} -I${CMAKE_CURRENT_BINARY_DIR} + --plugin=protoc-gen-nanopb=${NANOPB_GENERATOR_PLUGIN} + ${NANOPB_OPT_STRING} + ${PROTOC_OPTIONS} + ${ABS_FIL} + DEPENDS ${ABS_FIL} ${GENERATOR_CORE_PYTHON_SRC} + ${ABS_OPT_FIL} ${NANOPB_DEPENDS} + COMMENT "Running C++ protocol buffer compiler using nanopb plugin on ${FIL}" + VERBATIM ) + + endforeach() + + set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) + + if(NANOPB_GENERATE_CPP_TARGET) + add_library(${NANOPB_GENERATE_CPP_TARGET} STATIC EXCLUDE_FROM_ALL ${${SRCS}} ${${HDRS}}) + target_include_directories(${NANOPB_GENERATE_CPP_TARGET} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) + target_link_libraries(${NANOPB_GENERATE_CPP_TARGET} nanopb) + endif() + + if(NOT DEFINED NANOPB_GENERATE_CPP_STANDALONE) + set(NANOPB_GENERATE_CPP_STANDALONE TRUE) + endif() + + if(MSVC) + unset(CUSTOM_COMMAND_PREFIX) + endif() + + if(NOT NANOPB_GENERATE_CPP_TARGET) + if (NANOPB_GENERATE_CPP_STANDALONE) + set(${SRCS} ${${SRCS}} ${NANOPB_SRCS} PARENT_SCOPE) + set(${HDRS} ${${HDRS}} ${NANOPB_HDRS} PARENT_SCOPE) + else() + set(${SRCS} ${${SRCS}} PARENT_SCOPE) + set(${HDRS} ${${HDRS}} PARENT_SCOPE) + endif() + endif() +endfunction() + + + +# +# Main. +# + +# By default have NANOPB_GENERATE_CPP macro pass -I to protoc +# for each directory where a proto file is referenced. +if(NOT DEFINED NANOPB_GENERATE_CPP_APPEND_PATH) + set(NANOPB_GENERATE_CPP_APPEND_PATH TRUE) +endif() + +# Make a really good guess regarding location of NANOPB_SRC_ROOT_FOLDER +if(NOT DEFINED NANOPB_SRC_ROOT_FOLDER) + get_filename_component(NANOPB_SRC_ROOT_FOLDER + ${CMAKE_CURRENT_LIST_DIR}/.. ABSOLUTE) +endif() + +# Parse any options given to find_package(... COMPONENTS ...) +foreach(component ${Nanopb_FIND_COMPONENTS}) + list(APPEND NANOPB_OPTIONS "--${component}") +endforeach() + +# Find the include directory +find_path(NANOPB_INCLUDE_DIRS + pb.h + PATHS ${NANOPB_SRC_ROOT_FOLDER} + NO_CMAKE_FIND_ROOT_PATH +) +mark_as_advanced(NANOPB_INCLUDE_DIRS) + +# Find nanopb source files +set(NANOPB_SRCS) +set(NANOPB_HDRS) +list(APPEND _nanopb_srcs pb_decode.c pb_encode.c pb_common.c) +list(APPEND _nanopb_hdrs pb_decode.h pb_encode.h pb_common.h pb.h) + +foreach(FIL ${_nanopb_srcs}) + find_file(${FIL}__nano_pb_file NAMES ${FIL} PATHS ${NANOPB_SRC_ROOT_FOLDER} ${NANOPB_INCLUDE_DIRS} NO_CMAKE_FIND_ROOT_PATH) + list(APPEND NANOPB_SRCS "${${FIL}__nano_pb_file}") + mark_as_advanced(${FIL}__nano_pb_file) +endforeach() + +foreach(FIL ${_nanopb_hdrs}) + find_file(${FIL}__nano_pb_file NAMES ${FIL} PATHS ${NANOPB_INCLUDE_DIRS} NO_CMAKE_FIND_ROOT_PATH) + mark_as_advanced(${FIL}__nano_pb_file) + list(APPEND NANOPB_HDRS "${${FIL}__nano_pb_file}") +endforeach() + +# Create the library target +add_library(nanopb STATIC EXCLUDE_FROM_ALL ${NANOPB_SRCS}) +target_compile_features(nanopb PUBLIC c_std_11) +target_include_directories(nanopb PUBLIC ${NANOPB_INCLUDE_DIRS}) + +# Find the local protoc Executable +find_program(PROTOBUF_PROTOC_EXECUTABLE + NAMES protoc + DOC "The Google Protocol Buffers Compiler" + PATHS + ${PROTOBUF_SRC_ROOT_FOLDER}/vsprojects/Release + ${PROTOBUF_SRC_ROOT_FOLDER}/vsprojects/Debug + ${NANOPB_SRC_ROOT_FOLDER}/generator-bin + ${NANOPB_SRC_ROOT_FOLDER}/generator + NO_DEFAULT_PATH +) + +# Test protoc, try to get version +execute_process( + COMMAND ${PROTOBUF_PROTOC_EXECUTABLE} --version + OUTPUT_QUIET + ERROR_QUIET + RESULT_VARIABLE ret +) +if(NOT ret EQUAL 0) + # Fallback to system protoc + unset(PROTOBUF_PROTOC_EXECUTABLE) + find_program(PROTOBUF_PROTOC_EXECUTABLE + NAMES protoc + DOC "The Google Protocol Buffers Compiler" + ) +endif() + +mark_as_advanced(PROTOBUF_PROTOC_EXECUTABLE) + +# Find nanopb generator source dir +find_path(NANOPB_GENERATOR_SOURCE_DIR + NAMES nanopb_generator.py + DOC "nanopb generator source" + PATHS + ${NANOPB_SRC_ROOT_FOLDER}/generator + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH +) +mark_as_advanced(NANOPB_GENERATOR_SOURCE_DIR) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Nanopb DEFAULT_MSG + NANOPB_INCLUDE_DIRS + NANOPB_SRCS NANOPB_HDRS + NANOPB_GENERATOR_SOURCE_DIR + PROTOBUF_PROTOC_EXECUTABLE + ) diff --git a/platforms/ESP-IDF/CMakeLists.txt b/platforms/ESP-IDF/CMakeLists.txt index 12abbd25e..c0e251a01 100644 --- a/platforms/ESP-IDF/CMakeLists.txt +++ b/platforms/ESP-IDF/CMakeLists.txt @@ -1,5 +1,9 @@ set(SOURCE_FILES ../../src/Debug/debugger.cpp + ../../src/Debug/nanopb/debug.pb.c + ../../src/Debug/nanopb/pb_common.c + ../../src/Debug/nanopb/pb_decode.c + ../../src/Debug/nanopb/pb_encode.c ../../src/Interpreter/instructions.cpp ../../src/Interpreter/interpreter.cpp ../../src/Memory/mem.cpp @@ -15,7 +19,7 @@ set(SOURCE_FILES ../../src/WARDuino/WARDuino.cpp ) -idf_component_register(SRCS "main.cpp" ${SOURCE_FILES} INCLUDE_DIRS ../../lib/json/single_include/ REQUIRES driver) +idf_component_register(SRCS "main.cpp" ${SOURCE_FILES} INCLUDE_DIRS ../../lib/json/single_include/ ../../src/Debug/nanopb REQUIRES driver) add_definitions(-DINFO=0) add_definitions(-DDEBUG=0) diff --git a/platforms/Zephyr/CMakeLists.txt b/platforms/Zephyr/CMakeLists.txt index 23951adbe..f47956678 100644 --- a/platforms/Zephyr/CMakeLists.txt +++ b/platforms/Zephyr/CMakeLists.txt @@ -27,6 +27,10 @@ target_sources(app PRIVATE ../../src/Utils/macros.cpp ../../src/Utils/sockets.cpp ../../src/Debug/debugger.cpp + ../../src/Debug/nanopb/debug.pb.c + ../../src/Debug/nanopb/pb_common.c + ../../src/Debug/nanopb/pb_decode.c + ../../src/Debug/nanopb/pb_encode.c ../../src/Edward/proxy.cpp ../../src/Edward/proxy_supervisor.cpp ../../src/Edward/RFC.cpp @@ -38,4 +42,4 @@ add_definitions(-DDEBUG=0) add_definitions(-DTRACE=0) add_definitions(-DWARN=0) -include_directories(../../lib/json/single_include/) +include_directories(../../lib/json/single_include/ ../../src/Debug/nanopb) diff --git a/src/Debug/debug.proto b/src/Debug/debug.proto new file mode 100644 index 000000000..bc69bc5f1 --- /dev/null +++ b/src/Debug/debug.proto @@ -0,0 +1,257 @@ +// WARDuino debug protocol. +// +// Command and NotificationType are transport-level discriminators. They are +// written as a single byte before the protobuf payload; they are not encoded +// as fields inside another protobuf message. +// +// Frame packet structure: +// +// [type: uint8][payload length: varint][protobuf payload] +// +// Empty commands and notifications have a zero-length protobuf payload. + +syntax = "proto3"; + +package debug; + +option optimize_for = LITE_RUNTIME; +option cc_enable_arenas = false; + +// Frontend -> WARDuino. +// The receiver selects the payload schema from the command byte. +enum Command { + COMMAND_RUN = 0; // no payload + COMMAND_HALT = 1; // no payload + COMMAND_PAUSE = 2; // no payload + COMMAND_STEP = 3; // no payload + COMMAND_STEP_OVER = 4; // no payload + COMMAND_ADD_BREAKPOINT = 5; // Breakpoint + COMMAND_REMOVE_BREAKPOINT = 6; // Breakpoint + + COMMAND_DUMP = 7; // no payload + COMMAND_DUMP_LOCALS = 8; // no payload + COMMAND_SNAPSHOT = 9; // no payload + COMMAND_DUMP_EVENTS = 10; // Range + COMMAND_DUMP_CALLBACKS = 11; // no payload + + COMMAND_UPDATE_FUNCTION = 12; // Function + COMMAND_UPDATE_LOCAL = 13; // ValueUpdate + COMMAND_UPDATE_CALLBACKS = 14; // CallbackMapping + COMMAND_UPDATE_MODULE = 26; // ModuleUpdate + COMMAND_UPDATE_GLOBAL = 27; // ValueUpdate + COMMAND_UPDATE_STACK = 28; // ValueUpdate + + COMMAND_LOAD_SNAPSHOT = 15; // Snapshot + COMMAND_PROXIFY = 16; // no payload + COMMAND_ADD_PROXY = 17; // FunctionRef + COMMAND_REMOVE_PROXY = 18; // FunctionRef + COMMAND_PROXY_CALL = 19; // RemoteFunctionCall + COMMAND_POP_EVENT = 20; // no payload + COMMAND_PUSH_EVENT = 21; // Event + + COMMAND_CONTINUE_FOR = 22; // ContinueFor + COMMAND_INSPECT = 23; // Inspect + COMMAND_RESET = 24; // no payload + COMMAND_INVOKE = 25; // RemoteFunctionCall + + + COMMAND_SET_SNAPSHOT_POLICY = 29; // SnapshotPolicyConfig + COMMAND_SET_OVERRIDE = 30; // Override + COMMAND_REMOVE_OVERRIDE = 31; // Override +} + +// WARDuino -> frontend. +// The receiver selects the payload schema from the notification byte. +enum NotificationType { + NOTIFICATION_CONTINUED = 0; // no payload + NOTIFICATION_HALTED = 1; // no payload + NOTIFICATION_PAUSED = 2; // no payload + NOTIFICATION_STEPPED = 3; // no payload + NOTIFICATION_HIT_BREAKPOINT = 4; // HitBreakpoint + NOTIFICATION_NEW_EVENT = 5; // NewEvent (zero-length payload) + + NOTIFICATION_FUNCTION_DUMP = 6; // Function + NOTIFICATION_LOCALS_DUMP = 7; // Locals + NOTIFICATION_SNAPSHOT = 8; // Snapshot + NOTIFICATION_EVENTS_DUMP = 9; // EventsQueue + NOTIFICATION_CALLBACKS_DUMP = 10; // CallbackMapping + NOTIFICATION_CHANGE_AFFECTED = 11; // no payload + + NOTIFICATION_MALFORMED = 12; // no payload + NOTIFICATION_UNKNOWN_COMMAND = 13; // no payload + NOTIFICATION_OPERATION_RESULT = 14; // OperationResult + NOTIFICATION_REMOTE_FUNCTION_RESULT = 15; // RemoteFunctionResult + NOTIFICATION_CHECKPOINT = 16; // Checkpoint +} + +enum State { + STATE_WARDUINO_RUN = 0; + STATE_WARDUINO_PAUSE = 1; + STATE_WARDUINO_STEP = 2; + STATE_PROXY_RUN = 3; + STATE_PROXY_HALT = 4; +} + +// A virtual program address. +message CodeLocation { + uint32 module_index = 1; + uint32 program_counter = 2; +} + +message Breakpoint { + CodeLocation location = 1; +} + +message HitBreakpoint { + CodeLocation location = 1; +} + +// The notification type carries all information for this event. The empty +// message exists for host-side reflection, but no protobuf bytes are sent. +message NewEvent {} + +message ContinueFor { + uint32 count = 1; +} + +// Execution-state selectors understood by the VM. Keeping them as bytes lets +// the protocol add selectors without changing this schema. +message Inspect { + bytes state = 1; +} + +message FunctionRef { + uint32 function_index = 1; +} + +message ValueUpdate { + uint32 index = 1; + Value value = 2; +} + +message Snapshot { + uint32 program_counter = 1; + State state = 2; + repeated uint32 breakpoints = 3; + repeated Function functions = 4; + repeated CallstackEntry callstack = 5; + + Locals locals = 6; + EventsQueue queue = 7; + CallbackMapping callbacks = 8; + + repeated Value globals = 9; + repeated Value stack = 10; + TableState table = 11; + MemoryState memory = 12; + repeated uint32 branch_table = 13; + repeated IOState io = 14; + repeated Override overrides = 15; + uint32 heap_used = 16; +} + +message Function { + uint32 function_index = 1; + Range range = 2; + Locals locals = 3; + bytes instructions = 4; +} + +message RemoteFunctionCall { + uint32 function_index = 1; + repeated Value arguments = 2; +} + +message CallstackEntry { + uint32 type = 1; + uint32 function_index = 2; + uint32 stack_pointer = 3; + uint32 frame_pointer = 4; + uint32 start = 5; + uint32 return_address = 6; +} + +message Locals { + repeated Value values = 1; +} + +// The oneof tag is the value type, so a separate type enum and a decimal +// string are unnecessary. Fixed-width fields preserve WebAssembly bits and +// are fast to construct on the MCU. +message Value { + oneof data { + fixed32 i32_bits = 1; + fixed64 i64_bits = 2; + fixed32 f32_bits = 3; + fixed64 f64_bits = 4; + bytes raw = 5; + } + + uint32 index = 6; +} + +message CallbackMapping { + repeated CallbackEntry entries = 1; +} + +message CallbackEntry { + string topic = 1; + repeated uint32 table_indexes = 2; +} + +message EventsQueue { + // Total events in the queue; this can exceed the returned slice length. + uint32 total_count = 1; + repeated Event events = 2; + Range range = 3; +} + +message Event { + string topic = 1; + bytes payload = 2; +} + +message Range { + uint32 start = 1; + uint32 end = 2; +} + +message ModuleUpdate { bytes wasm = 1; } +message IndexedValues { repeated Value values = 1; } + +enum SnapshotPolicy { + SNAPSHOT_POLICY_NONE = 0; + SNAPSHOT_POLICY_EVERY_INSTRUCTION = 1; + SNAPSHOT_POLICY_CHECKPOINTING = 2; +} + +message SnapshotPolicyConfig { + SnapshotPolicy policy = 1; + uint32 interval = 2; + uint32 minimum_return_count = 3; + bytes selected_state = 4; +} + +message Override { + string primitive_name = 1; + repeated fixed32 argument_words = 2; + fixed32 result = 3; +} + +message OperationResult { Command command = 1; bool success = 2; } +message RemoteFunctionResult { + bool success = 1; + repeated Value results = 2; + bytes error = 3; +} +message Checkpoint { + uint32 instruction_count = 1; + bool has_primitive_call = 2; + uint32 primitive_function_index = 3; + repeated Value arguments = 4; + repeated Value results = 5; + Snapshot snapshot = 6; +} +message TableState { uint32 initial = 1; uint32 maximum = 2; repeated uint32 entries = 3; } +message MemoryState { uint32 initial = 1; uint32 maximum = 2; uint32 pages = 3; bytes bytes = 4; } +message IOState { string key = 1; bool output = 2; sint32 value = 3; } diff --git a/src/Debug/debugger.cpp b/src/Debug/debugger.cpp index 0cb65b9b3..96dc8c21e 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -4,11 +4,6 @@ #include #include #include -#ifndef ARDUINO -#include -#else -#include "../../lib/json/single_include/nlohmann/json.hpp" -#endif #include "../Memory/mem.h" #include "../Utils//util.h" @@ -39,101 +34,151 @@ void Debugger::setChannel(Channel *duplex) { this->channel = duplex; } -void Debugger::addDebugMessage(size_t len, const uint8_t *buff) { - this->parseDebugBuffer(len, buff); - uint8_t *data{}; - while (!this->parsedInterrupts.empty()) { - data = this->parsedInterrupts.front(); - this->parsedInterrupts.pop(); - if (*data == interruptRecvCallbackmapping) { - size_t startIdx = 0; - while (buff[startIdx] != '7' || buff[startIdx + 1] != '5' || - buff[startIdx + 2] != '{') { - startIdx++; - } - size_t endIdx = startIdx; - while (buff[endIdx] != '\n') { - endIdx++; +namespace { + +bool decodeFrameLength(const std::vector &bytes, size_t *headerSize, + size_t *payloadSize) { + if (bytes.size() < 2) return false; + uint32_t value = 0; + for (size_t i = 0; i < 5; ++i) { + const size_t offset = i + 1; + if (offset >= bytes.size()) return false; + const uint8_t byte = bytes[offset]; + if (i == 4 && (byte & 0xf0U) != 0) { + *headerSize = SIZE_MAX; + return false; + } + value |= static_cast(byte & 0x7fU) << (i * 7U); + if ((byte & 0x80U) == 0) { + if (i > 0 && value < (1U << (i * 7U))) { + *headerSize = SIZE_MAX; + return false; } - auto *msg = static_cast(acalloc( - sizeof(uint8_t), (endIdx - startIdx), "interrupt buffer")); - memcpy(msg, buff + startIdx, (endIdx - startIdx) * sizeof(uint8_t)); - *msg = *data; - free(data); - this->pushMessage(msg); - } else { - this->pushMessage(data); + *headerSize = offset + 1; + *payloadSize = value; + return true; } } + return false; } -void Debugger::pushMessage(uint8_t *msg) { - warduino::lock_guard const lg(messageQueueMutex); - this->debugMessages.push_back(msg); - this->freshMessages = !this->debugMessages.empty(); - this->messageQueueConditionVariable.notify_one(); +bool isKnownCommand(const uint8_t type) { + return type <= static_cast(debug_Command_COMMAND_REMOVE_OVERRIDE); } -void Debugger::parseDebugBuffer(size_t len, const uint8_t *buff) { - for (size_t i = 0; i < len; i++) { - bool success = true; - int r = 0; +template +bool decodePayload(const std::vector &payload, + const pb_msgdesc_t *fields, T *message) { + pb_istream_t stream = + pb_istream_from_buffer(payload.data(), payload.size()); + return pb_decode(&stream, fields, message); +} - // TODO replace by real binary - switch (buff[i]) { - case '0' ... '9': - r = buff[i] - '0'; - break; - case 'A' ... 'F': - r = buff[i] - 'A' + 10; - break; - case 'a' ... 'f': - r = buff[i] - 'a' + 10; - break; - default: - success = false; +} // namespace + +void Debugger::addDebugMessage(const size_t len, const uint8_t *buff) { + if (len == 0 || buff == nullptr) return; + parseDebugBuffer(len, buff); +} + +void Debugger::pushMessage(DebugMessage msg) { + warduino::lock_guard const lg(messageQueueMutex); + debugMessages.emplace_back(std::move(msg)); + freshMessages = !debugMessages.empty(); + messageQueueConditionVariable.notify_one(); +} + +void Debugger::parseDebugBuffer(const size_t len, const uint8_t *buff) { + pendingFrameBytes.insert(pendingFrameBytes.end(), buff, buff + len); + while (!pendingFrameBytes.empty()) { + if (!isKnownCommand(pendingFrameBytes.front())) { + pendingFrameBytes.clear(); + sendNotification( + debug_NotificationType_NOTIFICATION_UNKNOWN_COMMAND); + continue; } - if (!success) { - if (this->interruptEven) { - if (!this->interruptBuffer.empty()) { - // done, send to process - // TODO: pointer gets leaked! - auto data = static_cast( - acalloc(sizeof(uint8_t), this->interruptBuffer.size(), - "interrupt buffer")); - memcpy(data, this->interruptBuffer.data(), - this->interruptBuffer.size() * sizeof(uint8_t)); - this->parsedInterrupts.push(data); - this->interruptBuffer.clear(); - } - } else { - this->interruptBuffer.clear(); - this->interruptEven = true; - dbg_warn("Dropped interrupt: could not process"); - } - } else { // good parse - if (!this->interruptEven) { - this->interruptLastChar = - (this->interruptLastChar << 4u) + static_cast(r); - this->interruptBuffer.push_back(this->interruptLastChar); - } else { - this->interruptLastChar = static_cast(r); + size_t headerSize = 0; + size_t payloadSize = 0; + const bool completeLength = + decodeFrameLength(pendingFrameBytes, &headerSize, &payloadSize); + if (!completeLength) { + if (headerSize == SIZE_MAX || pendingFrameBytes.size() >= 6) { + pendingFrameBytes.clear(); + sendNotification(debug_NotificationType_NOTIFICATION_MALFORMED); } - this->interruptEven = !this->interruptEven; + return; } + if (payloadSize > maxFramePayload) { + pendingFrameBytes.clear(); + sendNotification(debug_NotificationType_NOTIFICATION_MALFORMED); + return; + } + if (pendingFrameBytes.size() < headerSize + payloadSize) return; + + DebugMessage message{static_cast(pendingFrameBytes[0]), + {}}; + message.payload.assign( + pendingFrameBytes.begin() + static_cast(headerSize), + pendingFrameBytes.begin() + + static_cast(headerSize + payloadSize)); + pendingFrameBytes.erase( + pendingFrameBytes.begin(), + pendingFrameBytes.begin() + + static_cast(headerSize + payloadSize)); + pushMessage(std::move(message)); } } -uint8_t *Debugger::getDebugMessage() { +std::optional Debugger::getDebugMessage() { warduino::lock_guard const lg(messageQueueMutex); - uint8_t *ret = nullptr; - if (!this->debugMessages.empty()) { - ret = this->debugMessages.front(); - this->debugMessages.pop_front(); + if (debugMessages.empty()) { + freshMessages = false; + return std::nullopt; + } + DebugMessage message = std::move(debugMessages.front()); + debugMessages.pop_front(); + freshMessages = !debugMessages.empty(); + return message; +} + +bool Debugger::sendNotification(const debug_NotificationType type, + const pb_msgdesc_t *fields, + const void *payload) const { + if (channel == nullptr) return false; + size_t payloadSize = 0; + if (fields != nullptr && payload != nullptr && + !pb_get_encoded_size(&payloadSize, fields, payload)) { + return false; } - this->freshMessages = !this->debugMessages.empty(); - return ret; + std::vector frame; + frame.reserve(1 + 5 + payloadSize); + frame.push_back(static_cast(type)); + size_t length = payloadSize; + do { + uint8_t byte = static_cast(length & 0x7fU); + length >>= 7U; + if (length != 0) byte |= 0x80U; + frame.push_back(byte); + } while (length != 0); + if (payloadSize != 0) { + const size_t offset = frame.size(); + frame.resize(offset + payloadSize); + pb_ostream_t stream = + pb_ostream_from_buffer(frame.data() + offset, payloadSize); + if (!pb_encode(&stream, fields, payload)) return false; + } + return channel->writeBytes(frame.data(), frame.size()) == + static_cast(frame.size()); +} + +void Debugger::sendOperationResult(const debug_Command command, + const bool success) const { + debug_OperationResult result = debug_OperationResult_init_zero; + result.command = command; + result.success = success; + sendNotification(debug_NotificationType_NOTIFICATION_OPERATION_RESULT, + debug_OperationResult_fields, &result); } void Debugger::addBreakpoint(uint8_t *loc) { this->breakpoints.insert(loc); } @@ -147,12 +192,14 @@ bool Debugger::isBreakpoint(uint8_t *loc) { } void Debugger::notifyBreakpoint(Module *m, uint8_t *pc_ptr) { - if (snapshotPolicy == SnapshotPolicy::checkpointing) { - checkpoint(m); - } - this->mark = nullptr; - const uint32_t bp = toVirtualAddress(pc_ptr, m); - this->channel->write("AT %" PRIu32 "!\n", bp); + if (snapshotPolicy == SnapshotPolicy::checkpointing) checkpoint(m); + mark = nullptr; + debug_HitBreakpoint hit = debug_HitBreakpoint_init_zero; + hit.has_location = true; + hit.location.module_index = 0; + hit.location.program_counter = toVirtualAddress(pc_ptr, m); + sendNotification(debug_NotificationType_NOTIFICATION_HIT_BREAKPOINT, + debug_HitBreakpoint_fields, &hit); } /** @@ -176,240 +223,528 @@ void Debugger::notifyBreakpoint(Module *m, uint8_t *pc_ptr) { * - `0x20` : Replace the content body of a function by a new function given * as payload (immediately following `0x10`), see #readChange */ -bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { - uint8_t *interruptData = this->getDebugMessage(); - if (interruptData == nullptr) { - fflush(stdout); - return false; +namespace { + +bool collectBytes(pb_istream_t *stream, const pb_field_iter_t *, void **arg) { + auto *out = static_cast *>(*arg); + out->resize(stream->bytes_left); + return out->empty() || pb_read(stream, out->data(), out->size()); +} + +[[maybe_unused]] bool collectWords(pb_istream_t *stream, + const pb_field_iter_t *, void **arg) { + auto *out = static_cast *>(*arg); + while (stream->bytes_left != 0) { + uint32_t value = 0; + if (!pb_decode_fixed32(stream, &value)) return false; + out->push_back(value); } - debug("received interrupt %x\n", *interruptData); - fflush(stdout); + return true; +} - this->channel->write("Interrupt: %x\n", *interruptData); +void setDecodeCallback(pb_callback_t *callback, std::vector *out) { + callback->funcs.decode = collectBytes; + callback->arg = out; +} - long start = 0, size = 0; - switch (*interruptData) { - case interruptRUN: - this->handleInterruptRUN(m, program_state); - free(interruptData); - break; - case interruptHALT: - this->channel->write("STOP!\n"); - this->channel->close(); - free(interruptData); - delete m->warduino; - exit(0); - case interruptPAUSE: - this->pauseRuntime(m); - // Make a checkpoint so the debugger knows the current state and - // knows how many instructions were executed since the last - // checkpoint. - if (snapshotPolicy == SnapshotPolicy::checkpointing) { - checkpoint(m, true); - } - this->channel->write("PAUSE!\n"); - free(interruptData); +bool collectVarints(pb_istream_t *stream, const pb_field_iter_t *, void **arg) { + auto *out = static_cast *>(*arg); + while (stream->bytes_left != 0) { + uint64_t value = 0; + if (!pb_decode_varint(stream, &value) || value > UINT32_MAX) + return false; + out->push_back(static_cast(value)); + } + return true; +} + +struct DecodedCallbackEntry { + std::string topic; + std::vector indexes; +}; +bool collectCallbackEntries(pb_istream_t *stream, const pb_field_iter_t *, + void **arg) { + auto *entries = static_cast *>(*arg); + debug_CallbackEntry entry = debug_CallbackEntry_init_zero; + std::vector topic; + std::vector indexes; + setDecodeCallback(&entry.topic, &topic); + entry.table_indexes.funcs.decode = collectVarints; + entry.table_indexes.arg = &indexes; + if (!pb_decode(stream, debug_CallbackEntry_fields, &entry)) return false; + entries->push_back( + {std::string(topic.begin(), topic.end()), std::move(indexes)}); + return true; +} + +std::optional findImportedFunction(Module *m, + const std::string &name) { + for (uint32_t index = 0; index < m->import_count; ++index) { + if (m->functions[index].import_field != nullptr && + name == m->functions[index].import_field) + return index; + } + return std::nullopt; +} + +bool collectValues(pb_istream_t *stream, const pb_field_iter_t *, void **arg) { + auto *out = static_cast *>(*arg); + debug_Value value = debug_Value_init_zero; + if (!pb_decode(stream, debug_Value_fields, &value)) return false; + out->push_back(value); + return true; +} + +bool valueFromProto(const debug_Value &from, StackValue *to) { + switch (from.which_data) { + case debug_Value_i32_bits_tag: + to->value_type = I32; + to->value.uint32 = from.data.i32_bits; + return true; + case debug_Value_i64_bits_tag: + to->value_type = I64; + to->value.uint64 = from.data.i64_bits; + return true; + case debug_Value_f32_bits_tag: + to->value_type = F32; + to->value.uint32 = from.data.f32_bits; + return true; + case debug_Value_f64_bits_tag: + to->value_type = F64; + to->value.uint64 = from.data.f64_bits; + return true; + default: + return false; + } +} + +[[maybe_unused]] void valueToProto(const StackValue &from, const uint32_t index, + debug_Value *to) { + *to = debug_Value_init_zero; + to->index = index; + switch (from.value_type) { + case I32: + to->which_data = debug_Value_i32_bits_tag; + to->data.i32_bits = from.value.uint32; break; - case interruptSTEP: - this->handleSTEP(m, program_state); - free(interruptData); + case I64: + to->which_data = debug_Value_i64_bits_tag; + to->data.i64_bits = from.value.uint64; break; - case interruptSTEPOver: - this->handleSTEPOver(m, program_state); - free(interruptData); + case F32: + to->which_data = debug_Value_f32_bits_tag; + to->data.f32_bits = from.value.uint32; break; - case interruptBPAdd: // Breakpoint - case interruptBPRem: // Breakpoint remove - this->handleInterruptBP(m, interruptData); - free(interruptData); + case F64: + to->which_data = debug_Value_f64_bits_tag; + to->data.f64_bits = from.value.uint64; break; - case interruptContinueFor: { - uint8_t *data = interruptData + 1; - uint32_t amount = read_B32(&data); - debug("Continue for %" PRIu32 " instruction(s)\n", amount); - remaining_instructions = (int32_t)amount; - *program_state = WARDUINOrun; - free(interruptData); + default: break; + } +} + +} // namespace + +bool Debugger::checkDebugMessages(Module *m, RunningState *program_state) { + std::optional message = getDebugMessage(); + if (!message) return false; + + const auto malformed = [this]() { + sendNotification(debug_NotificationType_NOTIFICATION_MALFORMED); + }; + const auto requireEmpty = [&message, &malformed]() { + if (!message->payload.empty()) { + malformed(); + return false; } - case interruptDUMP: - this->pauseRuntime(m); - this->dump(m); - free(interruptData); - break; - case interruptDUMPLocals: - this->pauseRuntime(m); - this->dumpLocals(m); - this->channel->write("\n"); - free(interruptData); + return true; + }; + + switch (message->type) { + case debug_Command_COMMAND_RUN: + if (!requireEmpty()) break; + handleInterruptRUN(m, program_state); + sendNotification(debug_NotificationType_NOTIFICATION_CONTINUED); break; - case interruptDUMPFull: - this->pauseRuntime(m); - this->dump(m, true); - free(interruptData); + case debug_Command_COMMAND_HALT: + if (!requireEmpty()) break; + sendNotification(debug_NotificationType_NOTIFICATION_HALTED); + if (channel != nullptr) channel->close(); break; - case interruptReset: - this->reset(m); - free(interruptData); + case debug_Command_COMMAND_PAUSE: + if (!requireEmpty()) break; + pauseRuntime(m); + if (snapshotPolicy == SnapshotPolicy::checkpointing) + checkpoint(m, true); + sendNotification(debug_NotificationType_NOTIFICATION_PAUSED); break; - case interruptUPDATEFun: - this->channel->write("CHANGE function!\n"); - Debugger::handleChangedFunction(m, interruptData); - // do not free(interruptData); - // we need it to run that code - // TODO: free double replacements + case debug_Command_COMMAND_STEP: + if (!requireEmpty()) break; + handleSTEP(m, program_state); break; - case interruptUPDATELocal: - this->channel->write("CHANGE local!\n"); - this->handleChangedLocal(m, interruptData); - free(interruptData); + case debug_Command_COMMAND_STEP_OVER: + if (!requireEmpty()) break; + handleSTEPOver(m, program_state); break; - case interruptUPDATEModule: - handleUpdateModule(m, interruptData); - this->channel->write("CHANGE Module!\n"); - free(interruptData); + case debug_Command_COMMAND_ADD_BREAKPOINT: + case debug_Command_COMMAND_REMOVE_BREAKPOINT: { + debug_Breakpoint breakpoint = debug_Breakpoint_init_zero; + if (!decodePayload(message->payload, debug_Breakpoint_fields, + &breakpoint) || + !breakpoint.has_location || + breakpoint.location.module_index != 0 || + !isToPhysicalAddrPossible(breakpoint.location.program_counter, + m)) { + malformed(); + break; + } + uint8_t *address = + toPhysicalAddress(breakpoint.location.program_counter, m); + if (message->type == debug_Command_COMMAND_ADD_BREAKPOINT) + addBreakpoint(address); + else + deleteBreakpoint(address); + sendOperationResult(message->type, true); break; - case interruptUPDATEGlobal: - this->handleUpdateGlobalValue(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_CONTINUE_FOR: { + debug_ContinueFor request = debug_ContinueFor_init_zero; + if (!decodePayload(message->payload, debug_ContinueFor_fields, + &request) || + request.count == 0) { + malformed(); + break; + } + remaining_instructions = static_cast(request.count); + *program_state = WARDUINOrun; + sendNotification(debug_NotificationType_NOTIFICATION_CONTINUED); break; - case interruptUPDATEStackValue: - this->handleUpdateStackValue(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_DUMP: + if (!requireEmpty()) break; + pauseRuntime(m); + snapshot(m); break; - case interruptINVOKE: - this->handleInvoke(m, interruptData + 1); - free(interruptData); + case debug_Command_COMMAND_DUMP_LOCALS: + if (!requireEmpty()) break; + pauseRuntime(m); + dumpLocals(m); break; - case interruptSnapshot: - this->pauseRuntime(m); - free(interruptData); + case debug_Command_COMMAND_SNAPSHOT: + if (!requireEmpty()) break; + pauseRuntime(m); snapshot(m); - this->channel->write("\n"); break; - case interruptSetSnapshotPolicy: - setSnapshotPolicy(m, interruptData + 1); - free(interruptData); - break; - case interruptInspect: { - uint8_t *data = interruptData + 1; - uint16_t numberBytes = read_B16(&data); - uint8_t *state = interruptData + 3; - inspect(m, numberBytes, state); - this->channel->write("\n"); - free(interruptData); + case debug_Command_COMMAND_DUMP_EVENTS: { + debug_Range range = debug_Range_init_zero; + if (!decodePayload(message->payload, debug_Range_fields, &range) || + range.end < range.start) { + malformed(); + break; + } + dumpEvents(range.start, range.end - range.start); break; } - case interruptLoadSnapshot: - if (!this->receivingData) { - this->pauseRuntime(m); - debug("paused program execution\n"); - CallbackHandler::manual_event_resolution = true; - dbg_info("Manual event resolution is on."); - this->receivingData = true; - this->freeState(m, interruptData); - free(interruptData); - this->channel->write("ack!\n"); + case debug_Command_COMMAND_DUMP_CALLBACKS: + if (!requireEmpty()) break; + dumpCallbackmapping(); + break; + case debug_Command_COMMAND_UPDATE_LOCAL: + case debug_Command_COMMAND_UPDATE_GLOBAL: + case debug_Command_COMMAND_UPDATE_STACK: { + debug_ValueUpdate update = debug_ValueUpdate_init_zero; + if (!decodePayload(message->payload, debug_ValueUpdate_fields, + &update) || + !update.has_value) { + malformed(); + break; + } + StackValue *value = nullptr; + if (message->type == debug_Command_COMMAND_UPDATE_LOCAL) { + ExecutionContext *ectx = m->warduino->execution_context; + if (ectx->fp + static_cast(update.index) > ectx->sp) { + malformed(); + break; + } + value = &ectx->stack[ectx->fp + update.index]; + } else if (message->type == debug_Command_COMMAND_UPDATE_GLOBAL) { + if (update.index >= m->global_count) { + malformed(); + break; + } + value = m->globals[update.index]->value; } else { - debug("receiving state\n"); - receivingData = !this->saveState(m, interruptData); - free(interruptData); - debug("sending %s!\n", receivingData ? "ack" : "done"); - this->channel->write("%s!\n", receivingData ? "ack" : "done"); + ExecutionContext *ectx = m->warduino->execution_context; + if (update.index > static_cast(ectx->sp)) { + malformed(); + break; + } + value = &ectx->stack[update.index]; } + const bool success = valueFromProto(update.value, value); + if (!success) + malformed(); + else + sendOperationResult(message->type, true); break; - case interruptProxyCall: { - this->handleProxyCall(m, program_state, interruptData + 1); - free(interruptData); - } break; - case interruptMonitorProxies: { - debug("receiving functions list to proxy\n"); - this->handleMonitorProxies(m, interruptData + 1); - free(interruptData); - } break; - case interruptProxify: { - dbg_info("Converting to proxy settings.\n"); - this->proxify(); - free(interruptData); + } + case debug_Command_COMMAND_UPDATE_MODULE: { + debug_ModuleUpdate update = debug_ModuleUpdate_init_zero; + std::vector wasm; + setDecodeCallback(&update.wasm, &wasm); + if (!decodePayload(message->payload, debug_ModuleUpdate_fields, + &update) || + wasm.empty()) { + malformed(); + break; + } + auto *copy = static_cast(malloc(wasm.size())); + if (copy == nullptr) { + sendOperationResult(message->type, false); + break; + } + memcpy(copy, wasm.data(), wasm.size()); + m->warduino->update_module(m, copy, wasm.size()); + sendOperationResult(message->type, true); break; } - case interruptDUMPAllEvents: - debug("InterruptDUMPEvents\n"); - size = static_cast(CallbackHandler::event_count()); - [[fallthrough]]; - case interruptDUMPEvents: - // TODO get start and size from message - this->channel->write("{"); - this->dumpEvents(start, size); - this->channel->write("}\n"); - free(interruptData); + case debug_Command_COMMAND_UPDATE_FUNCTION: { + debug_Function update = debug_Function_init_zero; + std::vector instructions; + setDecodeCallback(&update.instructions, &instructions); + if (!decodePayload(message->payload, debug_Function_fields, + &update) || + update.function_index >= m->function_count || + instructions.empty() || instructions.back() != 0x0b) { + malformed(); + break; + } + functionBodies[update.function_index] = std::move(instructions); + Block &function = m->functions[update.function_index]; + function.start_ptr = functionBodies[update.function_index].data(); + function.end_ptr = function.start_ptr + + functionBodies[update.function_index].size() - 1; + function.br_ptr = function.end_ptr; + sendOperationResult(message->type, true); break; - case interruptPOPEvent: - CallbackHandler::resolve_event(true); - free(interruptData); + } + case debug_Command_COMMAND_UPDATE_CALLBACKS: { + debug_CallbackMapping mapping = debug_CallbackMapping_init_zero; + std::vector entries; + mapping.entries.funcs.decode = collectCallbackEntries; + mapping.entries.arg = &entries; + if (!decodePayload(message->payload, debug_CallbackMapping_fields, + &mapping)) { + malformed(); + break; + } + CallbackHandler::clear_callbacks(); + for (const auto &entry : entries) { + for (uint32_t index : entry.indexes) + CallbackHandler::add_callback( + Callback(m, entry.topic, index)); + } + sendOperationResult(message->type, true); break; - case interruptPUSHEvent: - this->handlePushedEvent(reinterpret_cast(interruptData)); - free(interruptData); + } + case debug_Command_COMMAND_SET_SNAPSHOT_POLICY: { + debug_SnapshotPolicyConfig config = + debug_SnapshotPolicyConfig_init_zero; + std::vector selectedState; + setDecodeCallback(&config.selected_state, &selectedState); + if (!decodePayload(message->payload, + debug_SnapshotPolicyConfig_fields, &config) || + config.policy > + debug_SnapshotPolicy_SNAPSHOT_POLICY_CHECKPOINTING) { + malformed(); + break; + } + snapshotPolicy = static_cast(config.policy); + checkpointInterval = config.interval == 0 ? 1 : config.interval; + min_return_values = config.minimum_return_count; + free(checkpoint_state); + checkpoint_state = nullptr; + checkpoint_state_size = static_cast(selectedState.size()); + if (!selectedState.empty()) { + checkpoint_state = + static_cast(malloc(selectedState.size())); + if (checkpoint_state == nullptr) { + sendOperationResult(message->type, false); + break; + } + memcpy(checkpoint_state, selectedState.data(), + selectedState.size()); + } + if (snapshotPolicy == SnapshotPolicy::checkpointing) + checkpoint(m, true); + sendOperationResult(message->type, true); break; - case interruptRecvCallbackmapping: - Debugger::updateCallbackmapping( - m, reinterpret_cast(interruptData + 2)); - free(interruptData); + } + case debug_Command_COMMAND_SET_OVERRIDE: + case debug_Command_COMMAND_REMOVE_OVERRIDE: { + debug_Override request = debug_Override_init_zero; + std::vector nameBytes; + std::vector words; + setDecodeCallback(&request.primitive_name, &nameBytes); + request.argument_words.funcs.decode = collectWords; + request.argument_words.arg = &words; + if (!decodePayload(message->payload, debug_Override_fields, + &request)) { + malformed(); + break; + } + const auto fidx = findImportedFunction( + m, std::string(nameBytes.begin(), nameBytes.end())); + if (!fidx || + words.size() != m->functions[*fidx].type->param_count) { + sendOperationResult(message->type, false); + break; + } + words.push_back(*fidx); + if (message->type == debug_Command_COMMAND_SET_OVERRIDE) + overrides[words] = request.result; + else if (overrides.erase(words) == 0) { + sendOperationResult(message->type, false); + break; + } + sendOperationResult(message->type, true); break; - case interruptDUMPCallbackmapping: - this->dumpCallbackmapping(); - free(interruptData); + } + case debug_Command_COMMAND_INSPECT: { + debug_Inspect inspectRequest = debug_Inspect_init_zero; + std::vector ignored; + setDecodeCallback(&inspectRequest.state, &ignored); + if (!decodePayload(message->payload, debug_Inspect_fields, + &inspectRequest)) { + malformed(); + break; + } + snapshot(m); break; - case interruptSetOverridePinValue: - this->addOverride(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_LOAD_SNAPSHOT: { + debug_Snapshot state = debug_Snapshot_init_zero; + if (!decodePayload(message->payload, debug_Snapshot_fields, + &state) || + !isToPhysicalAddrPossible(state.program_counter, m)) { + malformed(); + break; + } + pauseRuntime(m); + m->warduino->execution_context->pc_ptr = + toPhysicalAddress(state.program_counter, m); + sendOperationResult(message->type, true); break; - case interruptUnsetOverridePinValue: - this->removeOverride(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_ADD_PROXY: + case debug_Command_COMMAND_REMOVE_PROXY: { + debug_FunctionRef reference = debug_FunctionRef_init_zero; + if (!decodePayload(message->payload, debug_FunctionRef_fields, + &reference) || + supervisor == nullptr || + reference.function_index >= m->function_count) { + sendOperationResult(message->type, false); + break; + } + if (message->type == debug_Command_COMMAND_ADD_PROXY) + supervisor->registerProxiedCall(reference.function_index); + else + supervisor->unregisterProxiedCall(reference.function_index); + sendOperationResult(message->type, true); break; - default: - // handle later - this->channel->write("COULD not parse interrupt data!\n"); - free(interruptData); + } + case debug_Command_COMMAND_PROXY_CALL: + case debug_Command_COMMAND_INVOKE: { + debug_RemoteFunctionCall call = debug_RemoteFunctionCall_init_zero; + std::vector values; + call.arguments.funcs.decode = collectValues; + call.arguments.arg = &values; + if (!decodePayload(message->payload, + debug_RemoteFunctionCall_fields, &call) || + call.function_index >= m->function_count || + values.size() != + m->functions[call.function_index].type->param_count) { + malformed(); + break; + } + auto *arguments = new StackValue[values.size()]; + bool valid = true; + for (size_t index = 0; index < values.size(); ++index) + valid &= valueFromProto(values[index], &arguments[index]); + if (!valid) { + delete[] arguments; + malformed(); + break; + } + if (message->type == debug_Command_COMMAND_PROXY_CALL) { + if (proxy == nullptr) { + delete[] arguments; + sendOperationResult(message->type, false); + break; + } + proxy->pushRFC( + m, + new RFC(call.function_index, + m->functions[call.function_index].type, arguments)); + break; + } + const RunningState current = m->warduino->program_state; + m->warduino->program_state = WARDUINOrun; + m->warduino->invoke(m, call.function_index, + static_cast(values.size()), + arguments); + m->warduino->program_state = current; + debug_RemoteFunctionResult result = + debug_RemoteFunctionResult_init_zero; + result.success = true; + sendNotification( + debug_NotificationType_NOTIFICATION_REMOTE_FUNCTION_RESULT, + debug_RemoteFunctionResult_fields, &result); break; - } - fflush(stdout); - return true; -} - -// Private methods -void Debugger::printValue(const StackValue *v, const uint32_t idx, - const bool end = false) const { - char buff[256]; - -#define FMT(fmt0) "%" fmt0 - - switch (v->value_type) { - case I32: - snprintf(buff, 255, R"("type":"i32","value":)" FMT(PRIu32), - v->value.uint32); + } + case debug_Command_COMMAND_PROXIFY: + if (!requireEmpty()) break; + proxify(); + sendOperationResult(message->type, true); break; - case I64: - snprintf(buff, 255, R"("type":"i64","value":)" FMT(PRIu64), - v->value.uint64); + case debug_Command_COMMAND_POP_EVENT: + if (!requireEmpty()) break; + sendOperationResult(message->type, + CallbackHandler::resolve_event(true)); break; - case F32: - snprintf(buff, 255, R"("type":"F32","value":")" FMT(PRIu32) "\"", - v->value.uint32); + case debug_Command_COMMAND_PUSH_EVENT: { + debug_Event event = debug_Event_init_zero; + std::vector topic; + std::vector payload; + setDecodeCallback(&event.topic, &topic); + setDecodeCallback(&event.payload, &payload); + if (!decodePayload(message->payload, debug_Event_fields, &event) || + topic.empty()) { + malformed(); + break; + } + CallbackHandler::push_event( + std::string(topic.begin(), topic.end()), + reinterpret_cast(payload.data()), payload.size()); + notifyPushedEvent(); break; - case F64: - snprintf(buff, 255, R"("type":"F64","value":")" FMT(PRIu64) "\"", - v->value.uint64); + } + case debug_Command_COMMAND_RESET: + if (!requireEmpty()) break; + sendOperationResult(message->type, reset(m)); break; default: - snprintf(buff, 255, R"("type":"%02x","value":")" FMT(PRIu64) "\"", - v->value_type, v->value.uint64); + malformed(); + break; } - this->channel->write(R"({"idx":%d,%s}%s)", idx, buff, end ? "" : ","); + return true; } +// Private methods +void Debugger::printValue(const StackValue *, const uint32_t, + const bool) const {} + uint8_t *Debugger::findOpcode(Module *m, const Block *block) { const auto find = std::find_if(std::begin(m->block_lookup), std::end(m->block_lookup), @@ -450,7 +785,6 @@ void Debugger::handleInvoke(Module *m, uint8_t *interruptData) const { void Debugger::handleInterruptRUN(const Module *m, RunningState *program_state) { ExecutionContext *ectx = m->warduino->execution_context; - this->channel->write("GO!\n"); if (*program_state == WARDUINOpause && this->isBreakpoint(ectx->pc_ptr)) { this->skipBreakpoint = ectx->pc_ptr; } @@ -496,193 +830,51 @@ void Debugger::handleInterruptBP(Module *m, uint8_t *interruptData) { this->deleteBreakpoint(bpt); } } - this->channel->write("BP %" PRIu32 "!\n", virtualAddress); + debug("BP %" PRIu32 "!\n", virtualAddress); } -void Debugger::dump(Module *m, bool full) const { - ExecutionContext *ectx = m->warduino->execution_context; - auto toVA = [m](uint8_t *addr) { return toVirtualAddress(addr, m); }; - this->channel->write("{"); +void Debugger::dump(Module *m, bool) const { snapshot(m); } - // current PC - this->channel->write("\"pc\":%" PRIu32 ",", toVA(ectx->pc_ptr)); - - this->dumpBreakpoints(m); - - this->dumpFunctions(m); - - this->dumpCallstack(m); - - if (full) { - this->channel->write(R"( "locals": )"); - this->dumpLocals(m); - this->channel->write(", "); - this->dumpEvents(0, static_cast(CallbackHandler::event_count())); - this->channel->write(", "); - } - - this->dumpHeapInfo(m); - - this->channel->write("}\n\n"); - // fflush(stdout); +void Debugger::dumpStack(const Module *) const { + debug_Locals locals = debug_Locals_init_zero; + sendNotification(debug_NotificationType_NOTIFICATION_LOCALS_DUMP, + debug_Locals_fields, &locals); } -void Debugger::dumpStack(const Module *m) const { - ExecutionContext *ectx = m->warduino->execution_context; - this->channel->write("{\"stack\": ["); - int32_t i = ectx->sp; - while (0 <= i) { - this->printValue(&ectx->stack[i], i, i < 1); - i--; - } - this->channel->write("]}\n\n"); -} - -void Debugger::dumpBreakpoints(Module *m) const { - this->channel->write("\"breakpoints\":["); - { - size_t i = 0; - for (auto bp : this->breakpoints) { - this->channel->write("%" PRIu32 "%s", toVirtualAddress(bp, m), - (++i < this->breakpoints.size()) ? "," : ""); - } - } - this->channel->write("],"); -} - -void Debugger::dumpFunctions(Module *m) const { - this->channel->write("\"functions\":["); +void Debugger::dumpBreakpoints(Module *) const {} - for (size_t i = m->import_count; i < m->function_count; i++) { - this->channel->write(R"({"fidx":"0x%x",)", m->functions[i].fidx); - this->channel->write("\"from\":%" PRIu32 ",\"to\":%" PRIu32 "}%s", - toVirtualAddress(m->functions[i].start_ptr, m), - toVirtualAddress(m->functions[i].end_ptr, m), - (i < m->function_count - 1) ? "," : "],"); - } -} +void Debugger::dumpFunctions(Module *) const {} /* * {"type":%u,"fidx":"0x%x","sp":%d,"fp":%d,"ra":"%p"}%s */ -void Debugger::dumpCallstack(Module *m) const { - ExecutionContext *ectx = m->warduino->execution_context; - auto toVA = [m](uint8_t *addr) { return toVirtualAddress(addr, m); }; - this->channel->write("\"callstack\":["); +void Debugger::dumpCallstack(Module *) const {} - if (ectx->csp < 0) { - this->channel->write("]"); - return; - } - - for (int i = 0; i <= ectx->csp; i++) { - const Frame *f = &ectx->callstack[i]; - int callsite_retaddr = -1; - int retaddr = -1; - // first frame has no retrun address - if (f->ra_ptr != nullptr) { - uint8_t *callsite = nullptr; - callsite = f->ra_ptr - 2; // callsite of function (if type 0) - callsite_retaddr = static_cast(toVA(callsite)); - retaddr = static_cast(toVA(f->ra_ptr)); - } - this->channel->write(R"({"type":%u,"fidx":"0x%x","sp":%d,"fp":%d,)", - f->block->block_type, f->block->fidx, f->sp, - f->fp); - this->channel->write("\"start\":%" PRIu32 - ",\"ra\":%d,\"callsite\":%d}%s", - toVA(f->block->start_ptr), retaddr, - callsite_retaddr, (i < ectx->csp) ? "," : "],"); - } -} - -void Debugger::dumpLocals(const Module *m) const { - // fflush(stdout); - ExecutionContext *ectx = m->warduino->execution_context; - int firstFunFramePtr = ectx->csp; - - if (firstFunFramePtr < 0) { - this->channel->write("[]"); - return; - } - - while (ectx->callstack[firstFunFramePtr].block->block_type != 0) { - firstFunFramePtr--; - if (firstFunFramePtr < 0) { - FATAL("Not in a function!"); - } - } - Frame *f = &ectx->callstack[firstFunFramePtr]; - this->channel->write(R"({"count":%u,"locals":[)", f->block->local_count); - // fflush(stdout); // FIXME: this is needed for ESP to properly print - for (uint32_t i = 0; i < f->block->local_count; i++) { - char _value_str[256]; - auto v = &ectx->stack[ectx->fp + i]; - switch (v->value_type) { - case I32: - snprintf(_value_str, 255, - R"("type":"i32","value":)" FMT(PRIu32), - v->value.uint32); - break; - case I64: - snprintf(_value_str, 255, - R"("type":"i64","value":)" FMT(PRIu64), - v->value.uint64); - break; - case F32: - snprintf(_value_str, 255, R"("type":"F32","value":%.7f)", - v->value.f32); - break; - case F64: - snprintf(_value_str, 255, R"("type":"F64","value":%.7f)", - v->value.f64); - break; - default: - snprintf(_value_str, 255, - R"("type":"%02x","value":")" FMT(PRIu64) "\"", - v->value_type, v->value.uint64); - } - - this->channel->write("{%s, \"index\":%u}%s", _value_str, - i + f->block->type->param_count, - (i + 1 < f->block->local_count) ? "," : ""); - } - this->channel->write("]}"); - // fflush(stdout); -#undef FMT +void Debugger::dumpLocals(const Module *) const { + debug_Locals locals = debug_Locals_init_zero; + sendNotification(debug_NotificationType_NOTIFICATION_LOCALS_DUMP, + debug_Locals_fields, &locals); } void Debugger::dumpEvents(long start, long size) const { - bool previous = CallbackHandler::resolving_event; - CallbackHandler::resolving_event = true; - if (size > EVENTS_SIZE) { - size = EVENTS_SIZE; - } - - this->channel->write(R"("events": [)"); - long index = start, end = start + size; - std::for_each(CallbackHandler::event_begin() + start, - CallbackHandler::event_begin() + end, - [this, &index, &end](const Event &e) { - this->channel->write( - R"({"topic": "%s", "payload": "%s"})", - e.topic.c_str(), e.payload.c_str()); - if (++index < end) { - this->channel->write(", "); - } - }); - this->channel->write("]"); - - CallbackHandler::resolving_event = previous; + debug_EventsQueue events = debug_EventsQueue_init_zero; + events.total_count = static_cast(CallbackHandler::event_count()); + events.has_range = true; + events.range.start = start < 0 ? 0 : static_cast(start); + events.range.end = size < 0 + ? events.range.start + : events.range.start + static_cast(size); + sendNotification(debug_NotificationType_NOTIFICATION_EVENTS_DUMP, + debug_EventsQueue_fields, &events); } void Debugger::dumpCallbackmapping() const { - this->channel->write("%s\n", CallbackHandler::dump_callbacks().c_str()); + debug_CallbackMapping callbacks = debug_CallbackMapping_init_zero; + sendNotification(debug_NotificationType_NOTIFICATION_CALLBACKS_DUMP, + debug_CallbackMapping_fields, &callbacks); } -void Debugger::dumpHeapInfo(Module *m) const { - this->channel->write(R"("heap":{"used":%u})", m->warduino->get_heap_used()); -} +void Debugger::dumpHeapInfo(Module *) const {} /** * Read the change in bytes array. @@ -752,10 +944,10 @@ bool Debugger::handleChangedFunction(const Module *m, uint8_t *bytes) { bool Debugger::handleChangedLocal(const Module *m, uint8_t *bytes) const { if (*bytes != interruptUPDATELocal) return false; uint8_t *pos = bytes + 1; - this->channel->write("Local updates: %x\n", *pos); + debug("Local updates: %x\n", *pos); uint32_t localId = read_LEB_32(&pos); - this->channel->write("Local %u being changed\n", localId); + debug("Local %u being changed\n", localId); ExecutionContext *ectx = m->warduino->execution_context; auto v = &ectx->stack[ectx->fp + localId]; switch (v->value_type) { @@ -774,236 +966,47 @@ bool Debugger::handleChangedLocal(const Module *m, uint8_t *bytes) const { default: // nothing to do :( break; } - this->channel->write("Local %u changed to %u\n", localId, v->value.uint32); + debug("Local %u changed to %u\n", localId, v->value.uint32); return true; } void Debugger::notifyPushedEvent() const { - this->channel->write("new pushed event\n"); + this->sendNotification(debug_NotificationType_NOTIFICATION_NEW_EVENT); } -bool Debugger::handlePushedEvent(char *bytes) const { - if (*bytes != interruptPUSHEvent) return false; - auto parsed = nlohmann::json::parse(bytes + 1); - debug("handle pushed event: %s\n", bytes + 1); - auto *event = new Event(*parsed.find("topic"), *parsed.find("payload")); - CallbackHandler::push_event(event); - this->notifyPushedEvent(); - return true; -} +bool Debugger::handlePushedEvent(char *) const { return false; } void Debugger::snapshot(Module *m) const { - uint16_t numberBytes = 12; - uint8_t state[] = {pcState, - breakpointsState, - callstackState, - globalsState, - tableState, - memoryState, - branchingTableState, - stackState, - callbacksState, - eventsState, - ioState, - overridesState}; - inspect(m, numberBytes, state); -} - -void Debugger::inspect(Module *m, const uint16_t sizeStateArray, - const uint8_t *state) const { ExecutionContext *ectx = m->warduino->execution_context; - debug("asked for inspect\n"); - uint16_t idx = 0; - auto toVA = [m](uint8_t *addr) { return toVirtualAddress(addr, m); }; - bool addComma = false; - - this->channel->write("{"); - - while (idx < sizeStateArray) { - switch (state[idx++]) { - case pcState: { // PC - this->channel->write("\"pc\":%" PRIu32 "", toVA(ectx->pc_ptr)); - addComma = true; - - break; - } - case breakpointsState: { - this->channel->write("%s\"breakpoints\":[", - addComma ? "," : ""); - addComma = true; - size_t i = 0; - for (auto bp : this->breakpoints) { - this->channel->write( - "%" PRIu32 "%s", toVA(bp), - (++i < this->breakpoints.size()) ? "," : ""); - } - this->channel->write("]"); - break; - } - case callstackState: { - this->channel->write("%s\"callstack\":[", addComma ? "," : ""); - addComma = true; - for (int j = 0; j <= ectx->csp; j++) { - const Frame *f = &ectx->callstack[j]; - const uint8_t bt = f->block->block_type; - const uint32_t block_key = - (bt == 0 || bt == 0xff || bt == 0xfe) - ? 0 - : toVA(findOpcode(m, f->block)); - const uint32_t fidx = bt == 0 ? f->block->fidx : 0; - const auto ra = f->ra_ptr == nullptr ? -1 : toVA(f->ra_ptr); - this->channel->write( - R"({"type":%u,"fidx":"0x%x","sp":%d,"fp":%d,"idx":%d,)", - bt, fidx, f->sp, f->fp, j); - this->channel->write( - "\"block_key\":%" PRIu32 ",\"ra\":%d}%s", block_key, ra, - (j < ectx->csp) ? "," : ""); - } - this->channel->write("]"); - break; - } - case stackState: { - this->channel->write("%s\"stack\":[", addComma ? "," : ""); - addComma = true; - for (int j = 0; j <= ectx->sp; j++) { - auto v = &ectx->stack[j]; - printValue(v, j, j == ectx->sp); - } - this->channel->write("]"); - break; - } - case globalsState: { - this->channel->write("%s\"globals\":[", addComma ? "," : ""); - addComma = true; - for (uint32_t j = 0; j < m->global_count; j++) { - auto v = (*(m->globals + j))->value; - printValue(v, j, j == (m->global_count - 1)); - } - this->channel->write("]"); // closing globals - break; - } - case tableState: { - this->channel->write( - R"(%s"table":{"max":%d, "init":%d, "elements":[)", - addComma ? "," : "", m->table.maximum, m->table.initial); - addComma = true; - for (uint32_t j = 0; j < m->table.size; j++) { - this->channel->write("%" PRIu32 "%s", m->table.entries[j], - (j + 1) == m->table.size ? "" : ","); - } - this->channel->write("]}"); // closing table - break; - } - case branchingTableState: { - this->channel->write( - R"(%s"br_table":{"size":"0x%x","labels":[)", - addComma ? "," : "", BR_TABLE_SIZE); - for (uint32_t j = 0; j < BR_TABLE_SIZE; j++) { - this->channel->write("%" PRIu32 "%s", ectx->br_table[j], - (j + 1) == BR_TABLE_SIZE ? "" : ","); - } - this->channel->write("]}"); - break; - } - case memoryState: { - uint32_t total_elems = - m->memory.pages * static_cast(PAGE_SIZE); - this->channel->write( - R"(%s"memory":{"pages":%d,"max":%d,"init":%d,"bytes":[)", - addComma ? "," : "", m->memory.pages, m->memory.maximum, - m->memory.initial); - addComma = true; - if (total_elems != 0) { - uint8_t data = m->memory.bytes[0]; - uint32_t count = 1; - bool arrayComma = false; - for (uint32_t j = 1; j < total_elems; j++) { - if (m->memory.bytes[j] == data) { - count++; - } else { - this->channel->write("%s%" PRIu8 ",%d", - arrayComma ? "," : "", data, - count); - arrayComma = true; - data = m->memory.bytes[j]; - count = 1; - } - } - this->channel->write("%s%" PRIu8 ",%d", - arrayComma ? "," : "", data, count); - } - this->channel->write("]}"); // closing memory - break; - } - case callbacksState: { - bool noOuterBraces = false; - this->channel->write( - "%s%s", addComma ? "," : "", - CallbackHandler::dump_callbacksV2(noOuterBraces).c_str()); - addComma = true; - break; - } - case eventsState: { - this->channel->write("%s", addComma ? "," : ""); - this->dumpEvents( - 0, static_cast(CallbackHandler::event_count())); - addComma = true; - break; - } - case ioState: { - this->channel->write("%s", addComma ? "," : ""); - this->channel->write("\"io\": ["); - bool comma = false; - std::vector external_state = - m->warduino->interpreter->get_io_state(m); - for (auto state_elem : external_state) { - this->channel->write("%s{", comma ? ", " : ""); - this->channel->write( - R"("key": "%s", "output": %s, "value": %d)", - state_elem->key.c_str(), - state_elem->output ? "true" : "false", - state_elem->value); - this->channel->write("}"); - comma = true; - delete state_elem; - } - this->channel->write("]"); - addComma = true; - break; - } - case overridesState: { - this->channel->write("%s", addComma ? "," : ""); - this->channel->write(R"("overrides": [)"); - bool comma = false; - for (const auto &[key, return_value] : overrides) { - this->channel->write("%s", comma ? ", " : ""); - const uint32_t fidx = key[key.size() - 1]; - this->channel->write(R"({"fidx": %d, "args": [)", fidx); - for (uint32_t i = 0; i < key.size() - 1; i++) { - this->channel->write("%s%d", i > 0 ? ", " : "", key[i]); - } - this->channel->write(R"(], "return_value": %d})", - return_value); - comma = true; - } - this->channel->write("]"); - addComma = true; - break; - } - case heapState: { - uint32_t heap_used = m->warduino->get_heap_used(); - this->channel->write(R"(%s"heap":{"used":%d})", - addComma ? "," : "", heap_used); - addComma = true; - break; - } - default: { - debug("dumpExecutionState: Received unknown state request\n"); - break; - } - } + debug_Snapshot state = debug_Snapshot_init_zero; + state.program_counter = toVirtualAddress(ectx->pc_ptr, m); + state.heap_used = m->warduino->get_heap_used(); + switch (m->warduino->program_state) { + case WARDUINOrun: + state.state = debug_State_STATE_WARDUINO_RUN; + break; + case WARDUINOpause: + state.state = debug_State_STATE_WARDUINO_PAUSE; + break; + case WARDUINOstep: + state.state = debug_State_STATE_WARDUINO_STEP; + break; + case PROXYrun: + state.state = debug_State_STATE_PROXY_RUN; + break; + case PROXYhalt: + state.state = debug_State_STATE_PROXY_HALT; + break; + default: + state.state = debug_State_STATE_WARDUINO_PAUSE; + break; } - this->channel->write("}"); + sendNotification(debug_NotificationType_NOTIFICATION_SNAPSHOT, + debug_Snapshot_fields, &state); +} + +void Debugger::inspect(Module *m, const uint16_t, const uint8_t *) const { + snapshot(m); } void Debugger::setSnapshotPolicy(Module *m, uint8_t *interruptData) { @@ -1061,9 +1064,9 @@ std::optional getPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { void Debugger::handleSnapshotPolicy(Module *m) { if (snapshotPolicy == SnapshotPolicy::atEveryInstruction) { - this->channel->write("SNAPSHOT "); + debug("SNAPSHOT "); snapshot(m); - this->channel->write("\n"); + debug("\n"); } else if (snapshotPolicy == SnapshotPolicy::checkpointing) { if (instructions_executed >= checkpointInterval || fidx_called) { if (min_return_values == 0) { @@ -1102,45 +1105,23 @@ void Debugger::handleSnapshotPolicy(Module *m) { } } } else if (snapshotPolicy != SnapshotPolicy::none) { - this->channel->write("WARNING: Invalid snapshot policy."); + debug("WARNING: Invalid snapshot policy."); } } -void Debugger::checkpoint(Module *m, const bool force, const bool full) { +void Debugger::checkpoint(Module *, const bool force, const bool) { if (instructions_executed == 0 && !force) { return; } - this->channel->write(R"(CHECKPOINT {"instructions_executed": %d, )", - instructions_executed); + debug_Checkpoint notification = debug_Checkpoint_init_zero; + notification.instruction_count = instructions_executed; if (fidx_called) { - this->channel->write(R"("fidx_called": %d, "args": [)", *fidx_called); - const Block &func_block = m->functions[*fidx_called]; - bool comma = false; - for (uint32_t i = 0; i < func_block.type->param_count; i++) { - channel->write("%s%d", comma ? ", " : "", prim_args[i]); - comma = true; - } - this->channel->write("], "); - - // Return values: - this->channel->write(R"("returns": [)"); - comma = false; - for (uint32_t i = 0; i < func_block.type->result_count; i++) { - ExecutionContext *ectx = m->warduino->execution_context; - channel->write("%s%d", comma ? ", " : "", - ectx->stack[ectx->sp - i].value.uint32); - comma = true; - } - this->channel->write("], "); + notification.has_primitive_call = true; + notification.primitive_function_index = *fidx_called; } - this->channel->write(R"("snapshot": )"); - if (!checkpoint_state || full) { - snapshot(m); - } else { - inspect(m, checkpoint_state_size, checkpoint_state); - } - this->channel->write("}\n"); + sendNotification(debug_NotificationType_NOTIFICATION_CHECKPOINT, + debug_Checkpoint_fields, ¬ification); instructions_executed = 0; } @@ -1568,10 +1549,14 @@ RFC *Debugger::topProxyCall() const { } void Debugger::sendProxyCallResult(Module *m) const { - if (proxy == nullptr) { - return; - } - this->proxy->returnResult(m); + if (proxy == nullptr) return; + RFC *rfc = proxy->returnResult(m); + if (rfc == nullptr) return; + debug_RemoteFunctionResult result = debug_RemoteFunctionResult_init_zero; + result.success = rfc->success; + sendNotification(debug_NotificationType_NOTIFICATION_REMOTE_FUNCTION_RESULT, + debug_RemoteFunctionResult_fields, &result); + delete rfc; } bool Debugger::isProxy() const { return this->proxy != nullptr; } @@ -1592,7 +1577,7 @@ void Debugger::handleMonitorProxies(const Module *m, m->warduino->debugger->supervisor->registerProxiedCall(fidx); } - this->channel->write("done!\n"); + debug("done!\n"); } void Debugger::startProxySupervisor(Channel *socket) { @@ -1612,17 +1597,8 @@ void Debugger::disconnect_proxy() const { this->supervisor->thread.join(); } -void Debugger::updateCallbackmapping(Module *m, const char *interruptData) { - nlohmann::basic_json<> parsed = nlohmann::json::parse(interruptData); - CallbackHandler::clear_callbacks(); - nlohmann::basic_json<> callbacks = *parsed.find("callbacks"); - for (auto &array : callbacks.items()) { - auto callback = array.value().begin(); - for (auto &functions : callback.value().items()) { - CallbackHandler::add_callback( - Callback(m, callback.key(), functions.value())); - } - } +void Debugger::updateCallbackmapping(Module *, const char *) { + // Legacy JSON callback mapping input is intentionally unsupported. } // Stop the debugger @@ -1650,16 +1626,16 @@ bool Debugger::handleUpdateModule(Module *m, uint8_t *data) { } bool Debugger::handleUpdateGlobalValue(const Module *m, uint8_t *data) const { - this->channel->write("Global updates: %x\n", *data); + debug("Global updates: %x\n", *data); const uint32_t index = read_LEB_32(&data); if (index >= m->global_count) return false; - this->channel->write("Global %u being changed\n", index); + debug("Global %u being changed\n", index); StackValue *v = m->globals[index]->value; constexpr bool decodeType = false; deserialiseStackValue(data, decodeType, v); - this->channel->write("Global %u changed to %u\n", index, v->value.uint32); + debug("Global %u changed to %u\n", index, v->value.uint32); return true; } @@ -1675,7 +1651,7 @@ bool Debugger::handleUpdateStackValue(const Module *m, uint8_t *bytes) const { if (!deserialiseStackValue(bytes, decodeType, sv)) { return false; } - this->channel->write("StackValue %" PRIu32 " changed\n", idx); + debug("StackValue %" PRIu32 " changed\n", idx); return true; } @@ -1683,7 +1659,7 @@ bool Debugger::reset(Module *m) { m->warduino->reset_module(m); instructions_executed = 0; instructions_since_full_snapshot = 0; - this->channel->write("Reset WARDuino.\n"); + debug("Reset WARDuino.\n"); return true; } @@ -1712,10 +1688,9 @@ void Debugger::addOverride(Module *m, uint8_t *interruptData) { const std::optional fidx = resolve_imported_function(m, primitive_name); if (!fidx) { - channel->write( - "Cannot override the result for unknown function \"%s\".\n", - primitive_name.c_str()); - channel->write("ack%x;0\n", interruptUnsetOverridePinValue); + debug("Cannot override the result for unknown function \"%s\".\n", + primitive_name.c_str()); + debug("ack%x;0\n", interruptUnsetOverridePinValue); return; } @@ -1727,7 +1702,7 @@ void Debugger::addOverride(Module *m, uint8_t *interruptData) { key[param_count] = fidx.value(); const uint32_t result = read_B32(&interruptData); - channel->write("ack%x;1\n", interruptSetOverridePinValue); + debug("ack%x;1\n", interruptSetOverridePinValue); overrides[key] = result; } @@ -1736,9 +1711,9 @@ void Debugger::removeOverride(Module *m, uint8_t *interruptData) { const std::optional fidx = resolve_imported_function(m, primitive_name); if (!fidx) { - channel->write("Cannot remove override for unknown function \"%s\".\n", - primitive_name.c_str()); - channel->write("ack%x;0\n", interruptUnsetOverridePinValue); + debug("Cannot remove override for unknown function \"%s\".\n", + primitive_name.c_str()); + debug("ack%x;0\n", interruptUnsetOverridePinValue); return; } @@ -1750,10 +1725,10 @@ void Debugger::removeOverride(Module *m, uint8_t *interruptData) { key[param_count] = fidx.value(); if (overrides.erase(key) == 0) { - channel->write("ack%x;0\n", interruptUnsetOverridePinValue); + debug("ack%x;0\n", interruptUnsetOverridePinValue); return; } - channel->write("ack%x;1\n", interruptUnsetOverridePinValue); + debug("ack%x;1\n", interruptUnsetOverridePinValue); } bool Debugger::getMockForArgs(Module *m, uint32_t fidx, uint32_t &result) { @@ -1780,7 +1755,7 @@ bool Debugger::handleContinueFor(Module *m) { if (snapshotPolicy == SnapshotPolicy::checkpointing) { checkpoint(m); } - this->channel->write("DONE!\n"); + this->sendNotification(debug_NotificationType_NOTIFICATION_PAUSED); pauseRuntime(m); return true; } @@ -1794,7 +1769,7 @@ void Debugger::notifyCompleteStep(Module *m) const { SnapshotPolicy::checkpointing) { m->warduino->debugger->checkpoint(m); } - this->channel->write("STEP!\n"); + this->sendNotification(debug_NotificationType_NOTIFICATION_STEPPED); } Debugger::~Debugger() { diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 3a5b66268..3fe7945fb 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include // std::queue @@ -15,11 +16,19 @@ #include "../Edward/proxy_supervisor.h" #include "../Threading/warduino-thread.h" #include "../Utils/sockets.h" +#include "nanopb/debug.pb.h" +#include "nanopb/pb_decode.h" +#include "nanopb/pb_encode.h" struct Module; struct Block; struct StackValue; +struct DebugMessage { + debug_Command type; + std::vector payload; +}; + enum operation { STORE = 0, LOAD = 1, @@ -132,17 +141,14 @@ struct FNV1aVectorHash { class Debugger { private: - std::deque debugMessages = {}; - - // Help variables - volatile bool interruptWrite{}; - volatile bool interruptRead{}; - bool interruptEven = true; - uint8_t interruptLastChar{}; - std::vector interruptBuffer; - std::queue parsedInterrupts{}; - long interruptSize{}; - bool receivingData = false; + std::deque debugMessages = {}; + + // Incomplete bytes from the binary framed stream. + std::vector pendingFrameBytes; + static constexpr size_t maxFramePayload = 65536; + + // Function replacement storage must outlive decoded queue frames. + std::unordered_map> functionBodies; Proxy *proxy = nullptr; // proxy module for debugger @@ -175,7 +181,12 @@ class Debugger { // TODO Move parsing to WARDuino class? void parseDebugBuffer(size_t len, const uint8_t *buff); - void pushMessage(uint8_t *msg); + void pushMessage(DebugMessage msg); + + bool sendNotification(debug_NotificationType type, + const pb_msgdesc_t *fields = nullptr, + const void *payload = nullptr) const; + void sendOperationResult(debug_Command command, bool success) const; //// Handle REPL interrupts @@ -281,7 +292,7 @@ class Debugger { void addDebugMessage(size_t len, const uint8_t *buff); - uint8_t *getDebugMessage(); + std::optional getDebugMessage(); bool checkDebugMessages(Module *m, RunningState *program_state); diff --git a/src/Debug/nanopb/debug.pb.c b/src/Debug/nanopb/debug.pb.c new file mode 100644 index 000000000..d5f6fee3c --- /dev/null +++ b/src/Debug/nanopb/debug.pb.c @@ -0,0 +1,104 @@ +/* Automatically generated nanopb constant definitions */ +/* Generated by nanopb-0.4.9.1 */ + +#include "debug.pb.h" +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +PB_BIND(debug_CodeLocation, debug_CodeLocation, AUTO) + + +PB_BIND(debug_Breakpoint, debug_Breakpoint, AUTO) + + +PB_BIND(debug_HitBreakpoint, debug_HitBreakpoint, AUTO) + + +PB_BIND(debug_NewEvent, debug_NewEvent, AUTO) + + +PB_BIND(debug_ContinueFor, debug_ContinueFor, AUTO) + + +PB_BIND(debug_Inspect, debug_Inspect, AUTO) + + +PB_BIND(debug_FunctionRef, debug_FunctionRef, AUTO) + + +PB_BIND(debug_ValueUpdate, debug_ValueUpdate, AUTO) + + +PB_BIND(debug_Snapshot, debug_Snapshot, 2) + + +PB_BIND(debug_Function, debug_Function, AUTO) + + +PB_BIND(debug_RemoteFunctionCall, debug_RemoteFunctionCall, AUTO) + + +PB_BIND(debug_CallstackEntry, debug_CallstackEntry, AUTO) + + +PB_BIND(debug_Locals, debug_Locals, AUTO) + + +PB_BIND(debug_Value, debug_Value, AUTO) + + +PB_BIND(debug_CallbackMapping, debug_CallbackMapping, AUTO) + + +PB_BIND(debug_CallbackEntry, debug_CallbackEntry, AUTO) + + +PB_BIND(debug_EventsQueue, debug_EventsQueue, AUTO) + + +PB_BIND(debug_Event, debug_Event, AUTO) + + +PB_BIND(debug_Range, debug_Range, AUTO) + + +PB_BIND(debug_ModuleUpdate, debug_ModuleUpdate, AUTO) + + +PB_BIND(debug_IndexedValues, debug_IndexedValues, AUTO) + + +PB_BIND(debug_SnapshotPolicyConfig, debug_SnapshotPolicyConfig, AUTO) + + +PB_BIND(debug_Override, debug_Override, AUTO) + + +PB_BIND(debug_OperationResult, debug_OperationResult, AUTO) + + +PB_BIND(debug_RemoteFunctionResult, debug_RemoteFunctionResult, AUTO) + + +PB_BIND(debug_Checkpoint, debug_Checkpoint, 2) + + +PB_BIND(debug_TableState, debug_TableState, AUTO) + + +PB_BIND(debug_MemoryState, debug_MemoryState, AUTO) + + +PB_BIND(debug_IOState, debug_IOState, AUTO) + + + + + + + + + + + diff --git a/src/Debug/nanopb/debug.pb.h b/src/Debug/nanopb/debug.pb.h new file mode 100644 index 000000000..cf2bed6be --- /dev/null +++ b/src/Debug/nanopb/debug.pb.h @@ -0,0 +1,809 @@ +/* Automatically generated nanopb header */ +/* Generated by nanopb-0.4.9.1 */ + +#ifndef PB_DEBUG_DEBUG_PB_H_INCLUDED +#define PB_DEBUG_DEBUG_PB_H_INCLUDED +#include "pb.h" + +#if PB_PROTO_HEADER_VERSION != 40 +#error Regenerate this file with the current version of nanopb generator. +#endif + +/* Enum definitions */ +/* Frontend -> WARDuino. + The receiver selects the payload schema from the command byte. */ +typedef enum _debug_Command { + debug_Command_COMMAND_RUN = 0, /* no payload */ + debug_Command_COMMAND_HALT = 1, /* no payload */ + debug_Command_COMMAND_PAUSE = 2, /* no payload */ + debug_Command_COMMAND_STEP = 3, /* no payload */ + debug_Command_COMMAND_STEP_OVER = 4, /* no payload */ + debug_Command_COMMAND_ADD_BREAKPOINT = 5, /* Breakpoint */ + debug_Command_COMMAND_REMOVE_BREAKPOINT = 6, /* Breakpoint */ + debug_Command_COMMAND_DUMP = 7, /* no payload */ + debug_Command_COMMAND_DUMP_LOCALS = 8, /* no payload */ + debug_Command_COMMAND_SNAPSHOT = 9, /* no payload */ + debug_Command_COMMAND_DUMP_EVENTS = 10, /* Range */ + debug_Command_COMMAND_DUMP_CALLBACKS = 11, /* no payload */ + debug_Command_COMMAND_UPDATE_FUNCTION = 12, /* Function */ + debug_Command_COMMAND_UPDATE_LOCAL = 13, /* ValueUpdate */ + debug_Command_COMMAND_UPDATE_CALLBACKS = 14, /* CallbackMapping */ + debug_Command_COMMAND_UPDATE_MODULE = 26, /* ModuleUpdate */ + debug_Command_COMMAND_UPDATE_GLOBAL = 27, /* ValueUpdate */ + debug_Command_COMMAND_UPDATE_STACK = 28, /* ValueUpdate */ + debug_Command_COMMAND_LOAD_SNAPSHOT = 15, /* Snapshot */ + debug_Command_COMMAND_PROXIFY = 16, /* no payload */ + debug_Command_COMMAND_ADD_PROXY = 17, /* FunctionRef */ + debug_Command_COMMAND_REMOVE_PROXY = 18, /* FunctionRef */ + debug_Command_COMMAND_PROXY_CALL = 19, /* RemoteFunctionCall */ + debug_Command_COMMAND_POP_EVENT = 20, /* no payload */ + debug_Command_COMMAND_PUSH_EVENT = 21, /* Event */ + debug_Command_COMMAND_CONTINUE_FOR = 22, /* ContinueFor */ + debug_Command_COMMAND_INSPECT = 23, /* Inspect */ + debug_Command_COMMAND_RESET = 24, /* no payload */ + debug_Command_COMMAND_INVOKE = 25, /* RemoteFunctionCall */ + debug_Command_COMMAND_SET_SNAPSHOT_POLICY = 29, /* SnapshotPolicyConfig */ + debug_Command_COMMAND_SET_OVERRIDE = 30, /* Override */ + debug_Command_COMMAND_REMOVE_OVERRIDE = 31 /* Override */ +} debug_Command; + +/* WARDuino -> frontend. + The receiver selects the payload schema from the notification byte. */ +typedef enum _debug_NotificationType { + debug_NotificationType_NOTIFICATION_CONTINUED = 0, /* no payload */ + debug_NotificationType_NOTIFICATION_HALTED = 1, /* no payload */ + debug_NotificationType_NOTIFICATION_PAUSED = 2, /* no payload */ + debug_NotificationType_NOTIFICATION_STEPPED = 3, /* no payload */ + debug_NotificationType_NOTIFICATION_HIT_BREAKPOINT = 4, /* HitBreakpoint */ + debug_NotificationType_NOTIFICATION_NEW_EVENT = 5, /* NewEvent (zero-length payload) */ + debug_NotificationType_NOTIFICATION_FUNCTION_DUMP = 6, /* Function */ + debug_NotificationType_NOTIFICATION_LOCALS_DUMP = 7, /* Locals */ + debug_NotificationType_NOTIFICATION_SNAPSHOT = 8, /* Snapshot */ + debug_NotificationType_NOTIFICATION_EVENTS_DUMP = 9, /* EventsQueue */ + debug_NotificationType_NOTIFICATION_CALLBACKS_DUMP = 10, /* CallbackMapping */ + debug_NotificationType_NOTIFICATION_CHANGE_AFFECTED = 11, /* no payload */ + debug_NotificationType_NOTIFICATION_MALFORMED = 12, /* no payload */ + debug_NotificationType_NOTIFICATION_UNKNOWN_COMMAND = 13, /* no payload */ + debug_NotificationType_NOTIFICATION_OPERATION_RESULT = 14, /* OperationResult */ + debug_NotificationType_NOTIFICATION_REMOTE_FUNCTION_RESULT = 15, /* RemoteFunctionResult */ + debug_NotificationType_NOTIFICATION_CHECKPOINT = 16 /* Checkpoint */ +} debug_NotificationType; + +typedef enum _debug_State { + debug_State_STATE_WARDUINO_RUN = 0, + debug_State_STATE_WARDUINO_PAUSE = 1, + debug_State_STATE_WARDUINO_STEP = 2, + debug_State_STATE_PROXY_RUN = 3, + debug_State_STATE_PROXY_HALT = 4 +} debug_State; + +typedef enum _debug_SnapshotPolicy { + debug_SnapshotPolicy_SNAPSHOT_POLICY_NONE = 0, + debug_SnapshotPolicy_SNAPSHOT_POLICY_EVERY_INSTRUCTION = 1, + debug_SnapshotPolicy_SNAPSHOT_POLICY_CHECKPOINTING = 2 +} debug_SnapshotPolicy; + +/* Struct definitions */ +/* A virtual program address. */ +typedef struct _debug_CodeLocation { + uint32_t module_index; + uint32_t program_counter; +} debug_CodeLocation; + +typedef struct _debug_Breakpoint { + bool has_location; + debug_CodeLocation location; +} debug_Breakpoint; + +typedef struct _debug_HitBreakpoint { + bool has_location; + debug_CodeLocation location; +} debug_HitBreakpoint; + +/* The notification type carries all information for this event. The empty + message exists for host-side reflection, but no protobuf bytes are sent. */ +typedef struct _debug_NewEvent { + char dummy_field; +} debug_NewEvent; + +typedef struct _debug_ContinueFor { + uint32_t count; +} debug_ContinueFor; + +/* Execution-state selectors understood by the VM. Keeping them as bytes lets + the protocol add selectors without changing this schema. */ +typedef struct _debug_Inspect { + pb_callback_t state; +} debug_Inspect; + +typedef struct _debug_FunctionRef { + uint32_t function_index; +} debug_FunctionRef; + +typedef struct _debug_RemoteFunctionCall { + uint32_t function_index; + pb_callback_t arguments; +} debug_RemoteFunctionCall; + +typedef struct _debug_CallstackEntry { + uint32_t type; + uint32_t function_index; + uint32_t stack_pointer; + uint32_t frame_pointer; + uint32_t start; + uint32_t return_address; +} debug_CallstackEntry; + +typedef struct _debug_Locals { + pb_callback_t values; +} debug_Locals; + +/* The oneof tag is the value type, so a separate type enum and a decimal + string are unnecessary. Fixed-width fields preserve WebAssembly bits and + are fast to construct on the MCU. */ +typedef struct _debug_Value { + pb_size_t which_data; + union { + uint32_t i32_bits; + uint64_t i64_bits; + uint32_t f32_bits; + uint64_t f64_bits; + pb_callback_t raw; + } data; + uint32_t index; +} debug_Value; + +typedef struct _debug_ValueUpdate { + uint32_t index; + bool has_value; + debug_Value value; +} debug_ValueUpdate; + +typedef struct _debug_CallbackMapping { + pb_callback_t entries; +} debug_CallbackMapping; + +typedef struct _debug_CallbackEntry { + pb_callback_t topic; + pb_callback_t table_indexes; +} debug_CallbackEntry; + +typedef struct _debug_Event { + pb_callback_t topic; + pb_callback_t payload; +} debug_Event; + +typedef struct _debug_Range { + uint32_t start; + uint32_t end; +} debug_Range; + +typedef struct _debug_Function { + uint32_t function_index; + bool has_range; + debug_Range range; + bool has_locals; + debug_Locals locals; + pb_callback_t instructions; +} debug_Function; + +typedef struct _debug_EventsQueue { + /* Total events in the queue; this can exceed the returned slice length. */ + uint32_t total_count; + pb_callback_t events; + bool has_range; + debug_Range range; +} debug_EventsQueue; + +typedef struct _debug_ModuleUpdate { + pb_callback_t wasm; +} debug_ModuleUpdate; + +typedef struct _debug_IndexedValues { + pb_callback_t values; +} debug_IndexedValues; + +typedef struct _debug_SnapshotPolicyConfig { + debug_SnapshotPolicy policy; + uint32_t interval; + uint32_t minimum_return_count; + pb_callback_t selected_state; +} debug_SnapshotPolicyConfig; + +typedef struct _debug_Override { + pb_callback_t primitive_name; + pb_callback_t argument_words; + uint32_t result; +} debug_Override; + +typedef struct _debug_OperationResult { + debug_Command command; + bool success; +} debug_OperationResult; + +typedef struct _debug_RemoteFunctionResult { + bool success; + pb_callback_t results; + pb_callback_t error; +} debug_RemoteFunctionResult; + +typedef struct _debug_TableState { + uint32_t initial; + uint32_t maximum; + pb_callback_t entries; +} debug_TableState; + +typedef struct _debug_MemoryState { + uint32_t initial; + uint32_t maximum; + uint32_t pages; + pb_callback_t bytes; +} debug_MemoryState; + +typedef struct _debug_Snapshot { + uint32_t program_counter; + debug_State state; + pb_callback_t breakpoints; + pb_callback_t functions; + pb_callback_t callstack; + bool has_locals; + debug_Locals locals; + bool has_queue; + debug_EventsQueue queue; + bool has_callbacks; + debug_CallbackMapping callbacks; + pb_callback_t globals; + pb_callback_t stack; + bool has_table; + debug_TableState table; + bool has_memory; + debug_MemoryState memory; + pb_callback_t branch_table; + pb_callback_t io; + pb_callback_t overrides; + uint32_t heap_used; +} debug_Snapshot; + +typedef struct _debug_Checkpoint { + uint32_t instruction_count; + bool has_primitive_call; + uint32_t primitive_function_index; + pb_callback_t arguments; + pb_callback_t results; + bool has_snapshot; + debug_Snapshot snapshot; +} debug_Checkpoint; + +typedef struct _debug_IOState { + pb_callback_t key; + bool output; + int32_t value; +} debug_IOState; + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Helper constants for enums */ +#define _debug_Command_MIN debug_Command_COMMAND_RUN +#define _debug_Command_MAX debug_Command_COMMAND_REMOVE_OVERRIDE +#define _debug_Command_ARRAYSIZE ((debug_Command)(debug_Command_COMMAND_REMOVE_OVERRIDE+1)) + +#define _debug_NotificationType_MIN debug_NotificationType_NOTIFICATION_CONTINUED +#define _debug_NotificationType_MAX debug_NotificationType_NOTIFICATION_CHECKPOINT +#define _debug_NotificationType_ARRAYSIZE ((debug_NotificationType)(debug_NotificationType_NOTIFICATION_CHECKPOINT+1)) + +#define _debug_State_MIN debug_State_STATE_WARDUINO_RUN +#define _debug_State_MAX debug_State_STATE_PROXY_HALT +#define _debug_State_ARRAYSIZE ((debug_State)(debug_State_STATE_PROXY_HALT+1)) + +#define _debug_SnapshotPolicy_MIN debug_SnapshotPolicy_SNAPSHOT_POLICY_NONE +#define _debug_SnapshotPolicy_MAX debug_SnapshotPolicy_SNAPSHOT_POLICY_CHECKPOINTING +#define _debug_SnapshotPolicy_ARRAYSIZE ((debug_SnapshotPolicy)(debug_SnapshotPolicy_SNAPSHOT_POLICY_CHECKPOINTING+1)) + + + + + + + + + +#define debug_Snapshot_state_ENUMTYPE debug_State + + + + + + + + + + + + + +#define debug_SnapshotPolicyConfig_policy_ENUMTYPE debug_SnapshotPolicy + + +#define debug_OperationResult_command_ENUMTYPE debug_Command + + + + + + + +/* Initializer values for message structs */ +#define debug_CodeLocation_init_default {0, 0} +#define debug_Breakpoint_init_default {false, debug_CodeLocation_init_default} +#define debug_HitBreakpoint_init_default {false, debug_CodeLocation_init_default} +#define debug_NewEvent_init_default {0} +#define debug_ContinueFor_init_default {0} +#define debug_Inspect_init_default {{{NULL}, NULL}} +#define debug_FunctionRef_init_default {0} +#define debug_ValueUpdate_init_default {0, false, debug_Value_init_default} +#define debug_Snapshot_init_default {0, _debug_State_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_Locals_init_default, false, debug_EventsQueue_init_default, false, debug_CallbackMapping_init_default, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_TableState_init_default, false, debug_MemoryState_init_default, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0} +#define debug_Function_init_default {0, false, debug_Range_init_default, false, debug_Locals_init_default, {{NULL}, NULL}} +#define debug_RemoteFunctionCall_init_default {0, {{NULL}, NULL}} +#define debug_CallstackEntry_init_default {0, 0, 0, 0, 0, 0} +#define debug_Locals_init_default {{{NULL}, NULL}} +#define debug_Value_init_default {0, {0}, 0} +#define debug_CallbackMapping_init_default {{{NULL}, NULL}} +#define debug_CallbackEntry_init_default {{{NULL}, NULL}, {{NULL}, NULL}} +#define debug_EventsQueue_init_default {0, {{NULL}, NULL}, false, debug_Range_init_default} +#define debug_Event_init_default {{{NULL}, NULL}, {{NULL}, NULL}} +#define debug_Range_init_default {0, 0} +#define debug_ModuleUpdate_init_default {{{NULL}, NULL}} +#define debug_IndexedValues_init_default {{{NULL}, NULL}} +#define debug_SnapshotPolicyConfig_init_default {_debug_SnapshotPolicy_MIN, 0, 0, {{NULL}, NULL}} +#define debug_Override_init_default {{{NULL}, NULL}, {{NULL}, NULL}, 0} +#define debug_OperationResult_init_default {_debug_Command_MIN, 0} +#define debug_RemoteFunctionResult_init_default {0, {{NULL}, NULL}, {{NULL}, NULL}} +#define debug_Checkpoint_init_default {0, 0, 0, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_Snapshot_init_default} +#define debug_TableState_init_default {0, 0, {{NULL}, NULL}} +#define debug_MemoryState_init_default {0, 0, 0, {{NULL}, NULL}} +#define debug_IOState_init_default {{{NULL}, NULL}, 0, 0} +#define debug_CodeLocation_init_zero {0, 0} +#define debug_Breakpoint_init_zero {false, debug_CodeLocation_init_zero} +#define debug_HitBreakpoint_init_zero {false, debug_CodeLocation_init_zero} +#define debug_NewEvent_init_zero {0} +#define debug_ContinueFor_init_zero {0} +#define debug_Inspect_init_zero {{{NULL}, NULL}} +#define debug_FunctionRef_init_zero {0} +#define debug_ValueUpdate_init_zero {0, false, debug_Value_init_zero} +#define debug_Snapshot_init_zero {0, _debug_State_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_Locals_init_zero, false, debug_EventsQueue_init_zero, false, debug_CallbackMapping_init_zero, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_TableState_init_zero, false, debug_MemoryState_init_zero, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0} +#define debug_Function_init_zero {0, false, debug_Range_init_zero, false, debug_Locals_init_zero, {{NULL}, NULL}} +#define debug_RemoteFunctionCall_init_zero {0, {{NULL}, NULL}} +#define debug_CallstackEntry_init_zero {0, 0, 0, 0, 0, 0} +#define debug_Locals_init_zero {{{NULL}, NULL}} +#define debug_Value_init_zero {0, {0}, 0} +#define debug_CallbackMapping_init_zero {{{NULL}, NULL}} +#define debug_CallbackEntry_init_zero {{{NULL}, NULL}, {{NULL}, NULL}} +#define debug_EventsQueue_init_zero {0, {{NULL}, NULL}, false, debug_Range_init_zero} +#define debug_Event_init_zero {{{NULL}, NULL}, {{NULL}, NULL}} +#define debug_Range_init_zero {0, 0} +#define debug_ModuleUpdate_init_zero {{{NULL}, NULL}} +#define debug_IndexedValues_init_zero {{{NULL}, NULL}} +#define debug_SnapshotPolicyConfig_init_zero {_debug_SnapshotPolicy_MIN, 0, 0, {{NULL}, NULL}} +#define debug_Override_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, 0} +#define debug_OperationResult_init_zero {_debug_Command_MIN, 0} +#define debug_RemoteFunctionResult_init_zero {0, {{NULL}, NULL}, {{NULL}, NULL}} +#define debug_Checkpoint_init_zero {0, 0, 0, {{NULL}, NULL}, {{NULL}, NULL}, false, debug_Snapshot_init_zero} +#define debug_TableState_init_zero {0, 0, {{NULL}, NULL}} +#define debug_MemoryState_init_zero {0, 0, 0, {{NULL}, NULL}} +#define debug_IOState_init_zero {{{NULL}, NULL}, 0, 0} + +/* Field tags (for use in manual encoding/decoding) */ +#define debug_CodeLocation_module_index_tag 1 +#define debug_CodeLocation_program_counter_tag 2 +#define debug_Breakpoint_location_tag 1 +#define debug_HitBreakpoint_location_tag 1 +#define debug_ContinueFor_count_tag 1 +#define debug_Inspect_state_tag 1 +#define debug_FunctionRef_function_index_tag 1 +#define debug_RemoteFunctionCall_function_index_tag 1 +#define debug_RemoteFunctionCall_arguments_tag 2 +#define debug_CallstackEntry_type_tag 1 +#define debug_CallstackEntry_function_index_tag 2 +#define debug_CallstackEntry_stack_pointer_tag 3 +#define debug_CallstackEntry_frame_pointer_tag 4 +#define debug_CallstackEntry_start_tag 5 +#define debug_CallstackEntry_return_address_tag 6 +#define debug_Locals_values_tag 1 +#define debug_Value_i32_bits_tag 1 +#define debug_Value_i64_bits_tag 2 +#define debug_Value_f32_bits_tag 3 +#define debug_Value_f64_bits_tag 4 +#define debug_Value_raw_tag 5 +#define debug_Value_index_tag 6 +#define debug_ValueUpdate_index_tag 1 +#define debug_ValueUpdate_value_tag 2 +#define debug_CallbackMapping_entries_tag 1 +#define debug_CallbackEntry_topic_tag 1 +#define debug_CallbackEntry_table_indexes_tag 2 +#define debug_Event_topic_tag 1 +#define debug_Event_payload_tag 2 +#define debug_Range_start_tag 1 +#define debug_Range_end_tag 2 +#define debug_Function_function_index_tag 1 +#define debug_Function_range_tag 2 +#define debug_Function_locals_tag 3 +#define debug_Function_instructions_tag 4 +#define debug_EventsQueue_total_count_tag 1 +#define debug_EventsQueue_events_tag 2 +#define debug_EventsQueue_range_tag 3 +#define debug_ModuleUpdate_wasm_tag 1 +#define debug_IndexedValues_values_tag 1 +#define debug_SnapshotPolicyConfig_policy_tag 1 +#define debug_SnapshotPolicyConfig_interval_tag 2 +#define debug_SnapshotPolicyConfig_minimum_return_count_tag 3 +#define debug_SnapshotPolicyConfig_selected_state_tag 4 +#define debug_Override_primitive_name_tag 1 +#define debug_Override_argument_words_tag 2 +#define debug_Override_result_tag 3 +#define debug_OperationResult_command_tag 1 +#define debug_OperationResult_success_tag 2 +#define debug_RemoteFunctionResult_success_tag 1 +#define debug_RemoteFunctionResult_results_tag 2 +#define debug_RemoteFunctionResult_error_tag 3 +#define debug_TableState_initial_tag 1 +#define debug_TableState_maximum_tag 2 +#define debug_TableState_entries_tag 3 +#define debug_MemoryState_initial_tag 1 +#define debug_MemoryState_maximum_tag 2 +#define debug_MemoryState_pages_tag 3 +#define debug_MemoryState_bytes_tag 4 +#define debug_Snapshot_program_counter_tag 1 +#define debug_Snapshot_state_tag 2 +#define debug_Snapshot_breakpoints_tag 3 +#define debug_Snapshot_functions_tag 4 +#define debug_Snapshot_callstack_tag 5 +#define debug_Snapshot_locals_tag 6 +#define debug_Snapshot_queue_tag 7 +#define debug_Snapshot_callbacks_tag 8 +#define debug_Snapshot_globals_tag 9 +#define debug_Snapshot_stack_tag 10 +#define debug_Snapshot_table_tag 11 +#define debug_Snapshot_memory_tag 12 +#define debug_Snapshot_branch_table_tag 13 +#define debug_Snapshot_io_tag 14 +#define debug_Snapshot_overrides_tag 15 +#define debug_Snapshot_heap_used_tag 16 +#define debug_Checkpoint_instruction_count_tag 1 +#define debug_Checkpoint_has_primitive_call_tag 2 +#define debug_Checkpoint_primitive_function_index_tag 3 +#define debug_Checkpoint_arguments_tag 4 +#define debug_Checkpoint_results_tag 5 +#define debug_Checkpoint_snapshot_tag 6 +#define debug_IOState_key_tag 1 +#define debug_IOState_output_tag 2 +#define debug_IOState_value_tag 3 + +/* Struct field encoding specification for nanopb */ +#define debug_CodeLocation_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, module_index, 1) \ +X(a, STATIC, SINGULAR, UINT32, program_counter, 2) +#define debug_CodeLocation_CALLBACK NULL +#define debug_CodeLocation_DEFAULT NULL + +#define debug_Breakpoint_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, location, 1) +#define debug_Breakpoint_CALLBACK NULL +#define debug_Breakpoint_DEFAULT NULL +#define debug_Breakpoint_location_MSGTYPE debug_CodeLocation + +#define debug_HitBreakpoint_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, location, 1) +#define debug_HitBreakpoint_CALLBACK NULL +#define debug_HitBreakpoint_DEFAULT NULL +#define debug_HitBreakpoint_location_MSGTYPE debug_CodeLocation + +#define debug_NewEvent_FIELDLIST(X, a) \ + +#define debug_NewEvent_CALLBACK NULL +#define debug_NewEvent_DEFAULT NULL + +#define debug_ContinueFor_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, count, 1) +#define debug_ContinueFor_CALLBACK NULL +#define debug_ContinueFor_DEFAULT NULL + +#define debug_Inspect_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, BYTES, state, 1) +#define debug_Inspect_CALLBACK pb_default_field_callback +#define debug_Inspect_DEFAULT NULL + +#define debug_FunctionRef_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, function_index, 1) +#define debug_FunctionRef_CALLBACK NULL +#define debug_FunctionRef_DEFAULT NULL + +#define debug_ValueUpdate_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, index, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, value, 2) +#define debug_ValueUpdate_CALLBACK NULL +#define debug_ValueUpdate_DEFAULT NULL +#define debug_ValueUpdate_value_MSGTYPE debug_Value + +#define debug_Snapshot_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, program_counter, 1) \ +X(a, STATIC, SINGULAR, UENUM, state, 2) \ +X(a, CALLBACK, REPEATED, UINT32, breakpoints, 3) \ +X(a, CALLBACK, REPEATED, MESSAGE, functions, 4) \ +X(a, CALLBACK, REPEATED, MESSAGE, callstack, 5) \ +X(a, STATIC, OPTIONAL, MESSAGE, locals, 6) \ +X(a, STATIC, OPTIONAL, MESSAGE, queue, 7) \ +X(a, STATIC, OPTIONAL, MESSAGE, callbacks, 8) \ +X(a, CALLBACK, REPEATED, MESSAGE, globals, 9) \ +X(a, CALLBACK, REPEATED, MESSAGE, stack, 10) \ +X(a, STATIC, OPTIONAL, MESSAGE, table, 11) \ +X(a, STATIC, OPTIONAL, MESSAGE, memory, 12) \ +X(a, CALLBACK, REPEATED, UINT32, branch_table, 13) \ +X(a, CALLBACK, REPEATED, MESSAGE, io, 14) \ +X(a, CALLBACK, REPEATED, MESSAGE, overrides, 15) \ +X(a, STATIC, SINGULAR, UINT32, heap_used, 16) +#define debug_Snapshot_CALLBACK pb_default_field_callback +#define debug_Snapshot_DEFAULT NULL +#define debug_Snapshot_functions_MSGTYPE debug_Function +#define debug_Snapshot_callstack_MSGTYPE debug_CallstackEntry +#define debug_Snapshot_locals_MSGTYPE debug_Locals +#define debug_Snapshot_queue_MSGTYPE debug_EventsQueue +#define debug_Snapshot_callbacks_MSGTYPE debug_CallbackMapping +#define debug_Snapshot_globals_MSGTYPE debug_Value +#define debug_Snapshot_stack_MSGTYPE debug_Value +#define debug_Snapshot_table_MSGTYPE debug_TableState +#define debug_Snapshot_memory_MSGTYPE debug_MemoryState +#define debug_Snapshot_io_MSGTYPE debug_IOState +#define debug_Snapshot_overrides_MSGTYPE debug_Override + +#define debug_Function_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, function_index, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, range, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, locals, 3) \ +X(a, CALLBACK, SINGULAR, BYTES, instructions, 4) +#define debug_Function_CALLBACK pb_default_field_callback +#define debug_Function_DEFAULT NULL +#define debug_Function_range_MSGTYPE debug_Range +#define debug_Function_locals_MSGTYPE debug_Locals + +#define debug_RemoteFunctionCall_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, function_index, 1) \ +X(a, CALLBACK, REPEATED, MESSAGE, arguments, 2) +#define debug_RemoteFunctionCall_CALLBACK pb_default_field_callback +#define debug_RemoteFunctionCall_DEFAULT NULL +#define debug_RemoteFunctionCall_arguments_MSGTYPE debug_Value + +#define debug_CallstackEntry_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, type, 1) \ +X(a, STATIC, SINGULAR, UINT32, function_index, 2) \ +X(a, STATIC, SINGULAR, UINT32, stack_pointer, 3) \ +X(a, STATIC, SINGULAR, UINT32, frame_pointer, 4) \ +X(a, STATIC, SINGULAR, UINT32, start, 5) \ +X(a, STATIC, SINGULAR, UINT32, return_address, 6) +#define debug_CallstackEntry_CALLBACK NULL +#define debug_CallstackEntry_DEFAULT NULL + +#define debug_Locals_FIELDLIST(X, a) \ +X(a, CALLBACK, REPEATED, MESSAGE, values, 1) +#define debug_Locals_CALLBACK pb_default_field_callback +#define debug_Locals_DEFAULT NULL +#define debug_Locals_values_MSGTYPE debug_Value + +#define debug_Value_FIELDLIST(X, a) \ +X(a, STATIC, ONEOF, FIXED32, (data,i32_bits,data.i32_bits), 1) \ +X(a, STATIC, ONEOF, FIXED64, (data,i64_bits,data.i64_bits), 2) \ +X(a, STATIC, ONEOF, FIXED32, (data,f32_bits,data.f32_bits), 3) \ +X(a, STATIC, ONEOF, FIXED64, (data,f64_bits,data.f64_bits), 4) \ +X(a, CALLBACK, ONEOF, BYTES, (data,raw,data.raw), 5) \ +X(a, STATIC, SINGULAR, UINT32, index, 6) +#define debug_Value_CALLBACK pb_default_field_callback +#define debug_Value_DEFAULT NULL + +#define debug_CallbackMapping_FIELDLIST(X, a) \ +X(a, CALLBACK, REPEATED, MESSAGE, entries, 1) +#define debug_CallbackMapping_CALLBACK pb_default_field_callback +#define debug_CallbackMapping_DEFAULT NULL +#define debug_CallbackMapping_entries_MSGTYPE debug_CallbackEntry + +#define debug_CallbackEntry_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, STRING, topic, 1) \ +X(a, CALLBACK, REPEATED, UINT32, table_indexes, 2) +#define debug_CallbackEntry_CALLBACK pb_default_field_callback +#define debug_CallbackEntry_DEFAULT NULL + +#define debug_EventsQueue_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, total_count, 1) \ +X(a, CALLBACK, REPEATED, MESSAGE, events, 2) \ +X(a, STATIC, OPTIONAL, MESSAGE, range, 3) +#define debug_EventsQueue_CALLBACK pb_default_field_callback +#define debug_EventsQueue_DEFAULT NULL +#define debug_EventsQueue_events_MSGTYPE debug_Event +#define debug_EventsQueue_range_MSGTYPE debug_Range + +#define debug_Event_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, STRING, topic, 1) \ +X(a, CALLBACK, SINGULAR, BYTES, payload, 2) +#define debug_Event_CALLBACK pb_default_field_callback +#define debug_Event_DEFAULT NULL + +#define debug_Range_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, start, 1) \ +X(a, STATIC, SINGULAR, UINT32, end, 2) +#define debug_Range_CALLBACK NULL +#define debug_Range_DEFAULT NULL + +#define debug_ModuleUpdate_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, BYTES, wasm, 1) +#define debug_ModuleUpdate_CALLBACK pb_default_field_callback +#define debug_ModuleUpdate_DEFAULT NULL + +#define debug_IndexedValues_FIELDLIST(X, a) \ +X(a, CALLBACK, REPEATED, MESSAGE, values, 1) +#define debug_IndexedValues_CALLBACK pb_default_field_callback +#define debug_IndexedValues_DEFAULT NULL +#define debug_IndexedValues_values_MSGTYPE debug_Value + +#define debug_SnapshotPolicyConfig_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, policy, 1) \ +X(a, STATIC, SINGULAR, UINT32, interval, 2) \ +X(a, STATIC, SINGULAR, UINT32, minimum_return_count, 3) \ +X(a, CALLBACK, SINGULAR, BYTES, selected_state, 4) +#define debug_SnapshotPolicyConfig_CALLBACK pb_default_field_callback +#define debug_SnapshotPolicyConfig_DEFAULT NULL + +#define debug_Override_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, STRING, primitive_name, 1) \ +X(a, CALLBACK, REPEATED, FIXED32, argument_words, 2) \ +X(a, STATIC, SINGULAR, FIXED32, result, 3) +#define debug_Override_CALLBACK pb_default_field_callback +#define debug_Override_DEFAULT NULL + +#define debug_OperationResult_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UENUM, command, 1) \ +X(a, STATIC, SINGULAR, BOOL, success, 2) +#define debug_OperationResult_CALLBACK NULL +#define debug_OperationResult_DEFAULT NULL + +#define debug_RemoteFunctionResult_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, BOOL, success, 1) \ +X(a, CALLBACK, REPEATED, MESSAGE, results, 2) \ +X(a, CALLBACK, SINGULAR, BYTES, error, 3) +#define debug_RemoteFunctionResult_CALLBACK pb_default_field_callback +#define debug_RemoteFunctionResult_DEFAULT NULL +#define debug_RemoteFunctionResult_results_MSGTYPE debug_Value + +#define debug_Checkpoint_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, instruction_count, 1) \ +X(a, STATIC, SINGULAR, BOOL, has_primitive_call, 2) \ +X(a, STATIC, SINGULAR, UINT32, primitive_function_index, 3) \ +X(a, CALLBACK, REPEATED, MESSAGE, arguments, 4) \ +X(a, CALLBACK, REPEATED, MESSAGE, results, 5) \ +X(a, STATIC, OPTIONAL, MESSAGE, snapshot, 6) +#define debug_Checkpoint_CALLBACK pb_default_field_callback +#define debug_Checkpoint_DEFAULT NULL +#define debug_Checkpoint_arguments_MSGTYPE debug_Value +#define debug_Checkpoint_results_MSGTYPE debug_Value +#define debug_Checkpoint_snapshot_MSGTYPE debug_Snapshot + +#define debug_TableState_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, initial, 1) \ +X(a, STATIC, SINGULAR, UINT32, maximum, 2) \ +X(a, CALLBACK, REPEATED, UINT32, entries, 3) +#define debug_TableState_CALLBACK pb_default_field_callback +#define debug_TableState_DEFAULT NULL + +#define debug_MemoryState_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, initial, 1) \ +X(a, STATIC, SINGULAR, UINT32, maximum, 2) \ +X(a, STATIC, SINGULAR, UINT32, pages, 3) \ +X(a, CALLBACK, SINGULAR, BYTES, bytes, 4) +#define debug_MemoryState_CALLBACK pb_default_field_callback +#define debug_MemoryState_DEFAULT NULL + +#define debug_IOState_FIELDLIST(X, a) \ +X(a, CALLBACK, SINGULAR, STRING, key, 1) \ +X(a, STATIC, SINGULAR, BOOL, output, 2) \ +X(a, STATIC, SINGULAR, SINT32, value, 3) +#define debug_IOState_CALLBACK pb_default_field_callback +#define debug_IOState_DEFAULT NULL + +extern const pb_msgdesc_t debug_CodeLocation_msg; +extern const pb_msgdesc_t debug_Breakpoint_msg; +extern const pb_msgdesc_t debug_HitBreakpoint_msg; +extern const pb_msgdesc_t debug_NewEvent_msg; +extern const pb_msgdesc_t debug_ContinueFor_msg; +extern const pb_msgdesc_t debug_Inspect_msg; +extern const pb_msgdesc_t debug_FunctionRef_msg; +extern const pb_msgdesc_t debug_ValueUpdate_msg; +extern const pb_msgdesc_t debug_Snapshot_msg; +extern const pb_msgdesc_t debug_Function_msg; +extern const pb_msgdesc_t debug_RemoteFunctionCall_msg; +extern const pb_msgdesc_t debug_CallstackEntry_msg; +extern const pb_msgdesc_t debug_Locals_msg; +extern const pb_msgdesc_t debug_Value_msg; +extern const pb_msgdesc_t debug_CallbackMapping_msg; +extern const pb_msgdesc_t debug_CallbackEntry_msg; +extern const pb_msgdesc_t debug_EventsQueue_msg; +extern const pb_msgdesc_t debug_Event_msg; +extern const pb_msgdesc_t debug_Range_msg; +extern const pb_msgdesc_t debug_ModuleUpdate_msg; +extern const pb_msgdesc_t debug_IndexedValues_msg; +extern const pb_msgdesc_t debug_SnapshotPolicyConfig_msg; +extern const pb_msgdesc_t debug_Override_msg; +extern const pb_msgdesc_t debug_OperationResult_msg; +extern const pb_msgdesc_t debug_RemoteFunctionResult_msg; +extern const pb_msgdesc_t debug_Checkpoint_msg; +extern const pb_msgdesc_t debug_TableState_msg; +extern const pb_msgdesc_t debug_MemoryState_msg; +extern const pb_msgdesc_t debug_IOState_msg; + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define debug_CodeLocation_fields &debug_CodeLocation_msg +#define debug_Breakpoint_fields &debug_Breakpoint_msg +#define debug_HitBreakpoint_fields &debug_HitBreakpoint_msg +#define debug_NewEvent_fields &debug_NewEvent_msg +#define debug_ContinueFor_fields &debug_ContinueFor_msg +#define debug_Inspect_fields &debug_Inspect_msg +#define debug_FunctionRef_fields &debug_FunctionRef_msg +#define debug_ValueUpdate_fields &debug_ValueUpdate_msg +#define debug_Snapshot_fields &debug_Snapshot_msg +#define debug_Function_fields &debug_Function_msg +#define debug_RemoteFunctionCall_fields &debug_RemoteFunctionCall_msg +#define debug_CallstackEntry_fields &debug_CallstackEntry_msg +#define debug_Locals_fields &debug_Locals_msg +#define debug_Value_fields &debug_Value_msg +#define debug_CallbackMapping_fields &debug_CallbackMapping_msg +#define debug_CallbackEntry_fields &debug_CallbackEntry_msg +#define debug_EventsQueue_fields &debug_EventsQueue_msg +#define debug_Event_fields &debug_Event_msg +#define debug_Range_fields &debug_Range_msg +#define debug_ModuleUpdate_fields &debug_ModuleUpdate_msg +#define debug_IndexedValues_fields &debug_IndexedValues_msg +#define debug_SnapshotPolicyConfig_fields &debug_SnapshotPolicyConfig_msg +#define debug_Override_fields &debug_Override_msg +#define debug_OperationResult_fields &debug_OperationResult_msg +#define debug_RemoteFunctionResult_fields &debug_RemoteFunctionResult_msg +#define debug_Checkpoint_fields &debug_Checkpoint_msg +#define debug_TableState_fields &debug_TableState_msg +#define debug_MemoryState_fields &debug_MemoryState_msg +#define debug_IOState_fields &debug_IOState_msg + +/* Maximum encoded size of messages (where known) */ +/* debug_Inspect_size depends on runtime parameters */ +/* debug_ValueUpdate_size depends on runtime parameters */ +/* debug_Snapshot_size depends on runtime parameters */ +/* debug_Function_size depends on runtime parameters */ +/* debug_RemoteFunctionCall_size depends on runtime parameters */ +/* debug_Locals_size depends on runtime parameters */ +/* debug_Value_size depends on runtime parameters */ +/* debug_CallbackMapping_size depends on runtime parameters */ +/* debug_CallbackEntry_size depends on runtime parameters */ +/* debug_EventsQueue_size depends on runtime parameters */ +/* debug_Event_size depends on runtime parameters */ +/* debug_ModuleUpdate_size depends on runtime parameters */ +/* debug_IndexedValues_size depends on runtime parameters */ +/* debug_SnapshotPolicyConfig_size depends on runtime parameters */ +/* debug_Override_size depends on runtime parameters */ +/* debug_RemoteFunctionResult_size depends on runtime parameters */ +/* debug_Checkpoint_size depends on runtime parameters */ +/* debug_TableState_size depends on runtime parameters */ +/* debug_MemoryState_size depends on runtime parameters */ +/* debug_IOState_size depends on runtime parameters */ +#define DEBUG_DEBUG_PB_H_MAX_SIZE debug_CallstackEntry_size +#define debug_Breakpoint_size 14 +#define debug_CallstackEntry_size 36 +#define debug_CodeLocation_size 12 +#define debug_ContinueFor_size 6 +#define debug_FunctionRef_size 6 +#define debug_HitBreakpoint_size 14 +#define debug_NewEvent_size 0 +#define debug_OperationResult_size 4 +#define debug_Range_size 12 + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/Debug/nanopb/pb.h b/src/Debug/nanopb/pb.h new file mode 100644 index 000000000..10249bb65 --- /dev/null +++ b/src/Debug/nanopb/pb.h @@ -0,0 +1,922 @@ +/* Common parts of the nanopb library. Most of these are quite low-level + * stuff. For the high-level interface, see pb_encode.h and pb_decode.h. + */ + +#ifndef PB_H_INCLUDED +#define PB_H_INCLUDED + +/***************************************************************** + * Nanopb compilation time options. You can change these here by * + * uncommenting the lines, or on the compiler command line. * + *****************************************************************/ + +/* Enable support for dynamically allocated fields */ +/* #define PB_ENABLE_MALLOC 1 */ + +/* Define this if your CPU / compiler combination does not support + * unaligned memory access to packed structures. Note that packed + * structures are only used when requested in .proto options. */ +/* #define PB_NO_PACKED_STRUCTS 1 */ + +/* Increase the number of required fields that are tracked. + * A compiler warning will tell if you need this. */ +/* #define PB_MAX_REQUIRED_FIELDS 256 */ + +/* Add support for tag numbers > 65536 and fields larger than 65536 bytes. */ +/* #define PB_FIELD_32BIT 1 */ + +/* Disable support for error messages in order to save some code space. */ +/* #define PB_NO_ERRMSG 1 */ + +/* Disable support for custom streams (support only memory buffers). */ +/* #define PB_BUFFER_ONLY 1 */ + +/* Disable support for 64-bit datatypes, for compilers without int64_t + or to save some code space. */ +/* #define PB_WITHOUT_64BIT 1 */ + +/* Don't encode scalar arrays as packed. This is only to be used when + * the decoder on the receiving side cannot process packed scalar arrays. + * Such example is older protobuf.js. */ +/* #define PB_ENCODE_ARRAYS_UNPACKED 1 */ + +/* Enable conversion of doubles to floats for platforms that do not + * support 64-bit doubles. Most commonly AVR. */ +/* #define PB_CONVERT_DOUBLE_FLOAT 1 */ + +/* Check whether incoming strings are valid UTF-8 sequences. Slows down + * the string processing slightly and slightly increases code size. */ +/* #define PB_VALIDATE_UTF8 1 */ + +/* This can be defined if the platform is little-endian and has 8-bit bytes. + * Normally it is automatically detected based on __BYTE_ORDER__ macro. */ +/* #define PB_LITTLE_ENDIAN_8BIT 1 */ + +/* Configure static assert mechanism. Instead of changing these, set your + * compiler to C11 standard mode if possible. */ +/* #define PB_C99_STATIC_ASSERT 1 */ +/* #define PB_NO_STATIC_ASSERT 1 */ + +/****************************************************************** + * You usually don't need to change anything below this line. * + * Feel free to look around and use the defined macros, though. * + ******************************************************************/ + + +/* Version of the nanopb library. Just in case you want to check it in + * your own program. */ +#define NANOPB_VERSION "nanopb-0.4.9.1" + +/* Include all the system headers needed by nanopb. You will need the + * definitions of the following: + * - strlen, memcpy, memset functions + * - [u]int_least8_t, uint_fast8_t, [u]int_least16_t, [u]int32_t, [u]int64_t + * - size_t + * - bool + * + * If you don't have the standard header files, you can instead provide + * a custom header that defines or includes all this. In that case, + * define PB_SYSTEM_HEADER to the path of this file. + */ +#ifdef PB_SYSTEM_HEADER +#include PB_SYSTEM_HEADER +#else +#include +#include +#include +#include +#include + +#ifdef PB_ENABLE_MALLOC +#include +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Macro for defining packed structures (compiler dependent). + * This just reduces memory requirements, but is not required. + */ +#if defined(PB_NO_PACKED_STRUCTS) + /* Disable struct packing */ +# define PB_PACKED_STRUCT_START +# define PB_PACKED_STRUCT_END +# define pb_packed +#elif defined(__GNUC__) || defined(__clang__) + /* For GCC and clang */ +# define PB_PACKED_STRUCT_START +# define PB_PACKED_STRUCT_END +# define pb_packed __attribute__((packed)) +#elif defined(__ICCARM__) || defined(__CC_ARM) + /* For IAR ARM and Keil MDK-ARM compilers */ +# define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)") +# define PB_PACKED_STRUCT_END _Pragma("pack(pop)") +# define pb_packed +#elif defined(_MSC_VER) && (_MSC_VER >= 1500) + /* For Microsoft Visual C++ */ +# define PB_PACKED_STRUCT_START __pragma(pack(push, 1)) +# define PB_PACKED_STRUCT_END __pragma(pack(pop)) +# define pb_packed +#else + /* Unknown compiler */ +# define PB_PACKED_STRUCT_START +# define PB_PACKED_STRUCT_END +# define pb_packed +#endif + +/* Detect endianness */ +#ifndef PB_LITTLE_ENDIAN_8BIT +#if ((defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \ + defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \ + defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM)) \ + && CHAR_BIT == 8 +#define PB_LITTLE_ENDIAN_8BIT 1 +#endif +#endif + +/* Handly macro for suppressing unreferenced-parameter compiler warnings. */ +#ifndef PB_UNUSED +#define PB_UNUSED(x) (void)(x) +#endif + +/* Harvard-architecture processors may need special attributes for storing + * field information in program memory. */ +#ifndef PB_PROGMEM +#ifdef __AVR__ +#include +#define PB_PROGMEM PROGMEM +#define PB_PROGMEM_READU32(x) pgm_read_dword(&x) +#else +#define PB_PROGMEM +#define PB_PROGMEM_READU32(x) (x) +#endif +#endif + +/* Compile-time assertion, used for checking compatible compilation options. + * If this does not work properly on your compiler, use + * #define PB_NO_STATIC_ASSERT to disable it. + * + * But before doing that, check carefully the error message / place where it + * comes from to see if the error has a real cause. Unfortunately the error + * message is not always very clear to read, but you can see the reason better + * in the place where the PB_STATIC_ASSERT macro was called. + */ +#ifndef PB_NO_STATIC_ASSERT +# ifndef PB_STATIC_ASSERT +# if defined(__ICCARM__) + /* IAR has static_assert keyword but no _Static_assert */ +# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG); +# elif defined(_MSC_VER) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 201112) + /* MSVC in C89 mode supports static_assert() keyword anyway */ +# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG); +# elif defined(PB_C99_STATIC_ASSERT) + /* Classic negative-size-array static assert mechanism */ +# define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1]; +# define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) +# define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##_##LINE##_##COUNTER +# elif defined(__cplusplus) + /* C++11 standard static_assert mechanism */ +# define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG); +# else + /* C11 standard _Static_assert mechanism */ +# define PB_STATIC_ASSERT(COND,MSG) _Static_assert(COND,#MSG); +# endif +# endif +#else + /* Static asserts disabled by PB_NO_STATIC_ASSERT */ +# define PB_STATIC_ASSERT(COND,MSG) +#endif + +/* Test that PB_STATIC_ASSERT works + * If you get errors here, you may need to do one of these: + * - Enable C11 standard support in your compiler + * - Define PB_C99_STATIC_ASSERT to enable C99 standard support + * - Define PB_NO_STATIC_ASSERT to disable static asserts altogether + */ +PB_STATIC_ASSERT(1, STATIC_ASSERT_IS_NOT_WORKING) + +/* Number of required fields to keep track of. */ +#ifndef PB_MAX_REQUIRED_FIELDS +#define PB_MAX_REQUIRED_FIELDS 64 +#endif + +#if PB_MAX_REQUIRED_FIELDS < 64 +#error You should not lower PB_MAX_REQUIRED_FIELDS from the default value (64). +#endif + +#ifdef PB_WITHOUT_64BIT +#ifdef PB_CONVERT_DOUBLE_FLOAT +/* Cannot use doubles without 64-bit types */ +#undef PB_CONVERT_DOUBLE_FLOAT +#endif +#endif + +/* Data type for storing encoded data and other byte streams. + * This typedef exists to support platforms where uint8_t does not exist. + * You can regard it as equivalent on uint8_t on other platforms. + */ +#if defined(PB_BYTE_T_OVERRIDE) +typedef PB_BYTE_T_OVERRIDE pb_byte_t; +#elif defined(UINT8_MAX) +typedef uint8_t pb_byte_t; +#else +typedef uint_least8_t pb_byte_t; +#endif + +/* List of possible field types. These are used in the autogenerated code. + * Least-significant 4 bits tell the scalar type + * Most-significant 4 bits specify repeated/required/packed etc. + */ +typedef pb_byte_t pb_type_t; + +/**** Field data types ****/ + +/* Numeric types */ +#define PB_LTYPE_BOOL 0x00U /* bool */ +#define PB_LTYPE_VARINT 0x01U /* int32, int64, enum, bool */ +#define PB_LTYPE_UVARINT 0x02U /* uint32, uint64 */ +#define PB_LTYPE_SVARINT 0x03U /* sint32, sint64 */ +#define PB_LTYPE_FIXED32 0x04U /* fixed32, sfixed32, float */ +#define PB_LTYPE_FIXED64 0x05U /* fixed64, sfixed64, double */ + +/* Marker for last packable field type. */ +#define PB_LTYPE_LAST_PACKABLE 0x05U + +/* Byte array with pre-allocated buffer. + * data_size is the length of the allocated PB_BYTES_ARRAY structure. */ +#define PB_LTYPE_BYTES 0x06U + +/* String with pre-allocated buffer. + * data_size is the maximum length. */ +#define PB_LTYPE_STRING 0x07U + +/* Submessage + * submsg_fields is pointer to field descriptions */ +#define PB_LTYPE_SUBMESSAGE 0x08U + +/* Submessage with pre-decoding callback + * The pre-decoding callback is stored as pb_callback_t right before pSize. + * submsg_fields is pointer to field descriptions */ +#define PB_LTYPE_SUBMSG_W_CB 0x09U + +/* Extension pseudo-field + * The field contains a pointer to pb_extension_t */ +#define PB_LTYPE_EXTENSION 0x0AU + +/* Byte array with inline, pre-allocated byffer. + * data_size is the length of the inline, allocated buffer. + * This differs from PB_LTYPE_BYTES by defining the element as + * pb_byte_t[data_size] rather than pb_bytes_array_t. */ +#define PB_LTYPE_FIXED_LENGTH_BYTES 0x0BU + +/* Number of declared LTYPES */ +#define PB_LTYPES_COUNT 0x0CU +#define PB_LTYPE_MASK 0x0FU + +/**** Field repetition rules ****/ + +#define PB_HTYPE_REQUIRED 0x00U +#define PB_HTYPE_OPTIONAL 0x10U +#define PB_HTYPE_SINGULAR 0x10U +#define PB_HTYPE_REPEATED 0x20U +#define PB_HTYPE_FIXARRAY 0x20U +#define PB_HTYPE_ONEOF 0x30U +#define PB_HTYPE_MASK 0x30U + +/**** Field allocation types ****/ + +#define PB_ATYPE_STATIC 0x00U +#define PB_ATYPE_POINTER 0x80U +#define PB_ATYPE_CALLBACK 0x40U +#define PB_ATYPE_MASK 0xC0U + +#define PB_ATYPE(x) ((x) & PB_ATYPE_MASK) +#define PB_HTYPE(x) ((x) & PB_HTYPE_MASK) +#define PB_LTYPE(x) ((x) & PB_LTYPE_MASK) +#define PB_LTYPE_IS_SUBMSG(x) (PB_LTYPE(x) == PB_LTYPE_SUBMESSAGE || \ + PB_LTYPE(x) == PB_LTYPE_SUBMSG_W_CB) + +/* Data type used for storing sizes of struct fields + * and array counts. + */ +#if defined(PB_FIELD_32BIT) + typedef uint32_t pb_size_t; + typedef int32_t pb_ssize_t; +#else + typedef uint_least16_t pb_size_t; + typedef int_least16_t pb_ssize_t; +#endif +#define PB_SIZE_MAX ((pb_size_t)-1) + +/* Forward declaration of struct types */ +typedef struct pb_istream_s pb_istream_t; +typedef struct pb_ostream_s pb_ostream_t; +typedef struct pb_field_iter_s pb_field_iter_t; + +/* This structure is used in auto-generated constants + * to specify struct fields. + */ +typedef struct pb_msgdesc_s pb_msgdesc_t; +struct pb_msgdesc_s { + const uint32_t *field_info; + const pb_msgdesc_t * const * submsg_info; + const pb_byte_t *default_value; + + bool (*field_callback)(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field); + + pb_size_t field_count; + pb_size_t required_field_count; + pb_size_t largest_tag; +}; + +/* Iterator for message descriptor */ +struct pb_field_iter_s { + const pb_msgdesc_t *descriptor; /* Pointer to message descriptor constant */ + void *message; /* Pointer to start of the structure */ + + pb_size_t index; /* Index of the field */ + pb_size_t field_info_index; /* Index to descriptor->field_info array */ + pb_size_t required_field_index; /* Index that counts only the required fields */ + pb_size_t submessage_index; /* Index that counts only submessages */ + + pb_size_t tag; /* Tag of current field */ + pb_size_t data_size; /* sizeof() of a single item */ + pb_size_t array_size; /* Number of array entries */ + pb_type_t type; /* Type of current field */ + + void *pField; /* Pointer to current field in struct */ + void *pData; /* Pointer to current data contents. Different than pField for arrays and pointers. */ + void *pSize; /* Pointer to count/has field */ + + const pb_msgdesc_t *submsg_desc; /* For submessage fields, pointer to field descriptor for the submessage. */ +}; + +/* For compatibility with legacy code */ +typedef pb_field_iter_t pb_field_t; + +/* Make sure that the standard integer types are of the expected sizes. + * Otherwise fixed32/fixed64 fields can break. + * + * If you get errors here, it probably means that your stdint.h is not + * correct for your platform. + */ +#ifndef PB_WITHOUT_64BIT +PB_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), INT64_T_WRONG_SIZE) +PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE) +#endif + +/* This structure is used for 'bytes' arrays. + * It has the number of bytes in the beginning, and after that an array. + * Note that actual structs used will have a different length of bytes array. + */ +#define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; } +#define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes)) + +struct pb_bytes_array_s { + pb_size_t size; + pb_byte_t bytes[1]; +}; +typedef struct pb_bytes_array_s pb_bytes_array_t; + +/* This structure is used for giving the callback function. + * It is stored in the message structure and filled in by the method that + * calls pb_decode. + * + * The decoding callback will be given a limited-length stream + * If the wire type was string, the length is the length of the string. + * If the wire type was a varint/fixed32/fixed64, the length is the length + * of the actual value. + * The function may be called multiple times (especially for repeated types, + * but also otherwise if the message happens to contain the field multiple + * times.) + * + * The encoding callback will receive the actual output stream. + * It should write all the data in one call, including the field tag and + * wire type. It can write multiple fields. + * + * The callback can be null if you want to skip a field. + */ +typedef struct pb_callback_s pb_callback_t; +struct pb_callback_s { + /* Callback functions receive a pointer to the arg field. + * You can access the value of the field as *arg, and modify it if needed. + */ + union { + bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg); + bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg); + } funcs; + + /* Free arg for use by callback */ + void *arg; +}; + +extern bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field); + +/* Wire types. Library user needs these only in encoder callbacks. */ +typedef enum { + PB_WT_VARINT = 0, + PB_WT_64BIT = 1, + PB_WT_STRING = 2, + PB_WT_32BIT = 5, + PB_WT_PACKED = 255 /* PB_WT_PACKED is internal marker for packed arrays. */ +} pb_wire_type_t; + +/* Structure for defining the handling of unknown/extension fields. + * Usually the pb_extension_type_t structure is automatically generated, + * while the pb_extension_t structure is created by the user. However, + * if you want to catch all unknown fields, you can also create a custom + * pb_extension_type_t with your own callback. + */ +typedef struct pb_extension_type_s pb_extension_type_t; +typedef struct pb_extension_s pb_extension_t; +struct pb_extension_type_s { + /* Called for each unknown field in the message. + * If you handle the field, read off all of its data and return true. + * If you do not handle the field, do not read anything and return true. + * If you run into an error, return false. + * Set to NULL for default handler. + */ + bool (*decode)(pb_istream_t *stream, pb_extension_t *extension, + uint32_t tag, pb_wire_type_t wire_type); + + /* Called once after all regular fields have been encoded. + * If you have something to write, do so and return true. + * If you do not have anything to write, just return true. + * If you run into an error, return false. + * Set to NULL for default handler. + */ + bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension); + + /* Free field for use by the callback. */ + const void *arg; +}; + +struct pb_extension_s { + /* Type describing the extension field. Usually you'll initialize + * this to a pointer to the automatically generated structure. */ + const pb_extension_type_t *type; + + /* Destination for the decoded data. This must match the datatype + * of the extension field. */ + void *dest; + + /* Pointer to the next extension handler, or NULL. + * If this extension does not match a field, the next handler is + * automatically called. */ + pb_extension_t *next; + + /* The decoder sets this to true if the extension was found. + * Ignored for encoding. */ + bool found; +}; + +#define pb_extension_init_zero {NULL,NULL,NULL,false} + +/* Memory allocation functions to use. You can define pb_realloc and + * pb_free to custom functions if you want. */ +#ifdef PB_ENABLE_MALLOC +# ifndef pb_realloc +# define pb_realloc(ptr, size) realloc(ptr, size) +# endif +# ifndef pb_free +# define pb_free(ptr) free(ptr) +# endif +#endif + +/* This is used to inform about need to regenerate .pb.h/.pb.c files. */ +#define PB_PROTO_HEADER_VERSION 40 + +/* These macros are used to declare pb_field_t's in the constant array. */ +/* Size of a structure member, in bytes. */ +#define pb_membersize(st, m) (sizeof ((st*)0)->m) +/* Number of entries in an array. */ +#define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0])) +/* Delta from start of one member to the start of another member. */ +#define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2)) + +/* Force expansion of macro value */ +#define PB_EXPAND(x) x + +/* Binding of a message field set into a specific structure */ +#define PB_BIND(msgname, structname, width) \ + const uint32_t structname ## _field_info[] PB_PROGMEM = \ + { \ + msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ ## width, structname) \ + 0 \ + }; \ + const pb_msgdesc_t* const structname ## _submsg_info[] = \ + { \ + msgname ## _FIELDLIST(PB_GEN_SUBMSG_INFO, structname) \ + NULL \ + }; \ + const pb_msgdesc_t structname ## _msg = \ + { \ + structname ## _field_info, \ + structname ## _submsg_info, \ + msgname ## _DEFAULT, \ + msgname ## _CALLBACK, \ + 0 msgname ## _FIELDLIST(PB_GEN_FIELD_COUNT, structname), \ + 0 msgname ## _FIELDLIST(PB_GEN_REQ_FIELD_COUNT, structname), \ + 0 msgname ## _FIELDLIST(PB_GEN_LARGEST_TAG, structname), \ + }; \ + msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ASSERT_ ## width, structname) + +#define PB_GEN_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) +1 +#define PB_GEN_REQ_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) \ + + (PB_HTYPE_ ## htype == PB_HTYPE_REQUIRED) +#define PB_GEN_LARGEST_TAG(structname, atype, htype, ltype, fieldname, tag) \ + * 0 + tag + +/* X-macro for generating the entries in struct_field_info[] array. */ +#define PB_GEN_FIELD_INFO_1(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_2(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_4(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_8(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_AUTO(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \ + tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_FIELDINFO_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \ + PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) + +#define PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \ + PB_FIELDINFO_ ## width(tag, type, data_offset, data_size, size_offset, array_size) + +/* X-macro for generating asserts that entries fit in struct_field_info[] array. + * The structure of macros here must match the structure above in PB_GEN_FIELD_INFO_x(), + * but it is not easily reused because of how macro substitutions work. */ +#define PB_GEN_FIELD_INFO_ASSERT_1(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_ASSERT_2(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_ASSERT_4(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_ASSERT_8(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_GEN_FIELD_INFO_ASSERT_AUTO(structname, atype, htype, ltype, fieldname, tag) \ + PB_FIELDINFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \ + tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \ + PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \ + PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname)) + +#define PB_FIELDINFO_ASSERT_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \ + PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) + +#define PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \ + PB_FIELDINFO_ASSERT_ ## width(tag, type, data_offset, data_size, size_offset, array_size) + +#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) +#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) +#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DO ## htype(structname, fieldname) +#define PB_DO_PB_HTYPE_REQUIRED(structname, fieldname) offsetof(structname, fieldname) +#define PB_DO_PB_HTYPE_SINGULAR(structname, fieldname) offsetof(structname, fieldname) +#define PB_DO_PB_HTYPE_ONEOF(structname, fieldname) offsetof(structname, PB_ONEOF_NAME(FULL, fieldname)) +#define PB_DO_PB_HTYPE_OPTIONAL(structname, fieldname) offsetof(structname, fieldname) +#define PB_DO_PB_HTYPE_REPEATED(structname, fieldname) offsetof(structname, fieldname) +#define PB_DO_PB_HTYPE_FIXARRAY(structname, fieldname) offsetof(structname, fieldname) + +#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SO ## htype(structname, fieldname) +#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SO_PTR ## htype(structname, fieldname) +#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SO_CB ## htype(structname, fieldname) +#define PB_SO_PB_HTYPE_REQUIRED(structname, fieldname) 0 +#define PB_SO_PB_HTYPE_SINGULAR(structname, fieldname) 0 +#define PB_SO_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname)) +#define PB_SO_PB_HTYPE_ONEOF2(structname, fullname, unionname) PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) +#define PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_ ## unionname) +#define PB_SO_PB_HTYPE_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_ ## fieldname) +#define PB_SO_PB_HTYPE_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname ## _count) +#define PB_SO_PB_HTYPE_FIXARRAY(structname, fieldname) 0 +#define PB_SO_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 0 +#define PB_SO_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 0 +#define PB_SO_PTR_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname) +#define PB_SO_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 0 +#define PB_SO_PTR_PB_HTYPE_REPEATED(structname, fieldname) PB_SO_PB_HTYPE_REPEATED(structname, fieldname) +#define PB_SO_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_REQUIRED(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_SINGULAR(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF(structname, fieldname) +#define PB_SO_CB_PB_HTYPE_OPTIONAL(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_REPEATED(structname, fieldname) 0 +#define PB_SO_CB_PB_HTYPE_FIXARRAY(structname, fieldname) 0 + +#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_AS ## htype(structname, fieldname) +#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_AS_PTR ## htype(structname, fieldname) +#define PB_ARRAY_SIZE_CALLBACK(htype, structname, fieldname) 1 +#define PB_AS_PB_HTYPE_REQUIRED(structname, fieldname) 1 +#define PB_AS_PB_HTYPE_SINGULAR(structname, fieldname) 1 +#define PB_AS_PB_HTYPE_OPTIONAL(structname, fieldname) 1 +#define PB_AS_PB_HTYPE_ONEOF(structname, fieldname) 1 +#define PB_AS_PB_HTYPE_REPEATED(structname, fieldname) pb_arraysize(structname, fieldname) +#define PB_AS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname) +#define PB_AS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_ONEOF(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_REPEATED(structname, fieldname) 1 +#define PB_AS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname[0]) + +#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DS ## htype(structname, fieldname) +#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DS_PTR ## htype(structname, fieldname) +#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DS_CB ## htype(structname, fieldname) +#define PB_DS_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)) +#define PB_DS_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)[0]) +#define PB_DS_PTR_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname[0]) +#define PB_DS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname[0][0]) +#define PB_DS_CB_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_CB_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_CB_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_CB_PB_HTYPE_ONEOF(structname, fieldname) pb_membersize(structname, PB_ONEOF_NAME(FULL, fieldname)) +#define PB_DS_CB_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname) +#define PB_DS_CB_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname) + +#define PB_ONEOF_NAME(type, tuple) PB_EXPAND(PB_ONEOF_NAME_ ## type tuple) +#define PB_ONEOF_NAME_UNION(unionname,membername,fullname) unionname +#define PB_ONEOF_NAME_MEMBER(unionname,membername,fullname) membername +#define PB_ONEOF_NAME_FULL(unionname,membername,fullname) fullname + +#define PB_GEN_SUBMSG_INFO(structname, atype, htype, ltype, fieldname, tag) \ + PB_SUBMSG_INFO_ ## htype(_PB_LTYPE_ ## ltype, structname, fieldname) + +#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SUBMSG_INFO_ONEOF(ltype, structname, fieldname) PB_SUBMSG_INFO_ONEOF2(ltype, structname, PB_ONEOF_NAME(UNION, fieldname), PB_ONEOF_NAME(MEMBER, fieldname)) +#define PB_SUBMSG_INFO_ONEOF2(ltype, structname, unionname, membername) PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) +#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) PB_SI ## ltype(structname ## _ ## unionname ## _ ## membername ## _MSGTYPE) +#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE) +#define PB_SI_PB_LTYPE_BOOL(t) +#define PB_SI_PB_LTYPE_BYTES(t) +#define PB_SI_PB_LTYPE_DOUBLE(t) +#define PB_SI_PB_LTYPE_ENUM(t) +#define PB_SI_PB_LTYPE_UENUM(t) +#define PB_SI_PB_LTYPE_FIXED32(t) +#define PB_SI_PB_LTYPE_FIXED64(t) +#define PB_SI_PB_LTYPE_FLOAT(t) +#define PB_SI_PB_LTYPE_INT32(t) +#define PB_SI_PB_LTYPE_INT64(t) +#define PB_SI_PB_LTYPE_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t) +#define PB_SI_PB_LTYPE_MSG_W_CB(t) PB_SUBMSG_DESCRIPTOR(t) +#define PB_SI_PB_LTYPE_SFIXED32(t) +#define PB_SI_PB_LTYPE_SFIXED64(t) +#define PB_SI_PB_LTYPE_SINT32(t) +#define PB_SI_PB_LTYPE_SINT64(t) +#define PB_SI_PB_LTYPE_STRING(t) +#define PB_SI_PB_LTYPE_UINT32(t) +#define PB_SI_PB_LTYPE_UINT64(t) +#define PB_SI_PB_LTYPE_EXTENSION(t) +#define PB_SI_PB_LTYPE_FIXED_LENGTH_BYTES(t) +#define PB_SUBMSG_DESCRIPTOR(t) &(t ## _msg), + +/* The field descriptors use a variable width format, with width of either + * 1, 2, 4 or 8 of 32-bit words. The two lowest bytes of the first byte always + * encode the descriptor size, 6 lowest bits of field tag number, and 8 bits + * of the field type. + * + * Descriptor size is encoded as 0 = 1 word, 1 = 2 words, 2 = 4 words, 3 = 8 words. + * + * Formats, listed starting with the least significant bit of the first word. + * 1 word: [2-bit len] [6-bit tag] [8-bit type] [8-bit data_offset] [4-bit size_offset] [4-bit data_size] + * + * 2 words: [2-bit len] [6-bit tag] [8-bit type] [12-bit array_size] [4-bit size_offset] + * [16-bit data_offset] [12-bit data_size] [4-bit tag>>6] + * + * 4 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit array_size] + * [8-bit size_offset] [24-bit tag>>6] + * [32-bit data_offset] + * [32-bit data_size] + * + * 8 words: [2-bit len] [6-bit tag] [8-bit type] [16-bit reserved] + * [8-bit size_offset] [24-bit tag>>6] + * [32-bit data_offset] + * [32-bit data_size] + * [32-bit array_size] + * [32-bit reserved] + * [32-bit reserved] + * [32-bit reserved] + */ + +#define PB_FIELDINFO_1(tag, type, data_offset, data_size, size_offset, array_size) \ + (0 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(data_offset) & 0xFF) << 16) | \ + (((uint32_t)(size_offset) & 0x0F) << 24) | (((uint32_t)(data_size) & 0x0F) << 28)), + +#define PB_FIELDINFO_2(tag, type, data_offset, data_size, size_offset, array_size) \ + (1 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFF) << 16) | (((uint32_t)(size_offset) & 0x0F) << 28)), \ + (((uint32_t)(data_offset) & 0xFFFF) | (((uint32_t)(data_size) & 0xFFF) << 16) | (((uint32_t)(tag) & 0x3c0) << 22)), + +#define PB_FIELDINFO_4(tag, type, data_offset, data_size, size_offset, array_size) \ + (2 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFFF) << 16)), \ + ((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \ + (data_offset), (data_size), + +#define PB_FIELDINFO_8(tag, type, data_offset, data_size, size_offset, array_size) \ + (3 | (((tag) << 2) & 0xFF) | ((type) << 8)), \ + ((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \ + (data_offset), (data_size), (array_size), 0, 0, 0, + +/* These assertions verify that the field information fits in the allocated space. + * The generator tries to automatically determine the correct width that can fit all + * data associated with a message. These asserts will fail only if there has been a + * problem in the automatic logic - this may be worth reporting as a bug. As a workaround, + * you can increase the descriptor width by defining PB_FIELDINFO_WIDTH or by setting + * descriptorsize option in .options file. + */ +#define PB_FITS(value,bits) ((uint32_t)(value) < ((uint32_t)1<2GB messages with nanopb anyway. + */ +#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \ + PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag) + +#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \ + PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,31), FIELDINFO_DOES_NOT_FIT_width8_field ## tag) +#endif + + +/* Automatic picking of FIELDINFO width: + * Uses width 1 when possible, otherwise resorts to width 2. + * This is used when PB_BIND() is called with "AUTO" as the argument. + * The generator will give explicit size argument when it knows that a message + * structure grows beyond 1-word format limits. + */ +#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FI_WIDTH ## atype(htype, ltype) +#define PB_FI_WIDTH_PB_ATYPE_STATIC(htype, ltype) PB_FI_WIDTH ## htype(ltype) +#define PB_FI_WIDTH_PB_ATYPE_POINTER(htype, ltype) PB_FI_WIDTH ## htype(ltype) +#define PB_FI_WIDTH_PB_ATYPE_CALLBACK(htype, ltype) 2 +#define PB_FI_WIDTH_PB_HTYPE_REQUIRED(ltype) PB_FI_WIDTH ## ltype +#define PB_FI_WIDTH_PB_HTYPE_SINGULAR(ltype) PB_FI_WIDTH ## ltype +#define PB_FI_WIDTH_PB_HTYPE_OPTIONAL(ltype) PB_FI_WIDTH ## ltype +#define PB_FI_WIDTH_PB_HTYPE_ONEOF(ltype) PB_FI_WIDTH ## ltype +#define PB_FI_WIDTH_PB_HTYPE_REPEATED(ltype) 2 +#define PB_FI_WIDTH_PB_HTYPE_FIXARRAY(ltype) 2 +#define PB_FI_WIDTH_PB_LTYPE_BOOL 1 +#define PB_FI_WIDTH_PB_LTYPE_BYTES 2 +#define PB_FI_WIDTH_PB_LTYPE_DOUBLE 1 +#define PB_FI_WIDTH_PB_LTYPE_ENUM 1 +#define PB_FI_WIDTH_PB_LTYPE_UENUM 1 +#define PB_FI_WIDTH_PB_LTYPE_FIXED32 1 +#define PB_FI_WIDTH_PB_LTYPE_FIXED64 1 +#define PB_FI_WIDTH_PB_LTYPE_FLOAT 1 +#define PB_FI_WIDTH_PB_LTYPE_INT32 1 +#define PB_FI_WIDTH_PB_LTYPE_INT64 1 +#define PB_FI_WIDTH_PB_LTYPE_MESSAGE 2 +#define PB_FI_WIDTH_PB_LTYPE_MSG_W_CB 2 +#define PB_FI_WIDTH_PB_LTYPE_SFIXED32 1 +#define PB_FI_WIDTH_PB_LTYPE_SFIXED64 1 +#define PB_FI_WIDTH_PB_LTYPE_SINT32 1 +#define PB_FI_WIDTH_PB_LTYPE_SINT64 1 +#define PB_FI_WIDTH_PB_LTYPE_STRING 2 +#define PB_FI_WIDTH_PB_LTYPE_UINT32 1 +#define PB_FI_WIDTH_PB_LTYPE_UINT64 1 +#define PB_FI_WIDTH_PB_LTYPE_EXTENSION 1 +#define PB_FI_WIDTH_PB_LTYPE_FIXED_LENGTH_BYTES 2 + +/* The mapping from protobuf types to LTYPEs is done using these macros. */ +#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL +#define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES +#define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64 +#define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT +#define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT +#define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32 +#define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64 +#define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32 +#define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT +#define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT +#define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE +#define PB_LTYPE_MAP_MSG_W_CB PB_LTYPE_SUBMSG_W_CB +#define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32 +#define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64 +#define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT +#define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT +#define PB_LTYPE_MAP_STRING PB_LTYPE_STRING +#define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT +#define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT +#define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION +#define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES + +/* These macros are used for giving out error messages. + * They are mostly a debugging aid; the main error information + * is the true/false return value from functions. + * Some code space can be saved by disabling the error + * messages if not used. + * + * PB_SET_ERROR() sets the error message if none has been set yet. + * msg must be a constant string literal. + * PB_GET_ERROR() always returns a pointer to a string. + * PB_RETURN_ERROR() sets the error and returns false from current + * function. + */ +#ifdef PB_NO_ERRMSG +#define PB_SET_ERROR(stream, msg) PB_UNUSED(stream) +#define PB_GET_ERROR(stream) "(errmsg disabled)" +#else +#define PB_SET_ERROR(stream, msg) (stream->errmsg = (stream)->errmsg ? (stream)->errmsg : (msg)) +#define PB_GET_ERROR(stream) ((stream)->errmsg ? (stream)->errmsg : "(none)") +#endif + +#define PB_RETURN_ERROR(stream, msg) return PB_SET_ERROR(stream, msg), false + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#ifdef __cplusplus +#if __cplusplus >= 201103L +#define PB_CONSTEXPR constexpr +#else // __cplusplus >= 201103L +#define PB_CONSTEXPR +#endif // __cplusplus >= 201103L + +#if __cplusplus >= 201703L +#define PB_INLINE_CONSTEXPR inline constexpr +#else // __cplusplus >= 201703L +#define PB_INLINE_CONSTEXPR PB_CONSTEXPR +#endif // __cplusplus >= 201703L + +extern "C++" +{ +namespace nanopb { +// Each type will be partially specialized by the generator. +template struct MessageDescriptor; +} // namespace nanopb +} +#endif /* __cplusplus */ + +#endif diff --git a/src/Debug/nanopb/pb_common.c b/src/Debug/nanopb/pb_common.c new file mode 100644 index 000000000..6aee76b1e --- /dev/null +++ b/src/Debug/nanopb/pb_common.c @@ -0,0 +1,388 @@ +/* pb_common.c: Common support functions for pb_encode.c and pb_decode.c. + * + * 2014 Petteri Aimonen + */ + +#include "pb_common.h" + +static bool load_descriptor_values(pb_field_iter_t *iter) +{ + uint32_t word0; + uint32_t data_offset; + int_least8_t size_offset; + + if (iter->index >= iter->descriptor->field_count) + return false; + + word0 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); + iter->type = (pb_type_t)((word0 >> 8) & 0xFF); + + switch(word0 & 3) + { + case 0: { + /* 1-word format */ + iter->array_size = 1; + iter->tag = (pb_size_t)((word0 >> 2) & 0x3F); + size_offset = (int_least8_t)((word0 >> 24) & 0x0F); + data_offset = (word0 >> 16) & 0xFF; + iter->data_size = (pb_size_t)((word0 >> 28) & 0x0F); + break; + } + + case 1: { + /* 2-word format */ + uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); + + iter->array_size = (pb_size_t)((word0 >> 16) & 0x0FFF); + iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 28) << 6)); + size_offset = (int_least8_t)((word0 >> 28) & 0x0F); + data_offset = word1 & 0xFFFF; + iter->data_size = (pb_size_t)((word1 >> 16) & 0x0FFF); + break; + } + + case 2: { + /* 4-word format */ + uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); + uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]); + uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]); + + iter->array_size = (pb_size_t)(word0 >> 16); + iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6)); + size_offset = (int_least8_t)(word1 & 0xFF); + data_offset = word2; + iter->data_size = (pb_size_t)word3; + break; + } + + default: { + /* 8-word format */ + uint32_t word1 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 1]); + uint32_t word2 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 2]); + uint32_t word3 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 3]); + uint32_t word4 = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index + 4]); + + iter->array_size = (pb_size_t)word4; + iter->tag = (pb_size_t)(((word0 >> 2) & 0x3F) | ((word1 >> 8) << 6)); + size_offset = (int_least8_t)(word1 & 0xFF); + data_offset = word2; + iter->data_size = (pb_size_t)word3; + break; + } + } + + if (!iter->message) + { + /* Avoid doing arithmetic on null pointers, it is undefined */ + iter->pField = NULL; + iter->pSize = NULL; + } + else + { + iter->pField = (char*)iter->message + data_offset; + + if (size_offset) + { + iter->pSize = (char*)iter->pField - size_offset; + } + else if (PB_HTYPE(iter->type) == PB_HTYPE_REPEATED && + (PB_ATYPE(iter->type) == PB_ATYPE_STATIC || + PB_ATYPE(iter->type) == PB_ATYPE_POINTER)) + { + /* Fixed count array */ + iter->pSize = &iter->array_size; + } + else + { + iter->pSize = NULL; + } + + if (PB_ATYPE(iter->type) == PB_ATYPE_POINTER && iter->pField != NULL) + { + iter->pData = *(void**)iter->pField; + } + else + { + iter->pData = iter->pField; + } + } + + if (PB_LTYPE_IS_SUBMSG(iter->type)) + { + iter->submsg_desc = iter->descriptor->submsg_info[iter->submessage_index]; + } + else + { + iter->submsg_desc = NULL; + } + + return true; +} + +static void advance_iterator(pb_field_iter_t *iter) +{ + iter->index++; + + if (iter->index >= iter->descriptor->field_count) + { + /* Restart */ + iter->index = 0; + iter->field_info_index = 0; + iter->submessage_index = 0; + iter->required_field_index = 0; + } + else + { + /* Increment indexes based on previous field type. + * All field info formats have the following fields: + * - lowest 2 bits tell the amount of words in the descriptor (2^n words) + * - bits 2..7 give the lowest bits of tag number. + * - bits 8..15 give the field type. + */ + uint32_t prev_descriptor = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); + pb_type_t prev_type = (prev_descriptor >> 8) & 0xFF; + pb_size_t descriptor_len = (pb_size_t)(1 << (prev_descriptor & 3)); + + /* Add to fields. + * The cast to pb_size_t is needed to avoid -Wconversion warning. + * Because the data is is constants from generator, there is no danger of overflow. + */ + iter->field_info_index = (pb_size_t)(iter->field_info_index + descriptor_len); + iter->required_field_index = (pb_size_t)(iter->required_field_index + (PB_HTYPE(prev_type) == PB_HTYPE_REQUIRED)); + iter->submessage_index = (pb_size_t)(iter->submessage_index + PB_LTYPE_IS_SUBMSG(prev_type)); + } +} + +bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message) +{ + memset(iter, 0, sizeof(*iter)); + + iter->descriptor = desc; + iter->message = message; + + return load_descriptor_values(iter); +} + +bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension) +{ + const pb_msgdesc_t *msg = (const pb_msgdesc_t*)extension->type->arg; + bool status; + + uint32_t word0 = PB_PROGMEM_READU32(msg->field_info[0]); + if (PB_ATYPE(word0 >> 8) == PB_ATYPE_POINTER) + { + /* For pointer extensions, the pointer is stored directly + * in the extension structure. This avoids having an extra + * indirection. */ + status = pb_field_iter_begin(iter, msg, &extension->dest); + } + else + { + status = pb_field_iter_begin(iter, msg, extension->dest); + } + + iter->pSize = &extension->found; + return status; +} + +bool pb_field_iter_next(pb_field_iter_t *iter) +{ + advance_iterator(iter); + (void)load_descriptor_values(iter); + return iter->index != 0; +} + +bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag) +{ + if (iter->tag == tag) + { + return true; /* Nothing to do, correct field already. */ + } + else if (tag > iter->descriptor->largest_tag) + { + return false; + } + else + { + pb_size_t start = iter->index; + uint32_t fieldinfo; + + if (tag < iter->tag) + { + /* Fields are in tag number order, so we know that tag is between + * 0 and our start position. Setting index to end forces + * advance_iterator() call below to restart from beginning. */ + iter->index = iter->descriptor->field_count; + } + + do + { + /* Advance iterator but don't load values yet */ + advance_iterator(iter); + + /* Do fast check for tag number match */ + fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); + + if (((fieldinfo >> 2) & 0x3F) == (tag & 0x3F)) + { + /* Good candidate, check further */ + (void)load_descriptor_values(iter); + + if (iter->tag == tag && + PB_LTYPE(iter->type) != PB_LTYPE_EXTENSION) + { + /* Found it */ + return true; + } + } + } while (iter->index != start); + + /* Searched all the way back to start, and found nothing. */ + (void)load_descriptor_values(iter); + return false; + } +} + +bool pb_field_iter_find_extension(pb_field_iter_t *iter) +{ + if (PB_LTYPE(iter->type) == PB_LTYPE_EXTENSION) + { + return true; + } + else + { + pb_size_t start = iter->index; + uint32_t fieldinfo; + + do + { + /* Advance iterator but don't load values yet */ + advance_iterator(iter); + + /* Do fast check for field type */ + fieldinfo = PB_PROGMEM_READU32(iter->descriptor->field_info[iter->field_info_index]); + + if (PB_LTYPE((fieldinfo >> 8) & 0xFF) == PB_LTYPE_EXTENSION) + { + return load_descriptor_values(iter); + } + } while (iter->index != start); + + /* Searched all the way back to start, and found nothing. */ + (void)load_descriptor_values(iter); + return false; + } +} + +static void *pb_const_cast(const void *p) +{ + /* Note: this casts away const, in order to use the common field iterator + * logic for both encoding and decoding. The cast is done using union + * to avoid spurious compiler warnings. */ + union { + void *p1; + const void *p2; + } t; + t.p2 = p; + return t.p1; +} + +bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message) +{ + return pb_field_iter_begin(iter, desc, pb_const_cast(message)); +} + +bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension) +{ + return pb_field_iter_begin_extension(iter, (pb_extension_t*)pb_const_cast(extension)); +} + +bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field) +{ + if (field->data_size == sizeof(pb_callback_t)) + { + pb_callback_t *pCallback = (pb_callback_t*)field->pData; + + if (pCallback != NULL) + { + if (istream != NULL && pCallback->funcs.decode != NULL) + { + return pCallback->funcs.decode(istream, field, &pCallback->arg); + } + + if (ostream != NULL && pCallback->funcs.encode != NULL) + { + return pCallback->funcs.encode(ostream, field, &pCallback->arg); + } + } + } + + return true; /* Success, but didn't do anything */ + +} + +#ifdef PB_VALIDATE_UTF8 + +/* This function checks whether a string is valid UTF-8 text. + * + * Algorithm is adapted from https://www.cl.cam.ac.uk/~mgk25/ucs/utf8_check.c + * Original copyright: Markus Kuhn 2005-03-30 + * Licensed under "Short code license", which allows use under MIT license or + * any compatible with it. + */ + +bool pb_validate_utf8(const char *str) +{ + const pb_byte_t *s = (const pb_byte_t*)str; + while (*s) + { + if (*s < 0x80) + { + /* 0xxxxxxx */ + s++; + } + else if ((s[0] & 0xe0) == 0xc0) + { + /* 110XXXXx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[0] & 0xfe) == 0xc0) /* overlong? */ + return false; + else + s += 2; + } + else if ((s[0] & 0xf0) == 0xe0) + { + /* 1110XXXX 10Xxxxxx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) || /* overlong? */ + (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) || /* surrogate? */ + (s[0] == 0xef && s[1] == 0xbf && + (s[2] & 0xfe) == 0xbe)) /* U+FFFE or U+FFFF? */ + return false; + else + s += 3; + } + else if ((s[0] & 0xf8) == 0xf0) + { + /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ + if ((s[1] & 0xc0) != 0x80 || + (s[2] & 0xc0) != 0x80 || + (s[3] & 0xc0) != 0x80 || + (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) || /* overlong? */ + (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4) /* > U+10FFFF? */ + return false; + else + s += 4; + } + else + { + return false; + } + } + + return true; +} + +#endif + diff --git a/src/Debug/nanopb/pb_common.h b/src/Debug/nanopb/pb_common.h new file mode 100644 index 000000000..58aa90f76 --- /dev/null +++ b/src/Debug/nanopb/pb_common.h @@ -0,0 +1,49 @@ +/* pb_common.h: Common support functions for pb_encode.c and pb_decode.c. + * These functions are rarely needed by applications directly. + */ + +#ifndef PB_COMMON_H_INCLUDED +#define PB_COMMON_H_INCLUDED + +#include "pb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initialize the field iterator structure to beginning. + * Returns false if the message type is empty. */ +bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_msgdesc_t *desc, void *message); + +/* Get a field iterator for extension field. */ +bool pb_field_iter_begin_extension(pb_field_iter_t *iter, pb_extension_t *extension); + +/* Same as pb_field_iter_begin(), but for const message pointer. + * Note that the pointers in pb_field_iter_t will be non-const but shouldn't + * be written to when using these functions. */ +bool pb_field_iter_begin_const(pb_field_iter_t *iter, const pb_msgdesc_t *desc, const void *message); +bool pb_field_iter_begin_extension_const(pb_field_iter_t *iter, const pb_extension_t *extension); + +/* Advance the iterator to the next field. + * Returns false when the iterator wraps back to the first field. */ +bool pb_field_iter_next(pb_field_iter_t *iter); + +/* Advance the iterator until it points at a field with the given tag. + * Returns false if no such field exists. */ +bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag); + +/* Find a field with type PB_LTYPE_EXTENSION, or return false if not found. + * There can be only one extension range field per message. */ +bool pb_field_iter_find_extension(pb_field_iter_t *iter); + +#ifdef PB_VALIDATE_UTF8 +/* Validate UTF-8 text string */ +bool pb_validate_utf8(const char *s); +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif + diff --git a/src/Debug/nanopb/pb_decode.c b/src/Debug/nanopb/pb_decode.c new file mode 100644 index 000000000..b3f96fc76 --- /dev/null +++ b/src/Debug/nanopb/pb_decode.c @@ -0,0 +1,1728 @@ +/* pb_decode.c -- decode a protobuf using minimal resources + * + * 2011 Petteri Aimonen + */ + +/* Use the GCC warn_unused_result attribute to check that all return values + * are propagated correctly. On other compilers, gcc before 3.4.0 and iar + * before 9.40.1 just ignore the annotation. + */ +#if (defined(__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))) || \ + (defined(__IAR_SYSTEMS_ICC__) && (__VER__ >= 9040001)) + #define checkreturn __attribute__((warn_unused_result)) +#else + #define checkreturn +#endif + +#include "pb.h" +#include "pb_decode.h" +#include "pb_common.h" + +/************************************** + * Declarations internal to this file * + **************************************/ + +static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); +static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof); +static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size); +static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field); +static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type); +static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_extension_t *extension); +static bool pb_field_set_to_default(pb_field_iter_t *field); +static bool pb_message_set_to_defaults(pb_field_iter_t *iter); +static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_skip_varint(pb_istream_t *stream); +static bool checkreturn pb_skip_string(pb_istream_t *stream); + +#ifdef PB_ENABLE_MALLOC +static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size); +static void initialize_pointer_field(void *pItem, pb_field_iter_t *field); +static bool checkreturn pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *field); +static void pb_release_single_field(pb_field_iter_t *field); +#endif + +#ifdef PB_WITHOUT_64BIT +#define pb_int64_t int32_t +#define pb_uint64_t uint32_t +#else +#define pb_int64_t int64_t +#define pb_uint64_t uint64_t +#endif + +typedef struct { + uint32_t bitfield[(PB_MAX_REQUIRED_FIELDS + 31) / 32]; +} pb_fields_seen_t; + +/******************************* + * pb_istream_t implementation * + *******************************/ + +static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) +{ + const pb_byte_t *source = (const pb_byte_t*)stream->state; + stream->state = (pb_byte_t*)stream->state + count; + + if (buf != NULL) + { + memcpy(buf, source, count * sizeof(pb_byte_t)); + } + + return true; +} + +bool checkreturn pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count) +{ + if (count == 0) + return true; + +#ifndef PB_BUFFER_ONLY + if (buf == NULL && stream->callback != buf_read) + { + /* Skip input bytes */ + pb_byte_t tmp[16]; + while (count > 16) + { + if (!pb_read(stream, tmp, 16)) + return false; + + count -= 16; + } + + return pb_read(stream, tmp, count); + } +#endif + + if (stream->bytes_left < count) + PB_RETURN_ERROR(stream, "end-of-stream"); + +#ifndef PB_BUFFER_ONLY + if (!stream->callback(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#else + if (!buf_read(stream, buf, count)) + return false; +#endif + + if (stream->bytes_left < count) + stream->bytes_left = 0; + else + stream->bytes_left -= count; + + return true; +} + +/* Read a single byte from input stream. buf may not be NULL. + * This is an optimization for the varint decoding. */ +static bool checkreturn pb_readbyte(pb_istream_t *stream, pb_byte_t *buf) +{ + if (stream->bytes_left == 0) + PB_RETURN_ERROR(stream, "end-of-stream"); + +#ifndef PB_BUFFER_ONLY + if (!stream->callback(stream, buf, 1)) + PB_RETURN_ERROR(stream, "io error"); +#else + *buf = *(const pb_byte_t*)stream->state; + stream->state = (pb_byte_t*)stream->state + 1; +#endif + + stream->bytes_left--; + + return true; +} + +pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen) +{ + pb_istream_t stream; + /* Cast away the const from buf without a compiler error. We are + * careful to use it only in a const manner in the callbacks. + */ + union { + void *state; + const void *c_state; + } state; +#ifdef PB_BUFFER_ONLY + stream.callback = NULL; +#else + stream.callback = &buf_read; +#endif + state.c_state = buf; + stream.state = state.state; + stream.bytes_left = msglen; +#ifndef PB_NO_ERRMSG + stream.errmsg = NULL; +#endif + return stream; +} + +/******************** + * Helper functions * + ********************/ + +static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof) +{ + pb_byte_t byte; + uint32_t result; + + if (!pb_readbyte(stream, &byte)) + { + if (stream->bytes_left == 0) + { + if (eof) + { + *eof = true; + } + } + + return false; + } + + if ((byte & 0x80) == 0) + { + /* Quick case, 1 byte value */ + result = byte; + } + else + { + /* Multibyte case */ + uint_fast8_t bitpos = 7; + result = byte & 0x7F; + + do + { + if (!pb_readbyte(stream, &byte)) + return false; + + if (bitpos >= 32) + { + /* Note: The varint could have trailing 0x80 bytes, or 0xFF for negative. */ + pb_byte_t sign_extension = (bitpos < 63) ? 0xFF : 0x01; + bool valid_extension = ((byte & 0x7F) == 0x00 || + ((result >> 31) != 0 && byte == sign_extension)); + + if (bitpos >= 64 || !valid_extension) + { + PB_RETURN_ERROR(stream, "varint overflow"); + } + } + else if (bitpos == 28) + { + if ((byte & 0x70) != 0 && (byte & 0x78) != 0x78) + { + PB_RETURN_ERROR(stream, "varint overflow"); + } + result |= (uint32_t)(byte & 0x0F) << bitpos; + } + else + { + result |= (uint32_t)(byte & 0x7F) << bitpos; + } + bitpos = (uint_fast8_t)(bitpos + 7); + } while (byte & 0x80); + } + + *dest = result; + return true; +} + +bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest) +{ + return pb_decode_varint32_eof(stream, dest, NULL); +} + +#ifndef PB_WITHOUT_64BIT +bool checkreturn pb_decode_varint(pb_istream_t *stream, uint64_t *dest) +{ + pb_byte_t byte; + uint_fast8_t bitpos = 0; + uint64_t result = 0; + + do + { + if (!pb_readbyte(stream, &byte)) + return false; + + if (bitpos >= 63 && (byte & 0xFE) != 0) + PB_RETURN_ERROR(stream, "varint overflow"); + + result |= (uint64_t)(byte & 0x7F) << bitpos; + bitpos = (uint_fast8_t)(bitpos + 7); + } while (byte & 0x80); + + *dest = result; + return true; +} +#endif + +bool checkreturn pb_skip_varint(pb_istream_t *stream) +{ + pb_byte_t byte; + do + { + if (!pb_read(stream, &byte, 1)) + return false; + } while (byte & 0x80); + return true; +} + +bool checkreturn pb_skip_string(pb_istream_t *stream) +{ + uint32_t length; + if (!pb_decode_varint32(stream, &length)) + return false; + + if ((size_t)length != length) + { + PB_RETURN_ERROR(stream, "size too large"); + } + + return pb_read(stream, NULL, (size_t)length); +} + +bool checkreturn pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof) +{ + uint32_t temp; + *eof = false; + *wire_type = (pb_wire_type_t) 0; + *tag = 0; + + if (!pb_decode_varint32_eof(stream, &temp, eof)) + { + return false; + } + + *tag = temp >> 3; + *wire_type = (pb_wire_type_t)(temp & 7); + return true; +} + +bool checkreturn pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type) +{ + switch (wire_type) + { + case PB_WT_VARINT: return pb_skip_varint(stream); + case PB_WT_64BIT: return pb_read(stream, NULL, 8); + case PB_WT_STRING: return pb_skip_string(stream); + case PB_WT_32BIT: return pb_read(stream, NULL, 4); + default: PB_RETURN_ERROR(stream, "invalid wire_type"); + } +} + +/* Read a raw value to buffer, for the purpose of passing it to callback as + * a substream. Size is maximum size on call, and actual size on return. + */ +static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size) +{ + size_t max_size = *size; + switch (wire_type) + { + case PB_WT_VARINT: + *size = 0; + do + { + (*size)++; + if (*size > max_size) + PB_RETURN_ERROR(stream, "varint overflow"); + + if (!pb_read(stream, buf, 1)) + return false; + } while (*buf++ & 0x80); + return true; + + case PB_WT_64BIT: + *size = 8; + return pb_read(stream, buf, 8); + + case PB_WT_32BIT: + *size = 4; + return pb_read(stream, buf, 4); + + case PB_WT_STRING: + /* Calling read_raw_value with a PB_WT_STRING is an error. + * Explicitly handle this case and fallthrough to default to avoid + * compiler warnings. + */ + + default: PB_RETURN_ERROR(stream, "invalid wire_type"); + } +} + +/* Decode string length from stream and return a substream with limited length. + * Remember to close the substream using pb_close_string_substream(). + */ +bool checkreturn pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream) +{ + uint32_t size; + if (!pb_decode_varint32(stream, &size)) + return false; + + *substream = *stream; + if (substream->bytes_left < size) + PB_RETURN_ERROR(stream, "parent stream too short"); + + substream->bytes_left = (size_t)size; + stream->bytes_left -= (size_t)size; + return true; +} + +bool checkreturn pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream) +{ + if (substream->bytes_left) { + if (!pb_read(substream, NULL, substream->bytes_left)) + return false; + } + + stream->state = substream->state; + +#ifndef PB_NO_ERRMSG + stream->errmsg = substream->errmsg; +#endif + return true; +} + +/************************* + * Decode a single field * + *************************/ + +static bool checkreturn decode_basic_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_BOOL: + if (wire_type != PB_WT_VARINT && wire_type != PB_WT_PACKED) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return pb_dec_bool(stream, field); + + case PB_LTYPE_VARINT: + case PB_LTYPE_UVARINT: + case PB_LTYPE_SVARINT: + if (wire_type != PB_WT_VARINT && wire_type != PB_WT_PACKED) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return pb_dec_varint(stream, field); + + case PB_LTYPE_FIXED32: + if (wire_type != PB_WT_32BIT && wire_type != PB_WT_PACKED) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return pb_decode_fixed32(stream, field->pData); + + case PB_LTYPE_FIXED64: + if (wire_type != PB_WT_64BIT && wire_type != PB_WT_PACKED) + PB_RETURN_ERROR(stream, "wrong wire type"); + +#ifdef PB_CONVERT_DOUBLE_FLOAT + if (field->data_size == sizeof(float)) + { + return pb_decode_double_as_float(stream, (float*)field->pData); + } +#endif + +#ifdef PB_WITHOUT_64BIT + PB_RETURN_ERROR(stream, "invalid data_size"); +#else + return pb_decode_fixed64(stream, field->pData); +#endif + + case PB_LTYPE_BYTES: + if (wire_type != PB_WT_STRING) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return pb_dec_bytes(stream, field); + + case PB_LTYPE_STRING: + if (wire_type != PB_WT_STRING) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return pb_dec_string(stream, field); + + case PB_LTYPE_SUBMESSAGE: + case PB_LTYPE_SUBMSG_W_CB: + if (wire_type != PB_WT_STRING) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return pb_dec_submessage(stream, field); + + case PB_LTYPE_FIXED_LENGTH_BYTES: + if (wire_type != PB_WT_STRING) + PB_RETURN_ERROR(stream, "wrong wire type"); + + return pb_dec_fixed_length_bytes(stream, field); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ + switch (PB_HTYPE(field->type)) + { + case PB_HTYPE_REQUIRED: + return decode_basic_field(stream, wire_type, field); + + case PB_HTYPE_OPTIONAL: + if (field->pSize != NULL) + *(bool*)field->pSize = true; + return decode_basic_field(stream, wire_type, field); + + case PB_HTYPE_REPEATED: + if (wire_type == PB_WT_STRING + && PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Packed array */ + bool status = true; + pb_istream_t substream; + pb_size_t *size = (pb_size_t*)field->pSize; + field->pData = (char*)field->pField + field->data_size * (*size); + + if (!pb_make_string_substream(stream, &substream)) + return false; + + while (substream.bytes_left > 0 && *size < field->array_size) + { + if (!decode_basic_field(&substream, PB_WT_PACKED, field)) + { + status = false; + break; + } + (*size)++; + field->pData = (char*)field->pData + field->data_size; + } + + if (substream.bytes_left != 0) + PB_RETURN_ERROR(stream, "array overflow"); + if (!pb_close_string_substream(stream, &substream)) + return false; + + return status; + } + else + { + /* Repeated field */ + pb_size_t *size = (pb_size_t*)field->pSize; + field->pData = (char*)field->pField + field->data_size * (*size); + + if ((*size)++ >= field->array_size) + PB_RETURN_ERROR(stream, "array overflow"); + + return decode_basic_field(stream, wire_type, field); + } + + case PB_HTYPE_ONEOF: + if (PB_LTYPE_IS_SUBMSG(field->type) && + *(pb_size_t*)field->pSize != field->tag) + { + /* We memset to zero so that any callbacks are set to NULL. + * This is because the callbacks might otherwise have values + * from some other union field. + * If callbacks are needed inside oneof field, use .proto + * option submsg_callback to have a separate callback function + * that can set the fields before submessage is decoded. + * pb_dec_submessage() will set any default values. */ + memset(field->pData, 0, (size_t)field->data_size); + + /* Set default values for the submessage fields. */ + if (field->submsg_desc->default_value != NULL || + field->submsg_desc->field_callback != NULL || + field->submsg_desc->submsg_info[0] != NULL) + { + pb_field_iter_t submsg_iter; + if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, field->pData)) + { + if (!pb_message_set_to_defaults(&submsg_iter)) + PB_RETURN_ERROR(stream, "failed to set defaults"); + } + } + } + *(pb_size_t*)field->pSize = field->tag; + + return decode_basic_field(stream, wire_type, field); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +#ifdef PB_ENABLE_MALLOC +/* Allocate storage for the field and store the pointer at iter->pData. + * array_size is the number of entries to reserve in an array. + * Zero size is not allowed, use pb_free() for releasing. + */ +static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size) +{ + void *ptr = *(void**)pData; + + if (data_size == 0 || array_size == 0) + PB_RETURN_ERROR(stream, "invalid size"); + +#ifdef __AVR__ + /* Workaround for AVR libc bug 53284: http://savannah.nongnu.org/bugs/?53284 + * Realloc to size of 1 byte can cause corruption of the malloc structures. + */ + if (data_size == 1 && array_size == 1) + { + data_size = 2; + } +#endif + + /* Check for multiplication overflows. + * This code avoids the costly division if the sizes are small enough. + * Multiplication is safe as long as only half of bits are set + * in either multiplicand. + */ + { + const size_t check_limit = (size_t)1 << (sizeof(size_t) * 4); + if (data_size >= check_limit || array_size >= check_limit) + { + const size_t size_max = (size_t)-1; + if (size_max / array_size < data_size) + { + PB_RETURN_ERROR(stream, "size too large"); + } + } + } + + /* Allocate new or expand previous allocation */ + /* Note: on failure the old pointer will remain in the structure, + * the message must be freed by caller also on error return. */ + ptr = pb_realloc(ptr, array_size * data_size); + if (ptr == NULL) + PB_RETURN_ERROR(stream, "realloc failed"); + + *(void**)pData = ptr; + return true; +} + +/* Clear a newly allocated item in case it contains a pointer, or is a submessage. */ +static void initialize_pointer_field(void *pItem, pb_field_iter_t *field) +{ + if (PB_LTYPE(field->type) == PB_LTYPE_STRING || + PB_LTYPE(field->type) == PB_LTYPE_BYTES) + { + *(void**)pItem = NULL; + } + else if (PB_LTYPE_IS_SUBMSG(field->type)) + { + /* We memset to zero so that any callbacks are set to NULL. + * Default values will be set by pb_dec_submessage(). */ + memset(pItem, 0, field->data_size); + } +} +#endif + +static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ +#ifndef PB_ENABLE_MALLOC + PB_UNUSED(wire_type); + PB_UNUSED(field); + PB_RETURN_ERROR(stream, "no malloc support"); +#else + switch (PB_HTYPE(field->type)) + { + case PB_HTYPE_REQUIRED: + case PB_HTYPE_OPTIONAL: + case PB_HTYPE_ONEOF: + if (PB_LTYPE_IS_SUBMSG(field->type) && *(void**)field->pField != NULL) + { + /* Duplicate field, have to release the old allocation first. */ + /* FIXME: Does this work correctly for oneofs? */ + pb_release_single_field(field); + } + + if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) + { + *(pb_size_t*)field->pSize = field->tag; + } + + if (PB_LTYPE(field->type) == PB_LTYPE_STRING || + PB_LTYPE(field->type) == PB_LTYPE_BYTES) + { + /* pb_dec_string and pb_dec_bytes handle allocation themselves */ + field->pData = field->pField; + return decode_basic_field(stream, wire_type, field); + } + else + { + if (!allocate_field(stream, field->pField, field->data_size, 1)) + return false; + + field->pData = *(void**)field->pField; + initialize_pointer_field(field->pData, field); + return decode_basic_field(stream, wire_type, field); + } + + case PB_HTYPE_REPEATED: + if (wire_type == PB_WT_STRING + && PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Packed array, multiple items come in at once. */ + bool status = true; + pb_size_t *size = (pb_size_t*)field->pSize; + size_t allocated_size = *size; + pb_istream_t substream; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + while (substream.bytes_left) + { + if (*size == PB_SIZE_MAX) + { +#ifndef PB_NO_ERRMSG + stream->errmsg = "too many array entries"; +#endif + status = false; + break; + } + + if ((size_t)*size + 1 > allocated_size) + { + /* Allocate more storage. This tries to guess the + * number of remaining entries. Round the division + * upwards. */ + size_t remain = (substream.bytes_left - 1) / field->data_size + 1; + if (remain < PB_SIZE_MAX - allocated_size) + allocated_size += remain; + else + allocated_size += 1; + + if (!allocate_field(&substream, field->pField, field->data_size, allocated_size)) + { + status = false; + break; + } + } + + /* Decode the array entry */ + field->pData = *(char**)field->pField + field->data_size * (*size); + if (field->pData == NULL) + { + /* Shouldn't happen, but satisfies static analyzers */ + status = false; + break; + } + initialize_pointer_field(field->pData, field); + if (!decode_basic_field(&substream, PB_WT_PACKED, field)) + { + status = false; + break; + } + + (*size)++; + } + if (!pb_close_string_substream(stream, &substream)) + return false; + + return status; + } + else + { + /* Normal repeated field, i.e. only one item at a time. */ + pb_size_t *size = (pb_size_t*)field->pSize; + + if (*size == PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "too many array entries"); + + if (!allocate_field(stream, field->pField, field->data_size, (size_t)(*size + 1))) + return false; + + field->pData = *(char**)field->pField + field->data_size * (*size); + (*size)++; + initialize_pointer_field(field->pData, field); + return decode_basic_field(stream, wire_type, field); + } + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +#endif +} + +static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ + if (!field->descriptor->field_callback) + return pb_skip_field(stream, wire_type); + + if (wire_type == PB_WT_STRING) + { + pb_istream_t substream; + size_t prev_bytes_left; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + do + { + prev_bytes_left = substream.bytes_left; + if (!field->descriptor->field_callback(&substream, NULL, field)) + { + PB_SET_ERROR(stream, substream.errmsg ? substream.errmsg : "callback failed"); + return false; + } + } while (substream.bytes_left > 0 && substream.bytes_left < prev_bytes_left); + + if (!pb_close_string_substream(stream, &substream)) + return false; + + return true; + } + else + { + /* Copy the single scalar value to stack. + * This is required so that we can limit the stream length, + * which in turn allows to use same callback for packed and + * not-packed fields. */ + pb_istream_t substream; + pb_byte_t buffer[10]; + size_t size = sizeof(buffer); + + if (!read_raw_value(stream, wire_type, buffer, &size)) + return false; + substream = pb_istream_from_buffer(buffer, size); + + return field->descriptor->field_callback(&substream, NULL, field); + } +} + +static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *field) +{ +#ifdef PB_ENABLE_MALLOC + /* When decoding an oneof field, check if there is old data that must be + * released first. */ + if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) + { + if (!pb_release_union_field(stream, field)) + return false; + } +#endif + + switch (PB_ATYPE(field->type)) + { + case PB_ATYPE_STATIC: + return decode_static_field(stream, wire_type, field); + + case PB_ATYPE_POINTER: + return decode_pointer_field(stream, wire_type, field); + + case PB_ATYPE_CALLBACK: + return decode_callback_field(stream, wire_type, field); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +/* Default handler for extension fields. Expects to have a pb_msgdesc_t + * pointer in the extension->type->arg field, pointing to a message with + * only one field in it. */ +static bool checkreturn default_extension_decoder(pb_istream_t *stream, + pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type) +{ + pb_field_iter_t iter; + + if (!pb_field_iter_begin_extension(&iter, extension)) + PB_RETURN_ERROR(stream, "invalid extension"); + + if (iter.tag != tag || !iter.message) + return true; + + extension->found = true; + return decode_field(stream, wire_type, &iter); +} + +/* Try to decode an unknown field as an extension field. Tries each extension + * decoder in turn, until one of them handles the field or loop ends. */ +static bool checkreturn decode_extension(pb_istream_t *stream, + uint32_t tag, pb_wire_type_t wire_type, pb_extension_t *extension) +{ + size_t pos = stream->bytes_left; + + while (extension != NULL && pos == stream->bytes_left) + { + bool status; + if (extension->type->decode) + status = extension->type->decode(stream, extension, tag, wire_type); + else + status = default_extension_decoder(stream, extension, tag, wire_type); + + if (!status) + return false; + + extension = extension->next; + } + + return true; +} + +/* Initialize message fields to default values, recursively */ +static bool pb_field_set_to_default(pb_field_iter_t *field) +{ + pb_type_t type; + type = field->type; + + if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) + { + pb_extension_t *ext = *(pb_extension_t* const *)field->pData; + while (ext != NULL) + { + pb_field_iter_t ext_iter; + if (pb_field_iter_begin_extension(&ext_iter, ext)) + { + ext->found = false; + if (!pb_message_set_to_defaults(&ext_iter)) + return false; + } + ext = ext->next; + } + } + else if (PB_ATYPE(type) == PB_ATYPE_STATIC) + { + bool init_data = true; + if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->pSize != NULL) + { + /* Set has_field to false. Still initialize the optional field + * itself also. */ + *(bool*)field->pSize = false; + } + else if (PB_HTYPE(type) == PB_HTYPE_REPEATED || + PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + /* REPEATED: Set array count to 0, no need to initialize contents. + ONEOF: Set which_field to 0. */ + *(pb_size_t*)field->pSize = 0; + init_data = false; + } + + if (init_data) + { + if (PB_LTYPE_IS_SUBMSG(field->type) && + (field->submsg_desc->default_value != NULL || + field->submsg_desc->field_callback != NULL || + field->submsg_desc->submsg_info[0] != NULL)) + { + /* Initialize submessage to defaults. + * Only needed if it has default values + * or callback/submessage fields. */ + pb_field_iter_t submsg_iter; + if (pb_field_iter_begin(&submsg_iter, field->submsg_desc, field->pData)) + { + if (!pb_message_set_to_defaults(&submsg_iter)) + return false; + } + } + else + { + /* Initialize to zeros */ + memset(field->pData, 0, (size_t)field->data_size); + } + } + } + else if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + /* Initialize the pointer to NULL. */ + *(void**)field->pField = NULL; + + /* Initialize array count to 0. */ + if (PB_HTYPE(type) == PB_HTYPE_REPEATED || + PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + *(pb_size_t*)field->pSize = 0; + } + } + else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) + { + /* Don't overwrite callback */ + } + + return true; +} + +static bool pb_message_set_to_defaults(pb_field_iter_t *iter) +{ + pb_istream_t defstream = PB_ISTREAM_EMPTY; + uint32_t tag = 0; + pb_wire_type_t wire_type = PB_WT_VARINT; + bool eof; + + if (iter->descriptor->default_value) + { + defstream = pb_istream_from_buffer(iter->descriptor->default_value, (size_t)-1); + if (!pb_decode_tag(&defstream, &wire_type, &tag, &eof)) + return false; + } + + do + { + if (!pb_field_set_to_default(iter)) + return false; + + if (tag != 0 && iter->tag == tag) + { + /* We have a default value for this field in the defstream */ + if (!decode_field(&defstream, wire_type, iter)) + return false; + if (!pb_decode_tag(&defstream, &wire_type, &tag, &eof)) + return false; + + if (iter->pSize) + *(bool*)iter->pSize = false; + } + } while (pb_field_iter_next(iter)); + + return true; +} + +/********************* + * Decode all fields * + *********************/ + +static bool checkreturn pb_decode_inner(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags) +{ + uint32_t extension_range_start = 0; + pb_extension_t *extensions = NULL; + + /* 'fixed_count_field' and 'fixed_count_size' track position of a repeated fixed + * count field. This can only handle _one_ repeated fixed count field that + * is unpacked and unordered among other (non repeated fixed count) fields. + */ + pb_size_t fixed_count_field = PB_SIZE_MAX; + pb_size_t fixed_count_size = 0; + pb_size_t fixed_count_total_size = 0; + + pb_fields_seen_t fields_seen = {{0, 0}}; + const uint32_t allbits = ~(uint32_t)0; + pb_field_iter_t iter; + + if (pb_field_iter_begin(&iter, fields, dest_struct)) + { + if ((flags & PB_DECODE_NOINIT) == 0) + { + if (!pb_message_set_to_defaults(&iter)) + PB_RETURN_ERROR(stream, "failed to set defaults"); + } + } + + while (stream->bytes_left) + { + uint32_t tag; + pb_wire_type_t wire_type; + bool eof; + + if (!pb_decode_tag(stream, &wire_type, &tag, &eof)) + { + if (eof) + break; + else + return false; + } + + if (tag == 0) + { + if (flags & PB_DECODE_NULLTERMINATED) + { + break; + } + else + { + PB_RETURN_ERROR(stream, "zero tag"); + } + } + + if (!pb_field_iter_find(&iter, tag) || PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION) + { + /* No match found, check if it matches an extension. */ + if (extension_range_start == 0) + { + if (pb_field_iter_find_extension(&iter)) + { + extensions = *(pb_extension_t* const *)iter.pData; + extension_range_start = iter.tag; + } + + if (!extensions) + { + extension_range_start = (uint32_t)-1; + } + } + + if (tag >= extension_range_start) + { + size_t pos = stream->bytes_left; + + if (!decode_extension(stream, tag, wire_type, extensions)) + return false; + + if (pos != stream->bytes_left) + { + /* The field was handled */ + continue; + } + } + + /* No match found, skip data */ + if (!pb_skip_field(stream, wire_type)) + return false; + continue; + } + + /* If a repeated fixed count field was found, get size from + * 'fixed_count_field' as there is no counter contained in the struct. + */ + if (PB_HTYPE(iter.type) == PB_HTYPE_REPEATED && iter.pSize == &iter.array_size) + { + if (fixed_count_field != iter.index) { + /* If the new fixed count field does not match the previous one, + * check that the previous one is NULL or that it finished + * receiving all the expected data. + */ + if (fixed_count_field != PB_SIZE_MAX && + fixed_count_size != fixed_count_total_size) + { + PB_RETURN_ERROR(stream, "wrong size for fixed count field"); + } + + fixed_count_field = iter.index; + fixed_count_size = 0; + fixed_count_total_size = iter.array_size; + } + + iter.pSize = &fixed_count_size; + } + + if (PB_HTYPE(iter.type) == PB_HTYPE_REQUIRED + && iter.required_field_index < PB_MAX_REQUIRED_FIELDS) + { + uint32_t tmp = ((uint32_t)1 << (iter.required_field_index & 31)); + fields_seen.bitfield[iter.required_field_index >> 5] |= tmp; + } + + if (!decode_field(stream, wire_type, &iter)) + return false; + } + + /* Check that all elements of the last decoded fixed count field were present. */ + if (fixed_count_field != PB_SIZE_MAX && + fixed_count_size != fixed_count_total_size) + { + PB_RETURN_ERROR(stream, "wrong size for fixed count field"); + } + + /* Check that all required fields were present. */ + { + pb_size_t req_field_count = iter.descriptor->required_field_count; + + if (req_field_count > 0) + { + pb_size_t i; + + if (req_field_count > PB_MAX_REQUIRED_FIELDS) + req_field_count = PB_MAX_REQUIRED_FIELDS; + + /* Check the whole words */ + for (i = 0; i < (req_field_count >> 5); i++) + { + if (fields_seen.bitfield[i] != allbits) + PB_RETURN_ERROR(stream, "missing required field"); + } + + /* Check the remaining bits (if any) */ + if ((req_field_count & 31) != 0) + { + if (fields_seen.bitfield[req_field_count >> 5] != + (allbits >> (uint_least8_t)(32 - (req_field_count & 31)))) + { + PB_RETURN_ERROR(stream, "missing required field"); + } + } + } + } + + return true; +} + +bool checkreturn pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags) +{ + bool status; + + if ((flags & PB_DECODE_DELIMITED) == 0) + { + status = pb_decode_inner(stream, fields, dest_struct, flags); + } + else + { + pb_istream_t substream; + if (!pb_make_string_substream(stream, &substream)) + return false; + + status = pb_decode_inner(&substream, fields, dest_struct, flags); + + if (!pb_close_string_substream(stream, &substream)) + status = false; + } + +#ifdef PB_ENABLE_MALLOC + if (!status) + pb_release(fields, dest_struct); +#endif + + return status; +} + +bool checkreturn pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct) +{ + bool status; + + status = pb_decode_inner(stream, fields, dest_struct, 0); + +#ifdef PB_ENABLE_MALLOC + if (!status) + pb_release(fields, dest_struct); +#endif + + return status; +} + +#ifdef PB_ENABLE_MALLOC +/* Given an oneof field, if there has already been a field inside this oneof, + * release it before overwriting with a different one. */ +static bool pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *field) +{ + pb_field_iter_t old_field = *field; + pb_size_t old_tag = *(pb_size_t*)field->pSize; /* Previous which_ value */ + pb_size_t new_tag = field->tag; /* New which_ value */ + + if (old_tag == 0) + return true; /* Ok, no old data in union */ + + if (old_tag == new_tag) + return true; /* Ok, old data is of same type => merge */ + + /* Release old data. The find can fail if the message struct contains + * invalid data. */ + if (!pb_field_iter_find(&old_field, old_tag)) + PB_RETURN_ERROR(stream, "invalid union tag"); + + pb_release_single_field(&old_field); + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { + /* Initialize the pointer to NULL to make sure it is valid + * even in case of error return. */ + *(void**)field->pField = NULL; + field->pData = NULL; + } + + return true; +} + +static void pb_release_single_field(pb_field_iter_t *field) +{ + pb_type_t type; + type = field->type; + + if (PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + if (*(pb_size_t*)field->pSize != field->tag) + return; /* This is not the current field in the union */ + } + + /* Release anything contained inside an extension or submsg. + * This has to be done even if the submsg itself is statically + * allocated. */ + if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) + { + /* Release fields from all extensions in the linked list */ + pb_extension_t *ext = *(pb_extension_t**)field->pData; + while (ext != NULL) + { + pb_field_iter_t ext_iter; + if (pb_field_iter_begin_extension(&ext_iter, ext)) + { + pb_release_single_field(&ext_iter); + } + ext = ext->next; + } + } + else if (PB_LTYPE_IS_SUBMSG(type) && PB_ATYPE(type) != PB_ATYPE_CALLBACK) + { + /* Release fields in submessage or submsg array */ + pb_size_t count = 1; + + if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + field->pData = *(void**)field->pField; + } + else + { + field->pData = field->pField; + } + + if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + count = *(pb_size_t*)field->pSize; + + if (PB_ATYPE(type) == PB_ATYPE_STATIC && count > field->array_size) + { + /* Protect against corrupted _count fields */ + count = field->array_size; + } + } + + if (field->pData) + { + for (; count > 0; count--) + { + pb_release(field->submsg_desc, field->pData); + field->pData = (char*)field->pData + field->data_size; + } + } + } + + if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + if (PB_HTYPE(type) == PB_HTYPE_REPEATED && + (PB_LTYPE(type) == PB_LTYPE_STRING || + PB_LTYPE(type) == PB_LTYPE_BYTES)) + { + /* Release entries in repeated string or bytes array */ + void **pItem = *(void***)field->pField; + pb_size_t count = *(pb_size_t*)field->pSize; + for (; count > 0; count--) + { + pb_free(*pItem); + *pItem++ = NULL; + } + } + + if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + /* We are going to release the array, so set the size to 0 */ + *(pb_size_t*)field->pSize = 0; + } + + /* Release main pointer */ + pb_free(*(void**)field->pField); + *(void**)field->pField = NULL; + } +} + +void pb_release(const pb_msgdesc_t *fields, void *dest_struct) +{ + pb_field_iter_t iter; + + if (!dest_struct) + return; /* Ignore NULL pointers, similar to free() */ + + if (!pb_field_iter_begin(&iter, fields, dest_struct)) + return; /* Empty message type */ + + do + { + pb_release_single_field(&iter); + } while (pb_field_iter_next(&iter)); +} +#else +void pb_release(const pb_msgdesc_t *fields, void *dest_struct) +{ + /* Nothing to release without PB_ENABLE_MALLOC. */ + PB_UNUSED(fields); + PB_UNUSED(dest_struct); +} +#endif + +/* Field decoders */ + +bool pb_decode_bool(pb_istream_t *stream, bool *dest) +{ + uint32_t value; + if (!pb_decode_varint32(stream, &value)) + return false; + + *(bool*)dest = (value != 0); + return true; +} + +bool pb_decode_svarint(pb_istream_t *stream, pb_int64_t *dest) +{ + pb_uint64_t value; + if (!pb_decode_varint(stream, &value)) + return false; + + if (value & 1) + *dest = (pb_int64_t)(~(value >> 1)); + else + *dest = (pb_int64_t)(value >> 1); + + return true; +} + +bool pb_decode_fixed32(pb_istream_t *stream, void *dest) +{ + union { + uint32_t fixed32; + pb_byte_t bytes[4]; + } u; + + if (!pb_read(stream, u.bytes, 4)) + return false; + +#if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 + /* fast path - if we know that we're on little endian, assign directly */ + *(uint32_t*)dest = u.fixed32; +#else + *(uint32_t*)dest = ((uint32_t)u.bytes[0] << 0) | + ((uint32_t)u.bytes[1] << 8) | + ((uint32_t)u.bytes[2] << 16) | + ((uint32_t)u.bytes[3] << 24); +#endif + return true; +} + +#ifndef PB_WITHOUT_64BIT +bool pb_decode_fixed64(pb_istream_t *stream, void *dest) +{ + union { + uint64_t fixed64; + pb_byte_t bytes[8]; + } u; + + if (!pb_read(stream, u.bytes, 8)) + return false; + +#if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 + /* fast path - if we know that we're on little endian, assign directly */ + *(uint64_t*)dest = u.fixed64; +#else + *(uint64_t*)dest = ((uint64_t)u.bytes[0] << 0) | + ((uint64_t)u.bytes[1] << 8) | + ((uint64_t)u.bytes[2] << 16) | + ((uint64_t)u.bytes[3] << 24) | + ((uint64_t)u.bytes[4] << 32) | + ((uint64_t)u.bytes[5] << 40) | + ((uint64_t)u.bytes[6] << 48) | + ((uint64_t)u.bytes[7] << 56); +#endif + return true; +} +#endif + +static bool checkreturn pb_dec_bool(pb_istream_t *stream, const pb_field_iter_t *field) +{ + return pb_decode_bool(stream, (bool*)field->pData); +} + +static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_iter_t *field) +{ + if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT) + { + pb_uint64_t value, clamped; + if (!pb_decode_varint(stream, &value)) + return false; + + /* Cast to the proper field size, while checking for overflows */ + if (field->data_size == sizeof(pb_uint64_t)) + clamped = *(pb_uint64_t*)field->pData = value; + else if (field->data_size == sizeof(uint32_t)) + clamped = *(uint32_t*)field->pData = (uint32_t)value; + else if (field->data_size == sizeof(uint_least16_t)) + clamped = *(uint_least16_t*)field->pData = (uint_least16_t)value; + else if (field->data_size == sizeof(uint_least8_t)) + clamped = *(uint_least8_t*)field->pData = (uint_least8_t)value; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (clamped != value) + PB_RETURN_ERROR(stream, "integer too large"); + + return true; + } + else + { + pb_uint64_t value; + pb_int64_t svalue; + pb_int64_t clamped; + + if (PB_LTYPE(field->type) == PB_LTYPE_SVARINT) + { + if (!pb_decode_svarint(stream, &svalue)) + return false; + } + else + { + if (!pb_decode_varint(stream, &value)) + return false; + + /* See issue 97: Google's C++ protobuf allows negative varint values to + * be cast as int32_t, instead of the int64_t that should be used when + * encoding. Nanopb versions before 0.2.5 had a bug in encoding. In order to + * not break decoding of such messages, we cast <=32 bit fields to + * int32_t first to get the sign correct. + */ + if (field->data_size == sizeof(pb_int64_t)) + svalue = (pb_int64_t)value; + else + svalue = (int32_t)value; + } + + /* Cast to the proper field size, while checking for overflows */ + if (field->data_size == sizeof(pb_int64_t)) + clamped = *(pb_int64_t*)field->pData = svalue; + else if (field->data_size == sizeof(int32_t)) + clamped = *(int32_t*)field->pData = (int32_t)svalue; + else if (field->data_size == sizeof(int_least16_t)) + clamped = *(int_least16_t*)field->pData = (int_least16_t)svalue; + else if (field->data_size == sizeof(int_least8_t)) + clamped = *(int_least8_t*)field->pData = (int_least8_t)svalue; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (clamped != svalue) + PB_RETURN_ERROR(stream, "integer too large"); + + return true; + } +} + +static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_iter_t *field) +{ + uint32_t size; + size_t alloc_size; + pb_bytes_array_t *dest; + + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size > PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "bytes overflow"); + + alloc_size = PB_BYTES_ARRAY_T_ALLOCSIZE(size); + if (size > alloc_size) + PB_RETURN_ERROR(stream, "size too large"); + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { +#ifndef PB_ENABLE_MALLOC + PB_RETURN_ERROR(stream, "no malloc support"); +#else + if (stream->bytes_left < size) + PB_RETURN_ERROR(stream, "end-of-stream"); + + if (!allocate_field(stream, field->pData, alloc_size, 1)) + return false; + dest = *(pb_bytes_array_t**)field->pData; +#endif + } + else + { + if (alloc_size > field->data_size) + PB_RETURN_ERROR(stream, "bytes overflow"); + dest = (pb_bytes_array_t*)field->pData; + } + + dest->size = (pb_size_t)size; + return pb_read(stream, dest->bytes, (size_t)size); +} + +static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_iter_t *field) +{ + uint32_t size; + size_t alloc_size; + pb_byte_t *dest = (pb_byte_t*)field->pData; + + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size == (uint32_t)-1) + PB_RETURN_ERROR(stream, "size too large"); + + /* Space for null terminator */ + alloc_size = (size_t)(size + 1); + + if (alloc_size < size) + PB_RETURN_ERROR(stream, "size too large"); + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { +#ifndef PB_ENABLE_MALLOC + PB_RETURN_ERROR(stream, "no malloc support"); +#else + if (stream->bytes_left < size) + PB_RETURN_ERROR(stream, "end-of-stream"); + + if (!allocate_field(stream, field->pData, alloc_size, 1)) + return false; + dest = *(pb_byte_t**)field->pData; +#endif + } + else + { + if (alloc_size > field->data_size) + PB_RETURN_ERROR(stream, "string overflow"); + } + + dest[size] = 0; + + if (!pb_read(stream, dest, (size_t)size)) + return false; + +#ifdef PB_VALIDATE_UTF8 + if (!pb_validate_utf8((const char*)dest)) + PB_RETURN_ERROR(stream, "invalid utf8"); +#endif + + return true; +} + +static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_iter_t *field) +{ + bool status = true; + bool submsg_consumed = false; + pb_istream_t substream; + + if (!pb_make_string_substream(stream, &substream)) + return false; + + if (field->submsg_desc == NULL) + PB_RETURN_ERROR(stream, "invalid field descriptor"); + + /* Submessages can have a separate message-level callback that is called + * before decoding the message. Typically it is used to set callback fields + * inside oneofs. */ + if (PB_LTYPE(field->type) == PB_LTYPE_SUBMSG_W_CB && field->pSize != NULL) + { + /* Message callback is stored right before pSize. */ + pb_callback_t *callback = (pb_callback_t*)field->pSize - 1; + if (callback->funcs.decode) + { + status = callback->funcs.decode(&substream, field, &callback->arg); + + if (substream.bytes_left == 0) + { + submsg_consumed = true; + } + } + } + + /* Now decode the submessage contents */ + if (status && !submsg_consumed) + { + unsigned int flags = 0; + + /* Static required/optional fields are already initialized by top-level + * pb_decode(), no need to initialize them again. */ + if (PB_ATYPE(field->type) == PB_ATYPE_STATIC && + PB_HTYPE(field->type) != PB_HTYPE_REPEATED) + { + flags = PB_DECODE_NOINIT; + } + + status = pb_decode_inner(&substream, field->submsg_desc, field->pData, flags); + } + + if (!pb_close_string_substream(stream, &substream)) + return false; + + return status; +} + +static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_iter_t *field) +{ + uint32_t size; + + if (!pb_decode_varint32(stream, &size)) + return false; + + if (size > PB_SIZE_MAX) + PB_RETURN_ERROR(stream, "bytes overflow"); + + if (size == 0) + { + /* As a special case, treat empty bytes string as all zeros for fixed_length_bytes. */ + memset(field->pData, 0, (size_t)field->data_size); + return true; + } + + if (size != field->data_size) + PB_RETURN_ERROR(stream, "incorrect fixed length bytes size"); + + return pb_read(stream, (pb_byte_t*)field->pData, (size_t)field->data_size); +} + +#ifdef PB_CONVERT_DOUBLE_FLOAT +bool pb_decode_double_as_float(pb_istream_t *stream, float *dest) +{ + uint_least8_t sign; + int exponent; + uint32_t mantissa; + uint64_t value; + union { float f; uint32_t i; } out; + + if (!pb_decode_fixed64(stream, &value)) + return false; + + /* Decompose input value */ + sign = (uint_least8_t)((value >> 63) & 1); + exponent = (int)((value >> 52) & 0x7FF) - 1023; + mantissa = (value >> 28) & 0xFFFFFF; /* Highest 24 bits */ + + /* Figure if value is in range representable by floats. */ + if (exponent == 1024) + { + /* Special value */ + exponent = 128; + mantissa >>= 1; + } + else + { + if (exponent > 127) + { + /* Too large, convert to infinity */ + exponent = 128; + mantissa = 0; + } + else if (exponent < -150) + { + /* Too small, convert to zero */ + exponent = -127; + mantissa = 0; + } + else if (exponent < -126) + { + /* Denormalized */ + mantissa |= 0x1000000; + mantissa >>= (-126 - exponent); + exponent = -127; + } + + /* Round off mantissa */ + mantissa = (mantissa + 1) >> 1; + + /* Check if mantissa went over 2.0 */ + if (mantissa & 0x800000) + { + exponent += 1; + mantissa &= 0x7FFFFF; + mantissa >>= 1; + } + } + + /* Combine fields */ + out.i = mantissa; + out.i |= (uint32_t)(exponent + 127) << 23; + out.i |= (uint32_t)sign << 31; + + *dest = out.f; + return true; +} +#endif diff --git a/src/Debug/nanopb/pb_decode.h b/src/Debug/nanopb/pb_decode.h new file mode 100644 index 000000000..3f392b293 --- /dev/null +++ b/src/Debug/nanopb/pb_decode.h @@ -0,0 +1,204 @@ +/* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c. + * The main function is pb_decode. You also need an input stream, and the + * field descriptions created by nanopb_generator.py. + */ + +#ifndef PB_DECODE_H_INCLUDED +#define PB_DECODE_H_INCLUDED + +#include "pb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structure for defining custom input streams. You will need to provide + * a callback function to read the bytes from your storage, which can be + * for example a file or a network socket. + * + * The callback must conform to these rules: + * + * 1) Return false on IO errors. This will cause decoding to abort. + * 2) You can use state to store your own data (e.g. buffer pointer), + * and rely on pb_read to verify that no-body reads past bytes_left. + * 3) Your callback may be used with substreams, in which case bytes_left + * is different than from the main stream. Don't use bytes_left to compute + * any pointers. + */ +struct pb_istream_s +{ +#ifdef PB_BUFFER_ONLY + /* Callback pointer is not used in buffer-only configuration. + * Having an int pointer here allows binary compatibility but + * gives an error if someone tries to assign callback function. + */ + int *callback; +#else + bool (*callback)(pb_istream_t *stream, pb_byte_t *buf, size_t count); +#endif + + /* state is a free field for use of the callback function defined above. + * Note that when pb_istream_from_buffer() is used, it reserves this field + * for its own use. + */ + void *state; + + /* Maximum number of bytes left in this stream. Callback can report + * EOF before this limit is reached. Setting a limit is recommended + * when decoding directly from file or network streams to avoid + * denial-of-service by excessively long messages. + */ + size_t bytes_left; + +#ifndef PB_NO_ERRMSG + /* Pointer to constant (ROM) string when decoding function returns error */ + const char *errmsg; +#endif +}; + +#ifndef PB_NO_ERRMSG +#define PB_ISTREAM_EMPTY {0,0,0,0} +#else +#define PB_ISTREAM_EMPTY {0,0,0} +#endif + +/*************************** + * Main decoding functions * + ***************************/ + +/* Decode a single protocol buffers message from input stream into a C structure. + * Returns true on success, false on any failure. + * The actual struct pointed to by dest must match the description in fields. + * Callback fields of the destination structure must be initialized by caller. + * All other fields will be initialized by this function. + * + * Example usage: + * MyMessage msg = {}; + * uint8_t buffer[64]; + * pb_istream_t stream; + * + * // ... read some data into buffer ... + * + * stream = pb_istream_from_buffer(buffer, count); + * pb_decode(&stream, MyMessage_fields, &msg); + */ +bool pb_decode(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct); + +/* Extended version of pb_decode, with several options to control + * the decoding process: + * + * PB_DECODE_NOINIT: Do not initialize the fields to default values. + * This is slightly faster if you do not need the default + * values and instead initialize the structure to 0 using + * e.g. memset(). This can also be used for merging two + * messages, i.e. combine already existing data with new + * values. + * + * PB_DECODE_DELIMITED: Input message starts with the message size as varint. + * Corresponds to parseDelimitedFrom() in Google's + * protobuf API. + * + * PB_DECODE_NULLTERMINATED: Stop reading when field tag is read as 0. This allows + * reading null terminated messages. + * NOTE: Until nanopb-0.4.0, pb_decode() also allows + * null-termination. This behaviour is not supported in + * most other protobuf implementations, so PB_DECODE_DELIMITED + * is a better option for compatibility. + * + * Multiple flags can be combined with bitwise or (| operator) + */ +#define PB_DECODE_NOINIT 0x01U +#define PB_DECODE_DELIMITED 0x02U +#define PB_DECODE_NULLTERMINATED 0x04U +bool pb_decode_ex(pb_istream_t *stream, const pb_msgdesc_t *fields, void *dest_struct, unsigned int flags); + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define pb_decode_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NOINIT) +#define pb_decode_delimited(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED) +#define pb_decode_delimited_noinit(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_DELIMITED | PB_DECODE_NOINIT) +#define pb_decode_nullterminated(s,f,d) pb_decode_ex(s,f,d, PB_DECODE_NULLTERMINATED) + +/* Release any allocated pointer fields. If you use dynamic allocation, you should + * call this for any successfully decoded message when you are done with it. If + * pb_decode() returns with an error, the message is already released. + */ +void pb_release(const pb_msgdesc_t *fields, void *dest_struct); + +/************************************** + * Functions for manipulating streams * + **************************************/ + +/* Create an input stream for reading from a memory buffer. + * + * msglen should be the actual length of the message, not the full size of + * allocated buffer. + * + * Alternatively, you can use a custom stream that reads directly from e.g. + * a file or a network socket. + */ +pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t msglen); + +/* Function to read from a pb_istream_t. You can use this if you need to + * read some custom header data, or to read data in field callbacks. + */ +bool pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count); + + +/************************************************ + * Helper functions for writing field callbacks * + ************************************************/ + +/* Decode the tag for the next field in the stream. Gives the wire type and + * field tag. At end of the message, returns false and sets eof to true. */ +bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof); + +/* Skip the field payload data, given the wire type. */ +bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type); + +/* Decode an integer in the varint format. This works for enum, int32, + * int64, uint32 and uint64 field types. */ +#ifndef PB_WITHOUT_64BIT +bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest); +#else +#define pb_decode_varint pb_decode_varint32 +#endif + +/* Decode an integer in the varint format. This works for enum, int32, + * and uint32 field types. */ +bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest); + +/* Decode a bool value in varint format. */ +bool pb_decode_bool(pb_istream_t *stream, bool *dest); + +/* Decode an integer in the zig-zagged svarint format. This works for sint32 + * and sint64. */ +#ifndef PB_WITHOUT_64BIT +bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest); +#else +bool pb_decode_svarint(pb_istream_t *stream, int32_t *dest); +#endif + +/* Decode a fixed32, sfixed32 or float value. You need to pass a pointer to + * a 4-byte wide C variable. */ +bool pb_decode_fixed32(pb_istream_t *stream, void *dest); + +#ifndef PB_WITHOUT_64BIT +/* Decode a fixed64, sfixed64 or double value. You need to pass a pointer to + * a 8-byte wide C variable. */ +bool pb_decode_fixed64(pb_istream_t *stream, void *dest); +#endif + +#ifdef PB_CONVERT_DOUBLE_FLOAT +/* Decode a double value into float variable. */ +bool pb_decode_double_as_float(pb_istream_t *stream, float *dest); +#endif + +/* Make a limited-length substream for reading a PB_WT_STRING field. */ +bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream); +bool pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/Debug/nanopb/pb_encode.c b/src/Debug/nanopb/pb_encode.c new file mode 100644 index 000000000..f9034a542 --- /dev/null +++ b/src/Debug/nanopb/pb_encode.c @@ -0,0 +1,1001 @@ +/* pb_encode.c -- encode a protobuf using minimal resources + * + * 2011 Petteri Aimonen + */ + +#include "pb.h" +#include "pb_encode.h" +#include "pb_common.h" + +/* Use the GCC warn_unused_result attribute to check that all return values + * are propagated correctly. On other compilers, gcc before 3.4.0 and iar + * before 9.40.1 just ignore the annotation. + */ +#if (defined(__GNUC__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4))) || \ + (defined(__IAR_SYSTEMS_ICC__) && (__VER__ >= 9040001)) + #define checkreturn __attribute__((warn_unused_result)) +#else + #define checkreturn +#endif + +/************************************** + * Declarations internal to this file * + **************************************/ +static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); +static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field); +static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field); +static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field); +static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension); +static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high); +static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field); +static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field); + +#ifdef PB_WITHOUT_64BIT +#define pb_int64_t int32_t +#define pb_uint64_t uint32_t +#else +#define pb_int64_t int64_t +#define pb_uint64_t uint64_t +#endif + +/******************************* + * pb_ostream_t implementation * + *******************************/ + +static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) +{ + pb_byte_t *dest = (pb_byte_t*)stream->state; + stream->state = dest + count; + + memcpy(dest, buf, count * sizeof(pb_byte_t)); + + return true; +} + +pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize) +{ + pb_ostream_t stream; +#ifdef PB_BUFFER_ONLY + /* In PB_BUFFER_ONLY configuration the callback pointer is just int*. + * NULL pointer marks a sizing field, so put a non-NULL value to mark a buffer stream. + */ + static const int marker = 0; + stream.callback = ▮ +#else + stream.callback = &buf_write; +#endif + stream.state = buf; + stream.max_size = bufsize; + stream.bytes_written = 0; +#ifndef PB_NO_ERRMSG + stream.errmsg = NULL; +#endif + return stream; +} + +bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count) +{ + if (count > 0 && stream->callback != NULL) + { + if (stream->bytes_written + count < stream->bytes_written || + stream->bytes_written + count > stream->max_size) + { + PB_RETURN_ERROR(stream, "stream full"); + } + +#ifdef PB_BUFFER_ONLY + if (!buf_write(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#else + if (!stream->callback(stream, buf, count)) + PB_RETURN_ERROR(stream, "io error"); +#endif + } + + stream->bytes_written += count; + return true; +} + +/************************* + * Encode a single field * + *************************/ + +/* Read a bool value without causing undefined behavior even if the value + * is invalid. See issue #434 and + * https://stackoverflow.com/questions/27661768/weird-results-for-conditional + */ +static bool safe_read_bool(const void *pSize) +{ + const char *p = (const char *)pSize; + size_t i; + for (i = 0; i < sizeof(bool); i++) + { + if (p[i] != 0) + return true; + } + return false; +} + +/* Encode a static array. Handles the size calculations and possible packing. */ +static bool checkreturn encode_array(pb_ostream_t *stream, pb_field_iter_t *field) +{ + pb_size_t i; + pb_size_t count; +#ifndef PB_ENCODE_ARRAYS_UNPACKED + size_t size; +#endif + + count = *(pb_size_t*)field->pSize; + + if (count == 0) + return true; + + if (PB_ATYPE(field->type) != PB_ATYPE_POINTER && count > field->array_size) + PB_RETURN_ERROR(stream, "array max size exceeded"); + +#ifndef PB_ENCODE_ARRAYS_UNPACKED + /* We always pack arrays if the datatype allows it. */ + if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE) + { + if (!pb_encode_tag(stream, PB_WT_STRING, field->tag)) + return false; + + /* Determine the total size of packed array. */ + if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32) + { + size = 4 * (size_t)count; + } + else if (PB_LTYPE(field->type) == PB_LTYPE_FIXED64) + { + size = 8 * (size_t)count; + } + else + { + pb_ostream_t sizestream = PB_OSTREAM_SIZING; + void *pData_orig = field->pData; + for (i = 0; i < count; i++) + { + if (!pb_enc_varint(&sizestream, field)) + PB_RETURN_ERROR(stream, PB_GET_ERROR(&sizestream)); + field->pData = (char*)field->pData + field->data_size; + } + field->pData = pData_orig; + size = sizestream.bytes_written; + } + + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + if (stream->callback == NULL) + return pb_write(stream, NULL, size); /* Just sizing.. */ + + /* Write the data */ + for (i = 0; i < count; i++) + { + if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32 || PB_LTYPE(field->type) == PB_LTYPE_FIXED64) + { + if (!pb_enc_fixed(stream, field)) + return false; + } + else + { + if (!pb_enc_varint(stream, field)) + return false; + } + + field->pData = (char*)field->pData + field->data_size; + } + } + else /* Unpacked fields */ +#endif + { + for (i = 0; i < count; i++) + { + /* Normally the data is stored directly in the array entries, but + * for pointer-type string and bytes fields, the array entries are + * actually pointers themselves also. So we have to dereference once + * more to get to the actual data. */ + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER && + (PB_LTYPE(field->type) == PB_LTYPE_STRING || + PB_LTYPE(field->type) == PB_LTYPE_BYTES)) + { + bool status; + void *pData_orig = field->pData; + field->pData = *(void* const*)field->pData; + + if (!field->pData) + { + /* Null pointer in array is treated as empty string / bytes */ + status = pb_encode_tag_for_field(stream, field) && + pb_encode_varint(stream, 0); + } + else + { + status = encode_basic_field(stream, field); + } + + field->pData = pData_orig; + + if (!status) + return false; + } + else + { + if (!encode_basic_field(stream, field)) + return false; + } + field->pData = (char*)field->pData + field->data_size; + } + } + + return true; +} + +/* In proto3, all fields are optional and are only encoded if their value is "non-zero". + * This function implements the check for the zero value. */ +static bool checkreturn pb_check_proto3_default_value(const pb_field_iter_t *field) +{ + pb_type_t type = field->type; + + if (PB_ATYPE(type) == PB_ATYPE_STATIC) + { + if (PB_HTYPE(type) == PB_HTYPE_REQUIRED) + { + /* Required proto2 fields inside proto3 submessage, pretty rare case */ + return false; + } + else if (PB_HTYPE(type) == PB_HTYPE_REPEATED) + { + /* Repeated fields inside proto3 submessage: present if count != 0 */ + return *(const pb_size_t*)field->pSize == 0; + } + else if (PB_HTYPE(type) == PB_HTYPE_ONEOF) + { + /* Oneof fields */ + return *(const pb_size_t*)field->pSize == 0; + } + else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->pSize != NULL) + { + /* Proto2 optional fields inside proto3 message, or proto3 + * submessage fields. */ + return safe_read_bool(field->pSize) == false; + } + else if (field->descriptor->default_value) + { + /* Proto3 messages do not have default values, but proto2 messages + * can contain optional fields without has_fields (generator option 'proto3'). + * In this case they must always be encoded, to make sure that the + * non-zero default value is overwritten. + */ + return false; + } + + /* Rest is proto3 singular fields */ + if (PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE) + { + /* Simple integer / float fields */ + pb_size_t i; + const char *p = (const char*)field->pData; + for (i = 0; i < field->data_size; i++) + { + if (p[i] != 0) + { + return false; + } + } + + return true; + } + else if (PB_LTYPE(type) == PB_LTYPE_BYTES) + { + const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)field->pData; + return bytes->size == 0; + } + else if (PB_LTYPE(type) == PB_LTYPE_STRING) + { + return *(const char*)field->pData == '\0'; + } + else if (PB_LTYPE(type) == PB_LTYPE_FIXED_LENGTH_BYTES) + { + /* Fixed length bytes is only empty if its length is fixed + * as 0. Which would be pretty strange, but we can check + * it anyway. */ + return field->data_size == 0; + } + else if (PB_LTYPE_IS_SUBMSG(type)) + { + /* Check all fields in the submessage to find if any of them + * are non-zero. The comparison cannot be done byte-per-byte + * because the C struct may contain padding bytes that must + * be skipped. Note that usually proto3 submessages have + * a separate has_field that is checked earlier in this if. + */ + pb_field_iter_t iter; + if (pb_field_iter_begin(&iter, field->submsg_desc, field->pData)) + { + do + { + if (!pb_check_proto3_default_value(&iter)) + { + return false; + } + } while (pb_field_iter_next(&iter)); + } + return true; + } + } + else if (PB_ATYPE(type) == PB_ATYPE_POINTER) + { + return field->pData == NULL; + } + else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK) + { + if (PB_LTYPE(type) == PB_LTYPE_EXTENSION) + { + const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData; + return extension == NULL; + } + else if (field->descriptor->field_callback == pb_default_field_callback) + { + pb_callback_t *pCallback = (pb_callback_t*)field->pData; + return pCallback->funcs.encode == NULL; + } + else + { + return field->descriptor->field_callback == NULL; + } + } + + return false; /* Not typically reached, safe default for weird special cases. */ +} + +/* Encode a field with static or pointer allocation, i.e. one whose data + * is available to the encoder directly. */ +static bool checkreturn encode_basic_field(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + if (!field->pData) + { + /* Missing pointer field */ + return true; + } + + if (!pb_encode_tag_for_field(stream, field)) + return false; + + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_BOOL: + return pb_enc_bool(stream, field); + + case PB_LTYPE_VARINT: + case PB_LTYPE_UVARINT: + case PB_LTYPE_SVARINT: + return pb_enc_varint(stream, field); + + case PB_LTYPE_FIXED32: + case PB_LTYPE_FIXED64: + return pb_enc_fixed(stream, field); + + case PB_LTYPE_BYTES: + return pb_enc_bytes(stream, field); + + case PB_LTYPE_STRING: + return pb_enc_string(stream, field); + + case PB_LTYPE_SUBMESSAGE: + case PB_LTYPE_SUBMSG_W_CB: + return pb_enc_submessage(stream, field); + + case PB_LTYPE_FIXED_LENGTH_BYTES: + return pb_enc_fixed_length_bytes(stream, field); + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } +} + +/* Encode a field with callback semantics. This means that a user function is + * called to provide and encode the actual data. */ +static bool checkreturn encode_callback_field(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + if (field->descriptor->field_callback != NULL) + { + if (!field->descriptor->field_callback(NULL, stream, field)) + PB_RETURN_ERROR(stream, "callback error"); + } + return true; +} + +/* Encode a single field of any callback, pointer or static type. */ +static bool checkreturn encode_field(pb_ostream_t *stream, pb_field_iter_t *field) +{ + /* Check field presence */ + if (PB_HTYPE(field->type) == PB_HTYPE_ONEOF) + { + if (*(const pb_size_t*)field->pSize != field->tag) + { + /* Different type oneof field */ + return true; + } + } + else if (PB_HTYPE(field->type) == PB_HTYPE_OPTIONAL) + { + if (field->pSize) + { + if (safe_read_bool(field->pSize) == false) + { + /* Missing optional field */ + return true; + } + } + else if (PB_ATYPE(field->type) == PB_ATYPE_STATIC) + { + /* Proto3 singular field */ + if (pb_check_proto3_default_value(field)) + return true; + } + } + + if (!field->pData) + { + if (PB_HTYPE(field->type) == PB_HTYPE_REQUIRED) + PB_RETURN_ERROR(stream, "missing required field"); + + /* Pointer field set to NULL */ + return true; + } + + /* Then encode field contents */ + if (PB_ATYPE(field->type) == PB_ATYPE_CALLBACK) + { + return encode_callback_field(stream, field); + } + else if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED) + { + return encode_array(stream, field); + } + else + { + return encode_basic_field(stream, field); + } +} + +/* Default handler for extension fields. Expects to have a pb_msgdesc_t + * pointer in the extension->type->arg field, pointing to a message with + * only one field in it. */ +static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension) +{ + pb_field_iter_t iter; + + if (!pb_field_iter_begin_extension_const(&iter, extension)) + PB_RETURN_ERROR(stream, "invalid extension"); + + return encode_field(stream, &iter); +} + + +/* Walk through all the registered extensions and give them a chance + * to encode themselves. */ +static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + const pb_extension_t *extension = *(const pb_extension_t* const *)field->pData; + + while (extension) + { + bool status; + if (extension->type->encode) + status = extension->type->encode(stream, extension); + else + status = default_extension_encoder(stream, extension); + + if (!status) + return false; + + extension = extension->next; + } + + return true; +} + +/********************* + * Encode all fields * + *********************/ + +bool checkreturn pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct) +{ + pb_field_iter_t iter; + if (!pb_field_iter_begin_const(&iter, fields, src_struct)) + return true; /* Empty message type */ + + do { + if (PB_LTYPE(iter.type) == PB_LTYPE_EXTENSION) + { + /* Special case for the extension field placeholder */ + if (!encode_extension_field(stream, &iter)) + return false; + } + else + { + /* Regular field */ + if (!encode_field(stream, &iter)) + return false; + } + } while (pb_field_iter_next(&iter)); + + return true; +} + +bool checkreturn pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags) +{ + if ((flags & PB_ENCODE_DELIMITED) != 0) + { + return pb_encode_submessage(stream, fields, src_struct); + } + else if ((flags & PB_ENCODE_NULLTERMINATED) != 0) + { + const pb_byte_t zero = 0; + + if (!pb_encode(stream, fields, src_struct)) + return false; + + return pb_write(stream, &zero, 1); + } + else + { + return pb_encode(stream, fields, src_struct); + } +} + +bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct) +{ + pb_ostream_t stream = PB_OSTREAM_SIZING; + + if (!pb_encode(&stream, fields, src_struct)) + return false; + + *size = stream.bytes_written; + return true; +} + +/******************** + * Helper functions * + ********************/ + +/* This function avoids 64-bit shifts as they are quite slow on many platforms. */ +static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high) +{ + size_t i = 0; + pb_byte_t buffer[10]; + pb_byte_t byte = (pb_byte_t)(low & 0x7F); + low >>= 7; + + while (i < 4 && (low != 0 || high != 0)) + { + byte |= 0x80; + buffer[i++] = byte; + byte = (pb_byte_t)(low & 0x7F); + low >>= 7; + } + + if (high) + { + byte = (pb_byte_t)(byte | ((high & 0x07) << 4)); + high >>= 3; + + while (high) + { + byte |= 0x80; + buffer[i++] = byte; + byte = (pb_byte_t)(high & 0x7F); + high >>= 7; + } + } + + buffer[i++] = byte; + + return pb_write(stream, buffer, i); +} + +bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value) +{ + if (value <= 0x7F) + { + /* Fast path: single byte */ + pb_byte_t byte = (pb_byte_t)value; + return pb_write(stream, &byte, 1); + } + else + { +#ifdef PB_WITHOUT_64BIT + return pb_encode_varint_32(stream, value, 0); +#else + return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)(value >> 32)); +#endif + } +} + +bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value) +{ + pb_uint64_t zigzagged; + pb_uint64_t mask = ((pb_uint64_t)-1) >> 1; /* Satisfy clang -fsanitize=integer */ + if (value < 0) + zigzagged = ~(((pb_uint64_t)value & mask) << 1); + else + zigzagged = (pb_uint64_t)value << 1; + + return pb_encode_varint(stream, zigzagged); +} + +bool checkreturn pb_encode_fixed32(pb_ostream_t *stream, const void *value) +{ +#if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 + /* Fast path if we know that we're on little endian */ + return pb_write(stream, (const pb_byte_t*)value, 4); +#else + uint32_t val = *(const uint32_t*)value; + pb_byte_t bytes[4]; + bytes[0] = (pb_byte_t)(val & 0xFF); + bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); + bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); + bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); + return pb_write(stream, bytes, 4); +#endif +} + +#ifndef PB_WITHOUT_64BIT +bool checkreturn pb_encode_fixed64(pb_ostream_t *stream, const void *value) +{ +#if defined(PB_LITTLE_ENDIAN_8BIT) && PB_LITTLE_ENDIAN_8BIT == 1 + /* Fast path if we know that we're on little endian */ + return pb_write(stream, (const pb_byte_t*)value, 8); +#else + uint64_t val = *(const uint64_t*)value; + pb_byte_t bytes[8]; + bytes[0] = (pb_byte_t)(val & 0xFF); + bytes[1] = (pb_byte_t)((val >> 8) & 0xFF); + bytes[2] = (pb_byte_t)((val >> 16) & 0xFF); + bytes[3] = (pb_byte_t)((val >> 24) & 0xFF); + bytes[4] = (pb_byte_t)((val >> 32) & 0xFF); + bytes[5] = (pb_byte_t)((val >> 40) & 0xFF); + bytes[6] = (pb_byte_t)((val >> 48) & 0xFF); + bytes[7] = (pb_byte_t)((val >> 56) & 0xFF); + return pb_write(stream, bytes, 8); +#endif +} +#endif + +bool checkreturn pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number) +{ + pb_uint64_t tag = ((pb_uint64_t)field_number << 3) | wiretype; + return pb_encode_varint(stream, tag); +} + +bool pb_encode_tag_for_field ( pb_ostream_t* stream, const pb_field_iter_t* field ) +{ + pb_wire_type_t wiretype; + switch (PB_LTYPE(field->type)) + { + case PB_LTYPE_BOOL: + case PB_LTYPE_VARINT: + case PB_LTYPE_UVARINT: + case PB_LTYPE_SVARINT: + wiretype = PB_WT_VARINT; + break; + + case PB_LTYPE_FIXED32: + wiretype = PB_WT_32BIT; + break; + + case PB_LTYPE_FIXED64: + wiretype = PB_WT_64BIT; + break; + + case PB_LTYPE_BYTES: + case PB_LTYPE_STRING: + case PB_LTYPE_SUBMESSAGE: + case PB_LTYPE_SUBMSG_W_CB: + case PB_LTYPE_FIXED_LENGTH_BYTES: + wiretype = PB_WT_STRING; + break; + + default: + PB_RETURN_ERROR(stream, "invalid field type"); + } + + return pb_encode_tag(stream, wiretype, field->tag); +} + +bool checkreturn pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size) +{ + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + return pb_write(stream, buffer, size); +} + +bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct) +{ + /* First calculate the message size using a non-writing substream. */ + pb_ostream_t substream = PB_OSTREAM_SIZING; + size_t size; + bool status; + + if (!pb_encode(&substream, fields, src_struct)) + { +#ifndef PB_NO_ERRMSG + stream->errmsg = substream.errmsg; +#endif + return false; + } + + size = substream.bytes_written; + + if (!pb_encode_varint(stream, (pb_uint64_t)size)) + return false; + + if (stream->callback == NULL) + return pb_write(stream, NULL, size); /* Just sizing */ + + if (stream->bytes_written + size > stream->max_size) + PB_RETURN_ERROR(stream, "stream full"); + + /* Use a substream to verify that a callback doesn't write more than + * what it did the first time. */ + substream.callback = stream->callback; + substream.state = stream->state; + substream.max_size = size; + substream.bytes_written = 0; +#ifndef PB_NO_ERRMSG + substream.errmsg = NULL; +#endif + + status = pb_encode(&substream, fields, src_struct); + + stream->bytes_written += substream.bytes_written; + stream->state = substream.state; +#ifndef PB_NO_ERRMSG + stream->errmsg = substream.errmsg; +#endif + + if (substream.bytes_written != size) + PB_RETURN_ERROR(stream, "submsg size changed"); + + return status; +} + +/* Field encoders */ + +static bool checkreturn pb_enc_bool(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + uint32_t value = safe_read_bool(field->pData) ? 1 : 0; + PB_UNUSED(field); + return pb_encode_varint(stream, value); +} + +static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + if (PB_LTYPE(field->type) == PB_LTYPE_UVARINT) + { + /* Perform unsigned integer extension */ + pb_uint64_t value = 0; + + if (field->data_size == sizeof(uint_least8_t)) + value = *(const uint_least8_t*)field->pData; + else if (field->data_size == sizeof(uint_least16_t)) + value = *(const uint_least16_t*)field->pData; + else if (field->data_size == sizeof(uint32_t)) + value = *(const uint32_t*)field->pData; + else if (field->data_size == sizeof(pb_uint64_t)) + value = *(const pb_uint64_t*)field->pData; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + return pb_encode_varint(stream, value); + } + else + { + /* Perform signed integer extension */ + pb_int64_t value = 0; + + if (field->data_size == sizeof(int_least8_t)) + value = *(const int_least8_t*)field->pData; + else if (field->data_size == sizeof(int_least16_t)) + value = *(const int_least16_t*)field->pData; + else if (field->data_size == sizeof(int32_t)) + value = *(const int32_t*)field->pData; + else if (field->data_size == sizeof(pb_int64_t)) + value = *(const pb_int64_t*)field->pData; + else + PB_RETURN_ERROR(stream, "invalid data_size"); + + if (PB_LTYPE(field->type) == PB_LTYPE_SVARINT) + return pb_encode_svarint(stream, value); +#ifdef PB_WITHOUT_64BIT + else if (value < 0) + return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)-1); +#endif + else + return pb_encode_varint(stream, (pb_uint64_t)value); + + } +} + +static bool checkreturn pb_enc_fixed(pb_ostream_t *stream, const pb_field_iter_t *field) +{ +#ifdef PB_CONVERT_DOUBLE_FLOAT + if (field->data_size == sizeof(float) && PB_LTYPE(field->type) == PB_LTYPE_FIXED64) + { + return pb_encode_float_as_double(stream, *(float*)field->pData); + } +#endif + + if (field->data_size == sizeof(uint32_t)) + { + return pb_encode_fixed32(stream, field->pData); + } +#ifndef PB_WITHOUT_64BIT + else if (field->data_size == sizeof(uint64_t)) + { + return pb_encode_fixed64(stream, field->pData); + } +#endif + else + { + PB_RETURN_ERROR(stream, "invalid data_size"); + } +} + +static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + const pb_bytes_array_t *bytes = NULL; + + bytes = (const pb_bytes_array_t*)field->pData; + + if (bytes == NULL) + { + /* Treat null pointer as an empty bytes field */ + return pb_encode_string(stream, NULL, 0); + } + + if (PB_ATYPE(field->type) == PB_ATYPE_STATIC && + bytes->size > field->data_size - offsetof(pb_bytes_array_t, bytes)) + { + PB_RETURN_ERROR(stream, "bytes size exceeded"); + } + + return pb_encode_string(stream, bytes->bytes, (size_t)bytes->size); +} + +static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + size_t size = 0; + size_t max_size = (size_t)field->data_size; + const char *str = (const char*)field->pData; + + if (PB_ATYPE(field->type) == PB_ATYPE_POINTER) + { + max_size = (size_t)-1; + } + else + { + /* pb_dec_string() assumes string fields end with a null + * terminator when the type isn't PB_ATYPE_POINTER, so we + * shouldn't allow more than max-1 bytes to be written to + * allow space for the null terminator. + */ + if (max_size == 0) + PB_RETURN_ERROR(stream, "zero-length string"); + + max_size -= 1; + } + + + if (str == NULL) + { + size = 0; /* Treat null pointer as an empty string */ + } + else + { + const char *p = str; + + /* strnlen() is not always available, so just use a loop */ + while (size < max_size && *p != '\0') + { + size++; + p++; + } + + if (*p != '\0') + { + PB_RETURN_ERROR(stream, "unterminated string"); + } + } + +#ifdef PB_VALIDATE_UTF8 + if (!pb_validate_utf8(str)) + PB_RETURN_ERROR(stream, "invalid utf8"); +#endif + + return pb_encode_string(stream, (const pb_byte_t*)str, size); +} + +static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + if (field->submsg_desc == NULL) + PB_RETURN_ERROR(stream, "invalid field descriptor"); + + if (PB_LTYPE(field->type) == PB_LTYPE_SUBMSG_W_CB && field->pSize != NULL) + { + /* Message callback is stored right before pSize. */ + pb_callback_t *callback = (pb_callback_t*)field->pSize - 1; + if (callback->funcs.encode) + { + if (!callback->funcs.encode(stream, field, &callback->arg)) + return false; + } + } + + return pb_encode_submessage(stream, field->submsg_desc, field->pData); +} + +static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_iter_t *field) +{ + return pb_encode_string(stream, (const pb_byte_t*)field->pData, (size_t)field->data_size); +} + +#ifdef PB_CONVERT_DOUBLE_FLOAT +bool pb_encode_float_as_double(pb_ostream_t *stream, float value) +{ + union { float f; uint32_t i; } in; + uint_least8_t sign; + int exponent; + uint64_t mantissa; + + in.f = value; + + /* Decompose input value */ + sign = (uint_least8_t)((in.i >> 31) & 1); + exponent = (int)((in.i >> 23) & 0xFF) - 127; + mantissa = in.i & 0x7FFFFF; + + if (exponent == 128) + { + /* Special value (NaN etc.) */ + exponent = 1024; + } + else if (exponent == -127) + { + if (!mantissa) + { + /* Zero */ + exponent = -1023; + } + else + { + /* Denormalized */ + mantissa <<= 1; + while (!(mantissa & 0x800000)) + { + mantissa <<= 1; + exponent--; + } + mantissa &= 0x7FFFFF; + } + } + + /* Combine fields */ + mantissa <<= 29; + mantissa |= (uint64_t)(exponent + 1023) << 52; + mantissa |= (uint64_t)sign << 63; + + return pb_encode_fixed64(stream, &mantissa); +} +#endif diff --git a/src/Debug/nanopb/pb_encode.h b/src/Debug/nanopb/pb_encode.h new file mode 100644 index 000000000..6dc089da3 --- /dev/null +++ b/src/Debug/nanopb/pb_encode.h @@ -0,0 +1,195 @@ +/* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c. + * The main function is pb_encode. You also need an output stream, and the + * field descriptions created by nanopb_generator.py. + */ + +#ifndef PB_ENCODE_H_INCLUDED +#define PB_ENCODE_H_INCLUDED + +#include "pb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Structure for defining custom output streams. You will need to provide + * a callback function to write the bytes to your storage, which can be + * for example a file or a network socket. + * + * The callback must conform to these rules: + * + * 1) Return false on IO errors. This will cause encoding to abort. + * 2) You can use state to store your own data (e.g. buffer pointer). + * 3) pb_write will update bytes_written after your callback runs. + * 4) Substreams will modify max_size and bytes_written. Don't use them + * to calculate any pointers. + */ +struct pb_ostream_s +{ +#ifdef PB_BUFFER_ONLY + /* Callback pointer is not used in buffer-only configuration. + * Having an int pointer here allows binary compatibility but + * gives an error if someone tries to assign callback function. + * Also, NULL pointer marks a 'sizing stream' that does not + * write anything. + */ + const int *callback; +#else + bool (*callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); +#endif + + /* state is a free field for use of the callback function defined above. + * Note that when pb_ostream_from_buffer() is used, it reserves this field + * for its own use. + */ + void *state; + + /* Limit number of output bytes written. Can be set to SIZE_MAX. */ + size_t max_size; + + /* Number of bytes written so far. */ + size_t bytes_written; + +#ifndef PB_NO_ERRMSG + /* Pointer to constant (ROM) string when decoding function returns error */ + const char *errmsg; +#endif +}; + +/*************************** + * Main encoding functions * + ***************************/ + +/* Encode a single protocol buffers message from C structure into a stream. + * Returns true on success, false on any failure. + * The actual struct pointed to by src_struct must match the description in fields. + * All required fields in the struct are assumed to have been filled in. + * + * Example usage: + * MyMessage msg = {}; + * uint8_t buffer[64]; + * pb_ostream_t stream; + * + * msg.field1 = 42; + * stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); + * pb_encode(&stream, MyMessage_fields, &msg); + */ +bool pb_encode(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct); + +/* Extended version of pb_encode, with several options to control the + * encoding process: + * + * PB_ENCODE_DELIMITED: Prepend the length of message as a varint. + * Corresponds to writeDelimitedTo() in Google's + * protobuf API. + * + * PB_ENCODE_NULLTERMINATED: Append a null byte to the message for termination. + * NOTE: This behaviour is not supported in most other + * protobuf implementations, so PB_ENCODE_DELIMITED + * is a better option for compatibility. + */ +#define PB_ENCODE_DELIMITED 0x02U +#define PB_ENCODE_NULLTERMINATED 0x04U +bool pb_encode_ex(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct, unsigned int flags); + +/* Defines for backwards compatibility with code written before nanopb-0.4.0 */ +#define pb_encode_delimited(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_DELIMITED) +#define pb_encode_nullterminated(s,f,d) pb_encode_ex(s,f,d, PB_ENCODE_NULLTERMINATED) + +/* Encode the message to get the size of the encoded data, but do not store + * the data. */ +bool pb_get_encoded_size(size_t *size, const pb_msgdesc_t *fields, const void *src_struct); + +/************************************** + * Functions for manipulating streams * + **************************************/ + +/* Create an output stream for writing into a memory buffer. + * The number of bytes written can be found in stream.bytes_written after + * encoding the message. + * + * Alternatively, you can use a custom stream that writes directly to e.g. + * a file or a network socket. + */ +pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize); + +/* Pseudo-stream for measuring the size of a message without actually storing + * the encoded data. + * + * Example usage: + * MyMessage msg = {}; + * pb_ostream_t stream = PB_OSTREAM_SIZING; + * pb_encode(&stream, MyMessage_fields, &msg); + * printf("Message size is %d\n", stream.bytes_written); + */ +#ifndef PB_NO_ERRMSG +#define PB_OSTREAM_SIZING {0,0,0,0,0} +#else +#define PB_OSTREAM_SIZING {0,0,0,0} +#endif + +/* Function to write into a pb_ostream_t stream. You can use this if you need + * to append or prepend some custom headers to the message. + */ +bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count); + + +/************************************************ + * Helper functions for writing field callbacks * + ************************************************/ + +/* Encode field header based on type and field number defined in the field + * structure. Call this from the callback before writing out field contents. */ +bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field); + +/* Encode field header by manually specifying wire type. You need to use this + * if you want to write out packed arrays from a callback field. */ +bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number); + +/* Encode an integer in the varint format. + * This works for bool, enum, int32, int64, uint32 and uint64 field types. */ +#ifndef PB_WITHOUT_64BIT +bool pb_encode_varint(pb_ostream_t *stream, uint64_t value); +#else +bool pb_encode_varint(pb_ostream_t *stream, uint32_t value); +#endif + +/* Encode an integer in the zig-zagged svarint format. + * This works for sint32 and sint64. */ +#ifndef PB_WITHOUT_64BIT +bool pb_encode_svarint(pb_ostream_t *stream, int64_t value); +#else +bool pb_encode_svarint(pb_ostream_t *stream, int32_t value); +#endif + +/* Encode a string or bytes type field. For strings, pass strlen(s) as size. */ +bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size); + +/* Encode a fixed32, sfixed32 or float value. + * You need to pass a pointer to a 4-byte wide C variable. */ +bool pb_encode_fixed32(pb_ostream_t *stream, const void *value); + +#ifndef PB_WITHOUT_64BIT +/* Encode a fixed64, sfixed64 or double value. + * You need to pass a pointer to a 8-byte wide C variable. */ +bool pb_encode_fixed64(pb_ostream_t *stream, const void *value); +#endif + +#ifdef PB_CONVERT_DOUBLE_FLOAT +/* Encode a float value so that it appears like a double in the encoded + * message. */ +bool pb_encode_float_as_double(pb_ostream_t *stream, float value); +#endif + +/* Encode a submessage field. + * You need to pass the pb_field_t array and pointer to struct, just like + * with pb_encode(). This internally encodes the submessage twice, first to + * calculate message size and then to actually write it out. + */ +bool pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/Edward/proxy.cpp b/src/Edward/proxy.cpp index 5b9021248..1b92d2482 100644 --- a/src/Edward/proxy.cpp +++ b/src/Edward/proxy.cpp @@ -48,31 +48,12 @@ void Proxy::pushRFC(Module *m, RFC *rfc) { RFC *Proxy::topRFC() { return this->calls->top(); } -void Proxy::returnResult(Module *m) { +RFC *Proxy::returnResult(Module *m) { + (void)m; + if (this->calls->empty()) return nullptr; RFC *rfc = this->calls->top(); - - // remove call from lifo queue this->calls->pop(); - - if (!rfc->success) { - // TODO exception msg - WARDuino::instance()->debugger->channel->write(R"({"success":false})"); - return; - } - - if (rfc->type->result_count == 0) { - // reading result from stack - WARDuino::instance()->debugger->channel->write(R"({"success":true})"); - return; - } - - // send the result to the client - ExecutionContext *ectx = m->warduino->execution_context; - rfc->result = &ectx->stack[ectx->sp]; - char *val = printValue(rfc->result); - WARDuino::instance()->debugger->channel->write(R"({"success":true,%s})", - val); - free(val); + return rfc; } char *printValue(StackValue *v) { diff --git a/src/Edward/proxy.h b/src/Edward/proxy.h index 20993f504..a7f8224f5 100644 --- a/src/Edward/proxy.h +++ b/src/Edward/proxy.h @@ -21,7 +21,7 @@ class Proxy { void pushRFC(Module *m, RFC *rfc); RFC *topRFC(); - void returnResult(Module *m); + RFC *returnResult(Module *m); // Server side ( arduino side ) static StackValue *readRFCArgs(Block *func, uint8_t *data); diff --git a/src/Edward/proxy_supervisor.cpp b/src/Edward/proxy_supervisor.cpp index 511efca99..ba4246d88 100644 --- a/src/Edward/proxy_supervisor.cpp +++ b/src/Edward/proxy_supervisor.cpp @@ -135,7 +135,7 @@ bool ProxySupervisor::send( nlohmann::basic_json<> ProxySupervisor::readReply() { while (!this->hasReplied); - WARDuino::instance()->debugger->channel->write("read reply: succeeded\n"); + dbg_info("read reply: succeeded\n"); this->hasReplied = false; return this->proxyResult; } diff --git a/src/Utils/sockets.cpp b/src/Utils/sockets.cpp index fc846a890..d3847b0ac 100644 --- a/src/Utils/sockets.cpp +++ b/src/Utils/sockets.cpp @@ -6,6 +6,7 @@ #include #endif +#include #include #include #include @@ -97,6 +98,19 @@ int Sink::write(const char *fmt, ...) { return written; } +ssize_t Sink::writeBytes(const uint8_t *data, const size_t size) { + if (data == nullptr && size != 0) return -1; + size_t offset = 0; + while (offset < size) { + const size_t written = + fwrite(data + offset, 1, size - offset, this->outStream); + if (written == 0) return -1; + offset += written; + } + fflush(this->outStream); + return static_cast(offset); +} + Duplex::Duplex(FILE *inStream, FILE *outStream) : Sink(outStream) { this->inDescriptor = fileno(inStream); } @@ -118,6 +132,21 @@ int FileDescriptorChannel::write(const char *fmt, ...) { return written; } +ssize_t FileDescriptorChannel::writeBytes(const uint8_t *data, + const size_t size) { + size_t offset = 0; + while (offset < size) { + const ssize_t written = ::write(this->fd, data + offset, size - offset); + if (written > 0) { + offset += static_cast(written); + continue; + } + if (written < 0 && errno == EINTR) continue; + return -1; + } + return static_cast(offset); +} + ssize_t FileDescriptorChannel::read(void *out, size_t size) { return ::read(this->fd, out, size); } @@ -174,6 +203,22 @@ int WebSocket::write(const char *fmt, ...) { return written; } +ssize_t WebSocket::writeBytes(const uint8_t *data, const size_t size) { + if (this->socket < 0) return -1; + size_t offset = 0; + while (offset < size) { + const ssize_t written = + ::write(this->socket, data + offset, size - offset); + if (written > 0) { + offset += static_cast(written); + continue; + } + if (written < 0 && errno == EINTR) continue; + return -1; + } + return static_cast(offset); +} + ssize_t WebSocket::read(void *out, size_t size) { if (this->socket < 0) { return 0; diff --git a/src/Utils/sockets.h b/src/Utils/sockets.h index bea15bb84..33002872e 100644 --- a/src/Utils/sockets.h +++ b/src/Utils/sockets.h @@ -2,6 +2,7 @@ #include +#include #include #ifdef __ZEPHYR__ @@ -31,6 +32,10 @@ class Channel { public: virtual void open() {} virtual int write(char const *, ...) { return 0; } + virtual ssize_t writeBytes(const uint8_t *data, size_t size) { + (void)data; + return static_cast(size); + } virtual ssize_t read(void *, size_t) { return 0; } virtual void close() {} virtual ~Channel() = default; @@ -47,6 +52,7 @@ class Sink : public Channel { public: explicit Sink(FILE *out); int write(char const *fmt, ...) override; + ssize_t writeBytes(const uint8_t *data, size_t size) override; }; class Duplex : public Sink { @@ -67,6 +73,7 @@ class FileDescriptorChannel : public Channel { explicit FileDescriptorChannel(int fileDescriptor); int write(char const *fmt, ...) override; + ssize_t writeBytes(const uint8_t *data, size_t size) override; ssize_t read(void *out, size_t size) override; }; @@ -81,6 +88,7 @@ class WebSocket : public Channel { void open() override; int write(char const *fmt, ...) override; + ssize_t writeBytes(const uint8_t *data, size_t size) override; ssize_t read(void *out, size_t size) override; void close() override; }; diff --git a/src/WARDuino/CallbackHandler.cpp b/src/WARDuino/CallbackHandler.cpp index 723c1384e..adce0a2ae 100644 --- a/src/WARDuino/CallbackHandler.cpp +++ b/src/WARDuino/CallbackHandler.cpp @@ -88,8 +88,6 @@ bool CallbackHandler::resolve_event(bool force) { CallbackHandler::events->empty()) { if (force) { printf("No events to be processed!\n"); - WARDuino::instance()->debugger->channel->write( - "no events to be processed"); } return false; } @@ -97,9 +95,7 @@ bool CallbackHandler::resolve_event(bool force) { if (should_push_event()) { Event e = CallbackHandler::events->at(CallbackHandler::pushed_cursor++); - WARDuino::instance()->debugger->channel->write( - R"({"topic":"%s","payload":"%s"})", e.topic.c_str(), - e.payload.c_str()); + WARDuino::instance()->debugger->notifyPushedEvent(); CallbackHandler::events->pop_front(); CallbackHandler::pushed_cursor--;