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..baf23bf62 --- /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 "${{ inputs.build-directory }}" -D BUILD_EMULATOR=ON + cmake --build "${{ inputs.build-directory }}" + wdcli="$(cd "${{ inputs.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..d5f5d48b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,17 +29,28 @@ if (BUILD_ESP) include($ENV{IDF_PATH}/tools/cmake/project.cmake "${PROJECT_BINARY_DIR}/../include") endif (BUILD_ESP) -project(WARDuino VERSION 0.8.1) +project(WARDuino VERSION 0.8.99) 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 @@ -54,6 +65,7 @@ if (BUILD_EMULATOR) src/Utils/macros.cpp src/Utils/sockets.cpp src/Debug/debugger.cpp + src/Debug/nanopb_encoder.cpp src/Edward/proxy.cpp src/Edward/proxy_supervisor.cpp src/Edward/RFC.cpp @@ -71,7 +83,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) @@ -95,6 +107,7 @@ if (BUILD_UNITTEST) src/Utils/macros.cpp src/Utils/sockets.cpp src/Debug/debugger.cpp + src/Debug/nanopb_encoder.cpp src/Edward/proxy.cpp src/Edward/proxy_supervisor.cpp src/Edward/RFC.cpp @@ -122,7 +135,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/benchmarks/edward.ino.template b/benchmarks/edward.ino.template index e566c6b7f..eb9e66b3e 100644 --- a/benchmarks/edward.ino.template +++ b/benchmarks/edward.ino.template @@ -34,7 +34,7 @@ void ICACHE_RAM_ATTR handleInput() { void startDebuggerStd(void* pvParameter) { int valread; uint8_t buffer[1024] = {0}; - wac->debugger->setChannel(fileno(stdout)); + wac->debugger->set_channel(fileno(stdout)); write(fileno(stdout), "Got a message ... \n", 19); while (true) { // taskYIELD(); 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/library.properties b/library.properties index 83c5fb4b7..fd7fd4829 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=WARDuino -version=0.8.1 +version=0.8.99 author=Robbert Gurdeep Singh, Christophe Scholliers , Tom Lauwaerts , Carlos Rojas Castillo , Maarten Steevens , Joel Martin maintainer=Tom Lauwaerts , Maarten Steevens , Christophe Scholliers sentence=A library that enables the use of WebAssembly on Arduino boards with debugging support diff --git a/platforms/Arduino/Arduino.ino.template b/platforms/Arduino/Arduino.ino.template index 59f838950..a254cad86 100644 --- a/platforms/Arduino/Arduino.ino.template +++ b/platforms/Arduino/Arduino.ino.template @@ -26,7 +26,7 @@ Module* m; void startDebuggerStd(void* pvParameter) { Channel* sink = new Sink(stdout); - wac->debugger->setChannel(sink); + wac->debugger->set_channel(sink); sink->open(); uint8_t buffer[1024] = {0}; diff --git a/platforms/CLI-Emulator/main.cpp b/platforms/CLI-Emulator/main.cpp index df626ec1b..e67944c49 100644 --- a/platforms/CLI-Emulator/main.cpp +++ b/platforms/CLI-Emulator/main.cpp @@ -187,7 +187,7 @@ void setupDebuggerCommunication(debugger_options &options) { duplex = new WebSocket(options.socket); } - wac->debugger->setChannel(duplex); + wac->debugger->set_channel(duplex); } const std::map &baudrateMap() { @@ -445,7 +445,7 @@ int main(int argc, const char *argv[]) { } if (initiallyPaused) { - wac->debugger->pauseRuntime(m); + wac->debugger->pause_runtime(m); } if (m) { @@ -548,7 +548,7 @@ int main(int argc, const char *argv[]) { } // Start supervising proxy device (new thread) - wac->debugger->startProxySupervisor(connection); + wac->debugger->start_proxy_supervisor(connection); } // Start debugger (new thread) diff --git a/platforms/ESP-IDF/CMakeLists.txt b/platforms/ESP-IDF/CMakeLists.txt index 12abbd25e..6cefab744 100644 --- a/platforms/ESP-IDF/CMakeLists.txt +++ b/platforms/ESP-IDF/CMakeLists.txt @@ -1,5 +1,10 @@ set(SOURCE_FILES ../../src/Debug/debugger.cpp + ../../src/Debug/nanopb_encoder.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 +20,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/ESP-IDF/main.cpp b/platforms/ESP-IDF/main.cpp index e49a9d3f6..a19838da0 100644 --- a/platforms/ESP-IDF/main.cpp +++ b/platforms/ESP-IDF/main.cpp @@ -38,7 +38,7 @@ std::vector loaded_modules; void startDebuggerStd(void* pvParameter) { Channel* duplex = new Duplex(stdin, stdout); - wac->debugger->setChannel(duplex); + wac->debugger->set_channel(duplex); duplex->open(); int valread; diff --git a/platforms/Zephyr/CMakeLists.txt b/platforms/Zephyr/CMakeLists.txt index 23951adbe..2db9fa172 100644 --- a/platforms/Zephyr/CMakeLists.txt +++ b/platforms/Zephyr/CMakeLists.txt @@ -27,6 +27,11 @@ target_sources(app PRIVATE ../../src/Utils/macros.cpp ../../src/Utils/sockets.cpp ../../src/Debug/debugger.cpp + ../../src/Debug/nanopb_encoder.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 +43,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/platforms/Zephyr/main.cpp b/platforms/Zephyr/main.cpp index 5e43ed0d2..3b5370452 100644 --- a/platforms/Zephyr/main.cpp +++ b/platforms/Zephyr/main.cpp @@ -71,7 +71,7 @@ std::vector loaded_modules; void startDebuggerStd() { Channel *duplex = new Duplex(stdin, stdout); - wac->debugger->setChannel(duplex); + wac->debugger->set_channel(duplex); duplex->open(); war_console_init(); 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..11b8a4576 100644 --- a/src/Debug/debugger.cpp +++ b/src/Debug/debugger.cpp @@ -4,16 +4,12 @@ #include #include #include -#ifndef ARDUINO -#include -#else -#include "../../lib/json/single_include/nlohmann/json.hpp" -#endif #include "../Memory/mem.h" #include "../Utils//util.h" #include "../Utils/macros.h" #include "../WARDuino/CallbackHandler.h" +#include "nanopb_encoder.h" // Debugger @@ -24,7 +20,6 @@ Debugger::Debugger(Channel *duplex) { this->snapshotPolicy = SnapshotPolicy::none; this->checkpointInterval = 10; this->instructions_executed = 0; - this->instructions_since_full_snapshot = 0; this->fidx_called = {}; this->min_return_values = 0; this->checkpoint_state = nullptr; @@ -34,125 +29,178 @@ Debugger::Debugger(Channel *duplex) { // Public methods -void Debugger::setChannel(Channel *duplex) { +void Debugger::set_channel(Channel *duplex) { delete this->channel; 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 decode_frame_length(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 is_known_command(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 decode_payload(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::add_debug_message(const size_t len, const uint8_t *buff) { + if (len == 0 || buff == nullptr) return; + parse_debug_buffer(len, buff); +} + +void Debugger::push_message(DebugMessage msg) { + warduino::lock_guard const lg(messageQueueMutex); + debugMessages.emplace_back(std::move(msg)); + freshMessages = !debugMessages.empty(); + messageQueueConditionVariable.notify_one(); +} + +void Debugger::parse_debug_buffer(const size_t len, const uint8_t *buff) { + pendingFrameBytes.insert(pendingFrameBytes.end(), buff, buff + len); + while (!pendingFrameBytes.empty()) { + if (!is_known_command(pendingFrameBytes.front())) { + pendingFrameBytes.clear(); + send_notification( + 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 = + decode_frame_length(pendingFrameBytes, &headerSize, &payloadSize); + if (!completeLength) { + if (headerSize == SIZE_MAX || pendingFrameBytes.size() >= 6) { + pendingFrameBytes.clear(); + send_notification( + debug_NotificationType_NOTIFICATION_MALFORMED); } - this->interruptEven = !this->interruptEven; + return; } + if (payloadSize > maxFramePayload) { + pendingFrameBytes.clear(); + send_notification(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)); + push_message(std::move(message)); } } -uint8_t *Debugger::getDebugMessage() { +std::optional Debugger::get_debug_message() { 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; } - this->freshMessages = !this->debugMessages.empty(); - return ret; + DebugMessage message = std::move(debugMessages.front()); + debugMessages.pop_front(); + freshMessages = !debugMessages.empty(); + return message; } -void Debugger::addBreakpoint(uint8_t *loc) { this->breakpoints.insert(loc); } +bool Debugger::send_notification(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; + } + 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::send_operation_result(const debug_Command command, + const bool success) const { + debug_OperationResult result = debug_OperationResult_init_zero; + result.command = command; + result.success = success; + send_notification(debug_NotificationType_NOTIFICATION_OPERATION_RESULT, + debug_OperationResult_fields, &result); +} -void Debugger::deleteBreakpoint(uint8_t *loc) { this->breakpoints.erase(loc); } +void Debugger::add_breakpoint(uint8_t *loc) { this->breakpoints.insert(loc); } + +void Debugger::delete_breakpoint(uint8_t *loc) { this->breakpoints.erase(loc); } // ReSharper disable once CppParameterMayBeConstPtrOrRef // incorrect warning -bool Debugger::isBreakpoint(uint8_t *loc) { +bool Debugger::is_breakpoint(uint8_t *loc) { return this->breakpoints.find(loc) != this->breakpoints.end() || this->mark == 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); +void Debugger::notify_breakpoint(Module *m, uint8_t *pc_ptr) { + 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); + send_notification(debug_NotificationType_NOTIFICATION_HIT_BREAKPOINT, + debug_HitBreakpoint_fields, &hit); } /** @@ -176,241 +224,832 @@ 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 collect_bytes(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 collect_words(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); + } + return true; +} + +void set_decode_callback(pb_callback_t *callback, std::vector *out) { + callback->funcs.decode = collect_bytes; + callback->arg = out; +} + +bool collect_varints(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 collect_callback_entries(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; + set_decode_callback(&entry.topic, &topic); + entry.table_indexes.funcs.decode = collect_varints; + 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 find_imported_function(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 collect_values(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 apply_value_update(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 value_to_proto(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 I64: + to->which_data = debug_Value_i64_bits_tag; + to->data.i64_bits = from.value.uint64; + break; + case F32: + to->which_data = debug_Value_f32_bits_tag; + to->data.f32_bits = from.value.uint32; + break; + case F64: + to->which_data = debug_Value_f64_bits_tag; + to->data.f64_bits = from.value.uint64; + break; + default: + break; + } +} + +using nanopb_encoder::ByteView; +using nanopb_encoder::Uint32View; + +struct ValueView { + const StackValue *values; + size_t size; + Global *const *globals; +}; + +struct EventRangeView { + size_t begin; + size_t size; +}; + +struct SnapshotView { + Module *module; + const Debugger *debugger; + const ExecutionContext *context; + const std::unordered_map, uint32_t, FNV1aVectorHash> + *overrides; +}; + +ValueView current_locals(const ExecutionContext *context) { + if (context->csp < 0 || context->fp < 0) return {nullptr, 0, nullptr}; + + for (int index = context->csp; index >= 0; --index) { + const Block *block = context->callstack[index].block; + if (block == nullptr || block->block_type != 0 || + block->type == nullptr) { + continue; + } + + const size_t count = block->type->param_count + block->local_count; + if (count == 0 || + context->fp + static_cast(count) > context->sp + 1) { + return {nullptr, 0, nullptr}; + } + return {context->stack + context->fp, count, nullptr}; + } + + return {nullptr, 0, nullptr}; +} + +bool encode_value(pb_ostream_t *stream, const pb_field_t *field, + const StackValue &source, const size_t index) { + debug_Value value = debug_Value_init_zero; + value_to_proto(source, static_cast(index), &value); + return pb_encode_tag_for_field(stream, field) && + pb_encode_submessage(stream, debug_Value_fields, &value); +} + +bool encode_value_range(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + for (size_t index = 0; index < view->size; ++index) { + const StackValue *value = view->globals == nullptr + ? &view->values[index] + : view->globals[index]->value; + if (!encode_value(stream, field, *value, index)) return false; + } + return true; +} + +bool encode_values(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *values = static_cast *>(*arg); + ValueView view{values->data(), values->size(), nullptr}; + void *range = &view; + return encode_value_range(stream, field, &range); +} + +bool encode_bytes(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *bytes = static_cast *>(*arg); + ByteView view{bytes->data(), bytes->size()}; + void *opaque = &view; + return nanopb_encoder::encode_bytes(stream, field, &opaque); +} + +bool encode_breakpoints(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + for (uint8_t *breakpoint : view->debugger->breakpoints) { + const uint32_t address = toVirtualAddress(breakpoint, view->module); + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_varint(stream, address)) + return false; + } + return true; +} + +bool encode_functions(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + Module *module = view->module; + + for (uint32_t index = module->import_count; index < module->function_count; + ++index) { + const Block &source = module->functions[index]; + if (source.start_ptr == nullptr || source.end_ptr == nullptr || + source.end_ptr < source.start_ptr) { + continue; + } + + debug_Function function = debug_Function_init_zero; + function.function_index = source.fidx; + function.has_range = true; + function.range.start = toVirtualAddress(source.start_ptr, module); + function.range.end = toVirtualAddress(source.end_ptr, module); + ByteView instructions{ + source.start_ptr, + static_cast(source.end_ptr - source.start_ptr + 1)}; + function.instructions.funcs.encode = nanopb_encoder::encode_bytes; + function.instructions.arg = &instructions; + + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_submessage(stream, debug_Function_fields, &function)) { + return false; + } + } + return true; +} + +bool encode_callstack(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + const ExecutionContext *context = view->context; + + for (int index = 0; index <= context->csp; ++index) { + const Frame &frame = context->callstack[index]; + const Block *block = frame.block; + const uint32_t type = block == nullptr ? 0 : block->block_type; + debug_CallstackEntry entry = debug_CallstackEntry_init_zero; + entry.type = type; + entry.function_index = block != nullptr && type == 0 ? block->fidx : 0; + entry.stack_pointer = static_cast(frame.sp); + entry.frame_pointer = static_cast(frame.fp); + entry.return_address = + frame.ra_ptr == nullptr + ? 0 + : toVirtualAddress(frame.ra_ptr, view->module); + + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_submessage(stream, debug_CallstackEntry_fields, + &entry)) { + return false; + } + } + return true; +} + +bool encode_callback_indexes(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *callbacks = static_cast *>(*arg); + for (const Callback &callback : *callbacks) { + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_varint(stream, callback.table_index)) { + return false; + } + } + return true; +} + +bool encode_callbacks(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *callbacks = + static_cast(*arg); + + for (const auto &[topic, entries] : *callbacks) { + debug_CallbackEntry entry = debug_CallbackEntry_init_zero; + ByteView topicBytes{reinterpret_cast(topic.data()), + topic.size()}; + + entry.topic.funcs.encode = nanopb_encoder::encode_bytes; + entry.topic.arg = &topicBytes; + entry.table_indexes.funcs.encode = encode_callback_indexes; + entry.table_indexes.arg = entries; + + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_submessage(stream, debug_CallbackEntry_fields, &entry)) { + return false; + } + } + return true; +} + +bool encode_events(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *range = static_cast(*arg); + + for (size_t index = 0; index < range->size; ++index) { + const Event *source = CallbackHandler::event_at(range->begin + index); + if (source == nullptr) return false; + + debug_Event event = debug_Event_init_zero; + ByteView topic{reinterpret_cast(source->topic.data()), + source->topic.size()}; + ByteView payload{ + reinterpret_cast(source->payload.data()), + source->payload.size()}; + event.topic.funcs.encode = nanopb_encoder::encode_bytes; + event.topic.arg = &topic; + event.payload.funcs.encode = nanopb_encoder::encode_bytes; + event.payload.arg = &payload; + + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_submessage(stream, debug_Event_fields, &event)) { + return false; + } } - debug("received interrupt %x\n", *interruptData); - fflush(stdout); + return true; +} + +bool encode_io_state(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *states = + static_cast *>(*arg); + for (const IOStateElement *source : *states) { + if (source == nullptr) continue; + debug_IOState state = debug_IOState_init_zero; + ByteView key{reinterpret_cast(source->key.data()), + source->key.size()}; + state.key.funcs.encode = nanopb_encoder::encode_bytes; + state.key.arg = &key; + state.output = source->output; + state.value = source->value; + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_submessage(stream, debug_IOState_fields, &state)) + return false; + } + return true; +} + +bool encode_overrides(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + + for (const auto &[key, result] : *view->overrides) { + if (key.empty()) continue; + + const uint32_t functionIndex = key.back(); + if (functionIndex >= view->module->import_count || + view->module->functions[functionIndex].import_field == nullptr) { + continue; + } + + debug_Override override = debug_Override_init_zero; + const char *name = view->module->functions[functionIndex].import_field; + ByteView nameView{reinterpret_cast(name), + strlen(name)}; + Uint32View arguments{key.data(), key.size() - 1}; + override.primitive_name.funcs.encode = nanopb_encoder::encode_bytes; + override.primitive_name.arg = &nameView; + override.argument_words.funcs.encode = nanopb_encoder::encode_fixed32s; + override.argument_words.arg = &arguments; + override.result = result; + + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_submessage(stream, debug_Override_fields, &override)) { + return false; + } + } + return true; +} + +} // namespace + +bool Debugger::check_debug_messages(Module *m, RunningState *program_state) { + std::optional message = get_debug_message(); + if (!message) return false; - this->channel->write("Interrupt: %x\n", *interruptData); + const auto malformed = [this]() { + send_notification(debug_NotificationType_NOTIFICATION_MALFORMED); + }; + const auto require_empty = [&message, &malformed]() { + if (!message->payload.empty()) { + malformed(); + return false; + } + return true; + }; - long start = 0, size = 0; - switch (*interruptData) { - case interruptRUN: - this->handleInterruptRUN(m, program_state); - free(interruptData); + switch (message->type) { + case debug_Command_COMMAND_RUN: + if (!require_empty()) break; + handle_interrupt_run(m, program_state); + send_notification(debug_NotificationType_NOTIFICATION_CONTINUED); + break; + case debug_Command_COMMAND_HALT: + if (!require_empty()) break; + send_notification(debug_NotificationType_NOTIFICATION_HALTED); + if (channel != nullptr) channel->close(); 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) { + case debug_Command_COMMAND_PAUSE: + if (!require_empty()) break; + pause_runtime(m); + if (snapshotPolicy == SnapshotPolicy::checkpointing) checkpoint(m, true); - } - this->channel->write("PAUSE!\n"); - free(interruptData); + send_notification(debug_NotificationType_NOTIFICATION_PAUSED); break; - case interruptSTEP: - this->handleSTEP(m, program_state); - free(interruptData); + case debug_Command_COMMAND_STEP: + if (!require_empty()) break; + handle_step(m, program_state); break; - case interruptSTEPOver: - this->handleSTEPOver(m, program_state); - free(interruptData); + case debug_Command_COMMAND_STEP_OVER: + if (!require_empty()) break; + handle_step_over(m, program_state); break; - case interruptBPAdd: // Breakpoint - case interruptBPRem: // Breakpoint remove - this->handleInterruptBP(m, interruptData); - free(interruptData); + case debug_Command_COMMAND_ADD_BREAKPOINT: + case debug_Command_COMMAND_REMOVE_BREAKPOINT: { + debug_Breakpoint breakpoint = debug_Breakpoint_init_zero; + if (!decode_payload(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) + add_breakpoint(address); + else + delete_breakpoint(address); + send_operation_result(message->type, true); 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; + } + case debug_Command_COMMAND_CONTINUE_FOR: { + debug_ContinueFor request = debug_ContinueFor_init_zero; + if (!decode_payload(message->payload, debug_ContinueFor_fields, + &request) || + request.count == 0) { + malformed(); + break; + } + remaining_instructions = static_cast(request.count); *program_state = WARDUINOrun; - free(interruptData); + send_notification(debug_NotificationType_NOTIFICATION_CONTINUED); break; } - case interruptDUMP: - this->pauseRuntime(m); - this->dump(m); - free(interruptData); + case debug_Command_COMMAND_DUMP: + if (!require_empty()) break; + pause_runtime(m); + encode_snapshot( + m, + snapshotPc | snapshotBreakpoints | snapshotCallstack | + snapshotGlobals | snapshotTable | snapshotBranchTable | + snapshotStack | snapshotCallbacks | snapshotEvents | + snapshotIO | snapshotOverrides | snapshotHeap | + snapshotLocals, + debug_NotificationType_NOTIFICATION_SNAPSHOT); break; - case interruptDUMPLocals: - this->pauseRuntime(m); - this->dumpLocals(m); - this->channel->write("\n"); - free(interruptData); + case debug_Command_COMMAND_DUMP_LOCALS: + if (!require_empty()) break; + pause_runtime(m); + dump_locals(m); break; - case interruptDUMPFull: - this->pauseRuntime(m); - this->dump(m, true); - free(interruptData); - break; - case interruptReset: - this->reset(m); - free(interruptData); + case debug_Command_COMMAND_SNAPSHOT: + if (!require_empty()) break; + pause_runtime(m); + snapshot(m); 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_DUMP_EVENTS: { + debug_Range range = debug_Range_init_zero; + if (!decode_payload(message->payload, debug_Range_fields, &range) || + range.end < range.start) { + malformed(); + break; + } + dump_events(range.start, range.end - range.start); break; - case interruptUPDATELocal: - this->channel->write("CHANGE local!\n"); - this->handleChangedLocal(m, interruptData); - free(interruptData); + } + case debug_Command_COMMAND_DUMP_CALLBACKS: + if (!require_empty()) break; + dump_callback_mapping(); break; - case interruptUPDATEModule: - handleUpdateModule(m, interruptData); - this->channel->write("CHANGE Module!\n"); - free(interruptData); + case debug_Command_COMMAND_UPDATE_LOCAL: { + const auto update = update_value(message->payload); + ExecutionContext *context = m->warduino->execution_context; + if (!update || + context->fp + static_cast(update->index) > context->sp) { + malformed(); + break; + } + StackValue *value = &context->stack[context->fp + update->index]; + if (!apply_value_update(update->value, value)) { + malformed(); + break; + } + send_operation_result(message->type, true); break; - case interruptUPDATEGlobal: - this->handleUpdateGlobalValue(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_UPDATE_GLOBAL: { + const auto update = update_value(message->payload); + if (!update || update->index >= m->global_count) { + malformed(); + break; + } + StackValue *value = m->globals[update->index]->value; + if (!apply_value_update(update->value, value)) { + malformed(); + break; + } + send_operation_result(message->type, true); break; - case interruptUPDATEStackValue: - this->handleUpdateStackValue(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_UPDATE_STACK: { + const auto update = update_value(message->payload); + ExecutionContext *context = m->warduino->execution_context; + if (!update || update->index > static_cast(context->sp)) { + malformed(); + break; + } + StackValue *value = &context->stack[update->index]; + if (!apply_value_update(update->value, value)) { + malformed(); + break; + } + send_operation_result(message->type, true); break; - case interruptINVOKE: - this->handleInvoke(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_UPDATE_MODULE: { + debug_ModuleUpdate update = debug_ModuleUpdate_init_zero; + std::vector wasm; + set_decode_callback(&update.wasm, &wasm); + if (!decode_payload(message->payload, debug_ModuleUpdate_fields, + &update) || + wasm.empty()) { + malformed(); + break; + } + auto *copy = static_cast(malloc(wasm.size())); + if (copy == nullptr) { + send_operation_result(message->type, false); + break; + } + memcpy(copy, wasm.data(), wasm.size()); + m->warduino->update_module(m, copy, wasm.size()); + send_operation_result(message->type, true); break; - case interruptSnapshot: - this->pauseRuntime(m); - free(interruptData); - snapshot(m); - this->channel->write("\n"); + } + case debug_Command_COMMAND_UPDATE_FUNCTION: { + debug_Function update = debug_Function_init_zero; + std::vector instructions; + set_decode_callback(&update.instructions, &instructions); + if (!decode_payload(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; + send_operation_result(message->type, true); break; - case interruptSetSnapshotPolicy: - setSnapshotPolicy(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_UPDATE_CALLBACKS: { + debug_CallbackMapping mapping = debug_CallbackMapping_init_zero; + std::vector entries; + mapping.entries.funcs.decode = collect_callback_entries; + mapping.entries.arg = &entries; + if (!decode_payload(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)); + } + send_operation_result(message->type, true); 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_SET_SNAPSHOT_POLICY: { + debug_SnapshotPolicyConfig config = + debug_SnapshotPolicyConfig_init_zero; + std::vector selectedState; + set_decode_callback(&config.selected_state, &selectedState); + SnapshotSelection selectedMask = 0; + if (!decode_payload(message->payload, + debug_SnapshotPolicyConfig_fields, &config) || + config.policy > + debug_SnapshotPolicy_SNAPSHOT_POLICY_CHECKPOINTING || + !parse_selection(selectedState.data(), selectedState.size(), + &selectedMask)) { + 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) { + send_operation_result(message->type, false); + break; + } + memcpy(checkpoint_state, selectedState.data(), + selectedState.size()); + } + if (snapshotPolicy == SnapshotPolicy::checkpointing) + checkpoint(m, true); + send_operation_result(message->type, true); 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"); - } 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"); + 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; + set_decode_callback(&request.primitive_name, &nameBytes); + request.argument_words.funcs.decode = collect_words; + request.argument_words.arg = &words; + if (!decode_payload(message->payload, debug_Override_fields, + &request)) { + malformed(); + break; + } + const auto fidx = find_imported_function( + m, std::string(nameBytes.begin(), nameBytes.end())); + if (!fidx || + words.size() != m->functions[*fidx].type->param_count) { + send_operation_result(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) { + send_operation_result(message->type, false); + break; + } + send_operation_result(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_INSPECT: { + debug_Inspect request = debug_Inspect_init_zero; + std::vector selected; + set_decode_callback(&request.state, &selected); + if (!decode_payload(message->payload, debug_Inspect_fields, + &request)) { + malformed(); + break; + } + SnapshotSelection selection = 0; + if (!parse_selection(selected.data(), selected.size(), + &selection)) { + malformed(); + break; + } + pause_runtime(m); + encode_snapshot(m, selection, + debug_NotificationType_NOTIFICATION_SNAPSHOT); 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_LOAD_SNAPSHOT: { + debug_Snapshot state = debug_Snapshot_init_zero; + if (!decode_payload(message->payload, debug_Snapshot_fields, + &state) || + !isToPhysicalAddrPossible(state.program_counter, m)) { + malformed(); + break; + } + pause_runtime(m); + m->warduino->execution_context->pc_ptr = + toPhysicalAddress(state.program_counter, m); + send_operation_result(message->type, true); break; - case interruptPOPEvent: - CallbackHandler::resolve_event(true); - free(interruptData); + } + case debug_Command_COMMAND_ADD_PROXY: + case debug_Command_COMMAND_REMOVE_PROXY: { + debug_FunctionRef reference = debug_FunctionRef_init_zero; + if (!decode_payload(message->payload, debug_FunctionRef_fields, + &reference) || + supervisor == nullptr || + reference.function_index >= m->function_count) { + send_operation_result(message->type, false); + break; + } + if (message->type == debug_Command_COMMAND_ADD_PROXY) + supervisor->registerProxiedCall(reference.function_index); + else + supervisor->unregisterProxiedCall(reference.function_index); + send_operation_result(message->type, true); break; - case interruptPUSHEvent: - this->handlePushedEvent(reinterpret_cast(interruptData)); - 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 = collect_values; + call.arguments.arg = &values; + if (!decode_payload(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 &= apply_value_update(values[index], &arguments[index]); + if (!valid) { + delete[] arguments; + malformed(); + break; + } + if (message->type == debug_Command_COMMAND_PROXY_CALL) { + if (proxy == nullptr) { + delete[] arguments; + send_operation_result(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; + exception[0] = "\0"[0]; + const auto results = m->warduino->invoke( + m, call.function_index, static_cast(values.size()), + arguments); + m->warduino->program_state = current; + delete[] arguments; + + debug_RemoteFunctionResult result = + debug_RemoteFunctionResult_init_zero; + result.success = exception[0] == "\0"[0]; + std::vector error; + if (result.success) { + result.results.funcs.encode = encode_values; + result.results.arg = + const_cast *>(&results); + } else { + error.assign(exception, exception + std::strlen(exception)); + result.error.funcs.encode = encode_bytes; + result.error.arg = &error; + } + send_notification( + debug_NotificationType_NOTIFICATION_REMOTE_FUNCTION_RESULT, + debug_RemoteFunctionResult_fields, &result); break; - case interruptRecvCallbackmapping: - Debugger::updateCallbackmapping( - m, reinterpret_cast(interruptData + 2)); - free(interruptData); + } + case debug_Command_COMMAND_PROXIFY: + if (!require_empty()) break; + proxify(); + send_operation_result(message->type, true); break; - case interruptDUMPCallbackmapping: - this->dumpCallbackmapping(); - free(interruptData); + case debug_Command_COMMAND_POP_EVENT: + if (!require_empty()) break; + send_operation_result(message->type, + CallbackHandler::resolve_event(true)); break; - case interruptSetOverridePinValue: - this->addOverride(m, interruptData + 1); - free(interruptData); + case debug_Command_COMMAND_PUSH_EVENT: { + debug_Event event = debug_Event_init_zero; + std::vector topic; + std::vector payload; + set_decode_callback(&event.topic, &topic); + set_decode_callback(&event.payload, &payload); + if (!decode_payload(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()); + notify_pushed_event(); break; - case interruptUnsetOverridePinValue: - this->removeOverride(m, interruptData + 1); - free(interruptData); + } + case debug_Command_COMMAND_RESET: + if (!require_empty()) break; + send_operation_result(message->type, reset(m)); break; default: - // handle later - this->channel->write("COULD not parse interrupt data!\n"); - free(interruptData); + malformed(); 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); - break; - case I64: - snprintf(buff, 255, R"("type":"i64","value":)" FMT(PRIu64), - v->value.uint64); - break; - case F32: - snprintf(buff, 255, R"("type":"F32","value":")" FMT(PRIu32) "\"", - v->value.uint32); - break; - case F64: - snprintf(buff, 255, R"("type":"F64","value":")" FMT(PRIu64) "\"", - v->value.uint64); - break; - default: - snprintf(buff, 255, R"("type":"%02x","value":")" FMT(PRIu64) "\"", - v->value_type, v->value.uint64); - } - this->channel->write(R"({"idx":%d,%s}%s)", idx, buff, end ? "" : ","); -} +void Debugger::print_value(const StackValue *, const uint32_t, + const bool) const {} -uint8_t *Debugger::findOpcode(Module *m, const Block *block) { +uint8_t *Debugger::find_opcode(Module *m, const Block *block) { const auto find = std::find_if(std::begin(m->block_lookup), std::end(m->block_lookup), [&](const std::pair &pair) { @@ -427,7 +1066,7 @@ uint8_t *Debugger::findOpcode(Module *m, const Block *block) { return opcode; } -void Debugger::handleInvoke(Module *m, uint8_t *interruptData) const { +void Debugger::handle_invoke(Module *m, uint8_t *interruptData) const { const uint32_t fidx = read_LEB_32(&interruptData); if (fidx >= m->function_count) { @@ -444,26 +1083,25 @@ void Debugger::handleInvoke(Module *m, uint8_t *interruptData) const { WARDuino::instance()->invoke(m, fidx, func.param_count, args); instance->program_state = current; - this->dumpStack(m); + this->dump_stack(m); } -void Debugger::handleInterruptRUN(const Module *m, - RunningState *program_state) { +void Debugger::handle_interrupt_run(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)) { + if (*program_state == WARDUINOpause && this->is_breakpoint(ectx->pc_ptr)) { this->skipBreakpoint = ectx->pc_ptr; } *program_state = WARDUINOrun; } -void Debugger::handleSTEP(const Module *m, RunningState *program_state) { +void Debugger::handle_step(const Module *m, RunningState *program_state) { ExecutionContext *ectx = m->warduino->execution_context; *program_state = WARDUINOstep; this->skipBreakpoint = ectx->pc_ptr; } -void Debugger::handleSTEPOver(const Module *m, RunningState *program_state) { +void Debugger::handle_step_over(const Module *m, RunningState *program_state) { ExecutionContext *ectx = m->warduino->execution_context; this->skipBreakpoint = ectx->pc_ptr; uint8_t const opcode = *ectx->pc_ptr; @@ -481,208 +1119,96 @@ void Debugger::handleSTEPOver(const Module *m, RunningState *program_state) { *program_state = WARDUINOrun; } else { // normal step - this->handleSTEP(m, program_state); + this->handle_step(m, program_state); } } -void Debugger::handleInterruptBP(Module *m, uint8_t *interruptData) { +void Debugger::handle_interrupt_bp(Module *m, uint8_t *interruptData) { uint8_t *bpData = interruptData + 1; uint32_t virtualAddress = read_B32(&bpData); if (isToPhysicalAddrPossible(virtualAddress, m)) { uint8_t *bpt = toPhysicalAddress(virtualAddress, m); if (*interruptData == 0x06) { - this->addBreakpoint(bpt); + this->add_breakpoint(bpt); } else { - this->deleteBreakpoint(bpt); + this->delete_breakpoint(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("{"); - - // 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(", "); +std::optional Debugger::update_value( + const std::vector &payload) const { + debug_ValueUpdate update = debug_ValueUpdate_init_zero; + if (!decode_payload(payload, debug_ValueUpdate_fields, &update) || + !update.has_value) { + return std::nullopt; } - - this->dumpHeapInfo(m); - - this->channel->write("}\n\n"); - // fflush(stdout); + return update; } -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::dump(Module *m, bool) const { snapshot(m); } + +void Debugger::dump_stack(const Module *m) const { + const ExecutionContext *ectx = m->warduino->execution_context; + ValueView values{ectx->stack, + ectx->sp >= 0 ? static_cast(ectx->sp + 1) : 0, + nullptr}; + /* ValueView points directly at the execution stack. */ + debug_Locals locals = debug_Locals_init_zero; + locals.values.funcs.encode = encode_value_range; + locals.values.arg = &values; + send_notification(debug_NotificationType_NOTIFICATION_LOCALS_DUMP, + debug_Locals_fields, &locals); } -void Debugger::dumpFunctions(Module *m) const { - this->channel->write("\"functions\":["); +void Debugger::dump_breakpoints(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::dump_functions(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\":["); - - 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::dump_callstack(Module *) const {} + +void Debugger::dump_locals(const Module *m) const { + ValueView values = current_locals(m->warduino->execution_context); + debug_Locals locals = debug_Locals_init_zero; + locals.values.funcs.encode = encode_value_range; + locals.values.arg = &values; + send_notification(debug_NotificationType_NOTIFICATION_LOCALS_DUMP, + debug_Locals_fields, &locals); } -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::dump_events(long start, long size) const { + const size_t total = CallbackHandler::event_count(); + const size_t first = + std::min(start < 0 ? size_t{0} : static_cast(start), total); + const size_t count = + size < 0 ? 0 : std::min(static_cast(size), total - first); + EventRangeView range{first, count}; + debug_EventsQueue queue = debug_EventsQueue_init_zero; + queue.total_count = static_cast(total); + queue.has_range = true; + queue.range.start = static_cast(first); + queue.range.end = static_cast(first + count); + queue.events.funcs.encode = encode_events; + queue.events.arg = ⦥ + send_notification(debug_NotificationType_NOTIFICATION_EVENTS_DUMP, + debug_EventsQueue_fields, &queue); } -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; +void Debugger::dump_callback_mapping() const { + const auto &callbacks = CallbackHandler::callback_map(); + debug_CallbackMapping mapping = debug_CallbackMapping_init_zero; + mapping.entries.funcs.encode = encode_callbacks; + mapping.entries.arg = + const_cast(&callbacks); + send_notification(debug_NotificationType_NOTIFICATION_CALLBACKS_DUMP, + debug_CallbackMapping_fields, &mapping); } -void Debugger::dumpCallbackmapping() const { - this->channel->write("%s\n", CallbackHandler::dump_callbacks().c_str()); -} - -void Debugger::dumpHeapInfo(Module *m) const { - this->channel->write(R"("heap":{"used":%u})", m->warduino->get_heap_used()); -} +void Debugger::dump_heap_info(Module *) const {} /** * Read the change in bytes array. @@ -691,7 +1217,7 @@ void Debugger::dumpHeapInfo(Module *m) const { * [0x10, index, ... new function body 0x0b] * Where index is the index without imports */ -bool Debugger::handleChangedFunction(const Module *m, uint8_t *bytes) { +bool Debugger::handle_changed_function(const Module *m, uint8_t *bytes) { // Check if this was a change request if (*bytes != interruptUPDATEFun) return false; @@ -749,13 +1275,13 @@ bool Debugger::handleChangedFunction(const Module *m, uint8_t *bytes) { * @param bytes * @return */ -bool Debugger::handleChangedLocal(const Module *m, uint8_t *bytes) const { +bool Debugger::handle_changed_local(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,239 +1300,165 @@ 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"); +void Debugger::notify_pushed_event() const { + this->send_notification(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(); +bool Debugger::handle_pushed_event(char *) const { return false; } + +bool Debugger::parse_selection(const uint8_t *state, const size_t size, + SnapshotSelection *selection) { + *selection = 0; + for (size_t index = 0; index < size; ++index) { + if (state[index] < pcState || state[index] > heapState) return false; + *selection |= static_cast(1u << (state[index] - 1)); + } return true; } -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 { +bool Debugger::encode_snapshot( + Module *m, const SnapshotSelection selection, + const debug_NotificationType notification) 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 + SnapshotView view{m, this, ectx, &overrides}; + debug_Snapshot state = debug_Snapshot_init_zero; + std::vector ioState; + if (selection & snapshotPc) { + state.program_counter = toVirtualAddress(ectx->pc_ptr, m); + switch (m->warduino->program_state) { + case WARDUINOrun: + state.state = debug_State_STATE_WARDUINO_RUN; 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("]}"); + case WARDUINOstep: + state.state = debug_State_STATE_WARDUINO_STEP; 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 + case PROXYrun: + state.state = debug_State_STATE_PROXY_RUN; 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; + case PROXYhalt: + state.state = debug_State_STATE_PROXY_HALT; 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"); + default: + state.state = debug_State_STATE_WARDUINO_PAUSE; break; - } } } - this->channel->write("}"); + if (selection & snapshotBreakpoints) { + state.breakpoints.funcs.encode = encode_breakpoints; + state.breakpoints.arg = &view; + } + if (selection & snapshotFunctions) { + state.functions.funcs.encode = encode_functions; + state.functions.arg = &view; + } + if (selection & snapshotCallstack) { + state.callstack.funcs.encode = encode_callstack; + state.callstack.arg = &view; + } + ValueView globals{nullptr, m->global_count, m->globals}; + if (selection & snapshotGlobals) { + state.globals.funcs.encode = encode_value_range; + state.globals.arg = &globals; + } + ValueView stackValues{ectx->stack, + ectx->sp >= 0 ? static_cast(ectx->sp + 1) : 0, + nullptr}; + if (selection & snapshotStack) { + state.stack.funcs.encode = encode_value_range; + state.stack.arg = &stackValues; + } + ValueView locals = current_locals(ectx); + if (selection & snapshotLocals) { + state.has_locals = true; + state.locals.values.funcs.encode = encode_value_range; + state.locals.values.arg = &locals; + } + Uint32View table{m->table.entries, + m->table.entries == nullptr ? 0 : m->table.size}; + if (selection & snapshotTable) { + state.has_table = true; + state.table.initial = m->table.initial; + state.table.maximum = m->table.maximum; + state.table.entries.funcs.encode = nanopb_encoder::encode_varints; + state.table.entries.arg = &table; + } + const size_t memorySize = static_cast(m->memory.pages) * PAGE_SIZE; + ByteView memory{m->memory.bytes, + m->memory.bytes == nullptr ? 0 : memorySize}; + if (selection & snapshotMemory) { + state.has_memory = true; + state.memory.initial = m->memory.initial; + state.memory.maximum = m->memory.maximum; + state.memory.pages = m->memory.pages; + state.memory.bytes.funcs.encode = nanopb_encoder::encode_bytes; + state.memory.bytes.arg = &memory; + } + Uint32View branch{ectx->br_table, ectx->br_table == nullptr + ? size_t{0} + : static_cast(BR_TABLE_SIZE)}; + if (selection & snapshotBranchTable) { + state.branch_table.funcs.encode = nanopb_encoder::encode_varints; + state.branch_table.arg = &branch; + } + const auto &callbacks = CallbackHandler::callback_map(); + if (selection & snapshotCallbacks) { + state.has_callbacks = true; + state.callbacks.entries.funcs.encode = encode_callbacks; + state.callbacks.entries.arg = + const_cast(&callbacks); + } + const size_t eventCount = CallbackHandler::event_count(); + EventRangeView events{0, eventCount}; + if (selection & snapshotEvents) { + state.has_queue = true; + state.queue.total_count = static_cast(eventCount); + state.queue.has_range = true; + state.queue.range.start = 0; + state.queue.range.end = static_cast(eventCount); + state.queue.events.funcs.encode = encode_events; + state.queue.events.arg = &events; + } + if (selection & snapshotIO) { + ioState = m->warduino->interpreter->get_io_state(m); + state.io.funcs.encode = encode_io_state; + state.io.arg = &ioState; + } + if (selection & snapshotOverrides) { + state.overrides.funcs.encode = encode_overrides; + state.overrides.arg = &view; + } + if (selection & snapshotHeap) + state.heap_used = m->warduino->get_heap_used(); + const bool sent = + send_notification(notification, debug_Snapshot_fields, &state); + for (IOStateElement *entry : ioState) delete entry; + return sent; } -void Debugger::setSnapshotPolicy(Module *m, uint8_t *interruptData) { +void Debugger::snapshot(Module *m) const { + constexpr SnapshotSelection complete = + snapshotPc | snapshotBreakpoints | snapshotCallstack | snapshotGlobals | + snapshotTable | snapshotMemory | snapshotBranchTable | snapshotStack | + snapshotCallbacks | snapshotEvents | snapshotIO | snapshotOverrides | + snapshotHeap | snapshotFunctions | snapshotLocals; + encode_snapshot(m, complete, debug_NotificationType_NOTIFICATION_SNAPSHOT); +} + +void Debugger::inspect(Module *m, const uint16_t size, + const uint8_t *state) const { + SnapshotSelection selection = 0; + if (!parse_selection(state, size, &selection)) { + send_notification(debug_NotificationType_NOTIFICATION_MALFORMED); + return; + } + encode_snapshot(m, selection, debug_NotificationType_NOTIFICATION_SNAPSHOT); +} + +void Debugger::set_snapshot_policy(Module *m, uint8_t *interruptData) { uint8_t **data_ptr = &interruptData; if (*interruptData <= 2) { snapshotPolicy = SnapshotPolicy{*interruptData}; @@ -1036,13 +1488,12 @@ void Debugger::setSnapshotPolicy(Module *m, uint8_t *interruptData) { if (snapshotPolicy == SnapshotPolicy::checkpointing) { checkpointInterval = read_B32(data_ptr); instructions_executed = 0; - instructions_since_full_snapshot = 0; checkpoint(m, true); } printf("ack%x\n", interruptSetSnapshotPolicy); } -std::optional getPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { +std::optional get_primitive_being_called(Module *m, uint8_t *pc_ptr) { if (!pc_ptr) { return std::nullopt; } @@ -1059,11 +1510,14 @@ std::optional getPrimitiveBeingCalled(Module *m, uint8_t *pc_ptr) { return std::nullopt; } -void Debugger::handleSnapshotPolicy(Module *m) { +void Debugger::handle_snapshot_policy(Module *m) { if (snapshotPolicy == SnapshotPolicy::atEveryInstruction) { - this->channel->write("SNAPSHOT "); - snapshot(m); - this->channel->write("\n"); + SnapshotSelection selection = 0; + if (checkpoint_state != nullptr && + parse_selection(checkpoint_state, checkpoint_state_size, + &selection)) + encode_snapshot(m, selection, + debug_NotificationType_NOTIFICATION_SNAPSHOT); } else if (snapshotPolicy == SnapshotPolicy::checkpointing) { if (instructions_executed >= checkpointInterval || fidx_called) { if (min_return_values == 0) { @@ -1078,23 +1532,11 @@ void Debugger::handleSnapshotPolicy(Module *m) { } } - // When using tracing, optionally (if the interval is 0xffffffff no full - // snapshots will be taken) take full checkpoints every - // checkpointInterval instructions. - if (checkpoint_state != nullptr) { - if (checkpointInterval != UINT32_MAX && - instructions_since_full_snapshot >= checkpointInterval) { - checkpoint(m, true, true); - instructions_since_full_snapshot = 0; - } - instructions_since_full_snapshot++; - } - instructions_executed++; ExecutionContext *ectx = m->warduino->execution_context; // Store arguments of last primitive call. - if ((fidx_called = getPrimitiveBeingCalled(m, ectx->pc_ptr))) { + if ((fidx_called = get_primitive_being_called(m, ectx->pc_ptr))) { const Type *type = m->functions[*fidx_called].type; for (uint32_t i = 0; i < type->param_count; i++) { prim_args[type->param_count - i - 1] = @@ -1102,49 +1544,60 @@ 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) { - if (instructions_executed == 0 && !force) { - return; - } +void Debugger::checkpoint(Module *m, const bool force) { + 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; + notification.has_primitive_call = true; + notification.primitive_function_index = *fidx_called; + } + + SnapshotSelection selection = 0; + if (checkpoint_state != nullptr && + parse_selection(checkpoint_state, checkpoint_state_size, &selection) && + selection != 0) { + // Checkpoints only materialize the requested fields. Keep their views + // on this stack through nanopb sizing and encoding. + ExecutionContext *ectx = m->warduino->execution_context; + notification.has_snapshot = true; + if (selection & snapshotPc) { + notification.snapshot.program_counter = + toVirtualAddress(ectx->pc_ptr, m); + notification.snapshot.state = + m->warduino->program_state == WARDUINOrun + ? debug_State_STATE_WARDUINO_RUN + : debug_State_STATE_WARDUINO_PAUSE; } - 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; + ValueView globals{nullptr, m->global_count, m->globals}; + /* Globals are read directly while nanopb encodes this checkpoint. */ + if (selection & snapshotGlobals) { + notification.snapshot.globals.funcs.encode = encode_value_range; + notification.snapshot.globals.arg = &globals; } - this->channel->write("], "); - } - this->channel->write(R"("snapshot": )"); - if (!checkpoint_state || full) { - snapshot(m); - } else { - inspect(m, checkpoint_state_size, checkpoint_state); + ValueView stack{ectx->stack, + ectx->sp >= 0 ? static_cast(ectx->sp + 1) : 0, + nullptr}; + /* Stack values are read directly while nanopb encodes this checkpoint. + */ + if (selection & snapshotStack) { + notification.snapshot.stack.funcs.encode = encode_value_range; + notification.snapshot.stack.arg = &stack; + } + if (selection & snapshotHeap) + notification.snapshot.heap_used = m->warduino->get_heap_used(); } - this->channel->write("}\n"); + send_notification(debug_NotificationType_NOTIFICATION_CHECKPOINT, + debug_Checkpoint_fields, ¬ification); instructions_executed = 0; } -void Debugger::freeState(Module *m, uint8_t *interruptData) { +void Debugger::free_state(Module *m, uint8_t *interruptData) { debug("freeing the program state\n"); uint8_t *first_msg = nullptr; uint8_t *endfm = nullptr; @@ -1160,7 +1613,6 @@ void Debugger::freeState(Module *m, uint8_t *interruptData) { // Reset checkpointing counters, new checkpoints will have instructions // executed since this snapshot. - instructions_since_full_snapshot = 0; instructions_executed = 0; while (first_msg < endfm) { @@ -1228,13 +1680,13 @@ void Debugger::freeState(Module *m, uint8_t *interruptData) { break; } default: - FATAL("freeState: receiving unknown command\n"); + FATAL("free_state: receiving unknown command\n"); } } debug("done with first msg\n"); } -bool Debugger::saveState(Module *m, uint8_t *interruptData) { +bool Debugger::save_state(Module *m, uint8_t *interruptData) { ExecutionContext *ectx = m->warduino->execution_context; uint8_t *program_state = nullptr; uint8_t *end_state = nullptr; @@ -1260,7 +1712,7 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) { for (size_t i = 0; i < quantity_bps; i++) { auto virtualBP = read_B32(&program_state); if (isToPhysicalAddrPossible(virtualBP, m)) { - this->addBreakpoint(toPhysicalAddress(virtualBP, m)); + this->add_breakpoint(toPhysicalAddress(virtualBP, m)); } } break; @@ -1517,7 +1969,7 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) { break; } default: { - FATAL("saveState: Received unknown program state\n"); + FATAL("save_state: Received unknown program state\n"); } } } @@ -1525,7 +1977,7 @@ bool Debugger::saveState(Module *m, uint8_t *interruptData) { return done == static_cast(1); } -uintptr_t Debugger::readPointer(uint8_t **data) { +uintptr_t Debugger::read_pointer(uint8_t **data) { const uint8_t len = (*data)[0]; uintptr_t bp = 0x0; for (size_t i = 0; i < len; i++) { @@ -1541,8 +1993,8 @@ void Debugger::proxify() { this->proxy = new Proxy(); // TODO delete } -void Debugger::handleProxyCall(Module *m, RunningState *, - uint8_t *interruptData) const { +void Debugger::handle_proxy_call(Module *m, RunningState *, + uint8_t *interruptData) const { if (this->proxy == nullptr) { dbg_info("No proxy available to send proxy call to.\n"); // TODO how to handle this error? @@ -1560,28 +2012,33 @@ void Debugger::handleProxyCall(Module *m, RunningState *, this->proxy->pushRFC(m, rfc); } -RFC *Debugger::topProxyCall() const { +RFC *Debugger::top_proxy_call() const { if (proxy == nullptr) { return nullptr; } return this->proxy->topRFC(); } -void Debugger::sendProxyCallResult(Module *m) const { - if (proxy == nullptr) { - return; - } - this->proxy->returnResult(m); +void Debugger::send_proxy_call_result(Module *m) const { + if (proxy == nullptr) return; + RFC *rfc = proxy->returnResult(m); + if (rfc == nullptr) return; + debug_RemoteFunctionResult result = debug_RemoteFunctionResult_init_zero; + result.success = rfc->success; + send_notification( + debug_NotificationType_NOTIFICATION_REMOTE_FUNCTION_RESULT, + debug_RemoteFunctionResult_fields, &result); + delete rfc; } -bool Debugger::isProxy() const { return this->proxy != nullptr; } +bool Debugger::is_proxy() const { return this->proxy != nullptr; } -bool Debugger::isProxied(const uint32_t fidx) const { - return this->supervisor != nullptr && this->supervisor->isProxied(fidx); +bool Debugger::is_proxied(const uint32_t fidx) const { + return this->supervisor != nullptr && this->supervisor->is_proxied(fidx); } -void Debugger::handleMonitorProxies(const Module *m, - uint8_t *interruptData) const { +void Debugger::handle_monitor_proxies(const Module *m, + uint8_t *interruptData) const { const uint32_t amount_funcs = read_B32(&interruptData); printf("funcs_total %" PRIu32 "\n", amount_funcs); @@ -1592,10 +2049,10 @@ 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) { +void Debugger::start_proxy_supervisor(Channel *socket) { this->connected_to_proxy = true; this->supervisor = new ProxySupervisor(socket, this->supervisor_mutex); printf("Connected to proxy.\n"); @@ -1612,17 +2069,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::update_callback_mapping(Module *, const char *) { + // Legacy JSON callback mapping input is intentionally unsupported. } // Stop the debugger @@ -1634,12 +2082,12 @@ void Debugger::stop() { } // -void Debugger::pauseRuntime(const Module *m) { +void Debugger::pause_runtime(const Module *m) { m->warduino->program_state = WARDUINOpause; this->mark = nullptr; } -bool Debugger::handleUpdateModule(Module *m, uint8_t *data) { +bool Debugger::handle_update_module(Module *m, uint8_t *data) { uint8_t *wasm_data = data + 1; const uint32_t wasm_len = read_LEB_32(&wasm_data); auto *wasm = static_cast(malloc(sizeof(uint8_t) * wasm_len)); @@ -1649,21 +2097,23 @@ bool Debugger::handleUpdateModule(Module *m, uint8_t *data) { return true; } -bool Debugger::handleUpdateGlobalValue(const Module *m, uint8_t *data) const { - this->channel->write("Global updates: %x\n", *data); +bool Debugger::handle_update_global_value(const Module *m, + uint8_t *data) const { + 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; } -bool Debugger::handleUpdateStackValue(const Module *m, uint8_t *bytes) const { +bool Debugger::handle_update_stack_value(const Module *m, + uint8_t *bytes) const { const uint32_t idx = read_LEB_32(&bytes); if (idx >= STACK_SIZE) { return false; @@ -1675,15 +2125,14 @@ 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; } 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; } @@ -1707,15 +2156,14 @@ std::string read_string(uint8_t **pos) { return str; } -void Debugger::addOverride(Module *m, uint8_t *interruptData) { +void Debugger::add_override(Module *m, uint8_t *interruptData) { const std::string primitive_name = read_string(&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,18 +2175,18 @@ 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; } -void Debugger::removeOverride(Module *m, uint8_t *interruptData) { +void Debugger::remove_override(Module *m, uint8_t *interruptData) { const std::string primitive_name = read_string(&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,13 +2198,13 @@ 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) { +bool Debugger::get_mock_for_args(Module *m, uint32_t fidx, uint32_t &result) { const uint32_t param_count = m->functions[fidx].type->param_count; std::vector key(param_count + 1); const ExecutionContext *ectx = m->warduino->execution_context; @@ -1772,7 +2220,7 @@ bool Debugger::getMockForArgs(Module *m, uint32_t fidx, uint32_t &result) { return true; } -bool Debugger::handleContinueFor(Module *m) { +bool Debugger::handle_continue_for(Module *m) { if (remaining_instructions < 0) return false; if (remaining_instructions == 0) { @@ -1780,21 +2228,21 @@ bool Debugger::handleContinueFor(Module *m) { if (snapshotPolicy == SnapshotPolicy::checkpointing) { checkpoint(m); } - this->channel->write("DONE!\n"); - pauseRuntime(m); + this->send_notification(debug_NotificationType_NOTIFICATION_PAUSED); + pause_runtime(m); return true; } remaining_instructions--; return false; } -void Debugger::notifyCompleteStep(Module *m) const { +void Debugger::notify_complete_step(Module *m) const { // Upon completing a step in checkpointing mode, make a checkpoint. - if (m->warduino->debugger->getSnapshotPolicy() == + if (m->warduino->debugger->get_snapshot_policy() == SnapshotPolicy::checkpointing) { m->warduino->debugger->checkpoint(m); } - this->channel->write("STEP!\n"); + this->send_notification(debug_NotificationType_NOTIFICATION_STEPPED); } Debugger::~Debugger() { diff --git a/src/Debug/debugger.h b/src/Debug/debugger.h index 3a5b66268..0716a2e0e 100644 --- a/src/Debug/debugger.h +++ b/src/Debug/debugger.h @@ -2,12 +2,10 @@ #include #include -#include +#include #include #include -#include // std::queue #include -#include #include #include @@ -15,11 +13,18 @@ #include "../Edward/proxy_supervisor.h" #include "../Threading/warduino-thread.h" #include "../Utils/sockets.h" +#include "nanopb/debug.pb.h" +#include "nanopb/pb_decode.h" struct Module; struct Block; struct StackValue; +struct DebugMessage { + debug_Command type; + std::vector payload; +}; + enum operation { STORE = 0, LOAD = 1, @@ -54,6 +59,25 @@ enum ExecutionState { heapState = 0x0D, }; +using SnapshotSelection = uint16_t; +enum SnapshotSection : SnapshotSelection { + snapshotPc = 1u << 0, + snapshotBreakpoints = 1u << 1, + snapshotCallstack = 1u << 2, + snapshotGlobals = 1u << 3, + snapshotTable = 1u << 4, + snapshotMemory = 1u << 5, + snapshotBranchTable = 1u << 6, + snapshotStack = 1u << 7, + snapshotCallbacks = 1u << 8, + snapshotEvents = 1u << 9, + snapshotIO = 1u << 10, + snapshotOverrides = 1u << 11, + snapshotHeap = 1u << 12, + snapshotFunctions = 1u << 13, + snapshotLocals = 1u << 14 +}; + enum InterruptTypes { // Remote Debugging interruptRUN = 0x01, @@ -132,17 +156,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 @@ -155,10 +176,8 @@ class Debugger { // Checkpointing SnapshotPolicy snapshotPolicy; - uint32_t checkpointInterval; // #instructions between checkpoints - uint32_t instructions_executed; // #instructions since last checkpoint - uint32_t instructions_since_full_snapshot; // #instructions since last full - // snapshot + uint32_t checkpointInterval; // #instructions between checkpoints + uint32_t instructions_executed; // #instructions since last checkpoint std::optional fidx_called; // The primitive that was executed uint32_t prim_args[8]; // The arguments of the executed prim uint32_t min_return_values; @@ -170,80 +189,92 @@ class Debugger { // Private methods - void printValue(const StackValue *v, uint32_t idx, bool end) const; + void print_value(const StackValue *v, uint32_t idx, bool end) const; // TODO Move parsing to WARDuino class? - void parseDebugBuffer(size_t len, const uint8_t *buff); + void parse_debug_buffer(size_t len, const uint8_t *buff); - void pushMessage(uint8_t *msg); + void push_message(DebugMessage msg); + + bool send_notification(debug_NotificationType type, + const pb_msgdesc_t *fields = nullptr, + const void *payload = nullptr) const; + void send_operation_result(debug_Command command, bool success) const; //// Handle REPL interrupts - void handleInvoke(Module *m, uint8_t *interruptData) const; + void handle_invoke(Module *m, uint8_t *interruptData) const; //// Handle Interrupt Types - void handleInterruptRUN(const Module *m, RunningState *program_state); + void handle_interrupt_run(const Module *m, RunningState *program_state); - void handleSTEP(const Module *m, RunningState *program_state); + void handle_step(const Module *m, RunningState *program_state); - void handleSTEPOver(const Module *m, RunningState *program_state); + void handle_step_over(const Module *m, RunningState *program_state); - void handleInterruptBP(Module *m, uint8_t *interruptData); + void handle_interrupt_bp(Module *m, uint8_t *interruptData); //// Information dumps void dump(Module *m, bool full = false) const; - void dumpStack(const Module *m) const; + void dump_stack(const Module *m) const; - void dumpLocals(const Module *m) const; + void dump_locals(const Module *m) const; - void dumpBreakpoints(Module *m) const; + void dump_breakpoints(Module *m) const; - void dumpFunctions(Module *m) const; + void dump_functions(Module *m) const; - void dumpCallstack(Module *m) const; + void dump_callstack(Module *m) const; - void dumpEvents(long start, long size) const; + void dump_events(long start, long size) const; - void dumpCallbackmapping() const; + void dump_callback_mapping() const; - void dumpHeapInfo(Module *m) const; + void dump_heap_info(Module *m) const; void inspect(Module *m, uint16_t sizeStateArray, const uint8_t *state) const; + bool encode_snapshot(Module *m, SnapshotSelection selection, + debug_NotificationType notification) const; + static bool parse_selection(const uint8_t *state, size_t size, + SnapshotSelection *selection); //// Handle live code update - static bool handleChangedFunction(const Module *m, uint8_t *bytes); + static bool handle_changed_function(const Module *m, uint8_t *bytes); + + bool handle_changed_local(const Module *m, uint8_t *bytes) const; - bool handleChangedLocal(const Module *m, uint8_t *bytes) const; + static bool handle_update_module(Module *m, uint8_t *data); - static bool handleUpdateModule(Module *m, uint8_t *data); + bool handle_update_global_value(const Module *m, uint8_t *data) const; - bool handleUpdateGlobalValue(const Module *m, uint8_t *data) const; + bool handle_update_stack_value(const Module *m, uint8_t *bytes) const; - bool handleUpdateStackValue(const Module *m, uint8_t *bytes) const; + std::optional update_value( + const std::vector &payload) const; bool reset(Module *m); //// Handle mocking - void addOverride(Module *m, uint8_t *interruptData); - void removeOverride(Module *m, uint8_t *interruptData); + void add_override(Module *m, uint8_t *interruptData); + void remove_override(Module *m, uint8_t *interruptData); //// Handle out-of-place debugging - void freeState(Module *m, uint8_t *interruptData); + void free_state(Module *m, uint8_t *interruptData); - static uint8_t *findOpcode(Module *m, const Block *block); + static uint8_t *find_opcode(Module *m, const Block *block); - bool saveState(Module *m, uint8_t *interruptData); + bool save_state(Module *m, uint8_t *interruptData); - static uintptr_t readPointer(uint8_t **data); + static uintptr_t read_pointer(uint8_t **data); - static void updateCallbackmapping(Module *m, const char *interruptData); + static void update_callback_mapping(Module *m, const char *interruptData); bool operation(Module *m, operation op); @@ -266,59 +297,59 @@ class Debugger { ~Debugger(); - void setChannel(Channel *duplex); + void set_channel(Channel *duplex); // Public methods void stop(); - void pauseRuntime(const Module *m); // pause runtime for given module + void pause_runtime(const Module *m); // pause runtime for given module - void notifyCompleteStep( + void notify_complete_step( Module *m) const; // notify the debugger frontend that a step was taken // Interrupts - void addDebugMessage(size_t len, const uint8_t *buff); + void add_debug_message(size_t len, const uint8_t *buff); - uint8_t *getDebugMessage(); + std::optional get_debug_message(); - bool checkDebugMessages(Module *m, RunningState *program_state); + bool check_debug_messages(Module *m, RunningState *program_state); // Breakpoints - void addBreakpoint(uint8_t *loc); + void add_breakpoint(uint8_t *loc); - void deleteBreakpoint(uint8_t *loc); + void delete_breakpoint(uint8_t *loc); - bool isBreakpoint(uint8_t *loc); + bool is_breakpoint(uint8_t *loc); - void notifyBreakpoint(Module *m, uint8_t *pc_ptr); + void notify_breakpoint(Module *m, uint8_t *pc_ptr); // Out-of-place debugging: EDWARD void snapshot(Module *m) const; - void setSnapshotPolicy(Module *m, uint8_t *interruptData); + void set_snapshot_policy(Module *m, uint8_t *interruptData); - void handleSnapshotPolicy(Module *m); + void handle_snapshot_policy(Module *m); - bool handleContinueFor(Module *m); + bool handle_continue_for(Module *m); void proxify(); - void handleProxyCall(Module *m, RunningState *program_state, - uint8_t *interruptData) const; + void handle_proxy_call(Module *m, RunningState *program_state, + uint8_t *interruptData) const; - RFC *topProxyCall() const; + RFC *top_proxy_call() const; - void sendProxyCallResult(Module *m) const; + void send_proxy_call_result(Module *m) const; - bool isProxy() const; + bool is_proxy() const; - bool isProxied(uint32_t fidx) const; + bool is_proxied(uint32_t fidx) const; - void startProxySupervisor(Channel *socket); + void start_proxy_supervisor(Channel *socket); bool proxy_connected() const; @@ -326,18 +357,18 @@ class Debugger { // Pull-based - void handleMonitorProxies(const Module *m, uint8_t *interruptData) const; + void handle_monitor_proxies(const Module *m, uint8_t *interruptData) const; // Push-based - void notifyPushedEvent() const; + void notify_pushed_event() const; - bool handlePushedEvent(char *bytes) const; + bool handle_pushed_event(char *bytes) const; // Concolic Multiverse Debugging - bool getMockForArgs(Module *m, uint32_t fidx, uint32_t &result); + bool get_mock_for_args(Module *m, uint32_t fidx, uint32_t &result); // Checkpointing - void checkpoint(Module *m, bool force = false, bool full = false); - inline SnapshotPolicy getSnapshotPolicy() { return snapshotPolicy; } + void checkpoint(Module *m, bool force = false); + inline SnapshotPolicy get_snapshot_policy() { return snapshotPolicy; } }; 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/Debug/nanopb_encoder.cpp b/src/Debug/nanopb_encoder.cpp new file mode 100644 index 000000000..6a98ea606 --- /dev/null +++ b/src/Debug/nanopb_encoder.cpp @@ -0,0 +1,36 @@ +#include "nanopb_encoder.h" + +namespace nanopb_encoder { + +bool encode_bytes(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + return view != nullptr && pb_encode_tag_for_field(stream, field) && + pb_encode_string(stream, view->data, view->size); +} + +bool encode_varints(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + if (view == nullptr) return false; + for (size_t index = 0; index < view->size; ++index) { + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_varint(stream, view->data[index])) + return false; + } + return true; +} + +bool encode_fixed32s(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + const auto *view = static_cast(*arg); + if (view == nullptr) return false; + for (size_t index = 0; index < view->size; ++index) { + if (!pb_encode_tag_for_field(stream, field) || + !pb_encode_fixed32(stream, &view->data[index])) + return false; + } + return true; +} + +} // namespace nanopb_encoder diff --git a/src/Debug/nanopb_encoder.h b/src/Debug/nanopb_encoder.h new file mode 100644 index 000000000..e6e538635 --- /dev/null +++ b/src/Debug/nanopb_encoder.h @@ -0,0 +1,26 @@ +#pragma once + +#include "nanopb/pb_encode.h" + +// Small, non-owning helpers for nanopb callback fields. The pointed-to data +// must remain stable for nanopb's sizing and output passes. +namespace nanopb_encoder { + +struct ByteView { + const uint8_t *data = nullptr; + size_t size = 0; +}; + +struct Uint32View { + const uint32_t *data = nullptr; + size_t size = 0; +}; + +bool encode_bytes(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg); +bool encode_varints(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg); +bool encode_fixed32s(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg); + +} // namespace nanopb_encoder diff --git a/src/Edward/proxy.cpp b/src/Edward/proxy.cpp index 5b9021248..3182874fa 100644 --- a/src/Edward/proxy.cpp +++ b/src/Edward/proxy.cpp @@ -34,7 +34,7 @@ void Proxy::pushRFC(Module *m, RFC *rfc) { ((Primitive)m->functions[rfc->fidx].func_ptr)(m); // send result directly m->warduino->program_state = PROXYhalt; - m->warduino->debugger->sendProxyCallResult(m); + m->warduino->debugger->send_proxy_call_result(m); return; } @@ -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..625bc9513 100644 --- a/src/Edward/proxy_supervisor.cpp +++ b/src/Edward/proxy_supervisor.cpp @@ -46,7 +46,7 @@ void runSupervisor(ProxySupervisor *supervisor) { } Event *parseJSON(char *buff) { - // TODO duplicate code in Debugger::handlePushedEvent + // TODO duplicate code in Debugger::handle_pushed_event nlohmann::basic_json<> parsed = nlohmann::json::parse(buff); printf("parseJSON: %s\n", parsed.dump().c_str()); std::string payload = *parsed.find("payload"); @@ -108,7 +108,7 @@ void ProxySupervisor::listenToSocket() { if (isEvent(parsed)) { CallbackHandler::push_event(new Event( *parsed.find("topic"), *parsed.find("payload"))); - WARDuino::instance()->debugger->notifyPushedEvent(); + WARDuino::instance()->debugger->notify_pushed_event(); } if (isReply(parsed)) { @@ -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; } @@ -291,6 +291,6 @@ void ProxySupervisor::unregisterProxiedCall(uint32_t fidx) { void ProxySupervisor::unregisterAllProxiedCalls() { this->proxied->clear(); } -bool ProxySupervisor::isProxied(uint32_t fidx) { +bool ProxySupervisor::is_proxied(uint32_t fidx) { return this->proxied->count(fidx) > 0; } diff --git a/src/Edward/proxy_supervisor.h b/src/Edward/proxy_supervisor.h index 40b0bb55c..8cf5e3143 100644 --- a/src/Edward/proxy_supervisor.h +++ b/src/Edward/proxy_supervisor.h @@ -43,5 +43,5 @@ class ProxySupervisor { void registerProxiedCall(uint32_t fidx); void unregisterProxiedCall(uint32_t fidx); void unregisterAllProxiedCalls(); - bool isProxied(uint32_t fidx); + bool is_proxied(uint32_t fidx); }; diff --git a/src/Interpreter/instructions.cpp b/src/Interpreter/instructions.cpp index 8be3a3db6..1f790dcc7 100644 --- a/src/Interpreter/instructions.cpp +++ b/src/Interpreter/instructions.cpp @@ -296,7 +296,7 @@ bool i_instr_call(Module *m) { ExecutionContext *ectx = m->warduino->execution_context; uint32_t fidx = read_LEB_32(&ectx->pc_ptr); - if (m->warduino->debugger->isProxied(fidx)) { + if (m->warduino->debugger->is_proxied(fidx)) { return proxy_call(m, fidx); } @@ -306,7 +306,8 @@ bool i_instr_call(Module *m) { // Mocking only works on primitives, no need to check for it otherwise. if (ectx->sp >= 0) { uint32_t mock_result; - if (m->warduino->debugger->getMockForArgs(m, fidx, mock_result)) { + if (m->warduino->debugger->get_mock_for_args(m, fidx, + mock_result)) { const uint32_t param_count = m->functions[fidx].type->param_count; ectx->sp -= static_cast(param_count) - 1; diff --git a/src/Interpreter/interpreter.cpp b/src/Interpreter/interpreter.cpp index e2b157cc9..84630dd60 100644 --- a/src/Interpreter/interpreter.cpp +++ b/src/Interpreter/interpreter.cpp @@ -37,7 +37,7 @@ Block *Interpreter::pop_block(Module *m) { if (frame->block->block_type == 0xfe) { m->warduino->program_state = PROXYhalt; - m->warduino->debugger->sendProxyCallResult(m); + m->warduino->debugger->send_proxy_call_result(m); // free if proxy guard free(frame->block); frame->block = nullptr; @@ -135,7 +135,7 @@ uint32_t STORE_SIZE[] = {4, 8, 4, 8, 1, 2, 1, 2, 4}; bool Interpreter::store(Module *m, uint8_t type, uint32_t addr, StackValue &sval) { - if (m->warduino->debugger->isProxy()) { + if (m->warduino->debugger->is_proxy()) { return m->warduino->debugger; } @@ -227,12 +227,12 @@ bool Interpreter::interpret(Module *m, bool waiting) { m = ectx->current_module; if (m->warduino->program_state == WARDUINOstep) { - m->warduino->debugger->notifyCompleteStep(m); - m->warduino->debugger->pauseRuntime(m); + m->warduino->debugger->notify_complete_step(m); + m->warduino->debugger->pause_runtime(m); } while (m->warduino->program_state != WARDUINOinit && - m->warduino->debugger->checkDebugMessages( + m->warduino->debugger->check_debug_messages( m, &m->warduino->program_state)) { } fflush(stdout); @@ -258,22 +258,22 @@ bool Interpreter::interpret(Module *m, bool waiting) { // Program state is not paused // If BP and not the one we just unpaused - if (m->warduino->debugger->isBreakpoint(ectx->pc_ptr) && + if (m->warduino->debugger->is_breakpoint(ectx->pc_ptr) && m->warduino->debugger->skipBreakpoint != ectx->pc_ptr && m->warduino->program_state != PROXYrun) { - m->warduino->debugger->pauseRuntime(m); - m->warduino->debugger->notifyBreakpoint(m, ectx->pc_ptr); + m->warduino->debugger->pause_runtime(m); + m->warduino->debugger->notify_breakpoint(m, ectx->pc_ptr); continue; } m->warduino->debugger->skipBreakpoint = nullptr; - if (m->warduino->debugger->handleContinueFor(m)) { + if (m->warduino->debugger->handle_continue_for(m)) { continue; } // Take snapshot before executing an instruction if (m->warduino->program_state != WARDUINOinit) { - m->warduino->debugger->handleSnapshotPolicy(m); + m->warduino->debugger->handle_snapshot_policy(m); } opcode = *ectx->pc_ptr; @@ -477,11 +477,11 @@ bool Interpreter::interpret(Module *m, bool waiting) { if (m->warduino->program_state == PROXYrun) { dbg_info("Trap was thrown during proxy call.\n"); - RFC *rfc = m->warduino->debugger->topProxyCall(); + RFC *rfc = m->warduino->debugger->top_proxy_call(); rfc->success = false; rfc->exception = strdup(exception); rfc->exception_size = strlen(exception); - m->warduino->debugger->sendProxyCallResult(m); + m->warduino->debugger->send_proxy_call_result(m); } // Resolve all unhandled callback events 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..4f9796a3a 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->notify_pushed_event(); CallbackHandler::events->pop_front(); CallbackHandler::pushed_cursor--; @@ -148,6 +144,14 @@ std::deque::const_iterator CallbackHandler::event_end() { return CallbackHandler::events->cend(); } +const CallbackHandler::CallbackMap &CallbackHandler::callback_map() { + return *callbacks; +} + +const Event *CallbackHandler::event_at(const size_t index) { + return index < events->size() ? &(*events)[index] : nullptr; +} + void CallbackHandler::clear_callbacks() { CallbackHandler::callbacks->clear(); } std::string CallbackHandler::dump_callbacks() { diff --git a/src/WARDuino/CallbackHandler.h b/src/WARDuino/CallbackHandler.h index c9d40c937..d81b7256f 100644 --- a/src/WARDuino/CallbackHandler.h +++ b/src/WARDuino/CallbackHandler.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -33,6 +34,10 @@ class CallbackHandler { static size_t event_count(); static std::deque::const_iterator event_begin(); static std::deque::const_iterator event_end(); + using CallbackMap = + std::unordered_map *>; + static const CallbackMap &callback_map(); + static const Event *event_at(size_t index); static bool resolving_event; diff --git a/src/WARDuino/WARDuino.cpp b/src/WARDuino/WARDuino.cpp index 1f411eb6c..d922ecd7c 100644 --- a/src/WARDuino/WARDuino.cpp +++ b/src/WARDuino/WARDuino.cpp @@ -1127,7 +1127,7 @@ int WARDuino::run_module(Module *m) { fflush(stdout); // wait - m->warduino->debugger->pauseRuntime(m); + m->warduino->debugger->pause_runtime(m); return interpreter->interpret(m, true); } @@ -1136,7 +1136,7 @@ int WARDuino::run_module(Module *m) { // parse numer per 2 chars (HEX) (stop if non-hex) // Don't use print in interrupt handlers void WARDuino::handleInterrupt(size_t len, uint8_t *buff) const { - this->debugger->addDebugMessage(len, buff); + this->debugger->add_debug_message(len, buff); } WARDuino *WARDuino::singleton = nullptr; @@ -1242,7 +1242,7 @@ void WARDuino::reset_module(Module *m) { } // wait - debugger->pauseRuntime(m); + debugger->pause_runtime(m); } void WARDuino::update_module(Module *m, uint8_t *wasm, uint32_t wasm_len) { @@ -1266,7 +1266,7 @@ void WARDuino::update_module(Module *m, uint8_t *wasm, uint32_t wasm_len) { } // wait - m->warduino->debugger->pauseRuntime(m); + m->warduino->debugger->pause_runtime(m); } uint32_t WARDuino::get_main_fidx(Module *m) { diff --git a/tests/compilation/esp32/esp32.ino b/tests/compilation/esp32/esp32.ino index cd94cb899..eae4b88f5 100644 --- a/tests/compilation/esp32/esp32.ino +++ b/tests/compilation/esp32/esp32.ino @@ -23,7 +23,7 @@ Module* m; void startDebuggerStd(void* pvParameter) { Channel* sink = new Sink(stdout); - wac->debugger->setChannel(sink); + wac->debugger->set_channel(sink); sink->open(); uint8_t buffer[1024] = {0}; diff --git a/tests/latch/latch-0.6.0.tgz b/tests/latch/latch-0.6.0.tgz deleted file mode 100644 index 836f7cea5..000000000 Binary files a/tests/latch/latch-0.6.0.tgz and /dev/null differ diff --git a/tests/latch/latch-0.7.0.tgz b/tests/latch/latch-0.7.0.tgz new file mode 100644 index 000000000..8cf24f776 Binary files /dev/null and b/tests/latch/latch-0.7.0.tgz differ diff --git a/tests/latch/package-lock.json b/tests/latch/package-lock.json index 2b851854d..d4701dcc7 100644 --- a/tests/latch/package-lock.json +++ b/tests/latch/package-lock.json @@ -8,7 +8,7 @@ "name": "warduino-testsuite", "version": "1.0.0", "devDependencies": { - "latch": "file:./latch-0.6.0.tgz", + "latch": "file:./latch-0.7.0.tgz", "mqtt": "^5.15.2", "serialport": "^10.4.0", "typescript": "^4.5.5" @@ -24,6 +24,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bufbuild/protobuf": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.14.0.tgz", + "integrity": "sha512-C3UGsiCwSprE2NKIIFA3hCDlpXTMCAXRZuEVp88L1GY36Y41+rYL5fryE+nOFhp4p4JPQvdV8PQ4DWgHgeTE+w==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -48,9 +55,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -258,9 +265,9 @@ } }, "node_modules/@thi.ng/checks": { - "version": "3.9.5", - "resolved": "https://registry.npmjs.org/@thi.ng/checks/-/checks-3.9.5.tgz", - "integrity": "sha512-hxSqs6DtE/RDkzWSa3xqbYEwqAhicqKcWRZSvUmQ9I2NEDrR+qBUj6icjXMtS51k+Mp3FRiTQ0FDCI8FbOBBmw==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@thi.ng/checks/-/checks-3.11.1.tgz", + "integrity": "sha512-S64V2nAPA0Y66VwpJdi3fdwv4xP9G7M13nvQWzp6YTERsnusI6FtihbxhENt/DQ/k+iP+aVSsy+LgtqwkFvvag==", "dev": true, "funding": [ { @@ -282,9 +289,9 @@ } }, "node_modules/@thi.ng/errors": { - "version": "2.6.14", - "resolved": "https://registry.npmjs.org/@thi.ng/errors/-/errors-2.6.14.tgz", - "integrity": "sha512-dSqLPZh5wOe329Ks2pJqoDmtjSv2g4KpXEP5/IQ5J9qvrEyNrRBCuaKHUKYUltQ1OUHGd9L5hBqGJl65Hlnu+g==", + "version": "2.6.17", + "resolved": "https://registry.npmjs.org/@thi.ng/errors/-/errors-2.6.17.tgz", + "integrity": "sha512-momw7wo3EXes1wa6Smckr+l4BaJi/h+E67R3furYOBjQpciie1OgoTa43PXPb1POsuWxynkEF8OCnQgreE8twg==", "dev": true, "funding": [ { @@ -306,9 +313,9 @@ } }, "node_modules/@thi.ng/leb128": { - "version": "3.1.90", - "resolved": "https://registry.npmjs.org/@thi.ng/leb128/-/leb128-3.1.90.tgz", - "integrity": "sha512-HR6dQnGPB3P9F+ILHkFw2cyse+aPOEHNS8loUOQ7uapGmZ4ummp+rsg/ZXQ6Bwqa+MZBHODBoeKrrUrARgcDYQ==", + "version": "3.1.94", + "resolved": "https://registry.npmjs.org/@thi.ng/leb128/-/leb128-3.1.94.tgz", + "integrity": "sha512-MNpWK3qnRJ7aUYB0qghtlm0uY4ULBWAPKcezqTMDAX+HVmGqONllhebTjgxmueCljKBqpJMugybnOO99cn5wCg==", "dev": true, "funding": [ { @@ -326,17 +333,17 @@ ], "license": "Apache-2.0", "dependencies": { - "@thi.ng/checks": "^3.9.5", - "@thi.ng/errors": "^2.6.14" + "@thi.ng/checks": "^3.11.1", + "@thi.ng/errors": "^2.6.17" }, "engines": { "node": ">=18" } }, "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", "dev": true, "license": "MIT" }, @@ -362,13 +369,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "version": "26.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.0.tgz", + "integrity": "sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.3.0" } }, "node_modules/@types/readable-stream": { @@ -391,6 +398,13 @@ "@types/node": "*" } }, + "node_modules/@types/yoga-layout": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz", + "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==", + "dev": true, + "license": "MIT" + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -405,9 +419,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", "bin": { @@ -430,27 +444,59 @@ "node": ">=0.4.0" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, "engines": { - "node": ">=6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/arg": { @@ -460,6 +506,29 @@ "dev": true, "license": "MIT" }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/auto-bind": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", + "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -494,23 +563,6 @@ "readable-stream": "^4.2.0" } }, - "node_modules/bl/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "dev": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/broker-factory": { "version": "3.1.15", "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.15.tgz", @@ -557,47 +609,105 @@ "license": "MIT" }, "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "license": "MIT", "dependencies": { - "restore-cursor": "^5.0.0" + "restore-cursor": "^3.1.0" }, "engines": { - "node": ">=18" + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-spinners": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", - "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "node_modules/code-excerpt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-3.0.0.tgz", + "integrity": "sha512-VHNTVhd7KsLGOqfX3SyeO8RyYPMp1GJOg194VITk04WMYCv4plV68YWe6TJZxd9MhobjtpMRnVky01gqZsalaw==", "dev": true, "license": "MIT", + "dependencies": { + "convert-to-spaces": "^1.0.1" + }, "engines": { - "node": ">=18.20" + "node": ">=10" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=7.0.0" } }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/commist": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz", @@ -621,6 +731,31 @@ "typedarray": "^0.0.6" } }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-to-spaces": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz", + "integrity": "sha512-cj09EBuObp9gZNQCzc7hByQyrs6jVGE+o9kSJmeUoj+GiPiJvi5LYqEH/Hmme4+MTLHM+Ejtq+FChpjjEnsPdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -656,6 +791,23 @@ "node": ">=0.3.1" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -690,17 +842,14 @@ "node": ">=18.2.0" } }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/help-me": { @@ -731,6 +880,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -738,40 +897,81 @@ "dev": true, "license": "ISC" }, + "node_modules/ink": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-3.2.0.tgz", + "integrity": "sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "auto-bind": "4.0.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "code-excerpt": "^3.0.0", + "indent-string": "^4.0.0", + "is-ci": "^2.0.0", + "lodash": "^4.17.20", + "patch-console": "^1.0.0", + "react-devtools-core": "^4.19.1", + "react-reconciler": "^0.26.2", + "scheduler": "^0.20.2", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "stack-utils": "^2.0.2", + "string-width": "^4.2.2", + "type-fest": "^0.12.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "ws": "^7.5.5", + "yoga-layout-prebuilt": "^1.9.6" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": ">=16.8.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/ip-address": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", - "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz", + "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==", "dev": true, "license": "MIT", "engines": { "node": ">= 12" } }, - "node_modules/is-interactive": { + "node_modules/is-ci": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "ci-info": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "is-ci": "bin.js" } }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/js-sdsl": { @@ -785,44 +985,328 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-with-bigint": { - "version": "3.5.8", - "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", - "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.12.tgz", + "integrity": "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==", "dev": true, "license": "MIT" }, "node_modules/latch": { - "version": "0.6.0", - "resolved": "file:latch-0.6.0.tgz", - "integrity": "sha512-HKyweJBOyp/o3TOXYr0+s3qbMW/Kj5/3s1lILsBzRBv7GxVdQGsvhZSgx6OyJazc0fpp50Kz2Tv81gMHG7oFBQ==", + "version": "0.7.0", + "resolved": "file:latch-0.7.0.tgz", + "integrity": "sha512-pccSG59+0izKUmhjCr/skg6V6CESHKGZlzHROCYt7HyYgEUXeli37hC2Gt3/935j/hhhojn2CAmmPQr2aj4J/w==", "dev": true, "dependencies": { + "@bufbuild/protobuf": "^2.14.0", "@thi.ng/leb128": "^3.1.90", - "ansi-colors": "^4.1.3", "ieee754": "^1.2.1", + "ink": "^3.2.0", "json-with-bigint": "^3.5.8", - "ora": "^9.4.0", + "react": "^17.0.2", + "serialport": "^13.0.0", "source-map": "^0.7.6", "ts-node": "^10.9.2", - "tslib": "^2.8.1" + "tslib": "^2.8.1", + "typescript": "^6.0.3" + }, + "bin": { + "latch": "bin/latch.js" } }, - "node_modules/log-symbols": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", - "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "node_modules/latch/node_modules/@serialport/bindings-cpp": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-13.0.0.tgz", + "integrity": "sha512-r25o4Bk/vaO1LyUfY/ulR6hCg/aWiN6Wo2ljVlb4Pj5bqWGcSRC4Vse4a9AcapuAu/FeBzHCbKMvRQeCuKjzIQ==", "dev": true, + "hasInstallScript": true, "license": "MIT", "dependencies": { - "is-unicode-supported": "^2.0.0", - "yoctocolors": "^2.1.1" + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "12.0.0", + "debug": "4.4.0", + "node-addon-api": "8.3.0", + "node-gyp-build": "4.8.4" }, "engines": { - "node": ">=18" + "node": ">=18.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@serialport/parser-delimiter": "12.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-byte-length": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-13.0.0.tgz", + "integrity": "sha512-32yvqeTAqJzAEtX5zCrN1Mej56GJ5h/cVFsCDPbF9S1ZSC9FWjOqNAgtByseHfFTSTs/4ZBQZZcZBpolt8sUng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-cctalk": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-13.0.0.tgz", + "integrity": "sha512-RErAe57g9gvnlieVYGIn1xymb1bzNXb2QtUQd14FpmbQQYlcrmuRnJwKa1BgTCujoCkhtaTtgHlbBWOxm8U2uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-delimiter": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-13.0.0.tgz", + "integrity": "sha512-Qqyb0FX1avs3XabQqNaZSivyVbl/yl0jywImp7ePvfZKLwx7jBZjvL+Hawt9wIG6tfq6zbFM24vzCCK7REMUig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-inter-byte-timeout": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-13.0.0.tgz", + "integrity": "sha512-a0w0WecTW7bD2YHWrpTz1uyiWA2fDNym0kjmPeNSwZ2XCP+JbirZt31l43m2ey6qXItTYVuQBthm75sPVeHnGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-packet-length": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-13.0.0.tgz", + "integrity": "sha512-60ZDDIqYRi0Xs2SPZUo4Jr5LLIjtb+rvzPKMJCohrO6tAqSDponcNpcB1O4W21mKTxYjqInSz+eMrtk0LLfZIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/latch/node_modules/@serialport/parser-readline": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-13.0.0.tgz", + "integrity": "sha512-dov3zYoyf0dt1Sudd1q42VVYQ4WlliF0MYvAMA3MOyiU1IeG4hl0J6buBA2w4gl3DOCC05tGgLDN/3yIL81gsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@serialport/parser-delimiter": "13.0.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-ready": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-13.0.0.tgz", + "integrity": "sha512-JNUQA+y2Rfs4bU+cGYNqOPnNMAcayhhW+XJZihSLQXOHcZsFnOa2F9YtMg9VXRWIcnHldHYtisp62Etjlw24bw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-regex": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-13.0.0.tgz", + "integrity": "sha512-m7HpIf56G5XcuDdA3DB34Z0pJiwxNRakThEHjSa4mG05OnWYv0IG8l2oUyYfuGMowQWaVnQ+8r+brlPxGVH+eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-slip-encoder": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-13.0.0.tgz", + "integrity": "sha512-fUHZEExm6izJ7rg0A1yjXwu4sOzeBkPAjDZPfb+XQoqgtKAk+s+HfICiYn7N2QU9gyaeCO8VKgWwi+b/DowYOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/parser-spacepacket": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-13.0.0.tgz", + "integrity": "sha512-DoXJ3mFYmyD8X/8931agJvrBPxqTaYDsPoly9/cwQSeh/q4EjQND9ySXBxpWz5WcpyCU4jOuusqCSAPsbB30Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/@serialport/stream": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-13.0.0.tgz", + "integrity": "sha512-F7xLJKsjGo2WuEWMSEO1SimRcOA+WtWICsY13r0ahx8s2SecPQH06338g28OT7cW7uRXI7oEQAk62qh5gHJW3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/latch/node_modules/node-addon-api": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz", + "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/latch/node_modules/serialport": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-13.0.0.tgz", + "integrity": "sha512-PHpnTd8isMGPfFTZNCzOZp9m4mAJSNWle9Jxu6BPTcWq7YXl5qN7tp8Sgn0h+WIGcD6JFz5QDgixC2s4VW7vzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "13.0.0", + "@serialport/parser-byte-length": "13.0.0", + "@serialport/parser-cctalk": "13.0.0", + "@serialport/parser-delimiter": "13.0.0", + "@serialport/parser-inter-byte-timeout": "13.0.0", + "@serialport/parser-packet-length": "13.0.0", + "@serialport/parser-readline": "13.0.0", + "@serialport/parser-ready": "13.0.0", + "@serialport/parser-regex": "13.0.0", + "@serialport/parser-slip-encoder": "13.0.0", + "@serialport/parser-spacepacket": "13.0.0", + "@serialport/stream": "13.0.0", + "debug": "4.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/latch/node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" } }, "node_modules/lru-cache": { @@ -839,17 +1323,14 @@ "dev": true, "license": "ISC" }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/minimist": { @@ -907,21 +1388,26 @@ "process-nextick-args": "^2.0.1" } }, - "node_modules/mqtt/node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "node_modules/mqtt/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", "dev": true, "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/ms": { @@ -961,43 +1447,40 @@ "js-sdsl": "4.3.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { - "mimic-function": "^5.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=18" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.1.tgz", - "integrity": "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==", + "node_modules/patch-console": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-1.0.0.tgz", + "integrity": "sha512-nxl9nrnLQmh64iTzMfyylSlRozL7kAXIaxw1fVcLYdyhNkJCRUzirRZTikXGJsg+hc4fqpneTK6iU2H1Q8THSA==", "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^5.6.2", - "cli-cursor": "^5.0.0", - "cli-spinners": "^3.2.0", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.1.0", - "log-symbols": "^7.0.1", - "stdin-discarder": "^0.3.2", - "string-width": "^8.1.0" - }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, "node_modules/process": { @@ -1017,36 +1500,78 @@ "dev": true, "license": "MIT" }, + "node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.5.tgz", + "integrity": "sha512-cq/o30z9W2Wb4rzBefjv5fBalHU0rJGZCHAkf/RHSBWSSYwh8PlQTqqOJmgIIbBtpj27T6FIPXeomIjZtCNVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-reconciler": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", + "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^17.0.2" + } + }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "dev": true, "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "license": "MIT", "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/rfdc": { @@ -1077,6 +1602,17 @@ ], "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, "node_modules/serialport": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/serialport/-/serialport-10.5.0.tgz", @@ -1106,17 +1642,39 @@ "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/smart-buffer": { @@ -1165,17 +1723,17 @@ "node": ">= 10.x" } }, - "node_modules/stdin-discarder": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", - "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "escape-string-regexp": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10" } }, "node_modules/string_decoder": { @@ -1189,36 +1747,44 @@ } }, "node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=12" + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "engines": { + "node": ">=8" } }, "node_modules/ts-node": { @@ -1272,6 +1838,19 @@ "dev": true, "license": "0BSD" }, + "node_modules/type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -1294,9 +1873,9 @@ } }, "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, @@ -1314,6 +1893,19 @@ "dev": true, "license": "MIT" }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/worker-factory": { "version": "7.0.50", "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.50.tgz", @@ -1365,18 +1957,33 @@ "worker-factory": "^7.0.50" } }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ws": { - "version": "8.21.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", - "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=8.3.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "utf-8-validate": "^5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -1397,17 +2004,17 @@ "node": ">=6" } }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "node_modules/yoga-layout-prebuilt": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz", + "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@types/yoga-layout": "1.9.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } } } diff --git a/tests/latch/package.json b/tests/latch/package.json index 9216f3a43..daeb30bc8 100644 --- a/tests/latch/package.json +++ b/tests/latch/package.json @@ -11,7 +11,7 @@ "tests:all": "npm run tests:spec ; npm run tests:integration" }, "devDependencies": { - "latch": "file:./latch-0.6.0.tgz", + "latch": "file:./latch-0.7.0.tgz", "mqtt": "^5.15.2", "serialport": "^10.4.0", "typescript": "^4.5.5" diff --git a/tests/latch/src/debugger.test.ts b/tests/latch/src/debugger.test.ts index e17c9e1b3..26ae179ab 100644 --- a/tests/latch/src/debugger.test.ts +++ b/tests/latch/src/debugger.test.ts @@ -3,17 +3,19 @@ import { ArduinoSpecification, Behaviour, + DebugProtocol, Description, EmulatorSpecification, Expectation, Expected, Framework, - getValue, HybridScheduler, + HybridScheduler, Kind, - Message, StyleType, - Step, Suite, + Message, + Request, Step, Suite, TestScenario, - Breakpoint + Breakpoint, + Verbosity } from 'latch'; export const EMULATOR: string = process.env.EMULATOR ?? `${require('os').homedir()}/Arduino/libraries/WARDuino/build-emu/wdcli`; @@ -26,7 +28,6 @@ const EXAMPLES: string = `${__dirname}/../static/examples/`; */ const framework = Framework.getImplementation(); -framework.reporter.style(StyleType.github); const integration: Suite = framework.suite('Integration tests: Debugger'); // must be called first @@ -34,7 +35,7 @@ integration.testee('emulator [:8500]', new EmulatorSpecification(8500)); //integration.testee('esp wrover', new ArduinoSpecification('/dev/ttyUSB0', 'esp32:esp32:esp32wrover'), new HybridScheduler(), {timeout: 0}); const expectDUMP: Expectation[] = [ - {'pc': {kind: 'description', value: Description.defined} as Expected}, + {'programCounter': {kind: 'description', value: Description.defined} as Expected}, { 'breakpoints': { kind: 'comparison', value: (state: Object, value: Array) => { @@ -42,22 +43,14 @@ const expectDUMP: Expectation[] = [ }, message: 'list of breakpoints should be empty' } as Expected> }, - {'callstack[0].sp': {kind: 'primitive', value: -1} as Expected}, - {'callstack[0].fp': {kind: 'primitive', value: -1} as Expected}]; + ]; const expectDUMPLocals: Expectation[] = [ - {'locals': {kind: 'description', value: Description.defined} as Expected}, - { - 'count': { - kind: 'comparison', value: (state: Object, value: number) => { - return value === getValue(state, 'locals').length; - }, message: 'count should equal length of locals array' - } as Expected - }]; + {'values': {kind: 'description', value: Description.defined} as Expected>}]; const DUMP: Step = { title: 'Send DUMP command', - instruction: {kind: Kind.Request, value: Message.dump}, + instruction: {kind: Kind.Request, value: Message.snapshot}, expected: expectDUMP }; @@ -161,23 +154,17 @@ integration.test({ // Test *dump full* command -const dumpFullTest: TestScenario = { - title: 'Test DUMPFull', +const snapshotTest: TestScenario = { + title: 'Test snapshot', program: `${EXAMPLES}blink.wast`, steps: [{ - title: 'Send DUMPFull command', - instruction: {kind: Kind.Request, value: Message.dumpAll}, - expected: expectDUMP.concat([{ - 'locals.count': { - kind: 'comparison', value: (state: Object, value: number) => { - return value === getValue(state, 'locals.locals').length; - }, message: 'locals.count should equal length of locals array' - } as Expected - }]) + title: 'Send snapshot command', + instruction: {kind: Kind.Request, value: Message.snapshot}, + expected: expectDUMP }] }; -integration.test(dumpFullTest); +integration.test(snapshotTest); // Test *run* command @@ -186,11 +173,11 @@ const running: Step[] = [DUMP, { instruction: {kind: Kind.Request, value: Message.run}, }, { title: 'CHECK: execution continues', - instruction: {kind: Kind.Request, value: Message.dump}, + instruction: {kind: Kind.Request, value: Message.snapshot}, expected: [{ - 'pc': {kind: 'description', value: Description.defined} as Expected + 'programCounter': {kind: 'description', value: Description.defined} as Expected }, { - 'pc': {kind: 'behaviour', value: Behaviour.changed} as Expected + 'programCounter': {kind: 'behaviour', value: Behaviour.changed} as Expected }] }]; @@ -219,17 +206,17 @@ const pauseTest: TestScenario = { instruction: {kind: Kind.Request, value: Message.pause}, }, { title: 'Send DUMP command', - instruction: {kind: Kind.Request, value: Message.dump}, + instruction: {kind: Kind.Request, value: Message.snapshot}, expected: [{ - 'pc': {kind: 'description', value: Description.defined} as Expected + 'programCounter': {kind: 'description', value: Description.defined} as Expected }] }, { title: 'CHECK: execution is stopped', - instruction: {kind: Kind.Request, value: Message.dump}, + instruction: {kind: Kind.Request, value: Message.snapshot}, expected: [{ - 'pc': {kind: 'description', value: Description.defined} as Expected + 'programCounter': {kind: 'description', value: Description.defined} as Expected }, { - 'pc': {kind: 'behaviour', value: Behaviour.unchanged} as Expected + 'programCounter': {kind: 'behaviour', value: Behaviour.unchanged} as Expected }] }] }; @@ -241,15 +228,15 @@ integration.test(pauseTest); function stepping(start: number, end: number): Step[] { return [{ title: 'Send DUMP command', - instruction: {kind: Kind.Request, value: Message.dump}, - expected: [{'pc': {kind: 'primitive', value: start} as Expected}] + instruction: {kind: Kind.Request, value: Message.snapshot}, + expected: [{'programCounter': {kind: 'primitive', value: start} as Expected}] }, { title: 'Send STEP command', instruction: {kind: Kind.Request, value: Message.step}, }, { title: 'CHECK: execution took one step', - instruction: {kind: Kind.Request, value: Message.dump}, - expected: [{'pc': {kind: 'primitive', value: end} as Expected}] + instruction: {kind: Kind.Request, value: Message.snapshot}, + expected: [{'programCounter': {kind: 'primitive', value: end} as Expected}] }]; } @@ -279,34 +266,40 @@ integration.test({ // Test *step over* command +const stepOverCall: Request = { + type: DebugProtocol.Command.COMMAND_STEP_OVER, + notification: DebugProtocol.NotificationType.NOTIFICATION_HIT_BREAKPOINT, + parser: DebugProtocol.HitBreakpoint.decode +}; + const stepOverTest: TestScenario = { title: 'Test STEP OVER', program: `${EXAMPLES}call.wast`, steps: [{ title: 'Send DUMP command', - instruction: {kind: Kind.Request, value: Message.dump}, - expected: [{'pc': {kind: 'primitive', value: 167} as Expected}] + instruction: {kind: Kind.Request, value: Message.snapshot}, + expected: [{'programCounter': {kind: 'primitive', value: 167} as Expected}] }, { title: 'Send STEP OVER command', - instruction: {kind: Kind.Request, value: Message.stepOver}, + instruction: {kind: Kind.Request, value: stepOverCall}, }, { title: 'CHECK: execution stepped over direct call', - instruction: {kind: Kind.Request, value: Message.dump}, - expected: [{'pc': {kind: 'primitive', value: 169} as Expected}] + instruction: {kind: Kind.Request, value: Message.snapshot}, + expected: [{'programCounter': {kind: 'primitive', value: 169} as Expected}] }, { title: 'Send STEP OVER command', instruction: {kind: Kind.Request, value: Message.stepOver} }, { title: 'CHECK: execution took one step', - instruction: {kind: Kind.Request, value: Message.dump}, - expected: [{'pc': {kind: 'primitive', value: 171} as Expected}] + instruction: {kind: Kind.Request, value: Message.snapshot}, + expected: [{'programCounter': {kind: 'primitive', value: 171} as Expected}] }, { title: 'Send STEP OVER command', - instruction: {kind: Kind.Request, value: Message.stepOver} + instruction: {kind: Kind.Request, value: stepOverCall} }, { title: 'CHECK: execution stepped over indirect call', - instruction: {kind: Kind.Request, value: Message.dump}, - expected: [{'pc': {kind: 'primitive', value: 174} as Expected}] + instruction: {kind: Kind.Request, value: Message.snapshot}, + expected: [{'programCounter': {kind: 'primitive', value: 174} as Expected}] }] } @@ -319,7 +312,7 @@ const dumpEventsTest: TestScenario = { program: `${EXAMPLES}button.wast`, steps: [{ title: 'CHECK: event queue', - instruction: {kind: Kind.Request, value: Message.dumpEvents}, + instruction: {kind: Kind.Request, value: Message.dumpAllEvents}, expected: [{ 'events': { kind: 'comparison', @@ -332,4 +325,5 @@ const dumpEventsTest: TestScenario = { integration.test(dumpEventsTest); +framework.reporter.verbosity(Verbosity.more); framework.run([integration]); diff --git a/tests/latch/src/primitives.test.ts b/tests/latch/src/primitives.test.ts index 317ea8bf9..731a95dde 100644 --- a/tests/latch/src/primitives.test.ts +++ b/tests/latch/src/primitives.test.ts @@ -8,14 +8,13 @@ import { Message, TestScenario, WASM, - awaitBreakpoint, PureAction, StyleType, Suite, Assertable, assertable + awaitBreakpoint, PureAction, Suite, Assertable, assertable } from 'latch'; import * as mqtt from 'mqtt'; import Type = WASM.Type; import {Breakpoint} from "latch/dist/types/debug/Breakpoint"; const framework = Framework.getImplementation(); -framework.reporter.style(StyleType.github); // TODO disclaimer: file is currently disabled until latch supports AS compilation diff --git a/tests/latch/src/spec.test.ts b/tests/latch/src/spec.test.ts index a8e31e853..ab71b797b 100644 --- a/tests/latch/src/spec.test.ts +++ b/tests/latch/src/spec.test.ts @@ -1,4 +1,4 @@ -import {EmulatorSpecification, Framework, invoke, returns, Step, StyleType, TestScenario, WASM} from 'latch'; +import {EmulatorSpecification, Framework, Verbosity, invoke, returns, Step, TestScenario, WASM} from 'latch'; import {readdirSync} from 'fs'; import {basename} from 'path'; import {find, parseArguments, parseAsserts, parseResult} from "./util/spec.util"; @@ -46,7 +46,7 @@ if (TESTFILE.length > 0) { // run tests const framework = Framework.getImplementation(); -framework.reporter.style(StyleType.github); +framework.reporter.verbosity(Verbosity.more); const spec = framework.suite('Specification test suite for WebAssembly'); spec.testee('emulator [:8500]', new EmulatorSpecification(8500)); diff --git a/tests/latch/src/util/spec.util.ts b/tests/latch/src/util/spec.util.ts index 318e552ec..4529d73fa 100644 --- a/tests/latch/src/util/spec.util.ts +++ b/tests/latch/src/util/spec.util.ts @@ -82,7 +82,7 @@ function consume(input: string, cursor: number, regex: RegExp = / /d): number { } function shouldParseLine(input: string): boolean { - return input.includes('(assert_return') && !input.replace(/\s+/g, '').startsWith(';;'); + return input.includes('(assert_return') && input.includes('(invoke') && !input.replace(/\s+/g, '').startsWith(';;'); } export function parseAsserts(file: string): string[] { @@ -136,13 +136,19 @@ function parseHexFloat(input: string): number { } function parseInteger(hex: string, type: WASM.Integer): WasmInt { + const literal = hex.trim().replace(/_/g, ''); const bytes = type === WASM.Integer.u32 || type === WASM.Integer.i32 ? 4 : 8; - if (!hex.includes('0x')) { - const n: number = parseInt(hex); - return typeof n !== 'bigint' && isNaN(n) ? WasmInt.nan() : typeof n !== 'bigint' && n === Infinity ? WasmInt.infinity() : WasmInt.finite(BigInt(hex)); + if (!literal.includes('0x')) { + const n: number = parseInt(literal); + return typeof n !== 'bigint' && isNaN(n) ? WasmInt.nan() : typeof n !== 'bigint' && n === Infinity ? WasmInt.infinity() : WasmInt.finite(BigInt(literal)); + } + const mask = BigInt('0x80' + '00'.repeat(bytes - 1)); + const negative = literal.startsWith('-'); + const positive = literal.startsWith('+'); + let integer = BigInt(negative || positive ? literal.slice(1) : literal); + if (negative) { + integer = -integer; } - const mask = BigInt(parseInt('0x80' + '00'.repeat(bytes - 1), 16)); - let integer = BigInt(parseInt(hex, 16)); if (integer >= mask) { integer = integer - mask * 2n; } @@ -155,4 +161,4 @@ export function find(regex: RegExp, input: string) { return ''; } return match[1]; -} \ No newline at end of file +} diff --git a/tutorials/wat/main/main.cpp b/tutorials/wat/main/main.cpp index 6acd43b87..81965c793 100644 --- a/tutorials/wat/main/main.cpp +++ b/tutorials/wat/main/main.cpp @@ -26,7 +26,7 @@ Module* m; void startDebuggerStd(void* pvParameter) { Channel* duplex = new Duplex(stdin, stdout); - wac->debugger->setChannel(duplex); + wac->debugger->set_channel(duplex); duplex->open(); int valread;