diff --git a/scripts/update_note_c.sh b/scripts/update_note_c.sh index 63ea2e0..b640676 100755 --- a/scripts/update_note_c.sh +++ b/scripts/update_note_c.sh @@ -40,6 +40,7 @@ NOTE_C_UNNEEDED_DIRS=( "$NOTE_C_DIR/docs" "$NOTE_C_DIR/scripts" "$NOTE_C_DIR/test" + "$NOTE_C_DIR/.github" ) for DIR in "${NOTE_C_UNNEEDED_DIRS[@]}"; do rm -rf "$DIR" diff --git a/src/note-c/.github/actions/load-ci-image/action.yml b/src/note-c/.github/actions/load-ci-image/action.yml deleted file mode 100644 index 7e1bc73..0000000 --- a/src/note-c/.github/actions/load-ci-image/action.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: 'Load note-c CI Docker image' -runs: - using: 'composite' - steps: - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Download image artifact - uses: actions/download-artifact@v3 - with: - name: note_c_ci_image - path: /tmp - - - name: Load Docker image - shell: bash - run: | - docker load --input /tmp/note_c_ci_image.tar diff --git a/src/note-c/.github/workflows/ci.yml b/src/note-c/.github/workflows/ci.yml deleted file mode 100644 index a6dfdee..0000000 --- a/src/note-c/.github/workflows/ci.yml +++ /dev/null @@ -1,166 +0,0 @@ -name: note-c CI Pipeline - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - check_dockerfile_changed: - runs-on: ubuntu-latest - outputs: - changed: ${{ steps.filter.outputs.changed }} - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - # TODO: This is a 3rd party GitHub action from some dude. Ideally, we'd - # use something more "official". - - name: Check if Dockerfile changed - uses: dorny/paths-filter@v2 - id: filter - with: - base: 'master' - filters: | - changed: - - 'Dockerfile' - - build_ci_docker_image: - runs-on: ubuntu-latest - needs: [check_dockerfile_changed] - if: ${{ needs.check_dockerfile_changed.outputs.changed == 'true' }} - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Rebuild image - uses: docker/build-push-action@v4 - with: - context: . - load: true - tags: ghcr.io/blues/note_c_ci:latest - outputs: type=docker,dest=/tmp/note_c_ci_image.tar - - - name: Upload image artifact - uses: actions/upload-artifact@v3 - with: - name: note_c_ci_image - path: /tmp/note_c_ci_image.tar - - build_docs: - runs-on: ubuntu-latest - if: ${{ always() }} - needs: [build_ci_docker_image] - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Load CI Docker image - # Only load the Docker image artifact if build_ci_docker_image actually - # ran (e.g. it wasn't skipped and was successful). - if: ${{ needs.build_ci_docker_image.result == 'success' }} - uses: ./.github/actions/load-ci-image - - - name: Build docs - run: | - docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/build_docs.sh ghcr.io/blues/note_c_ci:latest - - check_libc_dependencies: - runs-on: ubuntu-latest - if: ${{ always() }} - needs: [build_ci_docker_image] - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Load CI Docker image - # Only load the Docker image artifact if build_ci_docker_image actually - # ran (e.g. it wasn't skipped and was successful). - if: ${{ needs.build_ci_docker_image.result == 'success' }} - uses: ./.github/actions/load-ci-image - - - name: Check note-c's libc dependencies - run: | - docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/check_libc_dependencies.sh ghcr.io/blues/note_c_ci:latest - - run_unit_tests: - runs-on: ubuntu-latest - if: ${{ always() }} - needs: [build_ci_docker_image] - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Load CI Docker image - if: ${{ needs.build_ci_docker_image.result == 'success' }} - uses: ./.github/actions/load-ci-image - - - name: Run tests - run: | - docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/run_unit_tests.sh ghcr.io/blues/note_c_ci:latest --coverage --mem-check - - - name: Adjust lcov source file paths for Coveralls - run: sudo sed -i 's/\/note-c\///g' ./build/test/coverage/lcov.info - - - name: Publish test coverage - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ./build/test/coverage/lcov.info - - run_astyle: - runs-on: ubuntu-latest - if: ${{ always() }} - needs: [build_ci_docker_image] - - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Load CI Docker image - if: ${{ needs.build_ci_docker_image.result == 'success' }} - uses: ./.github/actions/load-ci-image - - - name: Run astyle - run: | - docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/run_astyle.sh ghcr.io/blues/note_c_ci:latest - - publish_ci_image: - runs-on: ubuntu-latest - # Make sure unit tests unit tests passed before publishing. - needs: [build_ci_docker_image, run_unit_tests] - # Only publish the image if this is a push event and the Docker image was rebuilt - if: ${{ github.event_name == 'push' && needs.build_ci_docker_image.result == 'success' }} - - steps: - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Push image to registry - uses: docker/build-push-action@v4 - with: - push: true - tags: ghcr.io/blues/note_c_ci:latest diff --git a/src/note-c/.github/workflows/md5srv-tests.yml b/src/note-c/.github/workflows/md5srv-tests.yml deleted file mode 100644 index 51cbb24..0000000 --- a/src/note-c/.github/workflows/md5srv-tests.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: MD5 Server Tests - -on: - workflow_dispatch: - workflow_call: # reusable workflow - -jobs: - test-md5srv: # job id - runs-on: ubuntu-latest - defaults: - run: - shell: bash - env: - TERM: xterm-256color - MD5SRV_TIMEOUT: 5 - MD5SRV_DIR: ./test/hitl/scripts - BATS_VERSION: 1.10.0 - BATS_LIB_PATH: /usr/lib - # /usr/local/lib on OSX - steps: - - name: Setup Bats and bats libs - uses: brokenpip3/setup-bats-libs@1.0.0 - with: - bats-install: true - file-install: false - detik-install: false - - name: Setup BATS_LIB_PATH - run: | - if [ -e /usr/local/lib/bats-support ]; then - echo "BATS_LIB_PATH=/usr/local/lib" >> $GITHUB_ENV - fi - - name: Checkout - uses: actions/checkout@v3 - - name: Run Tests - run: | - cd ${{env.MD5SRV_DIR}} - $HOME/.local/bin/bats -p --print-output-on-failure . - - name: Rerun Tests - if: failure() - run: | - cd ${{env.MD5SRV_DIR}} - $HOME/.local/bin/bats -p --print-output-on-failure -x . diff --git a/src/note-c/.github/workflows/notecard-binary-tests.yml b/src/note-c/.github/workflows/notecard-binary-tests.yml deleted file mode 100644 index 901cd40..0000000 --- a/src/note-c/.github/workflows/notecard-binary-tests.yml +++ /dev/null @@ -1,248 +0,0 @@ -name: Note Binary CI - -on: - pull_request: - branches: [ master ] - workflow_dispatch: - schedule: - # * is a special character in YAML so you have to quote this string - - cron: '45 4 * * 1' # 4.45am every Monday - -permissions: - checks: write - -jobs: - md5srv-test: - uses: ./.github/workflows/md5srv-tests.yml - - notecard-binary-test: - # needs: md5srv-test - runs-on: [self-hosted, swan, notecard, stlink, notecard-serial, md5srv, notehub-client] - defaults: - run: - shell: bash - env: - NOTEHUB: "notehub.io" - NOTEHUB_API: "api.notefile.net" - NOTEHUB_ROUTE_TIMEOUT: 180 - PIO_PROJECT_DIR: ./test/hitl/card.binary - NOTEHUB_PROXY_ROUTE_ALIAS: card.binary.${{github.run_id}} - NOTEHUB_PROXY_ROUTE_LABEL: card.binary.proxy.${{github.run_id}} - NOTEHUB_HTTP_ROUTE_LABEL: card.binary.http.${{github.run_id}} - - # Troubleshooting helpers - # DELETE_NOTEHUB_ROUTES set to false to see the created routes on notehub - DELETE_NOTEHUB_ROUTES: true - # CREATE_NOTEHUB_ROUTES set to false to use the already created routes on notehub - CREATE_NOTEHUB_ROUTES: true - # FLASH_TEST_FIRMWARE set to false to skip flashing firmware to the Host (Swan). - # Be sure to press reset on Swan before running the workflow unless you deliberately want to skip running the tests. - FLASH_TEST_FIRMWARE: true - # START_MD5SRV set to false to skip starting the MD5 server. There should be one - # already running locally with MD5SRV_PORT/ADDRESS/TOKEN set correspondingly. - START_MD5SRV: true - # START_LOCALTUNNEL, set to false to skip starting the localtunnel. - START_LOCALTUNNEL: false - # START_TUNNELMOLE: set to false to skip starting tunnel mole. - START_TUNNELMOLE: true - # When neither tunneling solution is used (because they're already instantiated outside of the workflow) - # be sure to set MD5SRV_URL in the environment - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Generate MD5 Server Token - run: | - [ -n "$MD5SRV_TOKEN" ] || echo "MD5SRV_TOKEN=`uuidgen`" >> $GITHUB_ENV - # propagate the environment variable so that it's available in the `env` context - echo "MD5SRV_PORT=$MD5SRV_PORT" >> $GITHUB_ENV - - name: Check Env Vars - run: | - . scripts/check_runner_config.sh - echo NOTEHUB_PROXY_ROUTE_ALIAS=$NOTEHUB_PROXY_ROUTE_ALIAS - - name: Prep MD5 Server - uses: pyTooling/Actions/with-post-step@v0.4.5 - with: - main: | - [ -e md5srv-files ] || mkdir md5srv-files - rm -rf md5srv-files/* - md5url=http://${MD5SRV_ADDRESS}:${MD5SRV_PORT}/ - post: | - # the JarvusInnovations/background-action@v1 that launches the background - # process doesn't clean them up. We do that here. MD5SRV_PID is set in the - # ./scripts/run_md5srv.sh script - echo Stop MD5 Server - [ -n "$MD5SRV_PID" ] || (echo "MD5SRV_PID not set" && exit 1) - [ -z "$MD5SRV_PID" ] || kill $MD5SRV_PID - rm -rf md5srv-files - - - name: Install PlatformIO dependencies - if: env.FLASH_TEST_FIRMWARE!='false' - run: | - python3 -m venv venv # python venv is also used by the md5server, so this comes first. - source venv/bin/activate - pip install platformio - cd $PIO_PROJECT_DIR - pio pkg install -l "Blues Wireless Notecard" -e debug - # Remove the bundled note-c and put the local working copy there - NOTE_C_DEP="$GITHUB_WORKSPACE/$PIO_PROJECT_DIR/.pio/libdeps/debug/Blues Wireless Notecard/src/note-c" - rm -rf "$NOTE_C_DEP" - mkdir "$NOTE_C_DEP" - # copy only files in note-c - find "$GITHUB_WORKSPACE" -maxdepth 1 -type f -exec cp "{}" "${NOTE_C_DEP}" \; - - - name: Start MD5 Server - uses: JarvusInnovations/background-action@v1 - with: - run: | - bash ./scripts/run_md5srv.sh - wait-on: - # just a dummy wait-on since this is required. - file:${{github.workspace}}/scripts/run_md5srv.sh - - # When done this way, the background process is terminated at the end of the step, - # At least when running with `act`. The same may be true of github runners also. - # - name: Start MD5 Server - # uses: pyTooling/Actions/with-post-step@v0.4.5 - # with: - # main: | - # ./run_md5srv.sh - # echo "MD5SRV_PID=$MD5SRV_PID" - # echo "MD5SRV_PID=$MD5SRV_PID" >> $GITHUB_ENV - # echo "writing server log to `realpath md5srv.log`" - # md5url=http://${MD5SRV_ADDRESS}:${MD5SRV_PORT}/ - # post: | - # echo Stop MD5 Server - # [ -n "$MD5SRV_PID" ] || (echo "MD5SRV_PID not set" && exit 1) - # # [ -z "$MD5SRV_PID" ] || kill $MD5SRV_PID - # rm -rf md5srv-files - - - name: Build and Upload Test Firmware - if: env.FLASH_TEST_FIRMWARE!='false' - run: | - source venv/bin/activate - export PLATFORMIO_BUILD_FLAGS="'-D NOTEHUB_PROXY_ROUTE_ALIAS=\"$NOTEHUB_PROXY_ROUTE_ALIAS\"' '-D PRODUCT_UID=\"$NOTEHUB_PRODUCT_UID\"'" - echo "build flags $PLATFORMIO_BUILD_FLAGS" - timeout 10 ./scripts/wait_for_file.sh "$STLINK_PROGRAMMER_PORT" - platformio test -e debug --without-testing --upload-port "$STLINK_PROGRAMMER_PORT" --project-dir "$PIO_PROJECT_DIR" - timeout 10 ./scripts/wait_for_file.sh "$SWAN_SERIAL" - - - name: Start localtunnel - if: env.START_LOCALTUNNEL!='false' - id: localtunnel - uses: Rei-x/expose-localtunnel-action@main - with: - ports: ${{ env.MD5SRV_PORT }} - - name: Fetch localtunnel URL - if: env.START_LOCALTUNNEL!='false' - run: | - MD5SRV_URL="${{steps.localtunnel.outputs.url-1}}" - echo "MD5SRV_URL=$MD5SRV_URL" >> $GITHUB_ENV - - - name: Prep tunnelmole - if: env.START_TUNNELMOLE!='false' - uses: pyTooling/Actions/with-post-step@v0.4.5 - with: - main: | - [ ! -e tmole.log ] || rm tmole.log - sleep 2 # otherwise it thinks we exited early - post: | - # the JarvusInnovations/background-action@v1 that launches the background - # process doesn't clean them up. We do that here. TMOLE_PID is set in the - # ./scripts/run_tunnelmole.sh script - echo Stop Tunnelmole - [ -n "$TMOLE_PID" ] || (echo "TMOLE_PID not set" && exit 1) - [ -z "$TMOLE_PID" ] || kill $TMOLE_PID - # Remove the tmole_ready file, which may be leftover from a prior - # run. - rm -f $GITHUB_WORKSPACE/tmole_ready - - - name: Start tunnelmole - uses: JarvusInnovations/background-action@v1 - if: env.START_TUNNELMOLE!='false' - with: - run: | - bash ./scripts/run_tunnelmole.sh - log-output-if: true - wait-on: - file:${{github.workspace}}/tmole_ready - - - name: Check server is available - run: | - # the request will return a 401 from md5srv, but that's expected without the access token - # Curl still returns success because it could contact the server - code=`curl -s -o /dev/null -w "%{http_code}" $MD5SRV_URL` - [ "$code" -lt "500" ] || ( echo "5xx error ($code) from tunnel." && exit 1 ) - - - name: Create Notehub accesss token - if: env.CREATE_NOTEHUB_ROUTES!='false' - run: | - curl -f -X POST \ - -L 'https://${{env.NOTEHUB}}/oauth2/token' \ - -H 'content-type: application/x-www-form-urlencoded' \ - -d grant_type=client_credentials \ - -d client_id=$NOTEHUB_CLIENT_ID \ - -d client_secret=$NOTEHUB_CLIENT_SECRET | \ - { token=$(jq -r .access_token); echo "NOTEHUB_ACCESS_TOKEN=$token" >> $GITHUB_ENV; } - - - name: Create Notehub HTTP Route - if: env.CREATE_NOTEHUB_ROUTES!='false' - uses: pyTooling/Actions/with-post-step@v0.4.5 - with: - main: | - # ?note=1 instructs the MD5 server to process the content as an event, extracting the path - # from the event body. - route_req=`jq -n --arg TOKEN "$MD5SRV_TOKEN" --arg LABEL "$NOTEHUB_HTTP_ROUTE_LABEL" --arg URL "$MD5SRV_URL/?note=1" --argjson TIMEOUT $NOTEHUB_ROUTE_TIMEOUT \ - '{ "label":$LABEL, "type":"http", "http":{ "timeout":$TIMEOUT, "filter": { "type":"include", "files": ["cardbinary.qo"] }, "url":$URL, "http_headers": { "X-Access-Token":$TOKEN } } }'` - echo $route_req - route=`echo "$route_req" | curl -s -f -X POST -L "https://$NOTEHUB_API/v1/projects/${NOTEHUB_PROJECT_UID}/routes" \ - -H "Authorization: Bearer $NOTEHUB_ACCESS_TOKEN" -d @-` - echo $route - route_uid=`echo $route | jq -r .uid` - [ -n "$route_uid" ] - echo "NOTEHUB_HTTP_ROUTE_UID=$route_uid" >> $GITHUB_ENV - post: | - echo Delete Notehub HTTP Route - [ "$DELETE_NOTEHUB_ROUTES" == "false" ] || ([ -n "$NOTEHUB_HTTP_ROUTE_UID" ] && curl -f -s -X DELETE \ - -L "https://$NOTEHUB_API/v1/projects/$NOTEHUB_PROJECT_UID/routes/$NOTEHUB_HTTP_ROUTE_UID" \ - -H "Authorization: Bearer $NOTEHUB_ACCESS_TOKEN") - - - name: Create Notehub Proxy Route - if: env.CREATE_NOTEHUB_ROUTES!='false' - uses: pyTooling/Actions/with-post-step@v0.4.5 - with: - main: | - ALIAS=$NOTEHUB_PROXY_ROUTE_ALIAS - route=`jq -n --arg TOKEN "$MD5SRV_TOKEN" --arg LABEL "$NOTEHUB_PROXY_ROUTE_LABEL" --arg URL "$MD5SRV_URL" --arg ALIAS "$ALIAS" --argjson TIMEOUT $NOTEHUB_ROUTE_TIMEOUT \ - '{ "label":$LABEL, "type":"proxy", "proxy":{ "timeout":$TIMEOUT, "url":$URL, "alias":$ALIAS, "http_headers": { "X-Access-Token":$TOKEN } } }' \ - | curl -s -f -X POST -L "https://api.notefile.net/v1/projects/${NOTEHUB_PROJECT_UID}/routes" \ - -H "Authorization: Bearer $NOTEHUB_ACCESS_TOKEN" -d @-` - echo $route - route_uid=`echo $route | jq -r .uid` - [ -n $route_uid ] - echo "NOTEHUB_PROXY_ROUTE_UID=$route_uid" >> $GITHUB_ENV - echo "NOTEHUB_PROXY_ROUTE_ALIAS=$ALIAS" >> $GITHUB_ENV - post: | - echo Delete Notehub Proxy Route - [ "$DELETE_NOTEHUB_ROUTES" == "false" ] || ([ -n "$NOTEHUB_PROXY_ROUTE_UID" ] && curl -f -s -X DELETE \ - -L "https://api.notefile.net/v1/projects/$NOTEHUB_PROJECT_UID/routes/$NOTEHUB_PROXY_ROUTE_UID" \ - -H "Authorization: Bearer $NOTEHUB_ACCESS_TOKEN") - - - name: Run Tests - run: | - source venv/bin/activate - cd $PIO_PROJECT_DIR - platformio test -v -e debug \ - --without-building --without-uploading \ - --test-port "$SWAN_SERIAL" \ - --json-output-path test.json \ - --junit-output-path test.xml \ - - - name: Publish Test Report - uses: mikepenz/action-junit-report@v3 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: env.GITHUB_TOKEN && (success() || failure()) # always run even if the previous step fails - with: - report_paths: '**/test/hitl/card.binary/test*.xml' - check_name: Notecard Binary HIL Tests - require_tests: true diff --git a/src/note-c/.github/workflows/publish_docs_site.yml b/src/note-c/.github/workflows/publish_docs_site.yml deleted file mode 100644 index b367659..0000000 --- a/src/note-c/.github/workflows/publish_docs_site.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Publish note-c docs site - -on: - push: - branches: [ master ] - workflow_dispatch: - -permissions: - contents: write - -jobs: - publish_docs_site: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - - - name: Install Python dependencies - run: | - pip install sphinx sphinx_rtd_theme breathe - - - name: Install other dependencies - run: | - sudo apt-get update - sudo apt-get install -y cmake doxygen - - - name: Build docs site - # The TZ=UTC thing is a workaround for this problem: - # https://github.com/nektos/act/issues/1853 - run: | - TZ=UTC ./scripts/build_docs.sh - - - name: Deploy docs site to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - if: ${{ github.event_name == 'push' }} - with: - publish_branch: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: build/docs/ - force_orphan: true diff --git a/src/note-c/CMakeLists.txt b/src/note-c/CMakeLists.txt index a0e64bf..449cbdb 100644 --- a/src/note-c/CMakeLists.txt +++ b/src/note-c/CMakeLists.txt @@ -19,6 +19,8 @@ option(NOTE_C_COVERAGE "Compile for test NOTE_C_COVERAGE reporting." OFF) option(NOTE_C_MEM_CHECK "Run tests with Valgrind." OFF) option(NOTE_C_BUILD_DOCS "Build docs." OFF) option(NOTE_C_NO_LIBC "Build the library without linking against libc, generating errors for any undefined symbols." OFF) +option(NOTE_C_LOW_MEM "Build the library tailored for low memory usage." OFF) +option(NOTE_C_TEST_SINGLE_PRECISION "Use single precision for JSON floating point numbers." OFF) set(NOTE_C_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) add_library(note_c SHARED) @@ -40,7 +42,6 @@ target_sources( ${NOTE_C_SRC_DIR}/n_request.c ${NOTE_C_SRC_DIR}/n_serial.c ${NOTE_C_SRC_DIR}/n_str.c - ${NOTE_C_SRC_DIR}/n_ua.c ) target_compile_options( note_c @@ -57,6 +58,23 @@ target_include_directories( PUBLIC ${NOTE_C_SRC_DIR} ) +if(NOTE_C_LOW_MEM) + target_compile_definitions( + note_c + PUBLIC + NOTE_C_LOW_MEM + ) +else() + # This file is empty if NOTE_C_LOW_MEM is defined, which leads to a warning + # about an empty translation unit, so we only add it to the build if + # NOTE_C_LOW_MEM is false. + target_sources( + note_c + PRIVATE + ${NOTE_C_SRC_DIR}/n_ua.c + ) +endif() + if(NOTE_C_NO_LIBC) target_link_options( note_c @@ -67,6 +85,14 @@ if(NOTE_C_NO_LIBC) ) endif() +if(NOTE_C_TEST_SINGLE_PRECISION) + target_compile_definitions( + note_c + PUBLIC + NOTE_C_TEST_SINGLE_PRECISION + ) +endif() + if(NOTE_C_BUILD_TESTS) # Including CTest here rather than in test/CMakeLists.txt allows us to run # ctest from the root build directory (e.g. build/ instead of build/test/). @@ -82,11 +108,13 @@ if(NOTE_C_BUILD_TESTS) target_compile_definitions( note_c - PUBLIC NOTE_C_TEST + PUBLIC + NOTE_C_TEST ) target_include_directories( note_c - PRIVATE ${CMAKE_CURRENT_LIST_DIR}/test/include + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/test/include ) add_subdirectory(test) diff --git a/src/note-c/README.md b/src/note-c/README.md index b53cabf..a6e8f3b 100644 --- a/src/note-c/README.md +++ b/src/note-c/README.md @@ -14,7 +14,7 @@ it as a git subtree. ## Documentation -The documentation for this library can be found [here](https://blues.github.io/note-c/html/index.html). +The documentation for this library can be found [here](https://blues.github.io/note-c/index.html). ## CMake diff --git a/src/note-c/n_atof.c b/src/note-c/n_atof.c index e98e009..d465dec 100644 --- a/src/note-c/n_atof.c +++ b/src/note-c/n_atof.c @@ -85,7 +85,7 @@ char **endPtr; /* If non-NULL, store terminating character's * address here. */ { int sign, expSign = FALSE; - JNUMBER fraction, dblExp; + JNUMBER fraction; register const char *p; register int c; int exp = 0; /* Exponent read from "EX" field. */ @@ -231,7 +231,6 @@ char **endPtr; /* If non-NULL, store terminating character's if (exp > MAX_EXPONENT) { exp = MAX_EXPONENT; } - dblExp = 1.0; int d; for (d = 0; exp != 0; exp >>= 1, d += 1) { /* Table giving binary powers of 10. Entry */ @@ -257,7 +256,7 @@ char **endPtr; /* If non-NULL, store terminating character's case 5: p10 = 1.0e32; break; -#ifndef NOTE_FLOAT +#ifndef NOTE_C_LOW_MEM case 6: p10 = 1.0e64; break; @@ -273,14 +272,13 @@ char **endPtr; /* If non-NULL, store terminating character's break; } if (exp & 01) { - dblExp *= p10; + if (expSign) { + fraction /= p10; + } else { + fraction *= p10; + } } } - if (expSign) { - fraction /= dblExp; - } else { - fraction *= dblExp; - } done: if (endPtr != NULL) { diff --git a/src/note-c/n_cjson.c b/src/note-c/n_cjson.c index 00ec67d..f4d5932 100644 --- a/src/note-c/n_cjson.c +++ b/src/note-c/n_cjson.c @@ -63,9 +63,6 @@ // For Note, disable dependencies #undef ENABLE_LOCALES -#ifndef CJSON_NO_CLIB -#define CJSON_NO_CLIB 1 // Use tiny but non-robust versions of conversions -#endif #include "n_lib.h" @@ -171,10 +168,29 @@ static unsigned char* Jstrdup(const unsigned char* string) return copy; } +/*! + @brief Dynamically allocate a block of memory of the given size. + + This is simply a wrapper around the memory allocation function provided by the + user via `NoteSetFn`. + + @param size The number of bytes to allocate. + + @returns A pointer to the first byte of the allocated memory or NULL on error. + */ N_CJSON_PUBLIC(void *) JMalloc(size_t size) { return _Malloc(size); } + +/*! + @brief Free a block of dynamically allocated memory. + + This is simply a wrapper around the memory free function provided by the user + via `NoteSetFn`. + + @param p A pointer to the block of memory to free. + */ N_CJSON_PUBLIC(void) JFree(void *p) { _Free(p); @@ -287,25 +303,19 @@ static Jbool parse_number(J * const item, parse_buffer * const input_buffer) loop_end: number_c_string[i] = '\0'; - /* some platforms may not have locale support */ -#if !CJSON_NO_CLIB - number = strtod((const char*)number_c_string, (char**)&after_end); -#else number = JAtoN((const char*)number_c_string, (char**)&after_end); -#endif if (number_c_string == after_end) { return false; /* parse_error */ } - item->valuenumber = number; - /* use saturation in case of overflow */ - if (number >= LONG_MAX) { - item->valueint = LONG_MAX; - } else if (number <= LONG_MIN) { - item->valueint = LONG_MIN; + // Saturate valueint in the case of overflow. + if (number >= JINTEGER_MAX) { + item->valueint = JINTEGER_MAX; + } else if (number <= JINTEGER_MIN) { + item->valueint = JINTEGER_MIN; } else { - item->valueint = (long int)number; + item->valueint = JAtoI((const char*)number_c_string); } item->type = JNumber; @@ -320,12 +330,14 @@ N_CJSON_PUBLIC(JNUMBER) JSetNumberHelper(J *object, JNUMBER number) if (object == NULL) { return number; } - if (number >= LONG_MAX) { - object->valueint = LONG_MAX; - } else if (number <= LONG_MIN) { - object->valueint = LONG_MIN; + + // Saturate valueint in the case of overflow. + if (number >= JINTEGER_MAX) { + object->valueint = JINTEGER_MAX; + } else if (number <= JINTEGER_MIN) { + object->valueint = JINTEGER_MIN; } else { - object->valueint = (long int)number; + object->valueint = (JINTEGER)number; } return object->valuenumber = number; @@ -421,7 +433,8 @@ static Jbool print_number(const J * const item, printbuffer * const output_buffe } unsigned char *output_pointer = NULL; - JNUMBER d = item->valuenumber; + JNUMBER vnum = item->valuenumber; + JINTEGER vint = item->valueint; int length = 0; size_t i = 0; unsigned char number_buffer[JNTOA_MAX]; /* temporary buffer to print the number into */ @@ -432,34 +445,20 @@ static Jbool print_number(const J * const item, printbuffer * const output_buffe } /* This checks for NaN and Infinity */ - if ((d * 0) != 0) { + if ((vnum * 0) != 0) { char *nbuf = (char *) number_buffer; strlcpy(nbuf, "null", JNTOA_MAX); length = strlen(nbuf); } else { -#if !CJSON_NO_CLIB - JNUMBER test; - /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ - length = sprintf((char*)number_buffer, "%1.15g", d); - - /* Check whether the original double can be recovered */ - if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || ((JNUMBER)test != d)) { - /* If not, print with 17 decimal places of precision */ - length = sprintf((char*)number_buffer, "%1.17g", d); - } - if (strchr((char*)number_buffer, '.') != NULL) { - while (length > 1 && number_buffer[length-1] == '0') { - number_buffer[--length] = '\0'; - } - if (length > 1 && number_buffer[length-1] == '.') { - number_buffer[--length] = '\0'; - } - } -#else char *nbuf = (char *) number_buffer; - JNtoA(d, nbuf, -1); + + if (vnum != (JNUMBER)vint) { + JNtoA(vnum, nbuf, -1); + } else { + JItoA(vint, nbuf); + } + length = strlen(nbuf); -#endif } /* conversion failed or buffer overrun occured */ @@ -1046,6 +1045,17 @@ N_CJSON_PUBLIC(char *) JPrint(const J *item) return (char*)print(item, true, false); } +/*! + @brief Get the unformatted string representation of a `J` object. + + The string returned by this function is dynamically allocated and MUST be freed + by the caller with `JFree`. Unformatted means that the minimum JSON string + is produced, without any additional whitespace. + + @param item The JSON object to get the unformatted string representation of. + + @returns The string or NULL on error. + */ N_CJSON_PUBLIC(char *) JPrintUnformatted(const J *item) { if (item == NULL) { @@ -1939,6 +1949,21 @@ N_CJSON_PUBLIC(J*) JAddNumberToObject(J * const object, const char * const name, return NULL; } +N_CJSON_PUBLIC(J*) JAddIntToObject(J * const object, const char * const name, const JINTEGER integer) +{ + if (object == NULL) { + return NULL; + } + + J *integer_item = JCreateInteger(integer); + if (add_item_to_object(object, name, integer_item, false)) { + return integer_item; + } + + JDelete(integer_item); + return NULL; +} + /*! @brief Add a string field to a `J` object. @@ -2257,18 +2282,30 @@ N_CJSON_PUBLIC(J *) JCreateNumber(JNUMBER num) if(item) { item->type = JNumber; item->valuenumber = num; - /* use saturation in case of overflow */ - if (num >= LONG_MAX) { - item->valueint = LONG_MAX; - } else if (num <= LONG_MIN) { - item->valueint = LONG_MIN; + + // Saturate valueint in the case of overflow. + if (num >= JINTEGER_MAX) { + item->valueint = JINTEGER_MAX; + } else if (num <= JINTEGER_MIN) { + item->valueint = JINTEGER_MIN; } else { - item->valueint = (long int)num; + item->valueint = (JINTEGER)num; } } return item; } +N_CJSON_PUBLIC(J *) JCreateInteger(JINTEGER integer) +{ + J *item = JNew_Item(); + if(item) { + item->type = JNumber; + item->valuenumber = (JNUMBER)integer; + item->valueint = integer; + } + return item; +} + N_CJSON_PUBLIC(J *) JCreateString(const char *string) { J *item = JNew_Item(); diff --git a/src/note-c/n_cjson.h b/src/note-c/n_cjson.h index b4c22a5..48de038 100644 --- a/src/note-c/n_cjson.h +++ b/src/note-c/n_cjson.h @@ -88,7 +88,7 @@ typedef struct J { /* The item's string, if type==JString and type == JRaw */ char *valuestring; /* writing to valueint is DEPRECATED, use JSetNumberValue instead */ - long int valueint; + JINTEGER valueint; /* The item's number, if type==JNumber */ JNUMBER valuenumber; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ @@ -236,6 +236,7 @@ N_CJSON_PUBLIC(J *) JCreateTrue(void); N_CJSON_PUBLIC(J *) JCreateFalse(void); N_CJSON_PUBLIC(J *) JCreateBool(Jbool boolean); N_CJSON_PUBLIC(J *) JCreateNumber(JNUMBER num); +N_CJSON_PUBLIC(J *) JCreateInteger(JINTEGER integer); N_CJSON_PUBLIC(J *) JCreateString(const char *string); /* raw json */ N_CJSON_PUBLIC(J *) JCreateRaw(const char *raw); @@ -302,7 +303,7 @@ N_CJSON_PUBLIC(J*) JAddTrueToObject(J * const object, const char * const name); N_CJSON_PUBLIC(J*) JAddFalseToObject(J * const object, const char * const name); N_CJSON_PUBLIC(J*) JAddBoolToObject(J * const object, const char * const name, const Jbool boolean); N_CJSON_PUBLIC(J*) JAddNumberToObject(J * const object, const char * const name, const JNUMBER number); -#define JAddIntToObject(object, name, integer) JAddNumberToObject(object, name, (JNUMBER)(integer)) +N_CJSON_PUBLIC(J*) JAddIntToObject(J * const object, const char * const name, const JINTEGER integer); N_CJSON_PUBLIC(J*) JAddStringToObject(J * const object, const char * const name, const char * const string); N_CJSON_PUBLIC(J*) JAddRawToObject(J * const object, const char * const name, const char * const raw); N_CJSON_PUBLIC(J*) JAddObjectToObject(J * const object, const char * const name); diff --git a/src/note-c/n_cjson_helpers.c b/src/note-c/n_cjson_helpers.c index d026180..96acbaa 100644 --- a/src/note-c/n_cjson_helpers.c +++ b/src/note-c/n_cjson_helpers.c @@ -179,7 +179,7 @@ JNUMBER JGetNumber(J *rsp, const char *field) @returns The number, or 0, if NULL. */ /**************************************************************************/ -long int JIntValue(J *item) +JINTEGER JIntValue(J *item) { if (item == NULL) { return 0; @@ -195,7 +195,7 @@ long int JIntValue(J *item) @returns The int found, or 0, if not present. */ /**************************************************************************/ -long int JGetInt(J *rsp, const char *field) +JINTEGER JGetInt(J *rsp, const char *field) { if (rsp == NULL) { return 0; @@ -428,18 +428,24 @@ const char *JGetItemName(const J * item) @note The buffer must be large enough because no bounds checking is done. */ /**************************************************************************/ -void JItoA(long int n, char *s) +void JItoA(JINTEGER n, char *s) { char c; - long int i, j, sign; - if ((sign = n) < 0) { - n = -n; + // Conversion to unsigned is required to handle the case where n is + // JINTEGER_MIN. In that case, applying the unary minus operator to the + // signed version of n overflows and the behavior is undefined. By changing + // n to be unsigned, the unary minus operator behaves differently, and there + // is no overflow. See https://stackoverflow.com/q/8026694. + JUINTEGER unsignedN = n; + long int i, j; + if (n < 0) { + unsignedN = -unsignedN; } i = 0; do { - s[i++] = n % 10 + '0'; - } while ((n /= 10) > 0); - if (sign < 0) { + s[i++] = unsignedN % 10 + '0'; + } while ((unsignedN /= 10) > 0); + if (n < 0) { s[i++] = '-'; } s[i] = '\0'; @@ -457,9 +463,9 @@ void JItoA(long int n, char *s) @returns An integer, or 0 if invalid */ /**************************************************************************/ -long int JAtoI(const char *string) +JINTEGER JAtoI(const char *string) { - long int result = 0; + JINTEGER result = 0; unsigned int digit; int sign; while (*string == ' ') { @@ -585,11 +591,7 @@ int JGetItemType(J *item) } int vlen = strlen(v); char *endstr; -#if !CJSON_NO_CLIB - JNUMBER value = strtod(v, &endstr); -#else JNUMBER value = JAtoN(v, &endstr); -#endif if (endstr[0] == 0) { if (value == 0) { return JTYPE_STRING_ZERO; diff --git a/src/note-c/n_ftoa.c b/src/note-c/n_ftoa.c index c9afaef..c1df61b 100644 --- a/src/note-c/n_ftoa.c +++ b/src/note-c/n_ftoa.c @@ -81,7 +81,7 @@ fmtflt(char *str, size_t *len, size_t size, JNUMBER fvalue, int width, const char *infnan = NULL; char iconvert[JNTOA_MAX]; char fconvert[JNTOA_MAX]; - char econvert[4]; /* "e-12" (without nul-termination). */ + char econvert[6]; /* "e-1024" (without nul-termination). */ char esign = 0; char sign = 0; int leadfraczeros = 0; @@ -258,12 +258,17 @@ fmtflt(char *str, size_t *len, size_t size, JNUMBER fvalue, int width, } /* - * Convert the exponent. The sizeof(econvert) is 4. So, the - * econvert buffer can hold e.g. "e+99" and "e-99". We don't - * support an exponent which contains more than two digits. - * Therefore, the following stores are safe. + * Convert the exponent. The sizeof(econvert) is 6. So, the + * econvert buffer can hold e.g. "e+1024" and "e-1024". */ - epos = convert(exponent, econvert, 2, 10, 0); + size_t digits = 2; + if (exponent > 99 || exponent < -99) { + digits++; + } + if (exponent > 999 || exponent < -999) { + digits++; + } + epos = convert(exponent, econvert, digits, 10, 0); /* * C99 says: "The exponent always contains at least two digits, * and only as many more digits as necessary to represent the @@ -424,16 +429,16 @@ static int getexponent(JNUMBER value) int exponent = 0; /* - * We check for 99 > exponent > -99 in order to work around possible + * We check for 1023 >= exponent >= -1022 in order to work around possible * endless loops which could happen (at least) in the second loop (at * least) if we're called with an infinite value. However, we checked * for infinity before calling this function using our ISINF() macro, so * this might be somewhat paranoid. */ - while (tmp < 1.0 && tmp > 0.0 && --exponent > -99) { + while (tmp < 1.0 && tmp > 0.0 && --exponent >= -1022) { tmp *= 10; } - while (tmp >= 10.0 && ++exponent < 99) { + while (tmp >= 10.0 && ++exponent <= 1023) { tmp /= 10; } diff --git a/src/note-c/n_helpers.c b/src/note-c/n_helpers.c index ee1674c..131989b 100644 --- a/src/note-c/n_helpers.c +++ b/src/note-c/n_helpers.c @@ -1207,7 +1207,7 @@ bool NoteSetEnvDefault(const char *variable, char *buf) @returns boolean indicating if variable was set. */ /**************************************************************************/ -bool NoteSetEnvDefaultInt(const char *variable, long int defaultVal) +bool NoteSetEnvDefaultInt(const char *variable, JINTEGER defaultVal) { char buf[32]; JItoA(defaultVal, buf); @@ -1253,7 +1253,7 @@ JNUMBER NoteGetEnvNumber(const char *variable, JNUMBER defaultVal) @returns environment variable value. */ /**************************************************************************/ -long int NoteGetEnvInt(const char *variable, long int defaultVal) +JINTEGER NoteGetEnvInt(const char *variable, JINTEGER defaultVal) { char buf[32], buf2[32]; JItoA(defaultVal, buf2); diff --git a/src/note-c/n_hooks.c b/src/note-c/n_hooks.c index 7e1b2ba..3e00d0e 100644 --- a/src/note-c/n_hooks.c +++ b/src/note-c/n_hooks.c @@ -174,8 +174,8 @@ i2cReceiveFn hookI2CReceive = NULL; // Internal hooks typedef bool (*nNoteResetFn) (void); -typedef const char * (*nTransactionFn) (const char *, size_t, char **, size_t); -typedef const char * (*nReceiveFn) (uint8_t *, uint32_t *, bool, size_t, uint32_t *); +typedef const char * (*nTransactionFn) (const char *, size_t, char **, uint32_t); +typedef const char * (*nReceiveFn) (uint8_t *, uint32_t *, bool, uint32_t, uint32_t *); typedef const char * (*nTransmitFn) (uint8_t *, uint32_t, bool); static nNoteResetFn notecardReset = NULL; static nTransactionFn notecardTransaction = NULL; @@ -470,7 +470,7 @@ void NoteDebugWithLevelLn(uint8_t level, const char *msg) @returns The current milliseconds value. */ /**************************************************************************/ -long unsigned int NoteGetMs(void) +uint32_t NoteGetMs(void) { if (hookGetMs == NULL) { return 0; @@ -491,7 +491,7 @@ void NoteDelayMs(uint32_t ms) } } -#if NOTE_SHOW_MALLOC || !defined(NOTE_LOWMEM) +#if NOTE_SHOW_MALLOC || !defined(NOTE_C_LOW_MEM) //**************************************************************************/ /*! @brief Convert number to a hex string @@ -857,7 +857,7 @@ bool noteHardReset(void) or the hook has not been set. */ /**************************************************************************/ -const char *noteJSONTransaction(const char *request, size_t reqLen, char **response, size_t timeoutMs) +const char *noteJSONTransaction(const char *request, size_t reqLen, char **response, uint32_t timeoutMs) { if (notecardTransaction == NULL || hookActiveInterface == interfaceNone) { return "i2c or serial interface must be selected"; @@ -884,7 +884,7 @@ const char *noteJSONTransaction(const char *request, size_t reqLen, char **respo */ /**************************************************************************/ const char *noteChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, - size_t timeoutMs, uint32_t *available) + uint32_t timeoutMs, uint32_t *available) { if (notecardChunkedReceive == NULL || hookActiveInterface == interfaceNone) { return "i2c or serial interface must be selected"; diff --git a/src/note-c/n_i2c.c b/src/note-c/n_i2c.c index d3e03f0..a5c552f 100644 --- a/src/note-c/n_i2c.c +++ b/src/note-c/n_i2c.c @@ -23,7 +23,7 @@ // Forwards NOTE_C_STATIC void delayIO(void); -NOTE_C_STATIC const char * i2cNoteQueryLength(uint32_t * available, size_t timeoutMs); +NOTE_C_STATIC const char * i2cNoteQueryLength(uint32_t * available, uint32_t timeoutMs); /**************************************************************************/ /*! @@ -50,11 +50,11 @@ NOTE_C_STATIC void delayIO(void) */ /**************************************************************************/ NOTE_C_STATIC const char * i2cNoteQueryLength(uint32_t * available, - size_t timeoutMs) + uint32_t timeoutMs) { uint8_t dummy_buffer = 0; - for (const size_t startMs = _GetMs() ; !(*available) ; _DelayMs(50)) { + for (const uint32_t startMs = _GetMs() ; !(*available) ; _DelayMs(50)) { // Send a dummy I2C transaction to prime the Notecard const char *err = _I2CReceive(_I2CAddress(), &dummy_buffer, 0, available); if (err) { @@ -92,7 +92,7 @@ NOTE_C_STATIC const char * i2cNoteQueryLength(uint32_t * available, @returns a c-string with an error, or `NULL` if no error occurred. */ /**************************************************************************/ -const char *i2cNoteTransaction(const char *request, size_t reqLen, char **response, size_t timeoutMs) +const char *i2cNoteTransaction(const char *request, size_t reqLen, char **response, uint32_t timeoutMs) { const char *err = NULL; @@ -256,7 +256,7 @@ bool i2cNoteReset(void) bool nonControlCharFound = false; // Read I2C data for at least `CARD_RESET_DRAIN_MS` continuously - for (size_t startMs = _GetMs() ; (_GetMs() - startMs) < CARD_RESET_DRAIN_MS ;) { + for (uint32_t startMs = _GetMs() ; (_GetMs() - startMs) < CARD_RESET_DRAIN_MS ;) { // Read the next chunk of available data uint32_t available = 0; @@ -302,7 +302,6 @@ bool i2cNoteReset(void) // then the Notecard has been successfully reset. if (!somethingFound || nonControlCharFound) { notecardReady = false; -#ifdef ERRDBG if (somethingFound) { NOTE_C_LOG_WARN(ERRSTR("unrecognized data from notecard", c_iobad)); } else { @@ -315,7 +314,6 @@ bool i2cNoteReset(void) } delayIO(); } -#endif } else { notecardReady = true; break; @@ -350,13 +348,13 @@ bool i2cNoteReset(void) @returns A c-string with an error, or `NULL` if no error ocurred. */ /**************************************************************************/ -const char *i2cChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, size_t timeoutMs, uint32_t *available) +const char *i2cChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, uint32_t timeoutMs, uint32_t *available) { // Load buffer with chunked I2C values size_t received = 0; uint16_t requested = 0; bool overflow = false; - size_t startMs = _GetMs(); + uint32_t startMs = _GetMs(); // Request all available bytes, up to the maximum request size requested = (*available > 0xFFFF) ? 0xFFFF : *available; diff --git a/src/note-c/n_lib.h b/src/note-c/n_lib.h index 1ded7c6..6551b70 100644 --- a/src/note-c/n_lib.h +++ b/src/note-c/n_lib.h @@ -94,21 +94,25 @@ extern "C" { @brief Memory allocation chunk size. */ /**************************************************************************/ -#ifdef NOTE_LOWMEM +#ifdef NOTE_C_LOW_MEM #define ALLOC_CHUNK 64 #else #define ALLOC_CHUNK 128 #endif +#ifdef NOTE_C_LOW_MEM +#define NOTE_DISABLE_USER_AGENT +#endif + // Transactions J *noteTransactionShouldLock(J *req, bool lockNotecard); -const char *i2cNoteTransaction(const char *request, size_t reqLen, char **response, size_t timeoutMs); +const char *i2cNoteTransaction(const char *request, size_t reqLen, char **response, uint32_t timeoutMs); bool i2cNoteReset(void); -const char *serialNoteTransaction(const char *request, size_t reqLen, char **response, size_t timeoutMs); +const char *serialNoteTransaction(const char *request, size_t reqLen, char **response, uint32_t timeoutMs); bool serialNoteReset(void); -const char *i2cChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, size_t timeoutMs, uint32_t *available); +const char *i2cChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, uint32_t timeoutMs, uint32_t *available); const char *i2cChunkedTransmit(uint8_t *buffer, uint32_t size, bool delay); -const char *serialChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, size_t timeoutMs, uint32_t *available); +const char *serialChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, uint32_t timeoutMs, uint32_t *available); const char *serialChunkedTransmit(uint8_t *buffer, uint32_t size, bool delay); // Hooks @@ -125,8 +129,8 @@ bool noteI2CReset(uint16_t DevAddress); const char *noteI2CTransmit(uint16_t DevAddress, uint8_t* pBuffer, uint16_t Size); const char *noteI2CReceive(uint16_t DevAddress, uint8_t* pBuffer, uint16_t Size, uint32_t *avail); bool noteHardReset(void); -const char *noteJSONTransaction(const char *request, size_t reqLen, char **response, size_t timeoutMs); -const char *noteChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, size_t timeoutMs, uint32_t *available); +const char *noteJSONTransaction(const char *request, size_t reqLen, char **response, uint32_t timeoutMs); +const char *noteChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, uint32_t timeoutMs, uint32_t *available); const char *noteChunkedTransmit(uint8_t *buffer, uint32_t size, bool delay); bool noteIsDebugOutputActive(void); diff --git a/src/note-c/n_request.c b/src/note-c/n_request.c index 880e404..3f7af28 100644 --- a/src/note-c/n_request.c +++ b/src/note-c/n_request.c @@ -25,7 +25,7 @@ static int suppressShowTransactions = 0; static bool resetRequired = true; // CRC data -#ifndef NOTE_LOWMEM +#ifndef NOTE_C_LOW_MEM static uint16_t lastRequestSeqno = 0; #define CRC_FIELD_LENGTH 22 // ,"crc":"SSSS:CCCCCCCC" #define CRC_FIELD_NAME_OFFSET 1 @@ -303,7 +303,7 @@ J *NoteRequestResponseWithRetry(J *req, uint32_t timeoutSeconds) */ char * NoteRequestResponseJSON(const char *reqJSON) { - size_t transactionTimeoutMs = (CARD_INTER_TRANSACTION_TIMEOUT_SEC * 1000); + uint32_t transactionTimeoutMs = (CARD_INTER_TRANSACTION_TIMEOUT_SEC * 1000); char *rspJSON = NULL; if (reqJSON == NULL) { @@ -450,8 +450,8 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) // where we can find out that the cmd failed. Note that a Seqno is included // as part of the CRC data so that two identical requests occurring within the // modulus of seqno never are mistaken as being the same request being retried. -#ifndef NOTE_LOWMEM uint8_t lastRequestRetries = 0; +#ifndef NOTE_C_LOW_MEM bool lastRequestCrcAdded = false; if (!noResponseExpected) { char *newJson = crcAdd(json, lastRequestSeqno); @@ -461,7 +461,7 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) lastRequestCrcAdded = true; } } -#endif // !NOTE_LOWMEM +#endif // !NOTE_C_LOW_MEM // When note.add or web.* requests are used to transfer binary data, the // time to complete the transaction can vary depending on the size of @@ -476,7 +476,7 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) // - If the request is a `web.*`, follow the same logic, but instead // of using the standard timeout, use the Notecard timeout of 90 // seconds for all `web.*` transactions. - size_t transactionTimeoutMs = (CARD_INTER_TRANSACTION_TIMEOUT_SEC * 1000); + uint32_t transactionTimeoutMs = (CARD_INTER_TRANSACTION_TIMEOUT_SEC * 1000); // Interrogate the request if (JContainsString(req, (reqType ? "req" : "cmd"), "note.add")) { @@ -509,7 +509,6 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) char *responseJSON = NULL; J *rsp = NULL; while (true) { -#ifndef NOTE_LOWMEM // If no retry possibility, break out if (lastRequestRetries > CARD_REQUEST_RETRIES_ALLOWED) { break; @@ -517,7 +516,6 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) // free on retry JDelete(rsp); } -#endif // !NOTE_LOWMEM // reset variables rsp = NULL; @@ -542,7 +540,6 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) // Swap newline-terminator for NULL-terminator json[jsonLen] = '\0'; -#ifndef NOTE_LOWMEM // If there's an I/O error on the transaction, retry if (errStr != NULL) { JFree(responseJSON); @@ -553,6 +550,7 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) continue; } +#ifndef NOTE_C_LOW_MEM // If we sent a CRC in the request, examine the response JSON to see if // it has a CRC error. Note that the CRC is stripped from the // responseJSON as a side-effect of this method. @@ -564,6 +562,7 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) _DelayMs(500); continue; } +#endif // !NOTE_C_LOW_MEM // See if the response JSON can't be unmarshaled, or if it contains an {io} error rsp = JParse(responseJSON); @@ -577,7 +576,7 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) if (responseJSON == NULL) { NOTE_C_LOG_ERROR(ERRSTR("response expected, but response is NULL.", c_ioerr)); } else { -#ifndef NOTE_LOWMEM +#ifndef NOTE_C_LOW_MEM _DebugWithLevel(NOTE_C_LOG_LEVEL_ERROR, "[ERROR] "); _DebugWithLevel(NOTE_C_LOG_LEVEL_ERROR, "invalid JSON: "); _DebugWithLevel(NOTE_C_LOG_LEVEL_ERROR, responseJSON); @@ -603,14 +602,13 @@ J *noteTransactionShouldLock(J *req, bool lockNotecard) continue; } } -#endif // !NOTE_LOWMEM // Transaction completed break; } // Bump the request sequence number now that we've processed this request, success or error -#ifndef NOTE_LOWMEM +#ifndef NOTE_C_LOW_MEM lastRequestSeqno++; #endif @@ -725,7 +723,7 @@ void NoteErrorClean(char *begin) } } -#ifndef NOTE_LOWMEM +#ifndef NOTE_C_LOW_MEM /*! @brief Convert a hex string to a 64-bit unsigned integer. @@ -889,4 +887,4 @@ NOTE_C_STATIC bool crcError(char *json, uint16_t shouldBeSeqno) return (shouldBeSeqno != actualSeqno || shouldBeCrc32 != actualCrc32); } -#endif // !NOTE_LOWMEM +#endif // !NOTE_C_LOW_MEM diff --git a/src/note-c/n_serial.c b/src/note-c/n_serial.c index 5d4fb8a..d2339c2 100644 --- a/src/note-c/n_serial.c +++ b/src/note-c/n_serial.c @@ -31,7 +31,7 @@ @returns a c-string with an error, or `NULL` if no error occurred. */ /**************************************************************************/ -const char *serialNoteTransaction(const char *request, size_t reqLen, char **response, size_t timeoutMs) +const char *serialNoteTransaction(const char *request, size_t reqLen, char **response, uint32_t timeoutMs) { // Strip off the newline and optional carriage return characters. This // allows for standardized output to be reapplied. @@ -167,7 +167,7 @@ bool serialNoteReset(void) bool nonControlCharFound = false; // Read Serial data for at least CARD_RESET_DRAIN_MS continously - for (size_t startMs = _GetMs() ; (_GetMs() - startMs) < CARD_RESET_DRAIN_MS ;) { + for (uint32_t startMs = _GetMs() ; (_GetMs() - startMs) < CARD_RESET_DRAIN_MS ;) { // Determine if Serial data is available while (_SerialAvailable()) { somethingFound = true; @@ -225,11 +225,11 @@ bool serialNoteReset(void) @returns A c-string with an error, or `NULL` if no error ocurred. */ /**************************************************************************/ -const char *serialChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, size_t timeoutMs, uint32_t *available) +const char *serialChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, uint32_t timeoutMs, uint32_t *available) { size_t received = 0; bool overflow = (received >= *size); - size_t startMs = _GetMs(); + uint32_t startMs = _GetMs(); for (bool eop = false ; !overflow && !eop ;) { while (!_SerialAvailable()) { if (timeoutMs && (_GetMs() - startMs >= timeoutMs)) { diff --git a/src/note-c/n_ua.c b/src/note-c/n_ua.c index 852330e..87dfa9e 100644 --- a/src/note-c/n_ua.c +++ b/src/note-c/n_ua.c @@ -11,7 +11,7 @@ * */ -#ifndef NOTE_LOWMEM +#ifndef NOTE_C_LOW_MEM #include "n_lib.h" diff --git a/src/note-c/note.h b/src/note-c/note.h index 312f526..1ad5941 100644 --- a/src/note-c/note.h +++ b/src/note-c/note.h @@ -23,38 +23,54 @@ #define NOTE_C_VERSION_MAJOR 2 #define NOTE_C_VERSION_MINOR 1 -#define NOTE_C_VERSION_PATCH 1 +#define NOTE_C_VERSION_PATCH 2 -// Determine our basic floating data type. In most cases "double" is the right answer, however for -// very small microcontrollers we must use single-precision. +// If double and float are the same size, then we must be on a small MCU. Turn +// on NOTE_C_LOW_MEM to conserve memory. #if defined(FLT_MAX_EXP) && defined(DBL_MAX_EXP) #if (FLT_MAX_EXP == DBL_MAX_EXP) -#define NOTE_FLOAT +#define NOTE_C_LOW_MEM #endif #elif defined(__FLT_MAX_EXP__) && defined(__DBL_MAX_EXP__) #if (__FLT_MAX_EXP__ == __DBL_MAX_EXP__) -#define NOTE_FLOAT +#define NOTE_C_LOW_MEM #endif #else #error What are floating point exponent length symbols for this compiler? #endif -// If using a short float, we must be on a VERY small MCU. In this case, define additional -// symbols that will save quite a bit of memory in the runtime image. -#ifdef NOTE_FLOAT -#define JNUMBER float +// NOTE_LOWMEM is the old name of NOTE_C_LOW_MEM. If NOTE_LOWMEM is defined, +// we define NOTE_C_LOW_MEM as well, for backwards compatibility. NOTE_FLOAT is +// also no longer used internally, but used to be defined when NOTE_LOWMEM was +// defined. It's also preserved here for backwards compatibility. +#ifdef NOTE_LOWMEM +#define NOTE_C_LOW_MEM +#define NOTE_FLOAT +#endif + +#ifdef NOTE_C_LOW_MEM #define ERRSTR(x,y) (y) -#define NOTE_LOWMEM #else -#define JNUMBER double #define ERRSTR(x,y) (x) #define ERRDBG #endif +#ifdef NOTE_C_TEST_SINGLE_PRECISION +typedef float JNUMBER; +#else +typedef double JNUMBER; +#endif + +typedef int64_t JINTEGER; +#define JINTEGER_MIN INT64_MIN +#define JINTEGER_MAX INT64_MAX + +typedef uint64_t JUINTEGER; + // UNIX Epoch time (also known as POSIX time) is the number of seconds that have elapsed since // 00:00:00 Thursday, 1 January 1970, Coordinated Universal Time (UTC). In this project, it always // originates from the Notecard, which synchronizes the time from both the cell network and GPS. -typedef unsigned long int JTIME; +typedef JUINTEGER JTIME; // C-callable functions #ifdef __cplusplus @@ -355,7 +371,7 @@ void NoteDebugWithLevelLn(uint8_t level, const char *msg); void *NoteMalloc(size_t size); void NoteFree(void *); -long unsigned int NoteGetMs(void); +uint32_t NoteGetMs(void); void NoteDelayMs(uint32_t ms); void NoteLockI2C(void); void NoteUnlockI2C(void); @@ -378,12 +394,12 @@ char *JGetString(J *rsp, const char *field); JNUMBER JGetNumber(J *rsp, const char *field); J *JGetArray(J *rsp, const char *field); J *JGetObject(J *rsp, const char *field); -long int JGetInt(J *rsp, const char *field); +JINTEGER JGetInt(J *rsp, const char *field); bool JGetBool(J *rsp, const char *field); JNUMBER JNumberValue(J *item); char *JStringValue(J *item); bool JBoolValue(J *item); -long int JIntValue(J *item); +JINTEGER JIntValue(J *item); bool JIsNullString(J *rsp, const char *field); bool JIsExactString(J *rsp, const char *field, const char *teststr); bool JContainsString(J *rsp, const char *field, const char *substr); @@ -418,8 +434,8 @@ int JBaseItemType(int type); #define JNTOA_MAX (44) char * JNtoA(JNUMBER f, char * buf, int precision); JNUMBER JAtoN(const char *string, char **endPtr); -void JItoA(long int n, char *s); -long int JAtoI(const char *s); +void JItoA(JINTEGER n, char *s); +JINTEGER JAtoI(const char *s); int JB64EncodeLen(int len); int JB64Encode(char * coded_dst, const char *plain_src,int len_plain_src); int JB64DecodeLen(const char * coded_src); @@ -468,12 +484,12 @@ bool NoteRegion(char **retCountry, char **retArea, char **retZone, int *retZoneO bool NoteLocationValid(char *errbuf, uint32_t errbuflen); bool NoteLocationValidST(char *errbuf, uint32_t errbuflen); void NoteTurboIO(bool enable); -long int NoteGetEnvInt(const char *variable, long int defaultVal); +JINTEGER NoteGetEnvInt(const char *variable, JINTEGER defaultVal); JNUMBER NoteGetEnvNumber(const char *variable, JNUMBER defaultVal); bool NoteGetEnv(const char *variable, const char *defaultVal, char *buf, uint32_t buflen); bool NoteSetEnvDefault(const char *variable, char *buf); bool NoteSetEnvDefaultNumber(const char *variable, JNUMBER defaultVal); -bool NoteSetEnvDefaultInt(const char *variable, long int defaultVal); +bool NoteSetEnvDefaultInt(const char *variable, JINTEGER defaultVal); bool NoteIsConnected(void); bool NoteIsConnectedST(void); bool NoteGetNetStatus(char *statusBuf, int statusBufLen); diff --git a/test/Notecard.test.cpp b/test/Notecard.test.cpp index 3a9ec9c..2026f5d 100644 --- a/test/Notecard.test.cpp +++ b/test/Notecard.test.cpp @@ -1381,7 +1381,7 @@ int test_notecard_begin_serial_sends_a_card_aux_serial_request_to_throttle_aux_s Notecard notecard; NoteSerial_Mock mockSerial; // Instantiate NoteSerial (mocked) - jAddNumberToObject_Parameters.reset(); + jAddIntToObject_Parameters.reset(); noteNewRequest_Parameters.reset(); noteRequestWithRetry_Parameters.reset(); noteSetFnDefault_Parameters.reset(); @@ -1421,7 +1421,7 @@ int test_notecard_begin_serial_sends_a_card_aux_serial_request_with_15_second_re Notecard notecard; NoteSerial_Mock mockSerial; // Instantiate NoteSerial (mocked) - jAddNumberToObject_Parameters.reset(); + jAddIntToObject_Parameters.reset(); noteNewRequest_Parameters.reset(); noteRequestWithRetry_Parameters.reset(); noteSetFnDefault_Parameters.reset(); @@ -1461,7 +1461,7 @@ int test_notecard_begin_serial_sends_a_card_aux_serial_request_with_max_paramete Notecard notecard; NoteSerial_Mock mockSerial; // Instantiate NoteSerial (mocked) - jAddNumberToObject_Parameters.reset(); + jAddIntToObject_Parameters.reset(); noteNewRequest_Parameters.reset(); noteRequestWithRetry_Parameters.reset(); noteSetFnDefault_Parameters.reset(); @@ -1476,8 +1476,8 @@ int test_notecard_begin_serial_sends_a_card_aux_serial_request_with_max_paramete // Assert /////////// - if ("max" == jAddNumberToObject_Parameters.name[0] - && (SERIAL_RX_BUFFER_SIZE - 1) == jAddNumberToObject_Parameters.number[0] + if ("max" == jAddIntToObject_Parameters.name[0] + && (SERIAL_RX_BUFFER_SIZE - 1) == jAddIntToObject_Parameters.number[0] ) { result = 0; } @@ -1485,8 +1485,8 @@ int test_notecard_begin_serial_sends_a_card_aux_serial_request_with_max_paramete { result = static_cast('n' + 'o' + 't' + 'e' + 'c' + 'a' + 'r' + 'd'); std::cout << "\33[31mFAILED\33[0m] " << __FILE__ << ":" << __LINE__ << std::endl; - std::cout << "\tjAddNumberToObject_Parameters.name == \"" << std::dec << jAddNumberToObject_Parameters.name[0] << "\", EXPECTED: \"max\"" << std::endl; - std::cout << "\tjAddNumberToObject_Parameters.number == " << std::dec << jAddNumberToObject_Parameters.number[0] << ", EXPECTED: " << (SERIAL_RX_BUFFER_SIZE - 1) << std::endl; + std::cout << "\tjAddIntToObject_Parameters.name == \"" << std::dec << jAddIntToObject_Parameters.name[0] << "\", EXPECTED: \"max\"" << std::endl; + std::cout << "\tjAddIntToObject_Parameters.number == " << std::dec << jAddIntToObject_Parameters.number[0] << ", EXPECTED: " << (SERIAL_RX_BUFFER_SIZE - 1) << std::endl; std::cout << "["; } @@ -1503,7 +1503,7 @@ int test_notecard_begin_serial_sends_a_card_aux_serial_request_with_ms_parameter Notecard notecard; NoteSerial_Mock mockSerial; // Instantiate NoteSerial (mocked) - jAddNumberToObject_Parameters.reset(); + jAddIntToObject_Parameters.reset(); noteNewRequest_Parameters.reset(); noteRequestWithRetry_Parameters.reset(); noteSetFnDefault_Parameters.reset(); @@ -1518,8 +1518,8 @@ int test_notecard_begin_serial_sends_a_card_aux_serial_request_with_ms_parameter // Assert /////////// - if ("ms" == jAddNumberToObject_Parameters.name[1] - && 1 == jAddNumberToObject_Parameters.number[1] + if ("ms" == jAddIntToObject_Parameters.name[1] + && 1 == jAddIntToObject_Parameters.number[1] ) { result = 0; } @@ -1527,8 +1527,8 @@ int test_notecard_begin_serial_sends_a_card_aux_serial_request_with_ms_parameter { result = static_cast('n' + 'o' + 't' + 'e' + 'c' + 'a' + 'r' + 'd'); std::cout << "\33[31mFAILED\33[0m] " << __FILE__ << ":" << __LINE__ << std::endl; - std::cout << "\tjAddNumberToObject_Parameters.name == \"" << std::dec << jAddNumberToObject_Parameters.name[1] << "\", EXPECTED: \"ms\"" << std::endl; - std::cout << "\tjAddNumberToObject_Parameters.number == " << std::dec << jAddNumberToObject_Parameters.number[1] << ", EXPECTED: 1" << std::endl; + std::cout << "\tjAddIntToObject_Parameters.name == \"" << std::dec << jAddIntToObject_Parameters.name[1] << "\", EXPECTED: \"ms\"" << std::endl; + std::cout << "\tjAddIntToObject_Parameters.number == " << std::dec << jAddIntToObject_Parameters.number[1] << ", EXPECTED: 1" << std::endl; std::cout << "["; } @@ -1544,7 +1544,7 @@ int test_notecard_begin_serial_does_not_send_a_card_aux_serial_request_when_inte //////////// Notecard notecard; - jAddNumberToObject_Parameters.reset(); + jAddIntToObject_Parameters.reset(); noteNewRequest_Parameters.reset(); noteRequestWithRetry_Parameters.reset(); noteSetFnDefault_Parameters.reset(); diff --git a/test/mock/mock-note-c-note.c b/test/mock/mock-note-c-note.c index 1a6f292..8a1ad81 100644 --- a/test/mock/mock-note-c-note.c +++ b/test/mock/mock-note-c-note.c @@ -1,6 +1,6 @@ #include "mock-parameters.hpp" -JAddNumberToObject_Parameters jAddNumberToObject_Parameters; +JAddIntToObject_Parameters jAddIntToObject_Parameters; NoteDebug_Parameters noteDebug_Parameters; NoteDebugSyncStatus_Parameters noteDebugSyncStatus_Parameters; NoteDeleteResponse_Parameters noteDeleteResponse_Parameters; @@ -21,24 +21,24 @@ NoteSetFnTransaction_Parameters noteSetFnTransaction_Parameters; NoteSetUserAgent_Parameters noteSetUserAgent_Parameters; J * -JAddNumberToObject ( +JAddIntToObject ( J * const object_, const char * const name_, - const JNUMBER number_ + const JINTEGER number_ ) { // Record invocation(s) - ++jAddNumberToObject_Parameters.invoked; + ++jAddIntToObject_Parameters.invoked; // Stash parameter(s) - jAddNumberToObject_Parameters.object.push_back(object_); - jAddNumberToObject_Parameters.name.push_back(name_); - jAddNumberToObject_Parameters.number.push_back(number_); + jAddIntToObject_Parameters.object.push_back(object_); + jAddIntToObject_Parameters.name.push_back(name_); + jAddIntToObject_Parameters.number.push_back(number_); // Return user-supplied result - if (jAddNumberToObject_Parameters.result.size() < jAddNumberToObject_Parameters.invoked) { - return jAddNumberToObject_Parameters.default_result; + if (jAddIntToObject_Parameters.result.size() < jAddIntToObject_Parameters.invoked) { + return jAddIntToObject_Parameters.default_result; } else { - return jAddNumberToObject_Parameters.result[(jAddNumberToObject_Parameters.invoked - 1)]; + return jAddIntToObject_Parameters.result[(jAddIntToObject_Parameters.invoked - 1)]; } } diff --git a/test/mock/mock-parameters.hpp b/test/mock/mock-parameters.hpp index fed4251..37eda5d 100644 --- a/test/mock/mock-parameters.hpp +++ b/test/mock/mock-parameters.hpp @@ -23,8 +23,8 @@ void MockNoteDeleteResponse(J*); bool MockNoteResponseError(J*); #define NoteResponseError(x) MockNoteResponseError(x) -struct JAddNumberToObject_Parameters { - JAddNumberToObject_Parameters( +struct JAddIntToObject_Parameters { + JAddIntToObject_Parameters( void ) : invoked(0), @@ -44,7 +44,7 @@ struct JAddNumberToObject_Parameters { size_t invoked; std::vector object; std::vector name; - std::vector number; + std::vector number; std::vector result; J *default_result; }; @@ -453,7 +453,7 @@ struct NoteSetUserAgent_Parameters { std::string agent_cache; }; -extern JAddNumberToObject_Parameters jAddNumberToObject_Parameters; +extern JAddIntToObject_Parameters jAddIntToObject_Parameters; extern NoteDebug_Parameters noteDebug_Parameters; extern NoteDebugSyncStatus_Parameters noteDebugSyncStatus_Parameters; extern NoteDeleteResponse_Parameters noteDeleteResponse_Parameters;