From 9cf373df7874c70e0d5073331d1b5d5a022efd33 Mon Sep 17 00:00:00 2001 From: "Zachary J. Fields" Date: Tue, 25 Feb 2025 14:59:11 +0000 Subject: [PATCH 1/4] Remove `note-c` before re-add --- src/note-c/.gitignore | 10 - src/note-c/CMakeLists.txt | 125 -- src/note-c/CODE_OF_CONDUCT.md | 7 - src/note-c/CONTRIBUTING.md | 64 - src/note-c/Dockerfile | 95 -- src/note-c/LICENSE | 19 - src/note-c/README.md | 64 - src/note-c/n_atof.c | 292 ---- src/note-c/n_b64.c | 200 --- src/note-c/n_cjson.c | 2787 --------------------------------- src/note-c/n_cjson.h | 332 ---- src/note-c/n_cjson_helpers.c | 694 -------- src/note-c/n_cobs.c | 161 -- src/note-c/n_const.c | 30 - src/note-c/n_ftoa.c | 510 ------ src/note-c/n_helpers.c | 2575 ------------------------------ src/note-c/n_hooks.c | 911 ----------- src/note-c/n_i2c.c | 466 ------ src/note-c/n_lib.h | 240 --- src/note-c/n_md5.c | 321 ---- src/note-c/n_printf.c | 66 - src/note-c/n_request.c | 954 ----------- src/note-c/n_serial.c | 316 ---- src/note-c/n_str.c | 99 -- src/note-c/n_ua.c | 209 --- src/note-c/note.h | 566 ------- 26 files changed, 12113 deletions(-) delete mode 100644 src/note-c/.gitignore delete mode 100644 src/note-c/CMakeLists.txt delete mode 100644 src/note-c/CODE_OF_CONDUCT.md delete mode 100644 src/note-c/CONTRIBUTING.md delete mode 100644 src/note-c/Dockerfile delete mode 100644 src/note-c/LICENSE delete mode 100644 src/note-c/README.md delete mode 100644 src/note-c/n_atof.c delete mode 100644 src/note-c/n_b64.c delete mode 100644 src/note-c/n_cjson.c delete mode 100644 src/note-c/n_cjson.h delete mode 100644 src/note-c/n_cjson_helpers.c delete mode 100644 src/note-c/n_cobs.c delete mode 100644 src/note-c/n_const.c delete mode 100644 src/note-c/n_ftoa.c delete mode 100644 src/note-c/n_helpers.c delete mode 100644 src/note-c/n_hooks.c delete mode 100644 src/note-c/n_i2c.c delete mode 100644 src/note-c/n_lib.h delete mode 100644 src/note-c/n_md5.c delete mode 100644 src/note-c/n_printf.c delete mode 100644 src/note-c/n_request.c delete mode 100644 src/note-c/n_serial.c delete mode 100644 src/note-c/n_str.c delete mode 100644 src/note-c/n_ua.c delete mode 100644 src/note-c/note.h diff --git a/src/note-c/.gitignore b/src/note-c/.gitignore deleted file mode 100644 index 9552ba0..0000000 --- a/src/note-c/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -Doxyfile.bak -latex/ -html/ -__pycache__/ - -# VS Code workspace files -*.code-workspace -*.orig -settings.json diff --git a/src/note-c/CMakeLists.txt b/src/note-c/CMakeLists.txt deleted file mode 100644 index 449cbdb..0000000 --- a/src/note-c/CMakeLists.txt +++ /dev/null @@ -1,125 +0,0 @@ -cmake_minimum_required(VERSION 3.13) - -if ("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") - message(FATAL_ERROR "In-source builds are not allowed. - Please create a build directory and use `cmake ..` inside it. - NOTE: cmake will now create CMakeCache.txt and CMakeFiles/*. - You must delete them, or cmake will refuse to work.") -endif() - -project(note_c) - -# Automatically ignore CMake build directory. -if(NOT EXISTS ${PROJECT_BINARY_DIR}/.gitignore) - file(WRITE ${PROJECT_BINARY_DIR}/.gitignore "*") -endif() - -option(NOTE_C_BUILD_TESTS "Build tests." OFF) -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) -target_sources( - note_c - PRIVATE - ${NOTE_C_SRC_DIR}/n_atof.c - ${NOTE_C_SRC_DIR}/n_b64.c - ${NOTE_C_SRC_DIR}/n_cjson.c - ${NOTE_C_SRC_DIR}/n_cjson_helpers.c - ${NOTE_C_SRC_DIR}/n_cobs.c - ${NOTE_C_SRC_DIR}/n_const.c - ${NOTE_C_SRC_DIR}/n_ftoa.c - ${NOTE_C_SRC_DIR}/n_helpers.c - ${NOTE_C_SRC_DIR}/n_hooks.c - ${NOTE_C_SRC_DIR}/n_i2c.c - ${NOTE_C_SRC_DIR}/n_md5.c - ${NOTE_C_SRC_DIR}/n_printf.c - ${NOTE_C_SRC_DIR}/n_request.c - ${NOTE_C_SRC_DIR}/n_serial.c - ${NOTE_C_SRC_DIR}/n_str.c -) -target_compile_options( - note_c - PRIVATE - -Wall - -Wextra - -Wpedantic - -Werror - -Og - -ggdb -) -target_include_directories( - note_c - 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 - PRIVATE - -nostdlib - -nodefaultlibs - LINKER:--no-undefined - ) -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/). - # We also need to set MEMORYCHECK_COMMAND_OPTIONS before including this. - # See: https://stackoverflow.com/a/60741757 - if(NOTE_C_MEM_CHECK) - # Go ahead and make sure we can find valgrind while we're here. - find_program(VALGRIND valgrind REQUIRED) - message(STATUS "Found valgrind: ${VALGRIND}") - set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --error-exitcode=1") - endif(NOTE_C_MEM_CHECK) - include(CTest) - - target_compile_definitions( - note_c - PUBLIC - NOTE_C_TEST - ) - target_include_directories( - note_c - PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/test/include - ) - - add_subdirectory(test) -endif(NOTE_C_BUILD_TESTS) - -if(NOTE_C_BUILD_DOCS) - add_subdirectory(docs) -endif(NOTE_C_BUILD_DOCS) diff --git a/src/note-c/CODE_OF_CONDUCT.md b/src/note-c/CODE_OF_CONDUCT.md deleted file mode 100644 index 50838cf..0000000 --- a/src/note-c/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,7 +0,0 @@ -# Code of conduct - -By participating in this project, you agree to abide by the -[Blues Inc code of conduct][1]. - -[1]: https://blues.github.io/opensource/code-of-conduct - diff --git a/src/note-c/CONTRIBUTING.md b/src/note-c/CONTRIBUTING.md deleted file mode 100644 index 39410cb..0000000 --- a/src/note-c/CONTRIBUTING.md +++ /dev/null @@ -1,64 +0,0 @@ -# Contributing to blues/note-c - -We love pull requests from everyone. By participating in this project, you -agree to abide by the Blues Inc [code of conduct]. - -[code of conduct]: https://blues.github.io/opensource/code-of-conduct - -Here are some ways *you* can contribute: - -* by using alpha, beta, and prerelease versions -* by reporting bugs -* by suggesting new features -* by writing or editing documentation -* by writing specifications -* by writing code ( **no patch is too small** : fix typos, add comments, -clean up inconsistent whitespace ) -* by refactoring code -* by closing [issues][] -* by reviewing patches - -[issues]: https://github.com/blues/note-c/issues - -## Submitting an Issue - -* We use the [GitHub issue tracker][issues] to track bugs and features. -* Before submitting a bug report or feature request, check to make sure it - hasn't - already been submitted. -* When submitting a bug report, please include a [Gist][] that includes a stack - trace and any details that may be necessary to reproduce the bug, including - your gem version, Ruby version, and operating system. Ideally, a bug report - should include a pull request with failing specs. - -[gist]: https://gist.github.com/ - -## Cleaning up issues - -* Issues that have no response from the submitter will be closed after 30 days. -* Issues will be closed once they're assumed to be fixed or answered. If the - maintainer is wrong, it can be opened again. -* If your issue is closed by mistake, please understand and explain the issue. - We will happily reopen the issue. - -## Submitting a Pull Request -1. [Fork][fork] the [official repository][repo]. -2. [Create a topic branch.][branch] -3. Implement your feature or bug fix. -4. Add, commit, and push your changes. -5. [Submit a pull request.][pr] - -## Notes -* Please add tests if you changed code. Contributions without tests won't be -* accepted. If you don't know how to add tests, please put in a PR and leave a -* comment asking for help. We love helping! - -[repo]: https://github.com/blues/note-c/tree/master -[fork]: https://help.github.com/articles/fork-a-repo/ -[branch]: -https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/ -[pr]: https://help.github.com/articles/creating-a-pull-request-from-a-fork/ - -Inspired by -https://github.com/thoughtbot/factory_bot/blob/master/CONTRIBUTING.md - diff --git a/src/note-c/Dockerfile b/src/note-c/Dockerfile deleted file mode 100644 index 0368b33..0000000 --- a/src/note-c/Dockerfile +++ /dev/null @@ -1,95 +0,0 @@ -# Container with dependencies necessary to run note-c unit tests. - -# Build development environment -# $ docker build . --tag note_c_run_unit_tests - -# Launch development environment (mount source root as /note-c/) -# $ docker run --rm --volume $(pwd)/../../../:/note-c/ --workdir /note-c/ note_c_run_unit_tests - -# Global Argument(s) -ARG DEBIAN_FRONTEND="noninteractive" -ARG UID=1000 -ARG USER="blues" - -# POSIX compatible (Linux/Unix) base image. -FROM --platform=linux/amd64 debian:stable-slim - -# Import Global Argument(s) -ARG DEBIAN_FRONTEND -ARG UID -ARG USER - -# Local Argument(s) - -# Create Non-Root User -RUN ["dash", "-c", "\ - addgroup \ - --gid ${UID} \ - \"${USER}\" \ - && adduser \ - --disabled-password \ - --gecos \"\" \ - --ingroup \"${USER}\" \ - --uid ${UID} \ - \"${USER}\" \ - && usermod \ - --append \ - --groups \"dialout,plugdev\" \ - \"${USER}\" \ -"] - -# Install whatever dependencies we can via apt-get. -RUN ["dash", "-c", "\ - apt-get update --quiet \ - && apt-get install --assume-yes --no-install-recommends --quiet \ - astyle \ - ca-certificates \ - curl \ - g++ \ - gcc \ - gdb \ - git \ - lcov \ - make \ - nano \ - python3-pip \ - python3-sphinx \ - valgrind \ - && pip install --break-system-packages \ - breathe \ - sphinx-rtd-theme \ - && apt-get clean \ - && apt-get purge \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ - "] - -# Download and install Doxygen 1.9.8. Currently, the version of Doxygen that -# gets installed via apt-get doesn't have support FAIL_ON_WARNINGS_PRINT for the -# WARN_AS_ERROR config option (added in 1.9.7). -RUN ["dash", "-c", "\ - curl -LO https://github.com/doxygen/doxygen/releases/download/Release_1_9_8/doxygen-1.9.8.linux.bin.tar.gz \ - && tar xf doxygen-1.9.8.linux.bin.tar.gz \ - && cd doxygen-1.9.8 \ - && make INSTALL=/usr install \ - && cd .. \ - && rm doxygen-1.9.8.linux.bin.tar.gz \ - && rm -rf doxygen-1.9.8 \ -"] - -# Download and install CMake v3.25.1. We need CMake v3.20+ in order to get the -# ctest --test-dir option used by run_unit_tests.sh. -RUN ["dash", "-c", "\ - curl -LO https://github.com/Kitware/CMake/releases/download/v3.25.1/cmake-3.25.1-linux-x86_64.tar.gz \ - && tar xf cmake-3.25.1-linux-x86_64.tar.gz --strip-components=1 -C /usr \ - && rm cmake-3.25.1-linux-x86_64.tar.gz \ -"] - -# Download and install Catch2 v3.2.1. -RUN ["dash", "-c", "\ - curl -LO https://github.com/catchorg/Catch2/archive/refs/tags/v3.2.1.tar.gz \ - && tar xf v3.2.1.tar.gz \ - && cd Catch2-3.2.1 \ - && cmake -DCATCH_INSTALL_DOCS=0 -B build/ \ - && cmake --build build/ --target install \ - && rm -rf Catch2-3.2.1 v3.2.1.tar.gz \ -"] diff --git a/src/note-c/LICENSE b/src/note-c/LICENSE deleted file mode 100644 index aed1af8..0000000 --- a/src/note-c/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2019 Blues Inc - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/note-c/README.md b/src/note-c/README.md deleted file mode 100644 index a6e8f3b..0000000 --- a/src/note-c/README.md +++ /dev/null @@ -1,64 +0,0 @@ -[![Coverage Status](https://coveralls.io/repos/github/blues/note-c/badge.svg?branch=master)](https://coveralls.io/github/blues/note-c?branch=master) - -# note-c - -The note-c C library for communicating with the -[Blues Wireless][blues] Notecard via serial or I²C. - -This library allows you to control a Notecard by writing a C -or C++ program. Your program may programmatically configure Notecard and send -Notes to [Notehub.io][notehub]. - -This library is used by the [note-arduino library][note-arduino], which includes -it as a git subtree. - -## Documentation - -The documentation for this library can be found [here](https://blues.github.io/note-c/index.html). - -## CMake - -The CMake build system is primarily here for testing note-c on a development -machine. You can use it to generate a static or shared note-c library, but -embedded users will typically just compile all the .c source files into their -firmware image. For more on testing, see test/README.md. - -### Options - -- BUILD_TESTS: Build the tests. See the tests directory. Default: ON. -- BUILD_SHARED_LIBS: Build the note-c library as shared instead of static. This -reduces the total size of the compiled tests. Default: ON. - -## Contributing - -We love issues, fixes, and pull requests from everyone. By participating in this -project, you agree to abide by the Blues Inc [code of conduct]. - -For details on contributions we accept and the process for contributing, see our -[contribution guide](CONTRIBUTING.md). - -## More Information - -For additional Notecard SDKs and Libraries, see: - -- [note-arduino][note-arduino] for Arduino support -- [note-python][note-python] for Python -- [note-go][note-go] for Go - -## To learn more about Blues Wireless, the Notecard and Notehub, see: - -- [blues.com](https://blues.io) -- [notehub.io][notehub] -- [wireless.dev](https://wireless.dev) - -## License - -Copyright (c) 2019 Blues Inc. Released under the MIT license. See -[LICENSE](LICENSE) for details. - -[blues]: https://blues.com -[notehub]: https://notehub.io -[note-arduino]: https://github.com/blues/note-arduino -[note-go]: https://github.com/blues/note-go -[note-python]: https://github.com/blues/note-python -[code of conduct]: https://blues.github.io/opensource/code-of-conduct diff --git a/src/note-c/n_atof.c b/src/note-c/n_atof.c deleted file mode 100644 index d465dec..0000000 --- a/src/note-c/n_atof.c +++ /dev/null @@ -1,292 +0,0 @@ -/*! - * @file n_atof.c - * - * Derived from the "strtod" library procedure to be locale-independent - * - * Copyright (c) 1988-1993 The Regents of the University of California. - * Copyright (c) 1994 Sun Microsystems, Inc. - * - * Permission to use, copy, modify, and distribute this - * software and its documentation for any purpose and without - * fee is hereby granted, provided that the above copyright - * notice appear in all copies. The University of California - * makes no representations about the suitability of this - * software for any purpose. It is provided "as is" without - * express or implied warranty. - * - * RCS: @(#) $Id$ - */ - -#ifdef HAVE_STDLIB_H -# include -#endif -#include - -#include "n_lib.h" - -#ifndef __STDC__ -# ifdef __GNUC__ -# define const __const__ -# else -# define const -# endif -#endif - -#ifndef TRUE -#define TRUE 1 -#define FALSE 0 -#endif -#ifndef NULL -#define NULL 0 -#endif - -#define MAX_EXPONENT 511 /* Largest possible base 10 exponent. Any - * exponent larger than this will already - * produce underflow or overflow, so there's - * no need to worry about additional digits. - */ - -/* - *---------------------------------------------------------------------- - * - * atof -- a LOCALE-INDEPENDENT string to floating point - * - * This procedure converts a floating-point number from an ASCII - * decimal representation to internal double-precision format. - * - * Results: - * The return value is the double-precision floating-point - * representation of the characters in string. If endPtr isn't - * NULL, then *endPtr is filled in with the address of the - * next character after the last one that was part of the - * floating-point number. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -JNUMBER -JAtoN(string, endPtr) -const char *string; /* A decimal ASCII floating-point number, - * optionally preceded by white space. - * Must have form "-I.FE-X", where I is the - * integer part of the mantissa, F is the - * fractional part of the mantissa, and X - * is the exponent. Either of the signs - * may be "+", "-", or omitted. Either I - * or F may be omitted, or both. The decimal - * point isn't necessary unless F is present. - * The "E" may actually be an "e". E and X - * may both be omitted (but not just one). - */ -char **endPtr; /* If non-NULL, store terminating character's - * address here. */ -{ - int sign, expSign = FALSE; - JNUMBER fraction; - register const char *p; - register int c; - int exp = 0; /* Exponent read from "EX" field. */ - int fracExp = 0; /* Exponent that derives from the fractional - * part. Under normal circumstatnces, it is - * the negative of the number of digits in F. - * However, if I is very long, the last digits - * of I get dropped (otherwise a long I with a - * large negative exponent could cause an - * unnecessary overflow on I alone). In this - * case, fracExp is incremented one for each - * dropped digit. */ - int mantSize; /* Number of digits in mantissa. */ - int decPt; /* Number of mantissa digits BEFORE decimal - * point. */ - const char *pExp; /* Temporarily holds location of exponent - * in string. */ - - /* - * Strip off leading blanks and check for a sign. - */ - - p = string; - while (*p == ' ') { - p += 1; - } - if (*p == '-') { - sign = TRUE; - p += 1; - } else { - if (*p == '+') { - p += 1; - } - sign = FALSE; - } - - /* - * Count the number of digits in the mantissa (including the decimal - * point), and also locate the decimal point. - */ - - decPt = -1; - for (mantSize = 0; ; mantSize += 1) { - c = *p; - if (c < '0' || c > '9') { - if ((c != '.') || (decPt >= 0)) { - break; - } - decPt = mantSize; - } - p += 1; - } - - /* - * Now suck up the digits in the mantissa. Use two integers to - * collect 9 digits each (this is faster than using floating-point). - * If the mantissa has more than 18 digits, ignore the extras, since - * they can't affect the value anyway. - */ - - pExp = p; - p -= mantSize; - if (decPt < 0) { - decPt = mantSize; - } else { - mantSize -= 1; /* One of the digits was the point. */ - } - if (mantSize > 18) { - fracExp = decPt - 18; - mantSize = 18; - } else { - fracExp = decPt - mantSize; - } - if (mantSize == 0) { - fraction = 0.0; - p = string; - goto done; - } else { - long frac1, frac2; - frac1 = 0L; - for ( ; mantSize > 9; mantSize -= 1) { - c = *p; - p += 1; - if (c == '.') { - c = *p; - p += 1; - } - frac1 = 10*frac1 + (c - '0'); - } - frac2 = 0L; - for (; mantSize > 0; mantSize -= 1) { - c = *p; - p += 1; - if (c == '.') { - c = *p; - p += 1; - } - frac2 = 10*frac2 + (c - '0'); - } - fraction = (1.0e9 * frac1) + frac2; - } - - /* - * Skim off the exponent. - */ - - p = pExp; - if ((*p == 'E') || (*p == 'e')) { - p += 1; - if (*p == '-') { - expSign = TRUE; - p += 1; - } else { - if (*p == '+') { - p += 1; - } - expSign = FALSE; - } - while (*p >= '0' && *p <= '9') { - exp = exp * 10 + (*p - '0'); - p += 1; - } - } - if (expSign) { - exp = fracExp - exp; - } else { - exp = fracExp + exp; - } - - /* - * Generate a floating-point number that represents the exponent. - * Do this by processing the exponent one bit at a time to combine - * many powers of 2 of 10. Then combine the exponent with the - * fraction. - */ - - if (exp < 0) { - expSign = TRUE; - exp = -exp; - } else { - expSign = FALSE; - } - if (exp > MAX_EXPONENT) { - exp = MAX_EXPONENT; - } - int d; - for (d = 0; exp != 0; exp >>= 1, d += 1) { - /* Table giving binary powers of 10. Entry */ - /* is 10^2^i. Used to convert decimal */ - /* exponents into floating-point numbers. */ - JNUMBER p10 = 0.0; - switch (d) { - case 0: - p10 = 10.0; - break; - case 1: - p10 = 100.0; - break; - case 2: - p10 = 1.0e4; - break; - case 3: - p10 = 1.0e8; - break; - case 4: - p10 = 1.0e16; - break; - case 5: - p10 = 1.0e32; - break; -#ifndef NOTE_C_LOW_MEM - case 6: - p10 = 1.0e64; - break; - case 7: - p10 = 1.0e128; - break; - case 8: - p10 = 1.0e256; - break; -#endif - } - if (p10 == 0.0) { - break; - } - if (exp & 01) { - if (expSign) { - fraction /= p10; - } else { - fraction *= p10; - } - } - } - -done: - if (endPtr != NULL) { - *endPtr = (char *) p; - } - - if (sign) { - return -fraction; - } - return fraction; -} diff --git a/src/note-c/n_b64.c b/src/note-c/n_b64.c deleted file mode 100644 index e718ce6..0000000 --- a/src/note-c/n_b64.c +++ /dev/null @@ -1,200 +0,0 @@ -/*! - * @file n_b64.c - * - * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. - * * - * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. - * - * This file contains Original Code and/or Modifications of Original Code - * as defined in and that are subject to the Apple Public Source License - * Version 2.0 (the 'License'). You may not use this file except in - * compliance with the License. Please obtain a copy of the License at - * http://www.opensource.apple.com/apsl/ and read it before using this - * file. - * - * The Original Code and all software distributed under the License are - * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER - * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, - * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. - * Please see the License for the specific language governing rights and - * limitations under the License. - * - */ -/* ==================================================================== - * Copyright (c) 1995-1999 The Apache Group. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. 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. - * - * 3. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the Apache Group - * for use in the Apache HTTP server project (http://www.apache.org/)." - * - * 4. The names "Apache Server" and "Apache Group" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache" - * nor may "Apache" appear in their names without prior written - * permission of the Apache Group. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the Apache Group - * for use in the Apache HTTP server project (http://www.apache.org/)." - * - * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY - * EXPRESSED 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 APACHE GROUP OR - * ITS 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. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Group and was originally based - * on public domain software written at the National Center for - * Supercomputing Applications, University of Illinois, Urbana-Champaign. - * For more information on the Apache Group and the Apache HTTP server - * project, please see . - * - * Base64 encoder/decoder. Originally Apache file ap_base64.c - */ - -#include -#include "n_lib.h" - -/* aaaack but it's fast and const should make it shared text page. */ -static const unsigned char pr2six[256] = { - /* ASCII table */ - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, - 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, - 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 -}; - -int JB64DecodeLen(const char *bufcoded) -{ - int nbytesdecoded; - register const unsigned char *bufin; - register int nprbytes; - - bufin = (const unsigned char *) bufcoded; - while (pr2six[*(bufin++)] <= 63); - - nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; - nbytesdecoded = ((nprbytes + 3) / 4) * 3; - - return nbytesdecoded + 1; -} - -int JB64Decode(char *bufplain, const char *bufcoded) -{ - int nbytesdecoded; - register const unsigned char *bufin; - register unsigned char *bufout; - register int nprbytes; - - bufin = (const unsigned char *) bufcoded; - while (pr2six[*(bufin++)] <= 63); - nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; - nbytesdecoded = ((nprbytes + 3) / 4) * 3; - - bufout = (unsigned char *) bufplain; - bufin = (const unsigned char *) bufcoded; - - while (nprbytes > 4) { - *(bufout++) = - (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); - *(bufout++) = - (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); - *(bufout++) = - (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); - bufin += 4; - nprbytes -= 4; - } - - /* Note: (nprbytes == 1) would be an error, so just ingore that case */ - if (nprbytes > 1) { - *(bufout++) = - (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); - } - if (nprbytes > 2) { - *(bufout++) = - (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); - } - if (nprbytes > 3) { - *(bufout++) = - (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); - } - - *(bufout++) = '\0'; - nbytesdecoded -= (4 - nprbytes) & 3; - return nbytesdecoded; -} - -static const char basis_64[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -int JB64EncodeLen(int len) -{ - return ((len + 2) / 3 * 4) + 1; -} - -int JB64Encode(char *encoded, const char *string, int len) -{ - int i; - char *p; - - p = encoded; - for (i = 0; i < len - 2; i += 3) { - *p++ = basis_64[(string[i] >> 2) & 0x3F]; - *p++ = basis_64[((string[i] & 0x3) << 4) | ((int) (string[i + 1] & 0xF0) >> 4)]; - *p++ = basis_64[((string[i + 1] & 0xF) << 2) | ((int) (string[i + 2] & 0xC0) >> 6)]; - *p++ = basis_64[string[i + 2] & 0x3F]; - } - if (i < len) { - *p++ = basis_64[(string[i] >> 2) & 0x3F]; - if (i == (len - 1)) { - *p++ = basis_64[((string[i] & 0x3) << 4)]; - *p++ = '='; - } else { - *p++ = basis_64[((string[i] & 0x3) << 4) | ((int) (string[i + 1] & 0xF0) >> 4)]; - *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; - } - *p++ = '='; - } - - *p++ = '\0'; - return p - encoded; -} diff --git a/src/note-c/n_cjson.c b/src/note-c/n_cjson.c deleted file mode 100644 index 365742b..0000000 --- a/src/note-c/n_cjson.c +++ /dev/null @@ -1,2787 +0,0 @@ -/*! - * @file n_cjson.c - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Portions Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - * MODIFIED for use in notecard primarily by altering default memory allocator - * and by renaming the functions so that they won't conflict with a developer's - * own decision to incorporate the actual production cJSON into their own app. - * In no way shall this interfere with a production cJSON. - * - * Renaming was done as follows: - * CJSON_ -> N_CJSON_ - * cJSON_ -> J - * cJSON -> J - * - * Portions Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software i - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/* disable warnings about old C89 functions in MSVC */ -#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) -#define _CRT_SECURE_NO_DEPRECATE -#endif - -#ifdef __GNUC__ -#pragma GCC visibility push(default) -#endif -#if defined(_MSC_VER) -#pragma warning (push) -/* disable warning about single line comments in system headers */ -#pragma warning (disable : 4001) -#endif - -#include -#include -#include -#include -#include -#include - -// For Note, disable dependencies -#undef ENABLE_LOCALES - -#include "n_lib.h" - -#ifdef ENABLE_LOCALES -#include -#endif - -#if defined(_MSC_VER) -#pragma warning (pop) -#endif -#ifdef __GNUC__ -#pragma GCC visibility pop -#endif - -#define PRINT_TAB_CHARS 4 - -#ifdef NOTE_C_TEST -#include "test_static.h" -#else -#define NOTE_C_STATIC static -#endif - -typedef struct { - const unsigned char *json; - size_t position; -} error; -static error global_error = { NULL, 0 }; - -// Forwards -static J *JNew_Item(void); - -N_CJSON_PUBLIC(const char *) JGetErrorPtr(void) -{ - return (const char*) (global_error.json + global_error.position); -} - -N_CJSON_PUBLIC(char *) JGetStringValue(J *item) -{ - if (!JIsString(item)) { - return NULL; - } - - return item->valuestring; -} - -/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ -#if (N_CJSON_VERSION_MAJOR != 1) || (N_CJSON_VERSION_MINOR != 7) || (N_CJSON_VERSION_PATCH != 7) -#error J.h and J.c have different versions. Make sure that both have the same. -#endif - -N_CJSON_PUBLIC(const char*) JVersion(void) -{ - return NOTE_C_STRINGIZE(N_CJSON_VERSION_MAJOR) "." NOTE_C_STRINGIZE(N_CJSON_VERSION_MINOR) "." NOTE_C_STRINGIZE(N_CJSON_VERSION_PATCH); -} - -NOTE_C_STATIC char Jtolower(char c) -{ - if (c < 'A' || c > 'Z') { - return c; - } - - // 32 is the distance between any ASCII uppercase letter and its lowercase - // counterpart. - return c + 32; -} - -/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ -static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) -{ - if ((string1 == NULL) || (string2 == NULL)) { - return 1; - } - - if (string1 == string2) { - return 0; - } - - for(; Jtolower(*string1) == Jtolower(*string2); (void)string1++, string2++) { - if (*string1 == '\0') { - return 0; - } - } - - return Jtolower(*string1) - Jtolower(*string2); -} - -static unsigned char* Jstrdup(const unsigned char* string) -{ - size_t length = 0; - unsigned char *copy = NULL; - - if (string == NULL) { - return NULL; - } - - length = strlen((const char*)string) + sizeof(""); - copy = (unsigned char*)_Malloc(length); - if (copy == NULL) { - return NULL; - } - memcpy(copy, string, length); - - 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); -} - -/* Internal constructor. */ -static J *JNew_Item(void) -{ - J* node = (J*)_Malloc(sizeof(J)); - if (node) { - memset(node, '\0', sizeof(J)); - } - - return node; -} - -/*! - @brief Free a `J` object. - - @param item A pointer to the object. - */ -N_CJSON_PUBLIC(void) JDelete(J *item) -{ - J *next = NULL; - while (item != NULL) { - next = item->next; - if (!(item->type & JIsReference) && (item->child != NULL)) { - JDelete(item->child); - } - if (!(item->type & JIsReference) && (item->valuestring != NULL)) { - _Free(item->valuestring); - } - if (!(item->type & JStringIsConst) && (item->string != NULL)) { - _Free(item->string); - } - _Free(item); - item = next; - } -} - -/* get the decimal point character of the current locale */ -static unsigned char get_decimal_point(void) -{ -#ifdef ENABLE_LOCALES - struct lconv *lconv = localeconv(); - return (unsigned char) lconv->decimal_point[0]; -#else - return '.'; -#endif -} - -typedef struct { - const unsigned char *content; - size_t length; - size_t offset; - size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ -} parse_buffer; - -/* check if the given size is left to read in a given parse buffer (starting with 1) */ -#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) -/* check if the buffer can be accessed at the given index (starting with 0) */ -#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) -#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) -/* get a pointer to the buffer at the position */ -#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) - -/* Parse the input text to generate a number, and populate the result into item. */ -static Jbool parse_number(J * const item, parse_buffer * const input_buffer) -{ - JNUMBER number = 0; - unsigned char *after_end = NULL; - unsigned char number_c_string[64]; - unsigned char decimal_point = get_decimal_point(); - size_t i = 0; - - if ((input_buffer == NULL) || (input_buffer->content == NULL)) { - return false; - } - - /* copy the number into a temporary buffer and replace '.' with the decimal point - * of the current locale (for strtod) - * This also takes care of '\0' not necessarily being available for marking the end of the input */ - for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) { - switch (buffer_at_offset(input_buffer)[i]) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '+': - case '-': - case 'e': - case 'E': - number_c_string[i] = buffer_at_offset(input_buffer)[i]; - break; - - case '.': - number_c_string[i] = decimal_point; - break; - - default: - goto loop_end; - } - } -loop_end: - number_c_string[i] = '\0'; - - number = JAtoN((const char*)number_c_string, (char**)&after_end); - if (number_c_string == after_end) { - return false; /* parse_error */ - } - item->valuenumber = number; - - // 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 = JAtoI((const char*)number_c_string); - } - - item->type = JNumber; - - input_buffer->offset += (size_t)(after_end - number_c_string); - return true; -} - -/* don't ask me, but the original JSetNumberValue returns an integer or JNUMBER */ -N_CJSON_PUBLIC(JNUMBER) JSetNumberHelper(J *object, JNUMBER number) -{ - if (object == NULL) { - return number; - } - - // 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 = (JINTEGER)number; - } - - return object->valuenumber = number; -} - -typedef struct { - unsigned char *buffer; - size_t length; - size_t offset; - size_t depth; /* current nesting depth (for formatted printing) */ - Jbool noalloc; - Jbool format; /* is this print a formatted print */ - Jbool omitempty; -} printbuffer; - -/* realloc printbuffer if necessary to have at least "needed" bytes more */ -static unsigned char* ensure(printbuffer * const p, size_t needed) -{ - unsigned char *newbuffer = NULL; - size_t newsize = 0; - - if ((p == NULL) || (p->buffer == NULL)) { - return NULL; - } - - if ((p->length > 0) && (p->offset >= p->length)) { - /* make sure that offset is valid */ - return NULL; - } - - if (needed > INT_MAX) { - /* sizes bigger than INT_MAX are currently not supported */ - return NULL; - } - - needed += p->offset + 1; - if (needed <= p->length) { - return p->buffer + p->offset; - } - - if (p->noalloc) { - return NULL; - } - - /* calculate new buffer size */ - if (needed > (INT_MAX / 2)) { - /* overflow of int, use INT_MAX if possible */ - if (needed <= INT_MAX) { - newsize = INT_MAX; - } else { - return NULL; - } - } else { - newsize = needed * 2; - } - - /* otherwise reallocate manually */ - newbuffer = (unsigned char*)_Malloc(newsize); - if (!newbuffer) { - _Free(p->buffer); - p->length = 0; - p->buffer = NULL; - return NULL; - } - if (newbuffer) { - memcpy(newbuffer, p->buffer, p->offset + 1); - } - _Free(p->buffer); - - p->length = newsize; - p->buffer = newbuffer; - - return newbuffer + p->offset; -} - -/* calculate the new length of the string in a printbuffer and update the offset */ -static void update_offset(printbuffer * const buffer) -{ - const unsigned char *buffer_pointer = NULL; - if ((buffer == NULL) || (buffer->buffer == NULL)) { - return; - } - buffer_pointer = buffer->buffer + buffer->offset; - - buffer->offset += strlen((const char*)buffer_pointer); -} - -/* Render the number nicely from the given item into a string. */ -static Jbool print_number(const J * const item, printbuffer * const output_buffer) -{ - if (item == NULL) { - return false; - } - - unsigned char *output_pointer = NULL; - 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 */ - unsigned char decimal_point = get_decimal_point(); - - if (output_buffer == NULL) { - return false; - } - - /* This checks for NaN and Infinity */ - if ((vnum * 0) != 0) { - char *nbuf = (char *) number_buffer; - strlcpy(nbuf, "null", JNTOA_MAX); - length = strlen(nbuf); - } else { - char *nbuf = (char *) number_buffer; - - if (vnum != (JNUMBER)vint) { - JNtoA(vnum, nbuf, -1); - } else { - JItoA(vint, nbuf); - } - - length = strlen(nbuf); - } - - /* conversion failed or buffer overrun occured */ - if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) { - return false; - } - - /* reserve appropriate space in the output */ - output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); - if (output_pointer == NULL) { - return false; - } - - /* copy the printed number to the output and replace locale - * dependent decimal point with '.' */ - for (i = 0; i < ((size_t)length); i++) { - if (number_buffer[i] == decimal_point) { - output_pointer[i] = '.'; - continue; - } - - output_pointer[i] = number_buffer[i]; - } - output_pointer[i] = '\0'; - - output_buffer->offset += (size_t)length; - - return true; -} - -/* parse 4 digit hexadecimal number */ -static unsigned long parse_hex4(const unsigned char * const input) -{ - unsigned long int h = 0; - size_t i = 0; - - for (i = 0; i < 4; i++) { - /* parse digit */ - if ((input[i] >= '0') && (input[i] <= '9')) { - h += (unsigned int) input[i] - '0'; - } else if ((input[i] >= 'A') && (input[i] <= 'F')) { - h += (unsigned int) 10 + input[i] - 'A'; - } else if ((input[i] >= 'a') && (input[i] <= 'f')) { - h += (unsigned int) 10 + input[i] - 'a'; - } else { /* invalid */ - return 0; - } - - if (i < 3) { - /* shift left to make place for the next nibble */ - h = h << 4; - } - } - - return h; -} - -/* converts a UTF-16 literal to UTF-8 - * A literal can be one or two sequences of the form \uXXXX */ -static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) -{ - long unsigned int codepoint = 0; - unsigned long int first_code = 0; - const unsigned char *first_sequence = input_pointer; - unsigned char utf8_length = 0; - unsigned char utf8_position = 0; - unsigned char sequence_length = 0; - unsigned char first_byte_mark = 0; - - if ((input_end - first_sequence) < 6) { - /* input ends unexpectedly */ - goto fail; - } - - /* get the first utf16 sequence */ - first_code = parse_hex4(first_sequence + 2); - - /* check that the code is valid */ - if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) { - goto fail; - } - - /* UTF16 surrogate pair */ - if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) { - const unsigned char *second_sequence = first_sequence + 6; - unsigned int second_code = 0; - sequence_length = 12; /* \uXXXX\uXXXX */ - - if ((input_end - second_sequence) < 6) { - /* input ends unexpectedly */ - goto fail; - } - - if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) { - /* missing second half of the surrogate pair */ - goto fail; - } - - /* get the second utf16 sequence */ - second_code = parse_hex4(second_sequence + 2); - /* check that the code is valid */ - if ((second_code < 0xDC00) || (second_code > 0xDFFF)) { - /* invalid second half of the surrogate pair */ - goto fail; - } - - - /* calculate the unicode codepoint from the surrogate pair */ - codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); - } else { - sequence_length = 6; /* \uXXXX */ - codepoint = first_code; - } - - /* encode as UTF-8 - * takes at maximum 4 bytes to encode: - * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ - if (codepoint < 0x80) { - /* normal ascii, encoding 0xxxxxxx */ - utf8_length = 1; - } else if (codepoint < 0x800) { - /* two bytes, encoding 110xxxxx 10xxxxxx */ - utf8_length = 2; - first_byte_mark = 0xC0; /* 11000000 */ - } else if (codepoint < 0x10000) { - /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ - utf8_length = 3; - first_byte_mark = 0xE0; /* 11100000 */ - } else if (codepoint <= 0x10FFFF) { - /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ - utf8_length = 4; - first_byte_mark = 0xF0; /* 11110000 */ - } else { - /* invalid unicode codepoint */ - goto fail; - } - - /* encode as utf8 */ - for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) { - /* 10xxxxxx */ - (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); - codepoint >>= 6; - } - /* encode first byte */ - if (utf8_length > 1) { - (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); - } else { - (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); - } - - *output_pointer += utf8_length; - - return sequence_length; - -fail: - return 0; -} - -/* Parse the input text into an unescaped cinput, and populate item. */ -static Jbool parse_string(J * const item, parse_buffer * const input_buffer) -{ - const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; - const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; - unsigned char *output_pointer = NULL; - unsigned char *output = NULL; - - /* not a string */ - if (buffer_at_offset(input_buffer)[0] != '\"') { - goto fail; - } - - { - /* calculate approximate size of the output (overestimate) */ - size_t allocation_length = 0; - size_t skipped_bytes = 0; - while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) { - /* is escape sequence */ - if (input_end[0] == '\\') { - if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) { - /* prevent buffer overflow when last input character is a backslash */ - goto fail; - } - skipped_bytes++; - input_end++; - } - input_end++; - } - if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) { - goto fail; /* string ended unexpectedly */ - } - - /* This is at most how much we need for the output */ - allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; - output = (unsigned char*)_Malloc(allocation_length + 1); // trailing '\0' - if (output == NULL) { - goto fail; /* allocation failure */ - } - } - - output_pointer = output; - /* loop through the string literal */ - while (input_pointer < input_end) { - if (*input_pointer != '\\') { - *output_pointer++ = *input_pointer++; - } - /* escape sequence */ - else { - unsigned char sequence_length = 2; - if ((input_end - input_pointer) < 1) { - goto fail; - } - - switch (input_pointer[1]) { - case 'b': - *output_pointer++ = '\b'; - break; - case 'f': - *output_pointer++ = '\f'; - break; - case 'n': - *output_pointer++ = '\n'; - break; - case 'r': - *output_pointer++ = '\r'; - break; - case 't': - *output_pointer++ = '\t'; - break; - case '\"': - case '\\': - case '/': - *output_pointer++ = input_pointer[1]; - break; - - /* UTF-16 literal */ - case 'u': - sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); - if (sequence_length == 0) { - /* failed to convert UTF16-literal to UTF-8 */ - goto fail; - } - break; - - default: - goto fail; - } - input_pointer += sequence_length; - } - } - - /* zero terminate the output */ - *output_pointer = '\0'; - - item->type = JString; - item->valuestring = (char*)output; - - input_buffer->offset = (size_t) (input_end - input_buffer->content); - input_buffer->offset++; - - return true; - -fail: - if (output != NULL) { - _Free(output); - } - - if (input_pointer != NULL) { - input_buffer->offset = (size_t)(input_pointer - input_buffer->content); - } - - return false; -} - -/* Convert a 16-bit number to 4 hex digits, null-terminating it */ -void n_htoa16(uint16_t n, unsigned char *p) -{ - int i; - for (i=0; i<4; i++) { - uint16_t nibble = (n >> 12) & 0xff; - n = n << 4; - if (nibble >= 10) { - *p++ = 'A' + (nibble-10); - } else { - *p++ = '0' + nibble; - } - } - *p = '\0'; -} - -/* Render the cstring provided to an escaped version that can be printed. */ -static Jbool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) -{ - const unsigned char *input_pointer = NULL; - unsigned char *output = NULL; - unsigned char *output_pointer = NULL; - size_t output_length = 0; - /* numbers of additional characters needed for escaping */ - size_t escape_characters = 0; - - if (output_buffer == NULL) { - return false; - } - - /* empty string */ - if (input == NULL) { - output = ensure(output_buffer, 2); // sizeof("\"\"") - if (output == NULL) { - return false; - } - output[0] = '"'; - output[1] = '"'; - output[2] = '\0'; - - return true; - } - - /* set "flag" to 1 if something needs to be escaped */ - for (input_pointer = input; *input_pointer; input_pointer++) { - switch (*input_pointer) { - case '\"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - /* one character escape sequence */ - escape_characters++; - break; - default: - if (*input_pointer < 32) { - /* UTF-16 escape sequence uXXXX */ - escape_characters += 5; - } - break; - } - } - output_length = (size_t)(input_pointer - input) + escape_characters; - - output = ensure(output_buffer, output_length + 2); // sizeof("\"\"") - if (output == NULL) { - return false; - } - - /* no characters have to be escaped */ - if (escape_characters == 0) { - output[0] = '\"'; - memcpy(output + 1, input, output_length); - output[output_length + 1] = '\"'; - output[output_length + 2] = '\0'; - - return true; - } - - output[0] = '\"'; - output_pointer = output + 1; - /* copy the string */ - for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) { - if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) { - /* normal character, copy */ - *output_pointer = *input_pointer; - } else { - /* character needs to be escaped */ - *output_pointer++ = '\\'; - switch (*input_pointer) { - case '\\': - *output_pointer = '\\'; - break; - case '\"': - *output_pointer = '\"'; - break; - case '\b': - *output_pointer = 'b'; - break; - case '\f': - *output_pointer = 'f'; - break; - case '\n': - *output_pointer = 'n'; - break; - case '\r': - *output_pointer = 'r'; - break; - case '\t': - *output_pointer = 't'; - break; - default: - /* escape and print as unicode codepoint */ - *output_pointer++ = 'u'; - n_htoa16(*input_pointer, output_pointer); - output_pointer += 4; - break; - } - } - } - output[output_length + 1] = '\"'; - output[output_length + 2] = '\0'; - - return true; -} - -/* Invoke print_string_ptr (which is useful) on an item. */ -static Jbool print_string(const J * const item, printbuffer * const p) -{ - return print_string_ptr((unsigned char*)item->valuestring, p); -} - -/* Predeclare these prototypes. */ -static Jbool parse_value(J * const item, parse_buffer * const input_buffer); -static Jbool print_value(const J * const item, printbuffer * const output_buffer); -static Jbool parse_array(J * const item, parse_buffer * const input_buffer); -static Jbool print_array(const J * const item, printbuffer * const output_buffer); -static Jbool parse_object(J * const item, parse_buffer * const input_buffer); -static Jbool print_object(const J * const item, printbuffer * const output_buffer); - -/* Utility to jump whitespace and cr/lf */ -static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) -{ - if ((buffer == NULL) || (buffer->content == NULL)) { - return NULL; - } - - while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) { - buffer->offset++; - } - - if (buffer->offset == buffer->length) { - buffer->offset--; - } - - return buffer; -} - -/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ -static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) -{ - if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) { - return NULL; - } - - if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) { - buffer->offset += 3; - } - - return buffer; -} - -/* Parse an object - create a new root, and populate. */ -N_CJSON_PUBLIC(J *) JParseWithOpts(const char *value, const char **return_parse_end, Jbool require_null_terminated) -{ - parse_buffer buffer = { 0, 0, 0, 0 }; - J *item = NULL; - - /* reset error position */ - global_error.json = NULL; - global_error.position = 0; - - if (value == NULL) { - goto fail; - } - - buffer.content = (const unsigned char*)value; - buffer.length = strlen((const char*)value) + 1; // Trailing '\0' - buffer.offset = 0; - - item = JNew_Item(); - if (item == NULL) { /* memory fail */ - goto fail; - } - - if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) { - /* parse failure. ep is set. */ - goto fail; - } - - /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ - if (require_null_terminated) { - buffer_skip_whitespace(&buffer); - if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') { - goto fail; - } - } - if (return_parse_end) { - *return_parse_end = (const char*)buffer_at_offset(&buffer); - } - - return item; - -fail: - if (item != NULL) { - JDelete(item); - } - - if (value != NULL) { - error local_error; - local_error.json = (const unsigned char*)value; - local_error.position = 0; - - if (buffer.offset < buffer.length) { - local_error.position = buffer.offset; - } else if (buffer.length > 0) { - local_error.position = buffer.length - 1; - } - - if (return_parse_end != NULL) { - *return_parse_end = (const char*)local_error.json + local_error.position; - } - - global_error = local_error; - } - - return NULL; -} - -/*! - @brief Parse the passed in C-string as JSON and return a `J` object - representing it. - - @param value The JSON object as a C-string. - - @returns A `J` object or NULL on error (e.g. the string was invalid JSON). - */ -N_CJSON_PUBLIC(J *) JParse(const char *value) -{ - return JParseWithOpts(value, 0, 0); -} - -#define cjson_min(a, b) ((a < b) ? a : b) - -static unsigned char *print(const J * const item, Jbool format, Jbool omitempty) -{ - static const size_t default_buffer_size = 128; - printbuffer buffer[1]; - unsigned char *printed = NULL; - - memset(buffer, 0, sizeof(buffer)); - - /* create buffer */ - buffer->buffer = (unsigned char*) _Malloc(default_buffer_size); - buffer->length = default_buffer_size; - buffer->format = format; - buffer->omitempty = omitempty; - if (buffer->buffer == NULL) { - goto fail; - } - - /* print the value */ - if (!print_value(item, buffer)) { - goto fail; - } - update_offset(buffer); - - /* copy the JSON over to a new buffer */ - printed = (unsigned char*) _Malloc(buffer->offset + 1); - if (printed == NULL) { - goto fail; - } - memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); - printed[buffer->offset] = '\0'; /* just to be sure */ - - /* free the buffer */ - _Free(buffer->buffer); - - return printed; - -fail: - if (buffer->buffer != NULL) { - _Free(buffer->buffer); - } - - if (printed != NULL) { - _Free(printed); - } - - return NULL; -} - -/* Render a J item/entity/structure to text. */ -N_CJSON_PUBLIC(char *) JPrint(const J *item) -{ - if (item == NULL) { - return NULL; - } - 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) { - return NULL; - } - return (char*)print(item, false, false); -} - -N_CJSON_PUBLIC(char *) JPrintUnformattedOmitEmpty(const J *item) -{ - if (item == NULL) { - return NULL; - } - return (char*)print(item, false, true); -} - -N_CJSON_PUBLIC(char *) JPrintBuffered(const J *item, int prebuffer, Jbool fmt) -{ - printbuffer p = { 0, 0, 0, 0, 0, 0, 0 }; - - if (item == NULL) { - return NULL; - } - - if (prebuffer < 0) { - return NULL; - } - - p.buffer = (unsigned char*)_Malloc((size_t)prebuffer); - if (!p.buffer) { - return NULL; - } - - p.length = (size_t)prebuffer; - p.offset = 0; - p.noalloc = false; - p.format = fmt; - - if (!print_value(item, &p)) { - _Free(p.buffer); - return NULL; - } - - return (char*)p.buffer; -} - -static Jbool printPreallocated(J *item, char *buf, const int len, const Jbool fmt, const Jbool omit) -{ - printbuffer p = { 0, 0, 0, 0, 0, 0, 0 }; - - if (item == NULL) { - return false; - } - if ((len < 0) || (buf == NULL)) { - return false; - } - - p.buffer = (unsigned char*)buf; - p.length = (size_t)len; - p.offset = 0; - p.noalloc = true; - p.format = fmt; - p.omitempty = omit; - - return print_value(item, &p); -} - -N_CJSON_PUBLIC(Jbool) JPrintPreallocatedOmitEmpty(J *item, char *buf, const int len, const Jbool fmt) -{ - return printPreallocated(item, buf, len, fmt, true); -} - -N_CJSON_PUBLIC(Jbool) JPrintPreallocated(J *item, char *buf, const int len, const Jbool fmt) -{ - return printPreallocated(item, buf, len, fmt, false); -} - -/* Parser core - when encountering text, process appropriately. */ -static Jbool parse_value(J * const item, parse_buffer * const input_buffer) -{ - if (item == NULL) { - return false; - } - if ((input_buffer == NULL) || (input_buffer->content == NULL)) { - return false; /* no input */ - } - - /* parse the different types of values */ - /* null */ - if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), c_null, c_null_len) == 0)) { - item->type = JNULL; - input_buffer->offset += 4; - return true; - } - /* false */ - if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), c_false, c_false_len) == 0)) { - item->type = JFalse; - input_buffer->offset += 5; - return true; - } - /* true */ - if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), c_true, c_true_len) == 0)) { - item->type = JTrue; - item->valueint = 1; - input_buffer->offset += 4; - return true; - } - /* string */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) { - return parse_string(item, input_buffer); - } - /* number */ - if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) { - return parse_number(item, input_buffer); - } - /* array */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) { - return parse_array(item, input_buffer); - } - /* object */ - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) { - return parse_object(item, input_buffer); - } - - return false; -} - -/* Render a value to text. */ -static Jbool print_value(const J * const item, printbuffer * const output_buffer) -{ - unsigned char *output = NULL; - - if ((item == NULL) || (output_buffer == NULL)) { - return false; - } - - switch ((item->type) & 0xFF) { - case JNULL: - output = ensure(output_buffer, c_null_len+1); - if (output == NULL) { - return false; - } - strlcpy((char*)output, c_null, c_null_len+1); - return true; - - case JFalse: - output = ensure(output_buffer, c_false_len+1); - if (output == NULL) { - return false; - } - strlcpy((char*)output, c_false, c_false_len+1); - return true; - - case JTrue: - output = ensure(output_buffer, c_true_len+1); - if (output == NULL) { - return false; - } - strlcpy((char*)output, c_true, c_true_len+1); - return true; - - case JNumber: - return print_number(item, output_buffer); - - case JRaw: { - size_t raw_length = 0; - if (item->valuestring == NULL) { - return false; - } - - raw_length = strlen(item->valuestring) + 1; // Trailing '\0'; - output = ensure(output_buffer, raw_length); - if (output == NULL) { - return false; - } - memcpy(output, item->valuestring, raw_length); - return true; - } - - case JString: - return print_string(item, output_buffer); - - case JArray: - return print_array(item, output_buffer); - - case JObject: - return print_object(item, output_buffer); - - default: - return false; - } -} - -/* Build an array from input text. */ -static Jbool parse_array(J * const item, parse_buffer * const input_buffer) -{ - J *head = NULL; /* head of the linked list */ - J *current_item = NULL; - - if (input_buffer->depth >= N_CJSON_NESTING_LIMIT) { - return false; /* to deeply nested */ - } - input_buffer->depth++; - - if (buffer_at_offset(input_buffer)[0] != '[') { - /* not an array */ - goto fail; - } - - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) { - /* empty array */ - goto success; - } - - /* check if we skipped to the end of the buffer */ - if (cannot_access_at_index(input_buffer, 0)) { - input_buffer->offset--; - goto fail; - } - - /* step back to character in front of the first element */ - input_buffer->offset--; - /* loop through the comma separated array elements */ - do { - /* allocate next item */ - J *new_item = JNew_Item(); - if (new_item == NULL) { - goto fail; /* allocation failure */ - } - - /* attach next item to list */ - if (head == NULL) { - /* start the linked list */ - current_item = head = new_item; - } else { - /* add to the end and advance */ - current_item->next = new_item; - new_item->prev = current_item; - current_item = new_item; - } - - /* parse next value */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_value(current_item, input_buffer)) { - goto fail; /* failed to parse value */ - } - buffer_skip_whitespace(input_buffer); - } while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); - - if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') { - goto fail; /* expected end of array */ - } - -success: - input_buffer->depth--; - - item->type = JArray; - item->child = head; - - input_buffer->offset++; - - return true; - -fail: - if (head != NULL) { - JDelete(head); - } - - return false; -} - -/* Render an array to text */ -static Jbool print_array(const J * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - size_t length = 0; - J *current_element = item->child; - - if (output_buffer == NULL) { - return false; - } - - /* Compose the output array. */ - /* opening square bracket */ - output_pointer = ensure(output_buffer, 1); - if (output_pointer == NULL) { - return false; - } - - *output_pointer = '['; - output_buffer->offset++; - output_buffer->depth++; - - while (current_element != NULL) { - if (!print_value(current_element, output_buffer)) { - return false; - } - update_offset(output_buffer); - if (current_element->next) { - length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) { - return false; - } - *output_pointer++ = ','; - if(output_buffer->format) { - *output_pointer++ = ' '; - } - *output_pointer = '\0'; - output_buffer->offset += length; - } - current_element = current_element->next; - } - - output_pointer = ensure(output_buffer, 2); - if (output_pointer == NULL) { - return false; - } - *output_pointer++ = ']'; - *output_pointer = '\0'; - output_buffer->depth--; - - return true; -} - -/* Build an object from the text. */ -static Jbool parse_object(J * const item, parse_buffer * const input_buffer) -{ - J *head = NULL; /* linked list head */ - J *current_item = NULL; - - if (input_buffer->depth >= N_CJSON_NESTING_LIMIT) { - return false; /* to deeply nested */ - } - input_buffer->depth++; - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) { - goto fail; /* not an object */ - } - - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) { - goto success; /* empty object */ - } - - /* check if we skipped to the end of the buffer */ - if (cannot_access_at_index(input_buffer, 0)) { - input_buffer->offset--; - goto fail; - } - - /* step back to character in front of the first element */ - input_buffer->offset--; - /* loop through the comma separated array elements */ - do { - /* allocate next item */ - J *new_item = JNew_Item(); - if (new_item == NULL) { - goto fail; /* allocation failure */ - } - - /* attach next item to list */ - if (head == NULL) { - /* start the linked list */ - current_item = head = new_item; - } else { - /* add to the end and advance */ - current_item->next = new_item; - new_item->prev = current_item; - current_item = new_item; - } - - /* parse the name of the child */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_string(current_item, input_buffer)) { - goto fail; /* faile to parse name */ - } - buffer_skip_whitespace(input_buffer); - - /* swap valuestring and string, because we parsed the name */ - current_item->string = current_item->valuestring; - current_item->valuestring = NULL; - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) { - goto fail; /* invalid object */ - } - - /* parse the value */ - input_buffer->offset++; - buffer_skip_whitespace(input_buffer); - if (!parse_value(current_item, input_buffer)) { - goto fail; /* failed to parse value */ - } - buffer_skip_whitespace(input_buffer); - } while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); - - if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) { - goto fail; /* expected end of object */ - } - -success: - input_buffer->depth--; - - item->type = JObject; - item->child = head; - - input_buffer->offset++; - return true; - -fail: - if (head != NULL) { - JDelete(head); - } - - return false; -} - -/* See if there is another non-omitted item looking forward */ -static bool last_non_omitted_object(J * item, printbuffer * const output_buffer) -{ - if (!output_buffer->omitempty) { - return (item->next == 0); - } - while (item->next != 0) { - item = item->next; - int type = JGetItemType(item); - if (type != JTYPE_BOOL_FALSE && type != JTYPE_NUMBER_ZERO && type != JTYPE_STRING_BLANK) { - return false; - } - } - return true; -} - -/* Render an object to text. */ -static Jbool print_object(const J * const item, printbuffer * const output_buffer) -{ - unsigned char *output_pointer = NULL; - size_t length = 0; - J *current_item = item->child; - - if (output_buffer == NULL) { - return false; - } - - /* Compose the output: */ - length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) { - return false; - } - - *output_pointer++ = '{'; - output_buffer->depth++; - if (output_buffer->format) { - *output_pointer++ = '\n'; - } - output_buffer->offset += length; - - while (current_item) { - if (output_buffer->format) { - size_t i; -#if (PRINT_TAB_CHARS == 0) - int needed = output_buffer->depth; -#else - int needed = output_buffer->depth * PRINT_TAB_CHARS; -#endif - output_pointer = ensure(output_buffer, needed); - if (output_pointer == NULL) { - return false; - } - for (i = 0; i < output_buffer->depth; i++) { -#if (PRINT_TAB_CHARS == 0) - *output_pointer++ = '\t'; - output_buffer_offset++; -#else - for (int tc=0; tcoffset++; - } -#endif - } - } - - /* See if it should be eliminated becase of omitempty */ - bool omit = false; - if (output_buffer->omitempty) { - int type = JGetItemType(current_item); - omit =(type == JTYPE_BOOL_FALSE || type == JTYPE_NUMBER_ZERO || type == JTYPE_STRING_BLANK); - } - - /* print item only if not omitted */ - if (!omit) { - - /* print key */ - if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) { - return false; - } - update_offset(output_buffer); - - length = (size_t) (output_buffer->format ? 2 : 1); - output_pointer = ensure(output_buffer, length); - if (output_pointer == NULL) { - return false; - } - *output_pointer++ = ':'; - if (output_buffer->format) { -#if (PRINT_TAB_CHARS == 0) - *output_pointer++ = '\t'; -#else - *output_pointer++ = ' '; -#endif - } - output_buffer->offset += length; - - /* print value */ - if (!print_value(current_item, output_buffer)) { - return false; - } - update_offset(output_buffer); - - /* print comma if not last */ - bool more_fields_coming = !last_non_omitted_object(current_item, output_buffer); - length = (size_t) ((output_buffer->format ? 1 : 0) + (more_fields_coming ? 1 : 0)); - output_pointer = ensure(output_buffer, length + 1); - if (output_pointer == NULL) { - return false; - } - if (more_fields_coming) { - *output_pointer++ = ','; - } - - if (output_buffer->format) { - *output_pointer++ = '\n'; - } - *output_pointer = '\0'; - output_buffer->offset += length; - } - - current_item = current_item->next; - } - -#if (PRINT_TAB_CHARS == 0) - int needed = output_buffer->format ? (output_buffer->depth - 1) : 0; -#else - int needed = output_buffer->format ? ((output_buffer->depth - 1) * PRINT_TAB_CHARS) : 0; -#endif - needed += 2; // }\0 - output_pointer = ensure(output_buffer, needed); - if (output_pointer == NULL) { - return false; - } - if (output_buffer->format) { - size_t i; - for (i = 0; i < (output_buffer->depth - 1); i++) { -#if (PRINT_TAB_CHARS == 0) - *output_pointer++ = '\t'; -#else - for (int tc=0; tcdepth--; - - return true; -} - -/* Get Array size/item / object item. */ -N_CJSON_PUBLIC(int) JGetArraySize(const J *array) -{ - J *child = NULL; - size_t size = 0; - - if (array == NULL) { - return 0; - } - - child = array->child; - - while(child != NULL) { - size++; - child = child->next; - } - - /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ - - return (int)size; -} - -static J* get_array_item(const J *array, size_t index) -{ - J *current_child = NULL; - - if (array == NULL) { - return NULL; - } - - current_child = array->child; - while ((current_child != NULL) && (index > 0)) { - index--; - current_child = current_child->next; - } - - return current_child; -} - -N_CJSON_PUBLIC(J *) JGetArrayItem(const J *array, int index) -{ - if (array == NULL) { - return NULL; - } - if (index < 0) { - return NULL; - } - - return get_array_item(array, (size_t)index); -} - -static J *get_object_item(const J * const object, const char * const name, const Jbool case_sensitive) -{ - J *current_element = NULL; - - if ((object == NULL) || (name == NULL)) { - return NULL; - } - - current_element = object->child; - if (case_sensitive) { - while ((current_element != NULL) && (strcmp(name, current_element->string) != 0)) { - current_element = current_element->next; - } - } else { - while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) { - current_element = current_element->next; - } - } - - return current_element; -} - -N_CJSON_PUBLIC(J *) JGetObjectItem(const J * const object, const char * const string) -{ - if (object == NULL) { - return NULL; - } - return get_object_item(object, string, false); -} - -N_CJSON_PUBLIC(J *) JGetObjectItemCaseSensitive(const J * const object, const char * const string) -{ - if (object == NULL) { - return NULL; - } - return get_object_item(object, string, true); -} - -N_CJSON_PUBLIC(Jbool) JHasObjectItem(const J *object, const char *string) -{ - if (object == NULL) { - return false; - } - return JGetObjectItem(object, string) ? 1 : 0; -} - -/* Utility for array list handling. */ -static void suffix_object(J *prev, J *item) -{ - prev->next = item; - item->prev = prev; -} - -/* Utility for handling references. */ -static J *create_reference(const J *item) -{ - J *reference = NULL; - if (item == NULL) { - return NULL; - } - - reference = JNew_Item(); - if (reference == NULL) { - return NULL; - } - - memcpy(reference, item, sizeof(J)); - reference->string = NULL; - reference->type |= JIsReference; - reference->next = reference->prev = NULL; - return reference; -} - -static Jbool add_item_to_array(J *array, J *item) -{ - J *child = NULL; - - if ((item == NULL) || (array == NULL)) { - return false; - } - - child = array->child; - - if (child == NULL) { - /* list is empty, start new one */ - array->child = item; - } else { - /* append to the end */ - while (child->next) { - child = child->next; - } - suffix_object(child, item); - } - - return true; -} - -/* Add item to array/object. */ -N_CJSON_PUBLIC(void) JAddItemToArray(J *array, J *item) -{ - if (array == NULL || item == NULL) { - return; - } - add_item_to_array(array, item); -} - -#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) -#pragma GCC diagnostic push -#endif -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif -/* helper function to cast away const */ -static void* cast_away_const(const void* string) -{ - return (void*)string; -} -#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) -#pragma GCC diagnostic pop -#endif - - -static Jbool add_item_to_object(J * const object, const char * const string, J * const item, const Jbool constant_key) -{ - char *new_key = NULL; - int new_type = JInvalid; - - if ((object == NULL) || (string == NULL) || (item == NULL)) { - return false; - } - - if (constant_key) { - new_key = (char*)cast_away_const(string); - new_type = item->type | JStringIsConst; - } else { - new_key = (char*)Jstrdup((const unsigned char*)string); - if (new_key == NULL) { - return false; - } - - new_type = item->type & ~JStringIsConst; - } - - if (!(item->type & JStringIsConst) && (item->string != NULL)) { - _Free(item->string); - } - - item->string = new_key; - item->type = new_type; - - return add_item_to_array(object, item); -} - -N_CJSON_PUBLIC(void) JAddItemToObject(J *object, const char *string, J *item) -{ - if (object == NULL || string == NULL || item == NULL) { - return; - } - add_item_to_object(object, string, item, false); -} - -/* Add an item to an object with constant string as key */ -N_CJSON_PUBLIC(void) JAddItemToObjectCS(J *object, const char *string, J *item) -{ - if (object == NULL || string == NULL || item == NULL) { - return; - } - add_item_to_object(object, string, item, true); -} - -N_CJSON_PUBLIC(void) JAddItemReferenceToArray(J *array, J *item) -{ - if (array == NULL || item == NULL) { - return; - } - add_item_to_array(array, create_reference(item)); -} - -N_CJSON_PUBLIC(void) JAddItemReferenceToObject(J *object, const char *string, J *item) -{ - if (object == NULL || string == NULL || item == NULL) { - return; - } - add_item_to_object(object, string, create_reference(item), false); -} - -N_CJSON_PUBLIC(J*) JAddTrueToObject(J * const object, const char * const name) -{ - if (object == NULL) { - return NULL; - } - - J *true_item = JCreateTrue(); - if (add_item_to_object(object, name, true_item, false)) { - return true_item; - } - - JDelete(true_item); - return NULL; -} - -N_CJSON_PUBLIC(J*) JAddFalseToObject(J * const object, const char * const name) -{ - if (object == NULL) { - return NULL; - } - - J *false_item = JCreateFalse(); - if (add_item_to_object(object, name, false_item, false)) { - return false_item; - } - - JDelete(false_item); - return NULL; -} - -/*! - @brief Add a boolean field to a `J` object. - - @param object The object to add the field to. - @param name The name of the field. - @param boolean The value of the field. - - @returns A pointer to the newly-added boolean field or NULL on error. - */ -N_CJSON_PUBLIC(J*) JAddBoolToObject(J * const object, const char * const name, const Jbool boolean) -{ - if (object == NULL) { - return NULL; - } - - J *bool_item = JCreateBool(boolean); - if (add_item_to_object(object, name, bool_item, false)) { - return bool_item; - } - - JDelete(bool_item); - return NULL; -} - -/*! - @brief Add a number field to a `J` object. - - @param object The object to add the field to. - @param name The name of the field. - @param number The value of the field. - - @returns A pointer to the newly-added number field or NULL on error. - */ -N_CJSON_PUBLIC(J*) JAddNumberToObject(J * const object, const char * const name, const JNUMBER number) -{ - if (object == NULL) { - return NULL; - } - - J *number_item = JCreateNumber(number); - if (add_item_to_object(object, name, number_item, false)) { - return number_item; - } - - JDelete(number_item); - 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. - - @param object The object to add the field to. - @param name The name of the field. - @param string The value of the field. - - @returns A pointer to the newly-added string field or NULL on error. - */ -N_CJSON_PUBLIC(J*) JAddStringToObject(J * const object, const char * const name, const char * const string) -{ - if (object == NULL || string == NULL) { - return NULL; - } - - J *string_item = JCreateString(string); - if (add_item_to_object(object, name, string_item, false)) { - return string_item; - } - - JDelete(string_item); - return NULL; -} - -N_CJSON_PUBLIC(J*) JAddRawToObject(J * const object, const char * const name, const char * const raw) -{ - if (object == NULL || raw == NULL) { - return NULL; - } - - J *raw_item = JCreateRaw(raw); - if (add_item_to_object(object, name, raw_item, false)) { - return raw_item; - } - - JDelete(raw_item); - return NULL; -} - -/*! - @brief Add an object field to another `J` object. - - @param object The object to add the field to. - @param name The name of the field. - - @returns A pointer to the newly-added object field or NULL on error. - */ -N_CJSON_PUBLIC(J*) JAddObjectToObject(J * const object, const char * const name) -{ - if (object == NULL) { - return NULL; - } - - J *object_item = JCreateObject(); - if (add_item_to_object(object, name, object_item, false)) { - return object_item; - } - - JDelete(object_item); - return NULL; -} - -/*! - @brief Add an array field to a `J` object. - - @param object The object to add the field to. - @param name The name of the field. - - @returns A pointer to the newly-added array field or NULL on error. - */ -N_CJSON_PUBLIC(J*) JAddArrayToObject(J * const object, const char * const name) -{ - if (object == NULL) { - return NULL; - } - - J *array = JCreateArray(); - if (add_item_to_object(object, name, array, false)) { - return array; - } - - JDelete(array); - return NULL; -} - -N_CJSON_PUBLIC(J *) JDetachItemViaPointer(J *parent, J * const item) -{ - if (parent == NULL || item == NULL) { - return NULL; - } - - if (item->prev != NULL) { - /* not the first element */ - item->prev->next = item->next; - } - if (item->next != NULL) { - /* not the last element */ - item->next->prev = item->prev; - } - - if (item == parent->child) { - /* first element */ - parent->child = item->next; - } - /* make sure the detached item doesn't point anywhere anymore */ - item->prev = NULL; - item->next = NULL; - - return item; -} - -N_CJSON_PUBLIC(J *) JDetachItemFromArray(J *array, int which) -{ - if (array == NULL) { - return NULL; - } - if (which < 0) { - return NULL; - } - - return JDetachItemViaPointer(array, get_array_item(array, (size_t)which)); -} - -N_CJSON_PUBLIC(void) JDeleteItemFromArray(J *array, int which) -{ - if (array == NULL) { - return; - } - JDelete(JDetachItemFromArray(array, which)); -} - -N_CJSON_PUBLIC(J *) JDetachItemFromObject(J *object, const char *string) -{ - if (object == NULL) { - return NULL; - } - - J *to_detach = JGetObjectItem(object, string); - - return JDetachItemViaPointer(object, to_detach); -} - -N_CJSON_PUBLIC(J *) JDetachItemFromObjectCaseSensitive(J *object, const char *string) -{ - if (object == NULL) { - return NULL; - } - - J *to_detach = JGetObjectItemCaseSensitive(object, string); - - return JDetachItemViaPointer(object, to_detach); -} - -N_CJSON_PUBLIC(void) JDeleteItemFromObject(J *object, const char *string) -{ - if (object == NULL) { - return; - } - JDelete(JDetachItemFromObject(object, string)); -} - -N_CJSON_PUBLIC(void) JDeleteItemFromObjectCaseSensitive(J *object, const char *string) -{ - if (object == NULL) { - return; - } - JDelete(JDetachItemFromObjectCaseSensitive(object, string)); -} - -/* Replace array/object items with new ones. */ -N_CJSON_PUBLIC(void) JInsertItemInArray(J *array, int which, J *newitem) -{ - if (array == NULL || newitem == NULL) { - return; - } - - J *after_inserted = NULL; - - if (which < 0) { - return; - } - - after_inserted = get_array_item(array, (size_t)which); - if (after_inserted == NULL) { - add_item_to_array(array, newitem); - return; - } - - newitem->next = after_inserted; - newitem->prev = after_inserted->prev; - after_inserted->prev = newitem; - if (after_inserted == array->child) { - array->child = newitem; - } else { - newitem->prev->next = newitem; - } -} - -N_CJSON_PUBLIC(Jbool) JReplaceItemViaPointer(J * const parent, J * const item, J * replacement) -{ - if (parent == NULL || replacement == NULL || item == NULL) { - return false; - } - - if (replacement == item) { - return true; - } - - replacement->next = item->next; - replacement->prev = item->prev; - - if (replacement->next != NULL) { - replacement->next->prev = replacement; - } - if (replacement->prev != NULL) { - replacement->prev->next = replacement; - } - if (parent->child == item) { - parent->child = replacement; - } - - item->next = NULL; - item->prev = NULL; - JDelete(item); - - return true; -} - -N_CJSON_PUBLIC(void) JReplaceItemInArray(J *array, int which, J *newitem) -{ - if (array == NULL || newitem == NULL) { - return; - } - - if (which < 0) { - return; - } - - JReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); -} - -static Jbool replace_item_in_object(J *object, const char *string, J *replacement, Jbool case_sensitive) -{ - if (object == NULL || replacement == NULL || string == NULL) { - return false; - } - - /* replace the name in the replacement */ - if (!(replacement->type & JStringIsConst) && (replacement->string != NULL)) { - _Free(replacement->string); - } - replacement->string = (char*)Jstrdup((const unsigned char*)string); - replacement->type &= ~JStringIsConst; - - JReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); - - return true; -} - -N_CJSON_PUBLIC(void) JReplaceItemInObject(J *object, const char *string, J *newitem) -{ - if (object == NULL || newitem == NULL) { - return; - } - replace_item_in_object(object, string, newitem, false); -} - -N_CJSON_PUBLIC(void) JReplaceItemInObjectCaseSensitive(J *object, const char *string, J *newitem) -{ - if (object == NULL || newitem == NULL) { - return; - } - replace_item_in_object(object, string, newitem, true); -} - -N_CJSON_PUBLIC(J *) JCreateTrue(void) -{ - J *item = JNew_Item(); - if(item) { - item->type = JTrue; - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateFalse(void) -{ - J *item = JNew_Item(); - if(item) { - item->type = JFalse; - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateBool(Jbool b) -{ - J *item = JNew_Item(); - if(item) { - item->type = b ? JTrue : JFalse; - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateNumber(JNUMBER num) -{ - J *item = JNew_Item(); - if(item) { - item->type = JNumber; - item->valuenumber = num; - - // 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 = (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(); - if(item) { - item->type = JString; - item->valuestring = (char*)Jstrdup((const unsigned char*)string); - if(!item->valuestring) { - JDelete(item); - return NULL; - } - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateStringValue(const char *string) -{ - J *item = JNew_Item(); - if (item != NULL) { - item->type = JString; - item->valuestring = (char*)cast_away_const(string); - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateStringReference(const char *string) -{ - J *item = JNew_Item(); - if (item != NULL) { - item->type = JString | JIsReference; - item->valuestring = (char*)cast_away_const(string); - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateObjectReference(const J *child) -{ - if (child == NULL) { - return NULL; - } - J *item = JNew_Item(); - if (item != NULL) { - item->type = JObject | JIsReference; - item->child = (J*)cast_away_const(child); - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateArrayReference(const J *child) -{ - if (child == NULL) { - return NULL; - } - J *item = JNew_Item(); - if (item != NULL) { - item->type = JArray | JIsReference; - item->child = (J*)cast_away_const(child); - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateRaw(const char *raw) -{ - J *item = JNew_Item(); - if(item) { - item->type = JRaw; - item->valuestring = (char*)Jstrdup((const unsigned char*)raw); - if(!item->valuestring) { - JDelete(item); - return NULL; - } - } - return item; -} - -N_CJSON_PUBLIC(J *) JCreateArray(void) -{ - J *item = JNew_Item(); - if(item) { - item->type=JArray; - } - return item; -} - -/*! - @brief Create a new `J` object. - - To free the object, use `JDelete`. - - @returns A pointer to the newly-created object. - */ -N_CJSON_PUBLIC(J *) JCreateObject(void) -{ - J *item = JNew_Item(); - if (item) { - item->type = JObject; - } - return item; -} - -/* Create Arrays: */ -N_CJSON_PUBLIC(J *) JCreateIntArray(const long int *numbers, int count) -{ - size_t i = 0; - J *n = NULL; - J *p = NULL; - J *a = NULL; - - if ((count < 0) || (numbers == NULL)) { - return NULL; - } - - a = JCreateArray(); - for(i = 0; a && (i < (size_t)count); i++) { - n = JCreateNumber(numbers[i]); - if (!n) { - JDelete(a); - return NULL; - } - if(!i) { - a->child = n; - } else { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -N_CJSON_PUBLIC(J *) JCreateNumberArray(const JNUMBER *numbers, int count) -{ - size_t i = 0; - J *n = NULL; - J *p = NULL; - J *a = NULL; - - if ((count < 0) || (numbers == NULL)) { - return NULL; - } - - a = JCreateArray(); - - for(i = 0; a && (i < (size_t)count); i++) { - n = JCreateNumber(numbers[i]); - if(!n) { - JDelete(a); - return NULL; - } - if(!i) { - a->child = n; - } else { - suffix_object(p, n); - } - p = n; - } - - return a; -} - -N_CJSON_PUBLIC(J *) JCreateStringArray(const char **strings, int count) -{ - size_t i = 0; - J *n = NULL; - J *p = NULL; - J *a = NULL; - - if ((count < 0) || (strings == NULL)) { - return NULL; - } - - a = JCreateArray(); - - for (i = 0; a && (i < (size_t)count); i++) { - n = JCreateString(strings[i]); - if(!n) { - JDelete(a); - return NULL; - } - if(!i) { - a->child = n; - } else { - suffix_object(p,n); - } - p = n; - } - - return a; -} - -/* Duplication */ -N_CJSON_PUBLIC(J *) JDuplicate(const J *item, Jbool recurse) -{ - J *newitem = NULL; - J *child = NULL; - J *next = NULL; - J *newchild = NULL; - - /* Bail on bad ptr */ - if (!item) { - goto fail; - } - /* Create new item */ - newitem = JNew_Item(); - if (!newitem) { - goto fail; - } - /* Copy over all vars */ - newitem->type = item->type & (~JIsReference); - newitem->valueint = item->valueint; - newitem->valuenumber = item->valuenumber; - if (item->valuestring) { - newitem->valuestring = (char*)Jstrdup((unsigned char*)item->valuestring); - if (!newitem->valuestring) { - goto fail; - } - } - if (item->string) { - newitem->string = (item->type&JStringIsConst) ? item->string : (char*)Jstrdup((unsigned char*)item->string); - if (!newitem->string) { - goto fail; - } - } - /* If non-recursive, then we're done! */ - if (!recurse) { - return newitem; - } - /* Walk the ->next chain for the child. */ - child = item->child; - while (child != NULL) { - newchild = JDuplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ - if (!newchild) { - goto fail; - } - if (next != NULL) { - /* If newitem->child already set, then crosswire ->prev and ->next and move on */ - next->next = newchild; - newchild->prev = next; - next = newchild; - } else { - /* Set newitem->child and move to it */ - newitem->child = newchild; - next = newchild; - } - child = child->next; - } - - return newitem; - -fail: - if (newitem != NULL) { - JDelete(newitem); - } - - return NULL; -} - -N_CJSON_PUBLIC(void) JMinify(char *json) -{ - unsigned char *into = (unsigned char*)json; - - if (json == NULL) { - return; - } - - while (*json) { - if (*json == ' ') { - json++; - } else if (*json == '\t') { - /* Whitespace characters. */ - json++; - } else if (*json == '\r') { - json++; - } else if (*json=='\n') { - json++; - } else if ((*json == '/') && (json[1] == '/')) { - /* double-slash comments, to end of line. */ - while (*json && (*json != '\n')) { - json++; - } - } else if ((*json == '/') && (json[1] == '*')) { - /* multiline comments. */ - while (*json && !((*json == '*') && (json[1] == '/'))) { - json++; - } - json += 2; - } else if (*json == '\"') { - /* string literals, which are \" sensitive. */ - *into++ = (unsigned char)*json++; - while (*json && (*json != '\"')) { - if (*json == '\\') { - *into++ = (unsigned char)*json++; - } - *into++ = (unsigned char)*json++; - } - *into++ = (unsigned char)*json++; - } else { - /* All other characters. */ - *into++ = (unsigned char)*json++; - } - } - - /* and null-terminate. */ - *into = '\0'; -} - -N_CJSON_PUBLIC(Jbool) JIsInvalid(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xFF) == JInvalid; -} - -N_CJSON_PUBLIC(Jbool) JIsFalse(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xFF) == JFalse; -} - -N_CJSON_PUBLIC(Jbool) JIsTrue(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xff) == JTrue; -} - - -N_CJSON_PUBLIC(Jbool) JIsBool(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & (JTrue | JFalse)) != 0; -} -N_CJSON_PUBLIC(Jbool) JIsNull(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xFF) == JNULL; -} - -N_CJSON_PUBLIC(Jbool) JIsNumber(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xFF) == JNumber; -} - -N_CJSON_PUBLIC(Jbool) JIsString(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xFF) == JString; -} - -N_CJSON_PUBLIC(Jbool) JIsArray(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xFF) == JArray; -} - -N_CJSON_PUBLIC(Jbool) JIsObject(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xFF) == JObject; -} - -N_CJSON_PUBLIC(Jbool) JIsRaw(const J * const item) -{ - if (item == NULL) { - return false; - } - return (item->type & 0xFF) == JRaw; -} - -N_CJSON_PUBLIC(Jbool) JCompare(const J * const a, const J * const b, const Jbool case_sensitive) -{ - if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || JIsInvalid(a)) { - return false; - } - - /* check if type is valid */ - switch (a->type & 0xFF) { - case JFalse: - case JTrue: - case JNULL: - case JNumber: - case JString: - case JRaw: - case JArray: - case JObject: - break; - - default: - return false; - } - - /* identical objects are equal */ - if (a == b) { - return true; - } - - switch (a->type & 0xFF) { - /* in these cases and equal type is enough */ - case JFalse: - case JTrue: - case JNULL: - return true; - - case JNumber: - if (a->valuenumber == b->valuenumber) { - return true; - } - return false; - - case JString: - case JRaw: - if ((a->valuestring == NULL) || (b->valuestring == NULL)) { - return false; - } - if (strcmp(a->valuestring, b->valuestring) == 0) { - return true; - } - - return false; - - case JArray: { - J *a_element = a->child; - J *b_element = b->child; - - for (; (a_element != NULL) && (b_element != NULL);) { - if (!JCompare(a_element, b_element, case_sensitive)) { - return false; - } - - a_element = a_element->next; - b_element = b_element->next; - } - - /* one of the arrays is longer than the other */ - if (a_element != b_element) { - return false; - } - - return true; - } - - case JObject: { - J *a_element = NULL; - J *b_element = NULL; - JArrayForEach(a_element, a) { - /* TODO This has O(n^2) runtime, which is horrible! */ - b_element = get_object_item(b, a_element->string, case_sensitive); - if (b_element == NULL) { - return false; - } - - if (!JCompare(a_element, b_element, case_sensitive)) { - return false; - } - } - - /* doing this twice, once on a and b to prevent true comparison if a subset of b - * TODO: Do this the proper way, this is just a fix for now */ - JArrayForEach(b_element, b) { - a_element = get_object_item(a, b_element->string, case_sensitive); - if (a_element == NULL) { - return false; - } - - if (!JCompare(b_element, a_element, case_sensitive)) { - return false; - } - } - - return true; - } - - default: - return false; - } -} diff --git a/src/note-c/n_cjson.h b/src/note-c/n_cjson.h deleted file mode 100644 index 6e85e95..0000000 --- a/src/note-c/n_cjson.h +++ /dev/null @@ -1,332 +0,0 @@ -/*! - * @file n_cjson.h - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Portions Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - * MODIFIED for use in notecard primarily by altering default memory allocator - * and by renaming the functions so that they won't conflict with a developer's - * own decision to incorporate the actual production cJSON into their own app. - * In no way shall this interfere with a production cJSON. - * - * Renaming was done as follows: - * CJSON_ -> N_CJSON_ - * cJSON_ -> J - * cJSON -> J - * - * Portions Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software i - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#ifndef J_h -#define J_h - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* project version */ -#define N_CJSON_VERSION_MAJOR 1 -#define N_CJSON_VERSION_MINOR 7 -#define N_CJSON_VERSION_PATCH 7 - -#include - -/* J Types: */ -#define JInvalid (0) -#define JFalse (1 << 0) -#define JTrue (1 << 1) -#define JNULL (1 << 2) -#define JNumber (1 << 3) -#define JString (1 << 4) -#define JArray (1 << 5) -#define JObject (1 << 6) -#define JRaw (1 << 7) /* raw json */ - -#define JIsReference 256 -#define JStringIsConst 512 - -/*! - @brief The core JSON object type used by note-c. - - When using note-c, treat this struct as opaque. You should never have to work - directly with its members. - */ -typedef struct J { - /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ - struct J *next; - struct J *prev; - /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ - struct J *child; - - /* The type of the item, as above. */ - int type; - - /* The item's string, if type==JString and type == JRaw */ - char *valuestring; - /* writing to valueint is DEPRECATED, use JSetNumberValue instead */ - 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. */ - char *string; -} J; - -typedef struct JHooks { - void *(*malloc_fn)(size_t sz); - void (*free_fn)(void *ptr); -} JHooks; - -typedef int Jbool; - -#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) -#define __WINDOWS__ -#endif -#ifdef __WINDOWS__ - -/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 2 define options: - -N_CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols -N_CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) -N_CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol - -For *nix builds that support visibility attribute, you can define similar behavior by - -setting default visibility to hidden by adding --fvisibility=hidden (for gcc) -or --xldscope=hidden (for sun cc) -to CFLAGS - -then using the N_CJSON_API_VISIBILITY flag to "export" the same symbols the way N_CJSON_EXPORT_SYMBOLS does - -*/ - -/* export symbols by default, this is necessary for copy pasting the C and header file */ -#if !defined(N_CJSON_HIDE_SYMBOLS) && !defined(N_CJSON_IMPORT_SYMBOLS) && !defined(N_CJSON_EXPORT_SYMBOLS) -#define N_CJSON_EXPORT_SYMBOLS -#endif - -#if defined(N_CJSON_HIDE_SYMBOLS) -#define N_CJSON_PUBLIC(type) type __stdcall -#elif defined(N_CJSON_EXPORT_SYMBOLS) -#define N_CJSON_PUBLIC(type) __declspec(dllexport) type __stdcall -#elif defined(N_CJSON_IMPORT_SYMBOLS) -#define N_CJSON_PUBLIC(type) __declspec(dllimport) type __stdcall -#endif -#else /* !WIN32 */ -#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(N_CJSON_API_VISIBILITY) -#define N_CJSON_PUBLIC(type) __attribute__((visibility("default"))) type -#else -#define N_CJSON_PUBLIC(type) type -#endif -#endif - -/*! - @brief The maximum nesting level for JSON objects before a parsing error. - - Default value: `100` - - For example, if you have a JSON object that contains multiple, nested - objects like this - - { - "x": - { - "x:" - { - . - . - . - } - } - } - - And the nesting level exceeds `N_CJSON_NESTING_LIMIT`, then calling `JParse` on - a `J *` representing this object will return an error (NULL). - - This exists to prevent the cJSON parser from causing a stack overflow. The - user may override this macro at build time (e.g. - -DN_CJSON_NESTING_LIMIT=200) to increase or reduce the limit. - */ -#ifndef N_CJSON_NESTING_LIMIT -#define N_CJSON_NESTING_LIMIT 100 -#endif - -/* returns the version of J as a string */ -N_CJSON_PUBLIC(const char*) JVersion(void); - -/* Supply malloc, realloc and free functions to J */ -N_CJSON_PUBLIC(void) JInitHooks(JHooks* hooks); - -/* Memory Management: the caller is always responsible to free the results from all variants of JParse (with JDelete) and JPrint (with stdlib free, JHooks.free_fn, or JFree as appropriate). The exception is JPrintPreallocated, where the caller has full responsibility of the buffer. */ -/* Supply a block of JSON, and this returns a J object you can interrogate. */ -N_CJSON_PUBLIC(J *) JParse(const char *value); -/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ -/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match JGetErrorPtr(). */ -N_CJSON_PUBLIC(J *) JParseWithOpts(const char *value, const char **return_parse_end, Jbool require_null_terminated); - -/* Render a J entity to text for transfer/storage. */ -N_CJSON_PUBLIC(char *) JPrint(const J *item); -/* Render a J entity to text for transfer/storage without any formatting. */ -N_CJSON_PUBLIC(char *) JPrintUnformatted(const J *item); -/* Render a J entity to text for transfer/storage without any formatting and without fields that are false, 0, or "". */ -N_CJSON_PUBLIC(char *) JPrintUnformattedOmitEmpty(const J *item); -/* Render a J entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ -N_CJSON_PUBLIC(char *) JPrintBuffered(const J *item, int prebuffer, Jbool fmt); -/* Render a J entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ -/* NOTE: J is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ -N_CJSON_PUBLIC(Jbool) JPrintPreallocated(J *item, char *buffer, const int length, const Jbool format); -N_CJSON_PUBLIC(Jbool) JPrintPreallocatedOmitEmpty(J *item, char *buffer, const int length, const Jbool format); -/* Delete a J entity and all subentities. */ -N_CJSON_PUBLIC(void) JDelete(J *c); - -/* Returns the number of items in an array (or object). */ -N_CJSON_PUBLIC(int) JGetArraySize(const J *array); -#define JGetObjectItems JGetArraySize -/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ -N_CJSON_PUBLIC(J *) JGetArrayItem(const J *array, int index); -/* Get item "string" from object. Case insensitive. */ -N_CJSON_PUBLIC(J *) JGetObjectItem(const J * const object, const char * const string); -N_CJSON_PUBLIC(J *) JGetObjectItemCaseSensitive(const J * const object, const char * const string); -N_CJSON_PUBLIC(Jbool) JHasObjectItem(const J *object, const char *string); -/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when JParse() returns 0. 0 when JParse() succeeds. */ -N_CJSON_PUBLIC(const char *) JGetErrorPtr(void); - -/* Check if the item is a string and return its valuestring */ -N_CJSON_PUBLIC(char *) JGetStringValue(J *item); - -/* These functions check the type of an item */ -N_CJSON_PUBLIC(Jbool) JIsInvalid(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsFalse(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsTrue(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsBool(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsNull(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsNumber(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsString(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsArray(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsObject(const J * const item); -N_CJSON_PUBLIC(Jbool) JIsRaw(const J * const item); - -/* These calls create a J item of the appropriate type. */ -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); -N_CJSON_PUBLIC(J *) JCreateArray(void); -N_CJSON_PUBLIC(J *) JCreateObject(void); - -/* Create a string where valuestring references a string so - * it will not be freed by JDelete */ -N_CJSON_PUBLIC(J *) JCreateStringValue(const char *string); -N_CJSON_PUBLIC(J *) JCreateStringReference(const char *string); -/* Create an object/arrray that only references it's elements so - * they will not be freed by JDelete */ -N_CJSON_PUBLIC(J *) JCreateObjectReference(const J *child); -N_CJSON_PUBLIC(J *) JCreateArrayReference(const J *child); - -/* These utilities create an Array of count items. */ -N_CJSON_PUBLIC(J *) JCreateIntArray(const long int *numbers, int count); -N_CJSON_PUBLIC(J *) JCreateNumberArray(const JNUMBER *numbers, int count); -N_CJSON_PUBLIC(J *) JCreateStringArray(const char **strings, int count); - -/* Append item to the specified array/object. */ -N_CJSON_PUBLIC(void) JAddItemToArray(J *array, J *item); -N_CJSON_PUBLIC(void) JAddItemToObject(J *object, const char *string, J *item); -/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the J object. - * WARNING: When this function was used, make sure to always check that (item->type & JStringIsConst) is zero before - * writing to `item->string` */ -N_CJSON_PUBLIC(void) JAddItemToObjectCS(J *object, const char *string, J *item); -/* Append reference to item to the specified array/object. Use this when you want to add an existing J to a new J, but don't want to corrupt your existing J. */ -N_CJSON_PUBLIC(void) JAddItemReferenceToArray(J *array, J *item); -N_CJSON_PUBLIC(void) JAddItemReferenceToObject(J *object, const char *string, J *item); - -/* Remove/Detatch items from Arrays/Objects. */ -N_CJSON_PUBLIC(J *) JDetachItemViaPointer(J *parent, J * const item); -N_CJSON_PUBLIC(J *) JDetachItemFromArray(J *array, int which); -N_CJSON_PUBLIC(void) JDeleteItemFromArray(J *array, int which); -N_CJSON_PUBLIC(J *) JDetachItemFromObject(J *object, const char *string); -N_CJSON_PUBLIC(J *) JDetachItemFromObjectCaseSensitive(J *object, const char *string); -N_CJSON_PUBLIC(void) JDeleteItemFromObject(J *object, const char *string); -N_CJSON_PUBLIC(void) JDeleteItemFromObjectCaseSensitive(J *object, const char *string); - -/* Update array items. */ -N_CJSON_PUBLIC(void) JInsertItemInArray(J *array, int which, J *newitem); /* Shifts pre-existing items to the right. */ -N_CJSON_PUBLIC(Jbool) JReplaceItemViaPointer(J * const parent, J * const item, J * replacement); -N_CJSON_PUBLIC(void) JReplaceItemInArray(J *array, int which, J *newitem); -N_CJSON_PUBLIC(void) JReplaceItemInObject(J *object,const char *string,J *newitem); -N_CJSON_PUBLIC(void) JReplaceItemInObjectCaseSensitive(J *object,const char *string,J *newitem); - -/* Duplicate a J item */ -N_CJSON_PUBLIC(J *) JDuplicate(const J *item, Jbool recurse); -/* Duplicate will create a new, identical J item to the one you pass, in new memory that will -need to be released. With recurse!=0, it will duplicate any children connected to the item. -The item->next and ->prev pointers are always zero on return from Duplicate. */ -/* Recursively compare two J items for equality. If either a or b is NULL or invalid, they will be considered unequal. - * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ -N_CJSON_PUBLIC(Jbool) JCompare(const J * const a, const J * const b, const Jbool case_sensitive); - - -N_CJSON_PUBLIC(void) JMinify(char *json); - -/* Helper functions for creating and adding items to an object at the same time. - * They return the added item or NULL on failure. */ -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); -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); -N_CJSON_PUBLIC(J*) JAddArrayToObject(J * const object, const char * const name); -#define JConvertToJSONString JPrintUnformatted -#define JConvertFromJSONString JParse - -/* When assigning an integer value, it needs to be propagated to valuenumber too. */ -#define JSetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuenumber = (number) : (number)) -/* helper for the JSetNumberValue macro */ -N_CJSON_PUBLIC(JNUMBER) JSetNumberHelper(J *object, JNUMBER number); -#define JSetNumberValue(object, number) ((object != NULL) ? JSetNumberHelper(object, (JNUMBER)number) : (number)) - -/* Macro for iterating over an array or object */ -#define JArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) -// Iterate over the fields of an object -#define JObjectForEach(element, array) JArrayForEach(element, array) - -/* malloc/free objects using the malloc/free functions that have been set with JInitHooks */ -N_CJSON_PUBLIC(void *) JMalloc(size_t size); -N_CJSON_PUBLIC(void) JFree(void *object); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/note-c/n_cjson_helpers.c b/src/note-c/n_cjson_helpers.c deleted file mode 100644 index 9f0279c..0000000 --- a/src/note-c/n_cjson_helpers.c +++ /dev/null @@ -1,694 +0,0 @@ -/*! - * @file n_cjson_helpers.c - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - */ - -#include -#include -#include -#include "n_lib.h" - -/*! - @brief Determine if a field is present in a JSON object. - - @param json The JSON object. - @param field The field to find. - - @returns True if the field is present, or false if the field is not present or - the JSON object is NULL. - */ -bool JIsPresent(J *json, const char *field) -{ - if (json == NULL) { - return false; - } - return (JGetObjectItem(json, field) != NULL); -} - -/*! - @brief Get the value of a string field from a JSON object. - - @param json The JSON object. - @param field The string field to query. - - @returns A pointer to the string if the field exists and is a string, otherwise - the empty string (""). - - @note The returned string is a pointer to the string contained in the JSON - object. It is not a copy of the string, so once the JSON object is freed, - the pointer is no longer valid. - */ -char *JGetString(J *json, const char *field) -{ - if (json == NULL) { - return (char *) c_nullstring; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return (char *) c_nullstring; - } - if (!JIsString(item)) { - return (char *) c_nullstring; - } - if (item->valuestring == NULL) { - return (char *) c_nullstring; - } - return item->valuestring; -} - -/*! - @brief Get the value of an array field from a JSON object. - - @param json The JSON object. - @param field The array field to query. - - @returns A pointer to the array, which is itself a JSON object (`J *`), if the - field exists and is an array, otherwise NULL. - - @note The returned JSON object is a pointer to the array contained in the - parent JSON object. It is not a copy, so once the parent JSON object is - freed, the pointer is no longer valid. - */ -J *JGetArray(J *json, const char *field) -{ - if (json == NULL) { - return NULL; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return NULL; - } - if (!JIsArray(item)) { - return NULL; - } - return item; -} - -/*! - @brief Get the value of an object field from a JSON object. - - @param json The JSON object. - @param field The object field to query. - - @returns A pointer to the object, which is itself a JSON object (`J *`), if the - field exists and is an object, otherwise NULL. - - @note The returned JSON object is a pointer to the object contained in the - parent JSON object. It is not a copy, so once the parent JSON object is - freed, the pointer is no longer valid. - */ -J *JGetObject(J *json, const char *field) -{ - if (json == NULL) { - return NULL; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return NULL; - } - if (!JIsObject(item)) { - return NULL; - } - return item; -} - -//**************************************************************************/ -/*! - @brief Return the boolean representation of an item. - @param item The JSON item. - @returns The boolean value. -*/ -/**************************************************************************/ -bool JBoolValue(J *item) -{ - if (item == NULL) { - return false; - } - return ((item->type & 0xff) == JTrue); -} - -//**************************************************************************/ -/*! - @brief Return the string representation of an item. - @param item The JSON item. - @returns The string value, or empty string, if NULL. -*/ -/**************************************************************************/ -char *JStringValue(J *item) -{ - if (item == NULL) { - return (char *)""; - } - return item->valuestring; -} - -//**************************************************************************/ -/*! - @brief Return the number representation of an item. - @param item The JSON item. - @returns The number, or 0.0, if NULL. -*/ -/**************************************************************************/ -JNUMBER JNumberValue(J *item) -{ - if (item == NULL) { - return 0.0; - } - return item->valuenumber; -} - -/*! - @brief Get the floating point value of a number field from a JSON object. - - @param json The JSON object. - @param field The number field to query. - - @returns The value of the number field if the field exists and is a number, - otherwise 0.0. - - @note The returned value is the floating point representation of the number. - - @see `JGetInt` - @see `JGetBool` - */ -JNUMBER JGetNumber(J *json, const char *field) -{ - if (json == NULL) { - return 0.0; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return 0.0; - } - if (!JIsNumber(item)) { - return 0.0; - } - return JNumberValue(item); -} - -//**************************************************************************/ -/*! - @brief Return the integer representation of an item. - @param item The JSON item. - @returns The number, or 0, if NULL. -*/ -/**************************************************************************/ -JINTEGER JIntValue(J *item) -{ - if (item == NULL) { - return 0; - } - return item->valueint; -} - -/*! - @brief Get the integer value of a number field from a JSON object. - - @param json The JSON object. - @param field The number field to query. - - @returns The value of the number field if the field exists and is a number, - otherwise 0. - - @note The returned value is the integer representation of the number. - - @see `JGetBool` - @see `JGetNumber` - */ -JINTEGER JGetInt(J *json, const char *field) -{ - if (json == NULL) { - return 0; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return 0; - } - if (!JIsNumber(item)) { - return 0; - } - return JIntValue(item); -} - -/*! - @brief Get the value of a boolean field from a JSON object. - - @param json The JSON object. - @param field The boolean field to query. - - @returns The value of the boolean field if it exists and is a boolean, - otherwise false. - - @see `JGetInt` - @see `JGetNumber` - */ -bool JGetBool(J *json, const char *field) -{ - if (json == NULL) { - return false; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return false; - } - if (!JIsBool(item)) { - return false; - } - return JIsTrue(item); -} - -//**************************************************************************/ -/*! - @brief Determine if a JSON object is valid and if a field is not present, - or null. - @param json The JSON response object. - @param field The field to return. - @returns bool. False if the field is not present, or NULL. -*/ -/**************************************************************************/ -bool JIsNullString(J *json, const char *field) -{ - if (json == NULL) { - return false; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return true; - } - if (!JIsString(item)) { - return false; - } - if (item->valuestring == NULL) { - return true; - } - if (item->valuestring[0] == '\0') { - return true; - } - return false; -} - -//**************************************************************************/ -/*! - @brief Determine if a field exists, is a string and matches a - provided value. - @param json The JSON response object. - @param field The field to check. - @param teststr The string to test against the returned value. - @returns bol. Whether the fields match exactly. -*/ -/**************************************************************************/ -bool JIsExactString(J *json, const char *field, const char *teststr) -{ - if (json == NULL) { - return false; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return false; - } - if (!JIsString(item)) { - return false; - } - if (item->valuestring == NULL) { - return false; - } - if (strlen(teststr) == 0) { - return false; - } - return (strcmp(item->valuestring, teststr) == 0); -} - -//**************************************************************************/ -/*! - @brief Determine if a field exists, is a string and contains a provided - value. - @param json The JSON response object. - @param field The field to check. - @param substr The string to test against the returned value. - @returns bol. Whether the provided string is found within the field. -*/ -/**************************************************************************/ -bool JContainsString(J *json, const char *field, const char *substr) -{ - if (json == NULL) { - return false; - } - J *item = JGetObjectItem(json, field); - if (item == NULL) { - return false; - } - if (!JIsString(item)) { - return false; - } - if (item->valuestring == NULL) { - return false; - } - if (strlen(substr) == 0) { - return false; - } - return (strstr(item->valuestring, substr) != NULL); -} - - -/*! - @brief Add binary data as a Base64-encoded string field to a JSON object. - - @param json The JSON object. - @param fieldName The name to use for the field. - @param binaryData A buffer of binary data to encode. - @param binaryDataLen The length of the binary data in bytes. - - @returns True if the string was successfully encoded and added to the object, - otherwise false. - */ -bool JAddBinaryToObject(J *json, const char *fieldName, const void *binaryData, uint32_t binaryDataLen) -{ - if (json == NULL) { - return false; - } - unsigned stringDataLen = JB64EncodeLen(binaryDataLen); - char *stringData = (char *) _Malloc(stringDataLen); - if (stringData == NULL) { - return false; - } - JB64Encode(stringData, binaryData, binaryDataLen); - J *stringItem = JCreateStringValue(stringData); - if (stringItem == NULL) { - _Free(stringData); - return false; - } - JAddItemToObject(json, fieldName, stringItem); - return true; -} - - -/*! - @brief Decode a Base64-encoded string field in a JSON object and return the - decoded bytes. - - @param json The JSON object. - @param fieldName The name of the field. - @param retBinaryData A pointer to a pointer, used to hold the pointer to the - decoded bytes. - @param retBinaryDataLen A pointer to an unsigned integer, used to hold the - length of the decoded bytes. - - @returns True if the string was successfully decoded and returned, otherwise - false. - - @note The returned binary buffer must be freed by the user with `JFree` when it - is no longer needed. - - @note On error, the returned binary buffer and data length shall be set to - `NULL` and zero (0), respectively. - */ -bool JGetBinaryFromObject(J *json, const char *fieldName, uint8_t **retBinaryData, uint32_t *retBinaryDataLen) -{ - // Initialize the return values to NULL and zero. - *retBinaryData = NULL; - *retBinaryDataLen = 0; - - if (json == NULL) { - return false; - } - - // In some cases, the caller may already have extracted the string from a different field, in which - // case "json" will be set to the payload pointer. - char *payload; - if (fieldName == NULL) { - payload = (char *) json; - } else { - payload = JGetString(json, fieldName); - } - if (payload[0] == '\0') { - return false; - } - - // Allocate a buffer for the payload, with an extra 'convenience byte' for null termination. (see below) - char *p = (char *) _Malloc(JB64DecodeLen(payload)+1); - if (p == NULL) { - return false; - } - uint32_t actualLen = JB64Decode(p, payload); - - // As a convenience to the caller, null-terminate the returned buffer in case it's a string. - // (If we didn't do this, the caller would be forced to alloc another buffer of length+1 and - // copy it to add the null terminator, while it's easy for us to do it here.) - p[actualLen] = '\0'; - - // Return the binary to the caller - *retBinaryData = (uint8_t *)p; - *retBinaryDataLen = actualLen; - return true; - -} - -//**************************************************************************/ -/*! - @brief Get the object name. - @param item The JSON object. - @returns The name, or the empty string, if NULL. -*/ -/**************************************************************************/ -const char *JGetItemName(const J * item) -{ - if (item == NULL || item->string == NULL) { - return ""; - } - return item->string; -} - -//**************************************************************************/ -/*! - @brief Convert an integer to text. - @param n The integer to convert. - @param s The buffer to hold the text. - @note The buffer must be large enough because no bounds checking is done. -*/ -/**************************************************************************/ -void JItoA(JINTEGER n, char *s) -{ - char c; - // 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++] = unsignedN % 10 + '0'; - } while ((unsignedN /= 10) > 0); - if (n < 0) { - s[i++] = '-'; - } - s[i] = '\0'; - for (i = 0, j = strlen(s)-1; i 9) { - break; - } - result = (10*result) + digit; - } - if (sign) { - result = -result; - } - return result; -} - -//**************************************************************************/ -/*! - @brief Convert a buffer/len to a null-terminated c-string - @param buffer A buffer containing the bytes to convert. - @param len The length of buffer in bytes. - @returns If buffer is NULL or length 0, the empty string. If allocation - fails, NULL. On success, the converted c-string. The returned - string must be freed with NoteFree. -*/ -/**************************************************************************/ -char *JAllocString(uint8_t *buffer, uint32_t len) -{ - char *buf = _Malloc(len+1); - if (buf == NULL) { - return NULL; - } - if (len > 0) { - memcpy(buf, buffer, len); - } - buf[len] = '\0'; - return buf; -} - -//**************************************************************************/ -/*! - @brief Return the type of an item, as a string - @param item The JSON item. - @returns The type -*/ -/**************************************************************************/ -const char *JType(J *item) -{ - if (item == NULL) { - return ""; - } - switch (item->type & 0xff) { - case JTrue: - case JFalse: - return "bool"; - case JNULL: - return "null"; - case JNumber: - return "number"; - case JRaw: - case JString: - return "string"; - case JObject: - return "object"; - case JArray: - return "array"; - } - return "invalid"; -} - -//**************************************************************************/ -/*! - @brief Get the type of a field, as an int usable in a switch statement. - @param json The JSON object containing the field. - @param field The field's name. - @returns The type of the field on success. JTYPE_NOT_PRESENT on error or if - the field doesn't exist. -*/ -/**************************************************************************/ -int JGetType(J *json, const char *field) -{ - if (json == NULL || field == NULL) { - return JTYPE_NOT_PRESENT; - } - return JGetItemType(JGetObjectItem(json, field)); -} - -// Get the -int JGetItemType(J *item) -{ - const char *v; - if (item == NULL) { - return JTYPE_NOT_PRESENT; - } - switch (item->type & 0xff) { - case JTrue: - return JTYPE_BOOL_TRUE; - case JFalse: - return JTYPE_BOOL_FALSE; - case JNULL: - return JTYPE_NULL; - case JNumber: - if (item->valueint == 0 && item->valuenumber == 0) { - return JTYPE_NUMBER_ZERO; - } - return JTYPE_NUMBER; - case JRaw: - case JString: { - v = item->valuestring; - if (v == NULL || v[0] == 0) { - return JTYPE_STRING_BLANK; - } - int vlen = strlen(v); - char *endstr; - JNUMBER value = JAtoN(v, &endstr); - if (endstr[0] == 0) { - if (value == 0) { - return JTYPE_STRING_ZERO; - } - return JTYPE_STRING_NUMBER; - } - if (vlen == 4 && ( - (v[0] == 't' || v[0] == 'T') - && (v[1] == 'r' || v[1] == 'R') - && (v[2] == 'u' || v[2] == 'U') - && (v[3] == 'e' || v[3] == 'E'))) { - return JTYPE_STRING_BOOL_TRUE; - } - if (vlen == 5 && ( - (v[0] == 'f' || v[0] == 'F') - && (v[1] == 'a' || v[1] == 'A') - && (v[2] == 'l' || v[2] == 'L') - && (v[3] == 's' || v[3] == 'S') - && (v[4] == 'e' || v[4] == 'E'))) { - return JTYPE_STRING_BOOL_FALSE; - } - return JTYPE_STRING; - } - case JObject: - return JTYPE_OBJECT; - case JArray: - return JTYPE_ARRAY; - } - return JTYPE_NOT_PRESENT; -} - -// Coalesce to the base types -int JBaseItemType(int type) -{ - switch (type) { - case JTYPE_BOOL_TRUE: - return JTYPE_BOOL; - case JTYPE_BOOL_FALSE: - return JTYPE_BOOL; - case JTYPE_NUMBER_ZERO: - return JTYPE_NUMBER; - case JTYPE_STRING_BLANK: - return JTYPE_STRING; - case JTYPE_STRING_ZERO: - return JTYPE_STRING; - case JTYPE_STRING_NUMBER: - return JTYPE_STRING; - case JTYPE_STRING_BOOL_TRUE: - return JTYPE_STRING; - case JTYPE_STRING_BOOL_FALSE: - return JTYPE_STRING; - } - return type; -} diff --git a/src/note-c/n_cobs.c b/src/note-c/n_cobs.c deleted file mode 100644 index 426a5a1..0000000 --- a/src/note-c/n_cobs.c +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2023 Blues Inc. All rights reserved. -// Use of this source code is governed by licenses granted by the -// copyright holder including that found in the LICENSE file. - -#include - -#define COBS_EOP_OVERHEAD 1 - -//**************************************************************************/ -/*! - @brief Decode a string encoded with COBS encoding - - @details Because the decoded length is guaranteed to be less than or equal to - length, the decode may be done in-place. Default behavior (with eop == 0) is - to restore all '\0' into output data, but if a different value is specified, - the input is XOR'ed such that THAT byte is the one that is assumed can't be - in the input. - - @param ptr Pointer to the data to decode - @param length Length of the data to decode - @param eop Byte to use as the end-of-packet marker - @param dst Pointer to the buffer for the decoded data - - @return the length of the decoded data - */ -/**************************************************************************/ -uint32_t cobsDecode(uint8_t *ptr, uint32_t length, uint8_t eop, uint8_t *dst) -{ - const uint8_t *start = dst, *end = ptr + length; - uint8_t code = 0xFF, copy = 0; - for (; ptr < end; copy--) { - if (copy != 0) { - *dst++ = (*ptr++) ^ eop; - } else { - if (code != 0xFF) { - *dst++ = 0; - } - copy = code = (*ptr++) ^ eop; - if (code == 0) { - break; - } - } - } - return dst - start; -} - -//**************************************************************************/ -/*! - @brief Encode a string with Consistent Overhead Byte Stuffing (COBS) encoding - - @details behavior (with eop == 0) is to eliminate all '\0' from input data, - but if a different value is specified, the output is XOR'ed such that THAT - byte is the one that won't be contained in output. - https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing - - @param ptr Pointer to the data to encode - @param length Length of the data to encode - @param eop Byte to use as the end-of-packet marker - @param dst Pointer to the buffer for the encoded data - - @return the length of the encoded data - - @note You may use `cobsEncodedLength()` to calculate the required size for - the buffer pointed to by the `dst` parameter. - - @see cobsEncodedLength() - */ -/**************************************************************************/ -uint32_t cobsEncode(uint8_t *ptr, uint32_t length, uint8_t eop, uint8_t *dst) -{ - uint8_t ch; - uint8_t *start = dst; - uint8_t code = 1; - uint8_t *code_ptr = dst++; // Where to insert the leading count - while (length--) { - ch = *ptr++; - if (ch != 0) { // Input byte not zero - *dst++ = ch ^ eop; - code++; - } - if (ch == 0 || code == 0xFF) { // Input is zero or complete block - *code_ptr = code ^ eop; - code = 1; - code_ptr = dst++; - } - } - *code_ptr = code ^ eop; // Final code - return (dst - start); -} - -//**************************************************************************/ -/*! - @brief Compute the encoding length of unencoded data - - @param ptr Pointer to the data to encode - @param length Length of the data to encode - - @return the length required for encoded data - - @note The computed length does not include the EOP (end-of-packet) marker - */ -/**************************************************************************/ -uint32_t cobsEncodedLength(const uint8_t *ptr, uint32_t length) -{ - uint8_t ch; - uint32_t dst = 1; - uint8_t code = 1; - while (length--) { - ch = *ptr++; - if (ch != 0) { // Input byte not zero - dst++; - code++; - } - if (ch == 0 || code == 0xFF) { // Input is zero or complete block - code = 1; - dst++; - } - } - return dst; -} - -//**************************************************************************/ -/*! - @brief Compute the max encoding length for a given length of unencoded data - - @param length Length of the data to encode - - @return The max length required to encode the data - - @note Since the contents of the buffer are unknown, then we must assume - that the entire buffer has no end-of-packet markers. This would - require the injection of overhead bytes (as opposed to the - replacement of end-of-packet markers with overhead bytes) at - intervals of 255, thus producing the worst case scenario. - @note An additional byte is added for the EOP (end-of-packet) marker. - */ -/**************************************************************************/ -uint32_t cobsEncodedMaxLength(uint32_t length) -{ - const uint32_t overheadBytes = ((length / 254) + ((length % 254) > 0)); - return (length + overheadBytes + COBS_EOP_OVERHEAD); -} - -//**************************************************************************/ -/*! - @brief Compute the maximum length of unencoded data that can fit into a - buffer of specified length. - - @param bufLen Length of the buffer in bytes - - @return the length of unencoded data - - @note The computation may leave additional space at the end. - @note An additional byte is added for the EOP (end-of-packet) marker. - */ -/**************************************************************************/ -uint32_t cobsGuaranteedFit(uint32_t bufLen) -{ - uint32_t cobsOverhead = 1 + (bufLen / 254) + COBS_EOP_OVERHEAD; - return (cobsOverhead > bufLen ? 0 : (bufLen - cobsOverhead)); -} diff --git a/src/note-c/n_const.c b/src/note-c/n_const.c deleted file mode 100644 index 41ce569..0000000 --- a/src/note-c/n_const.c +++ /dev/null @@ -1,30 +0,0 @@ -/*! - * @file n_const.c - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - */ - -#include "n_lib.h" - -const char *c_null = "null"; -const char *c_false = "false"; -const char *c_true = "true"; -const char *c_nullstring = ""; -const char *c_newline = "\r\n"; -const char *c_mem = "mem"; -const char *c_iotimeout = "timeout {io}"; -const char *c_err = "err"; -const char *c_req = "req"; -const char *c_cmd = "cmd"; -const char *c_bad = "bad"; -const char *c_iobad = "bad {io}"; -const char *c_ioerr = "{io}"; -const char *c_unsupported = "{not-supported}"; -const char *c_badbinerr = "{bad-bin}"; diff --git a/src/note-c/n_ftoa.c b/src/note-c/n_ftoa.c deleted file mode 100644 index c1df61b..0000000 --- a/src/note-c/n_ftoa.c +++ /dev/null @@ -1,510 +0,0 @@ -/* - * Modified by Dave Hart for integration into NTP 4.2.7 - * - * Copyright (c) 1995 Patrick Powell. - * - * This code is based on code written by Patrick Powell . - * It may be used for any purpose as long as this notice remains intact on all - * source code distributions. - * - * Copyright (c) 2008 Holger Weiss. - * - * This version of the code is maintained by Holger Weiss . - * My changes to the code may freely be used, modified and/or redistributed for - * any purpose. It would be nice if additions and fixes to this file (including - * trivial code cleanups) would be sent back in order to let me include them in - * the version available at . - * However, this is not a requirement for using or redistributing (possibly - * modified) versions of this file, nor is leaving this notice intact mandatory. - */ - - -#include "n_lib.h" -#include -#include - -#define PRINT_F_QUOTE 0x0001 -#define PRINT_F_TYPE_E 0x0002 -#define PRINT_F_TYPE_G 0x0004 -#define PRINT_F_NUM 0x0008 -#define PRINT_F_PLUS 0x0010 -#define PRINT_F_MINUS 0x0020 -#define PRINT_F_ZERO 0x0040 -#define PRINT_F_SPACE 0x0080 -#define PRINT_F_UP 0x0100 - -static void fmtstr(char *, size_t *, size_t, const char *, int, int, int); -static void fmtflt(char *, size_t *, size_t, JNUMBER, int, int, int, int *); -static void printsep(char *, size_t *, size_t); -static int getnumsep(int); -static int getexponent(JNUMBER); -static int convert(uintmax_t, char *, size_t, int, int); -static uintmax_t cast(JNUMBER); -static uintmax_t myround(JNUMBER); -static JNUMBER mypow10(int); -#define OUTCHAR(str, len, size, ch) \ -do { \ - if (len + 1 < size) \ - str[len] = ch; \ - (len)++; \ -} while (0) - -// Convert a JNUMBER into a null-terminated text string. Note that buf must -// be pointing at a buffer of JNTOA_MAX length, which is defined so that it -// includes enough space for the null terminator, so there's no need to -// have a buffer of JNTOA_MAX+1. -char * JNtoA(JNUMBER f, char * buf, int precision) -{ - int overflow = 0; - size_t len = 0; - int flags = PRINT_F_TYPE_G; - if (precision < 0) { - precision = JNTOA_PRECISION; - } - fmtflt(buf, &len, JNTOA_MAX, f, -1, precision, flags, &overflow); - if (overflow) { - len = 0; - buf[len++] = '0'; - } - buf[len] = '\0'; - return buf; -} - -static void -fmtflt(char *str, size_t *len, size_t size, JNUMBER fvalue, int width, - int precision, int flags, int *overflow) -{ - JNUMBER ufvalue; - uintmax_t intpart; - uintmax_t fracpart; - uintmax_t mask; - const char *infnan = NULL; - char iconvert[JNTOA_MAX]; - char fconvert[JNTOA_MAX]; - char econvert[6]; /* "e-1024" (without nul-termination). */ - char esign = 0; - char sign = 0; - int leadfraczeros = 0; - int exponent = 0; - int emitpoint = 0; - int omitzeros = 0; - int omitcount = 0; - int padlen = 0; - int epos = 0; - int fpos = 0; - int ipos = 0; - int separators = (flags & PRINT_F_QUOTE); - int estyle = (flags & PRINT_F_TYPE_E); - - /* - * AIX' man page says the default is 0, but C99 and at least Solaris' - * and NetBSD's man pages say the default is 6, and sprintf(3) on AIX - * defaults to 6. - */ - if (precision == -1) { - precision = 6; - } - - if (fvalue < 0.0) { - sign = '-'; - } else if (flags & PRINT_F_PLUS) { /* Do a sign. */ - sign = '+'; - } else if (flags & PRINT_F_SPACE) { - sign = ' '; - } - - if (isnan(fvalue)) { - infnan = (flags & PRINT_F_UP) ? "NAN" : "nan"; - } else if (isinf(fvalue)) { - infnan = (flags & PRINT_F_UP) ? "INF" : "inf"; - } - - if (infnan != NULL) { - if (sign != 0) { - iconvert[ipos++] = sign; - } - while (*infnan != '\0') { - iconvert[ipos++] = *infnan++; - } - fmtstr(str, len, size, iconvert, width, ipos, flags); - return; - } - - /* "%e" (or "%E") or "%g" (or "%G") conversion. */ - if (flags & PRINT_F_TYPE_E || flags & PRINT_F_TYPE_G) { - if (flags & PRINT_F_TYPE_G) { - /* - * For "%g" (and "%G") conversions, the precision - * specifies the number of significant digits, which - * includes the digits in the integer part. The - * conversion will or will not be using "e-style" (like - * "%e" or "%E" conversions) depending on the precision - * and on the exponent. However, the exponent can be - * affected by rounding the converted value, so we'll - * leave this decision for later. Until then, we'll - * assume that we're going to do an "e-style" conversion - * (in order to get the exponent calculated). For - * "e-style", the precision must be decremented by one. - */ - precision--; - /* - * For "%g" (and "%G") conversions, trailing zeros are - * removed from the fractional portion of the result - * unless the "#" flag was specified. - */ - if (!(flags & PRINT_F_NUM)) { - omitzeros = 1; - } - } - exponent = getexponent(fvalue); - estyle = 1; - } - -again: - /* - * Sorry, we only support 9, 19, or 38 digits (that is, the number of - * digits of the 32-bit, the 64-bit, or the 128-bit UINTMAX_MAX value - * minus one) past the decimal point due to our conversion method. - */ - switch (sizeof(uintmax_t)) { - case 16: - if (precision > 38) { - precision = 38; - } - break; - case 8: - if (precision > 19) { - precision = 19; - } - break; - default: - if (precision > 9) { - precision = 9; - } - break; - } - - ufvalue = (fvalue >= 0.0) ? fvalue : -fvalue; - if (estyle) { /* We want exactly one integer digit. */ - ufvalue /= mypow10(exponent); - } - - if ((intpart = cast(ufvalue)) == UINTMAX_MAX) { - *overflow = 1; - return; - } - - /* - * Factor of ten with the number of digits needed for the fractional - * part. For example, if the precision is 3, the mask will be 1000. - */ - mask = (uintmax_t)mypow10(precision); - /* - * We "cheat" by converting the fractional part to integer by - * multiplying by a factor of ten. - */ - if ((fracpart = myround(mask * (ufvalue - intpart))) >= mask) { - /* - * For example, ufvalue = 2.99962, intpart = 2, and mask = 1000 - * (because precision = 3). Now, myround(1000 * 0.99962) will - * return 1000. So, the integer part must be incremented by one - * and the fractional part must be set to zero. - */ - intpart++; - fracpart = 0; - if (estyle && intpart == 10) { - /* - * The value was rounded up to ten, but we only want one - * integer digit if using "e-style". So, the integer - * part must be set to one and the exponent must be - * incremented by one. - */ - intpart = 1; - exponent++; - } - } - - /* - * Now that we know the real exponent, we can check whether or not to - * use "e-style" for "%g" (and "%G") conversions. If we don't need - * "e-style", the precision must be adjusted and the integer and - * fractional parts must be recalculated from the original value. - * - * C99 says: "Let P equal the precision if nonzero, 6 if the precision - * is omitted, or 1 if the precision is zero. Then, if a conversion - * with style `E' would have an exponent of X: - * - * - if P > X >= -4, the conversion is with style `f' (or `F') and - * precision P - (X + 1). - * - * - otherwise, the conversion is with style `e' (or `E') and precision - * P - 1." (7.19.6.1, 8) - * - * Note that we had decremented the precision by one. - */ - if (flags & PRINT_F_TYPE_G && estyle && - precision + 1 > exponent && exponent >= -4) { - precision -= exponent; - estyle = 0; - goto again; - } - - if (estyle) { - if (exponent < 0) { - exponent = -exponent; - esign = '-'; - } else { - esign = '+'; - } - - /* - * Convert the exponent. The sizeof(econvert) is 6. So, the - * econvert buffer can hold e.g. "e+1024" and "e-1024". - */ - 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 - * exponent." (7.19.6.1, 8) - */ - if (epos == 1) { - econvert[epos++] = '0'; - } - econvert[epos++] = esign; - econvert[epos++] = (flags & PRINT_F_UP) ? 'E' : 'e'; - } - - /* Convert the integer part and the fractional part. */ - ipos = convert(intpart, iconvert, sizeof(iconvert), 10, 0); - if (fracpart != 0) { /* convert() would return 1 if fracpart == 0. */ - fpos = convert(fracpart, fconvert, sizeof(fconvert), 10, 0); - } - - leadfraczeros = precision - fpos; - - if (omitzeros) { - if (fpos > 0) { /* Omit trailing fractional part zeros. */ - while (omitcount < fpos && fconvert[omitcount] == '0') { - omitcount++; - } - } else { /* The fractional part is zero, omit it completely. */ - omitcount = precision; - leadfraczeros = 0; - } - precision -= omitcount; - } - - /* - * Print a decimal point if either the fractional part is non-zero - * and/or the "#" flag was specified. - */ - if (precision > 0 || flags & PRINT_F_NUM) { - emitpoint = 1; - } - if (separators) { /* Get the number of group separators we'll print. */ - separators = getnumsep(ipos); - } - - padlen = width /* Minimum field width. */ - - ipos /* Number of integer digits. */ - - epos /* Number of exponent characters. */ - - precision /* Number of fractional digits. */ - - separators /* Number of group separators. */ - - (emitpoint ? 1 : 0) /* Will we print a decimal point? */ - - ((sign != 0) ? 1 : 0); /* Will we print a sign character? */ - - if (padlen < 0) { - padlen = 0; - } - - /* - * C99 says: "If the `0' and `-' flags both appear, the `0' flag is - * ignored." (7.19.6.1, 6) - */ - if (flags & PRINT_F_MINUS) { /* Left justifty. */ - padlen = -padlen; - } else if (flags & PRINT_F_ZERO && padlen > 0) { - if (sign != 0) { /* Sign. */ - OUTCHAR(str, *len, size, sign); - sign = 0; - } - while (padlen > 0) { /* Leading zeros. */ - OUTCHAR(str, *len, size, '0'); - padlen--; - } - } - while (padlen > 0) { /* Leading spaces. */ - OUTCHAR(str, *len, size, ' '); - padlen--; - } - if (sign != 0) { /* Sign. */ - OUTCHAR(str, *len, size, sign); - } - while (ipos > 0) { /* Integer part. */ - ipos--; - OUTCHAR(str, *len, size, iconvert[ipos]); - if (separators > 0 && ipos > 0 && ipos % 3 == 0) { - printsep(str, len, size); - } - } - if (emitpoint) { /* Decimal point. */ - OUTCHAR(str, *len, size, '.'); - } - while (leadfraczeros > 0) { /* Leading fractional part zeros. */ - OUTCHAR(str, *len, size, '0'); - leadfraczeros--; - } - while (fpos > omitcount) { /* The remaining fractional part. */ - fpos--; - OUTCHAR(str, *len, size, fconvert[fpos]); - } - while (epos > 0) { /* Exponent. */ - epos--; - OUTCHAR(str, *len, size, econvert[epos]); - } - while (padlen < 0) { /* Trailing spaces. */ - OUTCHAR(str, *len, size, ' '); - padlen++; - } -} - -static void fmtstr(char *str, size_t *len, size_t size, const char *value, int width, - int precision, int flags) -{ - int padlen, strln; /* Amount to pad. */ - int noprecision = (precision == -1); - - if (value == NULL) { /* We're forgiving. */ - value = "(null)"; - } - - /* If a precision was specified, don't read the string past it. */ - for (strln = 0; value[strln] != '\0' && - (noprecision || strln < precision); strln++) { - continue; - } - - if ((padlen = width - strln) < 0) { - padlen = 0; - } - if (flags & PRINT_F_MINUS) { /* Left justify. */ - padlen = -padlen; - } - - while (padlen > 0) { /* Leading spaces. */ - OUTCHAR(str, *len, size, ' '); - padlen--; - } - while (*value != '\0' && (noprecision || precision-- > 0)) { - OUTCHAR(str, *len, size, *value); - value++; - } - while (padlen < 0) { /* Trailing spaces. */ - OUTCHAR(str, *len, size, ' '); - padlen++; - } -} - -static void printsep(char *str, size_t *len, size_t size) -{ - OUTCHAR(str, *len, size, ','); -} - -static int getnumsep(int digits) -{ - int separators = (digits - ((digits % 3 == 0) ? 1 : 0)) / 3; - return separators; -} - -static int getexponent(JNUMBER value) -{ - JNUMBER tmp = (value >= 0.0) ? value : -value; - int exponent = 0; - - /* - * 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 >= -1022) { - tmp *= 10; - } - while (tmp >= 10.0 && ++exponent <= 1023) { - tmp /= 10; - } - - return exponent; -} - -static int convert(uintmax_t value, char *buf, size_t size, int base, int caps) -{ - const char *digits = caps ? "0123456789ABCDEF" : "0123456789abcdef"; - size_t pos = 0; - - /* We return an unterminated buffer with the digits in reverse order. */ - do { - buf[pos++] = digits[value % base]; - value /= base; - } while (value != 0 && pos < size); - - return (int)pos; -} - -static uintmax_t cast(JNUMBER value) -{ - uintmax_t result; - - /* - * We check for ">=" and not for ">" because if UINTMAX_MAX cannot be - * represented exactly as an JNUMBER value (but is less than LDBL_MAX), - * it may be increased to the nearest higher representable value for the - * comparison (cf. C99: 6.3.1.4, 2). It might then equal the JNUMBER - * value although converting the latter to uintmax_t would overflow. - */ - if (value >= UINTMAX_MAX) { - return UINTMAX_MAX; - } - - result = (uintmax_t)value; - /* - * At least on NetBSD/sparc64 3.0.2 and 4.99.30, casting long double to - * an integer type converts e.g. 1.9 to 2 instead of 1 (which violates - * the standard). Sigh. - */ - return (result <= value) ? result : result - 1; -} - -static uintmax_t myround(JNUMBER value) -{ - uintmax_t intpart = cast(value); - - if (intpart == UINTMAX_MAX) { - return UINTMAX_MAX; - } - - return ((value -= intpart) < 0.5) ? intpart : intpart + 1; -} - -static JNUMBER mypow10(int exponent) -{ - JNUMBER result = 1; - - while (exponent > 0) { - result *= 10; - exponent--; - } - while (exponent < 0) { - result /= 10; - exponent++; - } - return result; -} diff --git a/src/note-c/n_helpers.c b/src/note-c/n_helpers.c deleted file mode 100644 index 131989b..0000000 --- a/src/note-c/n_helpers.c +++ /dev/null @@ -1,2575 +0,0 @@ -/*! - * @file n_helpers.c - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - */ - -#include -#include -#include -#include -#include - -#include "n_lib.h" - -#ifdef NOTE_C_TEST -#include "test_static.h" -#else -#define NOTE_C_STATIC static -#endif - -// When interfacing with the Notecard, it is generally encouraged that the JSON -// object manipulation and calls to the note-arduino library are done directly -// at point of need. However, there are cases in which it's convenient to have a -// wrapper. The most common reason is when it's best to have a suppression timer -// on the actual call to the Notecard so as not to assault the I2C bus or UART -// on a continuing basis - the most common examples of this being "card.time" -// and any other Notecard state that needs to be polled for an app, such as the -// device location, its voltage level, and so on. This file contains this kind -// of wrapper, just implemented here as a convenience to all developers. - -// Time-related suppression timer and cache -static uint32_t timeBaseSetAtMs = 0; -static JTIME timeBaseSec = 0; -static bool timeBaseSetManually = false; -static uint32_t suppressionTimerSecs = 10; -static uint32_t refreshTimerSecs = 86400; -static uint32_t timeTimer = 0; -static uint32_t timeRefreshTimer = 0; -static bool zoneStillUnavailable = true; -static bool zoneForceRefresh = false; -static char curZone[10] = {0}; -static char curArea[64] = {0}; -static char curCountry[8] = ""; -static int curZoneOffsetMins = 0; - -// Location-related suppression timer and cache -static uint32_t locationTimer = 0; -static char locationLastErr[64] = {0}; -static bool locationValid = false; - -// Connection-related suppression timer and cache -static uint32_t connectivityTimer = 0; -static bool cardConnected = false; - -// Status suppression timer -static uint32_t statusTimer = 0; - -// DEPRECATED. Turbo communications mode, for special use cases and well-tested -// hardware. -bool cardTurboIO = false; - -// Service config-related suppression timer and cache -static uint32_t serviceConfigTimer = 0; -static char scDevice[128] = {0}; -static char scSN[128] = {0}; -static char scProduct[128] = {0}; -static char scService[128] = {0}; - -// For date conversions -#define daysByMonth(y) ((y)&03||(y)==0?normalYearDaysByMonth:leapYearDaysByMonth) -static short leapYearDaysByMonth[] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; -static short normalYearDaysByMonth[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; -static const char *daynames[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; - -// Forwards -NOTE_C_STATIC void setTime(JTIME seconds); -NOTE_C_STATIC bool timerExpiredSecs(uint32_t *timer, uint32_t periodSecs); -NOTE_C_STATIC int yToDays(int year); - -static const char NOTE_C_BINARY_EOP = '\n'; - -//**************************************************************************/ -/*! - @brief Decode binary data received from the Notecard. - - @param encData The encoded binary data to decode. - @param encDataLen The length of the encoded binary data. - @param decBuf The target buffer for the decoded data. For in-place decoding, - `decBuf` can use the same address as `encData` (see note). - @param decBufSize The size of `decBuf`. - - @returns The length of the decoded data, or zero on error. - - @note Use `NoteBinaryCodecMaxDecodedLength()` to calculate the required - size for the buffer pointed to by the `decBuf` parameter, which MUST - accommodate both the encoded data and newline terminator. - @note This API supports in-place decoding. If you wish to utilize in-place - decoding, then set `decBuf` to `encData` and `decBufSize` to `encLen`. - */ -/**************************************************************************/ -uint32_t NoteBinaryCodecDecode(const uint8_t *encData, uint32_t encDataLen, - uint8_t *decBuf, uint32_t decBufSize) -{ - uint32_t result; - - // Validate parameter(s) - if (encData == NULL || decBuf == NULL) { - NOTE_C_LOG_ERROR(ERRSTR("NULL parameter", c_err)); - result = 0; - } else if (decBufSize < cobsGuaranteedFit(encDataLen)) { - NOTE_C_LOG_ERROR(ERRSTR("output buffer too small", c_err)); - result = 0; - } else { - result = cobsDecode((uint8_t *)encData, encDataLen, NOTE_C_BINARY_EOP, decBuf); - } - - return result; -} - -//**************************************************************************/ -/*! - - @brief Encode binary data to transmit to the Notecard. - - @param decData The decoded binary data to encode. - @param decDataLen The length of the decoded binary data. - @param encBuf The target buffer for the encoded data. For in-place encoding, - `encBuf` can use the same buffer as `decData`, but cannot - share the same address (see note). - @param encBufSize The size of `encBuf`. - - @returns The length of the encoded data, or zero on error. - - @note Use `NoteBinaryCodecMaxEncodedLength()` to calculate the required - size for the buffer pointed to by the `encBuf` parameter, which MUST - accommodate both the encoded data and newline terminator. - @note This API supports in-place encoding. If you wish to utilize in-place - encoding, shift the decoded data to the end of the buffer, update - `decBuf` accordingly, and set the value of `encBuf` to the beginning - of the buffer. - */ -/**************************************************************************/ -uint32_t NoteBinaryCodecEncode(const uint8_t *decData, uint32_t decDataLen, - uint8_t *encBuf, uint32_t encBufSize) -{ - uint32_t result; - - // Validate parameter(s) - if (decData == NULL || encBuf == NULL) { - NOTE_C_LOG_ERROR(ERRSTR("NULL parameter", c_err)); - result = 0; - } else if ((encBufSize < cobsEncodedMaxLength(decDataLen)) - && (encBufSize < cobsEncodedLength(decData, decDataLen))) { - // NOTE: `cobsEncodedMaxLength()` provides a constant time [O(1)] means - // of checking the buffer size. Only when it fails will the linear - // time [O(n)] check, `cobsEncodedLength()`, be invoked. - NOTE_C_LOG_ERROR(ERRSTR("output buffer too small", c_err)); - result = 0; - } else { - result = cobsEncode((uint8_t *)decData, decDataLen, NOTE_C_BINARY_EOP, encBuf); - } - - return result; -} - -//**************************************************************************/ -/*! - @brief Compute the maximum decoded data length guaranteed - to fit into a fixed-size buffer, after being encoded. - - This API is designed for a space constrained environment, where a - working buffer has been allocated to facilitate binary transactions. - - There are two primary use cases: - - 1. When data is retrieved from the Notecard, it must be requested in - terms of the unencoded offset and length. However, the data is - encoded prior to transmission, and, as a result, the buffer must be - capable of receiving the encoded (larger) data. This API returns a - length that is safe to request from the Notecard, because the - resulting encoded data is guaranteed to fit in the provided buffer. - 2. When data is transmitted to the Notecard, this API can be used to - verify whether or not unencoded data of a given length will fit in - the provided buffer after encoding. - - @param bufferSize The size of the fixed-size buffer. - - @returns The max length of unencoded data certain to fit in the fixed-size - buffer, after being encoded. - */ -/**************************************************************************/ -uint32_t NoteBinaryCodecMaxDecodedLength(uint32_t bufferSize) -{ - return cobsGuaranteedFit(bufferSize); -} - -//**************************************************************************/ -/*! - @brief Compute the maximum buffer size needed to encode - any unencoded buffer of the given length. - - @param unencodedLength The length of an unencoded buffer. - - @returns The max required buffer size to hold the encoded data. - */ -/**************************************************************************/ -uint32_t NoteBinaryCodecMaxEncodedLength(uint32_t unencodedLength) -{ - return cobsEncodedMaxLength(unencodedLength); -} - -//**************************************************************************/ -/*! - @brief Get the length of the data in the Notecard's binary store. If there's - no data on the Notecard, then `*len` will return 0. - - @param len [out] The length of the decoded contents of the Notecard's binary - store. - - @returns An error string on error and NULL on success. - */ -/**************************************************************************/ -const char * NoteBinaryStoreDecodedLength(uint32_t *len) -{ - // Validate parameter(s) - if (!len) { - const char *err = ERRSTR("len cannot be NULL", c_bad); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Issue a "card.binary" request. - J *rsp = NoteRequestResponse(NoteNewRequest("card.binary")); - if (!rsp) { - const char *err = ERRSTR("unable to issue binary request", c_err); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Ensure the transaction doesn't return an error - // and confirm the binary feature is available - if (NoteResponseError(rsp)) { - const char *jErr = JGetString(rsp, "err"); - // Swallow `{bad-bin}` errors, because we intend to overwrite the data. - if (!NoteErrorContains(jErr, c_badbinerr)) { - NOTE_C_LOG_ERROR(jErr); - JDelete(rsp); - const char *err = ERRSTR("unexpected error received during handshake", c_bad); - NOTE_C_LOG_ERROR(err); - return err; - } - } - - // Examine "length" from the response to evaluate the length of the decoded - // data residing on the Notecard. - *len = JGetInt(rsp, "length"); - JDelete(rsp); - - return NULL; -} - -//**************************************************************************/ -/*! - @brief Get the required buffer length to receive the entire binary object - stored in the Notecard's binary store. - - @param len [out] The length required to hold the entire contents of the - Notecard's binary store. If there's no data on the Notecard, then - `len` will return 0. - - @returns An error string on error and NULL on success. - */ -/**************************************************************************/ -const char * NoteBinaryStoreEncodedLength(uint32_t *len) -{ - // Validate parameter(s) - if (!len) { - const char *err = ERRSTR("size cannot be NULL", c_err); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Issue a "card.binary" request. - J *rsp = NoteRequestResponse(NoteNewRequest("card.binary")); - if (!rsp) { - const char *err = ERRSTR("unable to issue binary request", c_err); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Ensure the transaction doesn't return an error - // and confirm the binary feature is available - if (NoteResponseError(rsp)) { - const char *jErr = JGetString(rsp, "err"); - // Swallow `{bad-bin}` errors, because we intend to overwrite the data. - if (!NoteErrorContains(jErr, c_badbinerr)) { - NOTE_C_LOG_ERROR(jErr); - JDelete(rsp); - const char *err = ERRSTR("unexpected error received during handshake", c_bad); - NOTE_C_LOG_ERROR(err); - return err; - } - } - - // Examine "cobs" from the response to evaluate the space required to hold - // the encoded data to be received from the Notecard. - long int cobs = JGetInt(rsp, "cobs"); - JDelete(rsp); - *len = cobs; - - return NULL; -} - -//**************************************************************************/ -/*! - @brief Receive a large binary object from the Notecard's binary store. - - @param buffer A buffer to hold the binary range. - @param bufLen The total length of the provided buffer. - @param decodedOffset The offset to the decoded binary data residing - in the Notecard's binary store. - @param decodedLen The length of the decoded data to fetch from the Notecard. - - @returns NULL on success, else an error string pointer. - - @note The buffer must be large enough to hold the encoded value of the - data store contents from the requested offset for the specified length. - To determine the necessary buffer size for a given data length, use - `NoteBinaryCodecMaxEncodedLength()`, or if you wish to consume the - entire buffer use `(NoteBinaryStoreEncodedLength() + 1)` instead. - */ -/**************************************************************************/ -const char * NoteBinaryStoreReceive(uint8_t *buffer, uint32_t bufLen, - uint32_t decodedOffset, uint32_t decodedLen) -{ - // Validate parameter(s) - if (!buffer) { - const char *err = ERRSTR("NULL buffer", c_bad); - NOTE_C_LOG_ERROR(err); - return err; - } - if (bufLen < cobsEncodedMaxLength(decodedLen)) { - const char *err = ERRSTR("insufficient buffer size", c_bad); - NOTE_C_LOG_ERROR(err); - return err; - } - if (decodedLen == 0) { - const char *err = ERRSTR("decodedLen cannot be zero (0)", c_bad); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Claim Notecard Mutex - _LockNote(); - - // Issue `card.binary.get` and capture `"status"` from response - char status[NOTE_MD5_HASH_STRING_SIZE] = {0}; - J *req = NoteNewRequest("card.binary.get"); - if (req) { - JAddIntToObject(req, "offset", decodedOffset); - JAddIntToObject(req, "length", decodedLen); - - // We already have the Notecard lock, so call - // noteTransactionShouldLock with `lockNotecard` set to false so we - // don't try to lock again. - J *rsp = noteTransactionShouldLock(req, false); - JDelete(req); - // Ensure the transaction doesn't return an error. - if (!rsp || NoteResponseError(rsp)) { - if (rsp) { - NOTE_C_LOG_ERROR(JGetString(rsp,"err")); - JDelete(rsp); - } - - const char *err = ERRSTR("failed to initialize binary transaction", c_err); - NOTE_C_LOG_ERROR(err); - _UnlockNote(); - return err; - } - - // Examine "status" from the response to evaluate the MD5 checksum. - strlcpy(status, JGetString(rsp,"status"), NOTE_MD5_HASH_STRING_SIZE); - JDelete(rsp); - } else { - const char *err = ERRSTR("unable to allocate request", c_mem); - NOTE_C_LOG_ERROR(err); - _UnlockNote(); - return err; - } - - // Read raw bytes from the active interface into a predefined buffer - uint32_t available = 0; - NOTE_C_LOG_DEBUG("receiving binary data..."); - const char *err = _ChunkedReceive(buffer, &bufLen, false, (CARD_INTRA_TRANSACTION_TIMEOUT_SEC * 1000), &available); - NOTE_C_LOG_DEBUG("binary receive complete."); - - // Release Notecard Mutex - _UnlockNote(); - - // Ensure transaction was successful - if (err) { - // Queue a reset when a problem is detected, otherwise `note-c` will - // attempt to allocate memory to receive the response. - NoteResetRequired(); - return ERRSTR(err, c_err); - } - - // Check buffer overflow condition - if (available) { - const char *err = ERRSTR("unexpected data available", c_err); - NOTE_C_LOG_ERROR(err); - // Queue a reset when a problem is detected, otherwise `note-c` will - // attempt to allocate memory to receive the response. - NoteResetRequired(); - return err; - } - - // _ChunkedReceive returns the raw bytes that came off the wire, which - // includes a terminating newline that ends the packet. This newline isn't - // part of the binary payload, so we decrement the length by 1 to remove it. - --bufLen; - - // Decode it in place, which is safe because decoding shrinks - const uint32_t decLen = NoteBinaryCodecDecode(buffer, bufLen, buffer, bufLen); - - // Ensure the decoded length matches the caller's expectations. - if (decodedLen != decLen) { - const char *err = ERRSTR("length mismatch after decoding", c_err); - NOTE_C_LOG_ERROR(err); - // Queue a reset when a problem is detected, otherwise `note-c` will - // attempt to allocate memory to receive the response. - NoteResetRequired(); - return err; - } - - // Put a hard marker at the end of the decoded portion of the buffer. This - // enables easier human reasoning when interrogating the buffer, if the - // buffer holds a string. - buffer[decLen] = '\0'; - - // Verify MD5 - char hashString[NOTE_MD5_HASH_STRING_SIZE] = {0}; - NoteMD5HashString(buffer, decLen, hashString, NOTE_MD5_HASH_STRING_SIZE); - if (strncmp(hashString, status, NOTE_MD5_HASH_STRING_SIZE)) { - const char *err = ERRSTR("computed MD5 does not match received MD5", c_err); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Return `NULL` if success, else error string pointer - return NULL; -} - -//**************************************************************************/ -/*! - @brief Reset the Notecard's binary store. - - @returns NULL on success, else an error string pointer. - - @note This operation is necessary to clear the Notecard's binary buffer after - a binary object is received from the Notecard, or if the Notecard's - binary store has been left in an unknown state due to an error arising - from a binary transfer to the Notecard. - */ -/**************************************************************************/ -const char * NoteBinaryStoreReset(void) -{ - J *req = NoteNewRequest("card.binary"); - if (req) { - JAddBoolToObject(req, "delete", true); - - // Ensure the transaction doesn't return an error. - J *rsp = NoteRequestResponse(req); - if (NoteResponseError(rsp)) { - NOTE_C_LOG_ERROR(JGetString(rsp,"err")); - JDelete(rsp); - const char *err = ERRSTR("failed to reset binary buffer", c_err); - NOTE_C_LOG_ERROR(err); - return err; - } - - JDelete(rsp); - } else { - const char *err = ERRSTR("unable to allocate request", c_mem); - NOTE_C_LOG_ERROR(err); - return err; - } - - return NULL; -} - -//**************************************************************************/ -/*! - @brief Transmit a large binary object to the Notecard's binary store. - - @param unencodedData A buffer with data to encode in place. - @param unencodedLen The length of the data in the buffer. - @param bufLen The total length of the buffer (see notes). - @param notecardOffset The offset where the data buffer should be appended - to the decoded binary data residing in the Notecard's - binary store. This does not provide random access, but - rather ensures alignment across sequential writes. - - @returns NULL on success, else an error string pointer. - - @note Buffers are encoded in place, the buffer _MUST_ be larger than the data - to be encoded. The original contents of the buffer will be modified. - Use `NoteBinaryCodecMaxEncodedLength()` to calculate the required size - for the buffer pointed to by the `unencodedData` parameter, which MUST - accommodate both the encoded data and newline terminator. - */ -/**************************************************************************/ -const char * NoteBinaryStoreTransmit(uint8_t *unencodedData, uint32_t unencodedLen, - uint32_t bufLen, uint32_t notecardOffset) -{ - // Validate parameter(s) - if (!unencodedData) { - const char *err = ERRSTR("unencodedData cannot be NULL", c_err); - NOTE_C_LOG_ERROR(err); - return err; - } else if ((bufLen < cobsEncodedMaxLength(unencodedLen)) - && (bufLen < (cobsEncodedLength(unencodedData, unencodedLen) + 1))) { - // NOTE: `cobsEncodedMaxLength()` provides a constant time [O(1)] means - // of checking the buffer size. Only when it fails will the linear - // time [O(n)] check, `cobsEncodedLength()`, be invoked. - const char *err = ERRSTR("insufficient buffer size", c_bad); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Issue a "card.binary" request. - J *rsp = NoteRequestResponse(NoteNewRequest("card.binary")); - if (!rsp) { - const char *err = ERRSTR("unable to issue binary request", c_err); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Ensure the transaction doesn't return an error - // and confirm the binary feature is available - if (NoteResponseError(rsp)) { - const char *jErr = JGetString(rsp, "err"); - // Swallow `{bad-bin}` errors, because we intend to overwrite the data. - if (!NoteErrorContains(jErr, c_badbinerr)) { - NOTE_C_LOG_ERROR(jErr); - JDelete(rsp); - const char *err = ERRSTR("unexpected error received during handshake", c_bad); - NOTE_C_LOG_ERROR(err); - return err; - } - } - - // Examine "length" and "max" from the response to evaluate the unencoded - // space available to "card.binary.put" on the Notecard. - const long len = JGetInt(rsp,"length"); - const long max = JGetInt(rsp,"max"); - JDelete(rsp); - if (!max) { - const char *err = ERRSTR("unexpected response: max is zero or not present", c_err); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Validate the index provided by the caller, against the `length` value - // returned from the Notecard to ensure the caller and Notecard agree on - // how much data is residing on the Notecard. - if ((long)notecardOffset != len) { - const char *err = ERRSTR("notecard data length is misaligned with offset", c_mem); - NOTE_C_LOG_ERROR(err); - return err; - } - - // When offset is zero, the Notecard's entire binary buffer is available - const uint32_t remaining = (notecardOffset ? (max - len) : max); - if (unencodedLen > remaining) { - const char *err = ERRSTR("buffer size exceeds available memory", c_mem); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Calculate MD5 - char hashString[NOTE_MD5_HASH_STRING_SIZE] = {0}; - NoteMD5HashString(unencodedData, unencodedLen, hashString, NOTE_MD5_HASH_STRING_SIZE); - - // Shift the data to the end of the buffer. Next, we'll encode the data, - // outputting the encoded data to the front of the buffer. - const uint32_t dataShift = (bufLen - unencodedLen); - memmove(unencodedData + dataShift, unencodedData, unencodedLen); - - // Create an alias to help reason about the buffer after in-place encoding. - uint8_t * const encodedData = unencodedData; - - // Update unencoded data pointer - unencodedData += dataShift; - - // Capture encoded length - // NOTE: `(bufLen - 1)` accounts for one byte of space we need to save for a - // newline to mark the end of the packet. - const uint32_t encLen = NoteBinaryCodecEncode(unencodedData, unencodedLen, encodedData, (bufLen - 1)); - - // Append the \n, which marks the end of a packet. - encodedData[encLen] = '\n'; - - const size_t NOTE_C_BINARY_RETRIES = 3; - for (size_t i = 0 ; i < NOTE_C_BINARY_RETRIES ; ++i) { - // Claim Notecard Mutex - _LockNote(); - - // Issue a "card.binary.put" - J *req = NoteNewRequest("card.binary.put"); - if (req) { - JAddIntToObject(req, "cobs", encLen); - if (notecardOffset) { - JAddIntToObject(req, "offset", notecardOffset); - } - JAddStringToObject(req, "status", hashString); - - // We already have the Notecard lock, so call - // noteTransactionShouldLock with `lockNotecard` set to false so we - // don't try to lock again. - rsp = noteTransactionShouldLock(req, false); - JDelete(req); - // Ensure the transaction doesn't return an error. - if (!rsp || NoteResponseError(rsp)) { - if (rsp) { - NOTE_C_LOG_ERROR(JGetString(rsp,"err")); - JDelete(rsp); - } - - const char *err = ERRSTR("failed to initialize binary transaction", c_err); - NOTE_C_LOG_ERROR(err); - _UnlockNote(); - // On errors, we restore the caller's input buffer by decoding - // it. The caller is then able to retry transmission with their - // original pointer to this buffer. - NoteBinaryCodecDecode(encodedData, encLen, encodedData, bufLen); - return err; - } - - JDelete(rsp); - } else { - const char *err = ERRSTR("unable to allocate request", c_mem); - NOTE_C_LOG_ERROR(err); - _UnlockNote(); - NoteBinaryCodecDecode(encodedData, encLen, encodedData, bufLen); - return err; - } - - // Immediately send the encoded binary. - NOTE_C_LOG_DEBUG("transmitting binary data..."); - const char *err = _ChunkedTransmit(encodedData, (encLen + 1), false); - NOTE_C_LOG_DEBUG("binary transmission complete."); - - // Release Notecard Mutex - _UnlockNote(); - - // Ensure transaction was successful - if (err) { - NoteBinaryCodecDecode(encodedData, encLen, encodedData, bufLen); - return ERRSTR(err, c_err); - } - - // Issue a `"card.binary"` request. - rsp = NoteRequestResponse(NoteNewRequest("card.binary")); - if (!rsp) { - const char *err = ERRSTR("unable to validate request", c_err); - NOTE_C_LOG_ERROR(err); - NoteBinaryCodecDecode(encodedData, encLen, encodedData, bufLen); - return err; - } - - // Ensure the transaction doesn't return an error - // to confirm the binary was received - if (NoteResponseError(rsp)) { - const char *jErr = JGetString(rsp, "err"); - if (NoteErrorContains(jErr, c_badbinerr)) { - NOTE_C_LOG_WARN(jErr); - JDelete(rsp); - if ( i < (NOTE_C_BINARY_RETRIES - 1) ) { - NOTE_C_LOG_WARN("retrying binary transmission..."); - continue; - } - const char *err = ERRSTR("binary data invalid", c_bad); - NOTE_C_LOG_ERROR(err); - NoteBinaryCodecDecode(encodedData, encLen, encodedData, bufLen); - return err; - } else { - NOTE_C_LOG_ERROR(jErr); - JDelete(rsp); - const char *err = ERRSTR("unexpected error received during confirmation", c_bad); - NOTE_C_LOG_ERROR(err); - NoteBinaryCodecDecode(encodedData, encLen, encodedData, bufLen); - return err; - } - } - JDelete(rsp); - break; - } - - // Return `NULL` on success - return NULL; -} - -//**************************************************************************/ -/*! - @brief Determine if the card time is "real" calendar/clock time, or if - it is simply the millisecond clock since boot. - @returns A boolean indicating whether the current time is valid. -*/ -/**************************************************************************/ -bool NoteTimeValid(void) -{ - timeTimer = 0; - return NoteTimeValidST(); -} - -//**************************************************************************/ -/*! - @brief Determine if if the suppression-timer card time is valid. - @returns A boolean indicating whether the current time is valid. -*/ -/**************************************************************************/ -bool NoteTimeValidST(void) -{ - NoteTimeST(); - return (timeBaseSec != 0); -} - -//**************************************************************************/ -/*! - @brief Get the current epoch time, unsuppressed. - @returns The current time. -*/ -/**************************************************************************/ -JTIME NoteTime(void) -{ - timeTimer = 0; - return NoteTimeST(); -} - -/*! - @brief Set the number of minutes between refreshes of the time from the - Notecard, to help minimize clock drift on this host. Set this to 0 for - no auto-refresh; it defaults to daily. - - @param mins Minutes between time refreshes. - */ -void NoteTimeRefreshMins(uint32_t mins) -{ - refreshTimerSecs = mins * 60; -} - -//**************************************************************************/ -/*! - @brief Set the time. - @param seconds The UNIX Epoch time. -*/ -/**************************************************************************/ -NOTE_C_STATIC void setTime(JTIME seconds) -{ - timeBaseSec = seconds; - timeBaseSetAtMs = _GetMs(); -} - -/*! - @brief Set the time from a source that is NOT the Notecard - @param secondsUTC The UNIX Epoch time, or 0 for automatic Notecard time - @param offset The local time zone offset, in minutes, to adjust UTC - @param zone The optional local time zone name (3 character c-string). Note - that this isn't used in any time calculations. To compute - accurate local time, only the offset is used. See - https://www.iana.org/time-zones for a time zone database. - @param country The optional country - @param area The optional region - */ -void NoteTimeSet(JTIME secondsUTC, int offset, char *zone, char *country, char *area) -{ - if (secondsUTC == 0) { - timeBaseSec = 0; - timeBaseSetAtMs = 0; - timeBaseSetManually = false; - zoneStillUnavailable = true; - zoneForceRefresh = false; - } else { - timeBaseSec = secondsUTC; - timeBaseSetAtMs = _GetMs(); - timeBaseSetManually = true; - zoneStillUnavailable = false; - curZoneOffsetMins = offset; - strlcpy(curZone, zone == NULL ? "UTC" : zone, sizeof(curZone)); - strlcpy(curArea, area == NULL ? "" : area, sizeof(curArea)); - strlcpy(curCountry, country == NULL ? "" : country, sizeof(curCountry)); - } -} - -//**************************************************************************/ -/*! - @brief Print a full, newline-terminated line. - @param line The c-string line to print. -*/ -/**************************************************************************/ -void NotePrintln(const char *line) -{ - NotePrint(line); - NotePrint(c_newline); -} - -//**************************************************************************/ -/*! - @brief Log text "raw" to either the active debug console or to the - Notecard's USB console. - @param text A debug string for output. -*/ -/**************************************************************************/ -bool NotePrint(const char *text) -{ - bool success = false; - - if (noteIsDebugOutputActive()) { - NoteDebug(text); - return true; - } - - J *req = NoteNewRequest("card.log"); - JAddStringToObject(req, "text", text); - success = NoteRequest(req); - - return success; -} - -//**************************************************************************/ -/*! - @brief Get the current epoch time as known by the module. If it isn't known - by the module, just return the time since boot as indicated by the - millisecond clock. - @returns The current time, or the time since boot. -*/ -/**************************************************************************/ -JTIME NoteTimeST(void) -{ - - // Handle timer tick wrap by resetting the base - uint32_t nowMs = _GetMs(); - if (timeBaseSec != 0 && nowMs < timeBaseSetAtMs) { - int64_t actualTimeMs = 0x100000000LL + (int64_t) nowMs; - int64_t elapsedTimeMs = actualTimeMs - (int64_t) timeBaseSetAtMs; - uint32_t elapsedTimeSecs = (uint32_t) (elapsedTimeMs / 1000); - timeBaseSec += elapsedTimeSecs; - timeBaseSetAtMs = nowMs; - } - - // If it's time to refresh the time, do so - if (refreshTimerSecs != 0 && timerExpiredSecs(&timeRefreshTimer, refreshTimerSecs)) { - timeTimer = 0; - } - - // If we haven't yet fetched the time, or if we still need the timezone, do - // so with a suppression timer so that we don't hammer the module before - // it's had a chance to connect to the network to fetch time. - if (!timeBaseSetManually && (timeTimer == 0 || timeBaseSec == 0 || zoneStillUnavailable || zoneForceRefresh)) { - if (timerExpiredSecs(&timeTimer, suppressionTimerSecs)) { - - // Request time and zone info from the card - J *rsp = NoteRequestResponse(NoteNewRequest("card.time")); - if (rsp != NULL) { - if (!NoteResponseError(rsp)) { - JTIME seconds = JGetInt(rsp, "time"); - if (seconds != 0) { - - // Set the time - setTime(seconds); - - // Get the zone - char *z = JGetString(rsp, "zone"); - if (z[0] != '\0') { - char zone[64]; - strlcpy(zone, z, sizeof(zone)); - // Only use the 3-letter abbrev - char *sep = strchr(zone, ','); - if (sep == NULL) { - zone[0] = '\0'; - } else { - *sep = '\0'; - } - zoneStillUnavailable = (memcmp(zone, "UTC", 3) == 0); - zoneForceRefresh = false; - strlcpy(curZone, zone, sizeof(curZone)); - curZoneOffsetMins = JGetInt(rsp, "minutes"); - strlcpy(curCountry, JGetString(rsp, "country"), sizeof(curCountry)); - strlcpy(curArea, JGetString(rsp, "area"), sizeof(curArea)); - } - - } - } - NoteDeleteResponse(rsp); - } - } - } - - // Adjust the base time by the number of seconds that have elapsed since - // the base. - JTIME adjustedTime = timeBaseSec + (int32_t) (((int64_t) nowMs - (int64_t) timeBaseSetAtMs) / 1000); - - // Done - return adjustedTime; - -} - -/*! - @brief Set suppression timer secs, returning the previous value. - - @param secs New suppression timer seconds. - - @returns Previous suppression timer seconds - */ -uint32_t NoteSetSTSecs(uint32_t secs) -{ - uint32_t prev = suppressionTimerSecs; - suppressionTimerSecs = secs; - return prev; -} - -//**************************************************************************/ -/*! - @brief Return local region info, if known. Returns true if valid. - @param retCountry (out) The country. - @param retArea (out) The area value. - @param retZone (out) The timezone value. - @param retZoneOffset (out) The timezone offset. - @returns boolean indicating if the region information is valid. -*/ -/**************************************************************************/ -bool NoteRegion(char **retCountry, char **retArea, char **retZone, int *retZoneOffset) -{ - NoteTimeST(); - if (zoneStillUnavailable) { - if (retCountry != NULL) { - *retCountry = (char *) ""; - } - if (retArea != NULL) { - *retArea = (char *) ""; - } - if (retZone != NULL) { - *retZone = (char *) "UTC"; - } - if (retZoneOffset != NULL) { - *retZoneOffset = 0; - } - return false; - } - if (retCountry != NULL) { - *retCountry = curCountry; - } - if (retArea != NULL) { - *retArea = curArea; - } - if (retZone != NULL) { - *retZone = curZone; - } - if (retZoneOffset != NULL) { - *retZoneOffset = curZoneOffsetMins; - } - return true; -} - -/*! - @brief Return local time info, if known. Returns true if valid. - - @param retYear [out] Pointer to return year value. - @param retMonth [out] Pointer to return month value. - @param retDay [out] Pointer to return day value. - @param retHour [out] Pointer to return hour value. - @param retMinute [out] Pointer to return minute value. - @param retSecond [out] Pointer to return seconds value. - @param retWeekday [out] Pointer to return weekday string. - @param retZone [in,out] If NULL, local time will be returned in UTC, else - returns pointer to zone string - - @returns True if either the zone or DST have changed since last call, false - otherwise. - - @note Only call this if time is valid. - */ -bool NoteLocalTimeST(uint16_t *retYear, uint8_t *retMonth, uint8_t *retDay, - uint8_t *retHour, uint8_t *retMinute, uint8_t *retSecond, char **retWeekday, - char **retZone) -{ - - // Preset - if (retYear != NULL) { - *retYear = 0; - } - if (retMonth != NULL) { - *retMonth = 0; - } - if (retDay != NULL) { - *retDay = 0; - } - if (retHour != NULL) { - *retHour = 0; - } - if (retMinute != NULL) { - *retMinute = 0; - } - if (retSecond != NULL) { - *retSecond = 0; - } - if (retWeekday != NULL) { - *retWeekday = (char *) ""; - } - if (retZone != NULL) { - *retZone = (char *) ""; - } - - // Exit if time isn't yet valid - if (!NoteTimeValidST()) { - return false; - } - - // Get the current time and region, and compute local time - char *currentZone; - int currentOffsetMins; - JTIME currentEpochTime = NoteTimeST(); - bool regionAvailable = NoteRegion(NULL, NULL, ¤tZone, ¤tOffsetMins); - - // Offset the epoch time by time zone offset - currentEpochTime += currentOffsetMins * 60; - - // Convert from unix epoch time to local date/time - int i, year, mon; - int32_t days; - uint32_t secs; - secs = (uint32_t) currentEpochTime + ((70*365L+17)*86400LU); - days = secs / 86400; - if (retWeekday != NULL) { - *retWeekday = (char *) daynames[(days + 1) % 7]; - } - for (year = days / 365; days < (i = yToDays(year) + 365L * year); ) { - --year; - } - days -= i; - if (retYear != NULL) { - *retYear = (uint16_t) year+1900; - } - short *pm = daysByMonth(year); - for (mon = 12; days < pm[--mon]; ) ; - if (retMonth != NULL) { - *retMonth = (uint8_t) mon+1; - } - if (retDay != NULL) { - *retDay = days - pm[mon] + 1; - } - secs = secs % 86400; - uint8_t currentHour = (uint8_t) (secs / 3600); - if (retHour != NULL) { - *retHour = currentHour; - } - secs %= 3600; - if (retMinute != NULL) { - *retMinute = (uint8_t) (secs/60); - } - if (retSecond != NULL) { - *retSecond = (uint8_t) (secs % 60); - } - if (retZone != NULL) { - *retZone = currentZone; - } - - // Determine whether or not we should refresh to check whether DST offset has changed, which - // is between midnight and 3am local time (if available). - static uint8_t lastHour; - static bool lastInfoIsKnown = false; - if (regionAvailable && lastInfoIsKnown) { - if ((lastHour != 1 && currentHour == 1) || (lastHour != 2 && currentHour == 2) || (lastHour != 3 && currentHour == 3)) { - zoneForceRefresh = true; - } - } - lastInfoIsKnown = true; - lastHour = currentHour; - - // Done - return regionAvailable; - -} - -// Figure out how many days at start of the year -NOTE_C_STATIC int yToDays(int year) -{ - int days = 0; - if (0 < year) { - days = (year - 1) / 4; - } else if (year <= -4) { - days = year / 4; - } - return days + daysByMonth(year)[0]; -} - -//**************************************************************************/ -/*! - @brief See if the card location is valid. If it is OFF, it is - treated as valid. - @param errbuf (out) A buffer with any error information to return. - @param errbuflen The length of the error buffer. - @returns boolean indicating if the location information is valid. -*/ -/**************************************************************************/ -bool NoteLocationValid(char *errbuf, uint32_t errbuflen) -{ - locationTimer = 0; - locationValid = false; - locationLastErr[0] = '\0'; - return NoteLocationValidST(errbuf, errbuflen); -} - -//**************************************************************************/ -/*! - @brief See if the card location is valid, time-suppressed. If it is OFF, - it is treated as valid. - @param errbuf (out) A buffer with any error information to return. - @param errbuflen The length of the error buffer. - @returns boolean indicating if the location information is valid. -*/ -/**************************************************************************/ -bool NoteLocationValidST(char *errbuf, uint32_t errbuflen) -{ - - // Preset - if (errbuf != NULL) { - strlcpy(errbuf, locationLastErr, errbuflen); - } - - // If it was ever valid, return true - if (locationValid) { - locationLastErr[0] = '\0'; - if (errbuf != NULL) { - *errbuf = '\0'; - } - return true; - } - - // If we haven't yet fetched the location, do so with a suppression - // timer so that we don't hammer the module before it's had a chance to - // connect to the gps to fetch location. - if (!timerExpiredSecs(&locationTimer, suppressionTimerSecs)) { - return false; - } - - // Request location from the card - J *rsp = NoteRequestResponse(NoteNewRequest("card.location")); - if (rsp == NULL) { - return false; - } - - // If valid, or the location mode is OFF, we're done - if (!NoteResponseError(rsp) || strcmp(JGetString(rsp, "mode"), "off") == 0) { - NoteDeleteResponse(rsp); - locationValid = true; - locationLastErr[0] = '\0'; - if (errbuf != NULL) { - *errbuf = '\0'; - } - return true; - } - - // Remember the error for next iteration - strlcpy(locationLastErr, JGetString(rsp, "err"), sizeof(locationLastErr)); - if (errbuf != NULL) { - strlcpy(errbuf, locationLastErr, errbuflen); - } - NoteDeleteResponse(rsp); - return false; - -} - -//**************************************************************************/ -/*! - @brief Set a service environment variable default string. - @param variable The variable key. - @param buf The variable value. - @returns boolean indicating if variable was set. -*/ -/**************************************************************************/ -bool NoteSetEnvDefault(const char *variable, char *buf) -{ - bool success = false; - J *req = NoteNewRequest("env.default"); - if (req != NULL) { - JAddStringToObject(req, "name", variable); - JAddStringToObject(req, "text", buf); - success = NoteRequest(req); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Set a service environment variable default integer. - @param variable The variable key. - @param defaultVal The variable value. - @returns boolean indicating if variable was set. -*/ -/**************************************************************************/ -bool NoteSetEnvDefaultInt(const char *variable, JINTEGER defaultVal) -{ - char buf[32]; - JItoA(defaultVal, buf); - return NoteSetEnvDefault(variable, buf); -} - -//**************************************************************************/ -/*! - @brief Set a service environment variable default number. - @param variable The variable key. - @param defaultVal The variable value. - @returns boolean indicating if variable was set. -*/ -/**************************************************************************/ -bool NoteSetEnvDefaultNumber(const char *variable, JNUMBER defaultVal) -{ - char buf[JNTOA_MAX]; - JNtoA(defaultVal, buf, -1); - return NoteSetEnvDefault(variable, buf); -} - -//**************************************************************************/ -/*! - @brief Get a service environment variable number. - @param variable The variable key. - @param defaultVal The default variable value. - @returns environment variable value. -*/ -/**************************************************************************/ -JNUMBER NoteGetEnvNumber(const char *variable, JNUMBER defaultVal) -{ - char buf[JNTOA_MAX], buf2[JNTOA_MAX]; - JNtoA(defaultVal, buf2, -1); - NoteGetEnv(variable, buf2, buf, sizeof(buf)); - return JAtoN((const char*)buf, NULL); -} - -//**************************************************************************/ -/*! - @brief Get a service environment variable integer. - @param variable The variable key. - @param defaultVal The default variable value. - @returns environment variable value. -*/ -/**************************************************************************/ -JINTEGER NoteGetEnvInt(const char *variable, JINTEGER defaultVal) -{ - char buf[32], buf2[32]; - JItoA(defaultVal, buf2); - NoteGetEnv(variable, buf2, buf, sizeof(buf)); - return atoi(buf); -} - -//**************************************************************************/ -/*! - @brief Get a service environment variable. - @param variable The variable key. - @param defaultVal The default variable value, if the environment variable - hasn't been set. - @param buf (out) The buffer in which to place the variable value. If the - environment variable hasn't been set, defaultVal will be copied into - buf. - @param buflen The length of the output buffer. - @returns true if there is no error (JSON response with no explicit error) -*/ -/**************************************************************************/ -bool NoteGetEnv(const char *variable, const char *defaultVal, char *buf, uint32_t buflen) -{ - bool success = false; - if (defaultVal == NULL) { - buf[0] = '\0'; - } else { - strlcpy(buf, defaultVal, buflen); - } - J *req = NoteNewRequest("env.get"); - if (req != NULL) { - JAddStringToObject(req, "name", variable); - J *rsp = NoteRequestResponse(req); - if (rsp != NULL) { - if (!NoteResponseError(rsp)) { - success = true; - char *val = JGetString(rsp, "text"); - if (val[0] != '\0') { - strlcpy(buf, val, buflen); - } - } - NoteDeleteResponse(rsp); - } - } - return success; -} - -//**************************************************************************/ -/*! - @brief Determine if the Notecard is connected to the network. - @returns boolean. `true` if connected. -*/ -/**************************************************************************/ -bool NoteIsConnected(void) -{ - connectivityTimer = 0; - return NoteIsConnectedST(); -} - -//**************************************************************************/ -/*! - @brief Determine if the Notecard is connected to the network. - @returns boolean. `true` if connected. -*/ -/**************************************************************************/ -bool NoteIsConnectedST(void) -{ - if (timerExpiredSecs(&connectivityTimer, suppressionTimerSecs)) { - J *rsp = NoteRequestResponse(NoteNewRequest("hub.status")); - if (rsp != NULL) { - if (!NoteResponseError(rsp)) { - cardConnected = JGetBool(rsp, "connected"); - } - NoteDeleteResponse(rsp); - } - } - return cardConnected; -} - -//**************************************************************************/ -/*! - @brief Get Full Network Status via `hub.status`. - @param statusBuf (out) a buffer to populate with the status response. - @param statusBufLen The length of the status buffer - @returns boolean. `true` if the status was retrieved. -*/ -/**************************************************************************/ -bool NoteGetNetStatus(char *statusBuf, int statusBufLen) -{ - bool success = false; - statusBuf[0] = '\0'; - J *rsp = NoteRequestResponse(NoteNewRequest("hub.status")); - if (rsp != NULL) { - success = !NoteResponseError(rsp); - if (success) { - strlcpy(statusBuf, JGetString(rsp, "status"), statusBufLen); - } - NoteDeleteResponse(rsp); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Make a `card.version` request - @param versionBuf (out) a buffer to populate with the version response. - @param versionBufLen The length of the version buffer - @returns boolean. `true` if the version information was retrieved. -*/ -/**************************************************************************/ -bool NoteGetVersion(char *versionBuf, int versionBufLen) -{ - bool success = false; - versionBuf[0] = '\0'; - J *rsp = NoteRequestResponse(NoteNewRequest("card.version")); - if (rsp != NULL) { - success = !NoteResponseError(rsp); - if (success) { - strlcpy(versionBuf, JGetString(rsp, "version"), versionBufLen); - } - NoteDeleteResponse(rsp); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Make a `card.location` request - @param retLat (out) The retrieved latitude value. - @param retLon (out) The retrieved longitude value. - @param time (out) The retrieved time. - @param statusBuf (out) a buffer to populate with the location response. - @param statusBufLen The length of the location buffer - @returns boolean. `true` if the location information was retrieved. -*/ -/**************************************************************************/ -bool NoteGetLocation(JNUMBER *retLat, JNUMBER *retLon, JTIME *time, char *statusBuf, int statusBufLen) -{ - bool locValid = false; - if (statusBuf != NULL) { - *statusBuf = '\0'; - } - if (retLat != NULL) { - *retLat = 0.0; - } - if (retLon != NULL) { - *retLon = 0.0; - } - if (time != NULL) { - *time = 0; - } - J *rsp = NoteRequestResponse(NoteNewRequest("card.location")); - if (rsp != NULL) { - if (statusBuf != NULL) { - strlcpy(statusBuf, JGetString(rsp, "status"), statusBufLen); - } - if (JIsPresent(rsp, "lat") && JIsPresent(rsp, "lon")) { - if (retLat != NULL) { - *retLat = JGetNumber(rsp, "lat"); - } - if (retLon != NULL) { - *retLon = JGetNumber(rsp, "lon"); - } - locValid = true; - } - JTIME seconds = JGetInt(rsp, "time"); - if (seconds != 0 && time != NULL) { - *time = seconds; - } - NoteDeleteResponse(rsp); - } - return locValid; -} - -//**************************************************************************/ -/*! - @brief Set the card location to a static value. - @param lat The latitude value to set on the Notecard. - @param lon The longitude value to set on the Notecard. - @returns boolean. `true` if the location information was set. -*/ -/**************************************************************************/ -bool NoteSetLocation(JNUMBER lat, JNUMBER lon) -{ - bool success = false; - J *req = NoteNewRequest("card.location.mode"); - if (req != NULL) { - JAddStringToObject(req, "mode", "fixed"); - JAddNumberToObject(req, "lat", lat); - JAddNumberToObject(req, "lon", lon); - success = NoteRequest(req); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Clear last known Location. - @returns boolean. `true` if the location information was cleared. -*/ -/**************************************************************************/ -bool NoteClearLocation(void) -{ - bool success = false; - J *req = NoteNewRequest("card.location.mode"); - if (req != NULL) { - JAddBoolToObject(req, "delete", true); - success = NoteRequest(req); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Get the current location Mode. - @param modeBuf (out) a buffer to populate with the location mode response. - @param modeBufLen The length of the mode buffer - @returns boolean. `true` if the mode information was retrieved. -*/ -/**************************************************************************/ -bool NoteGetLocationMode(char *modeBuf, int modeBufLen) -{ - bool success = false; - modeBuf[0] = '\0'; - J *rsp = NoteRequestResponse(NoteNewRequest("card.location.mode")); - if (rsp != NULL) { - success = !NoteResponseError(rsp); - if (success) { - strlcpy(modeBuf, JGetString(rsp, "mode"), modeBufLen); - } - NoteDeleteResponse(rsp); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Make a `card.location.mode` request. - @param mode the Notecard location mode to set. - @param seconds The value of the `seconds` field for the request. - @returns boolean. `true` if the mode was set. -*/ -/**************************************************************************/ -bool NoteSetLocationMode(const char *mode, uint32_t seconds) -{ - bool success = false; - J *req = NoteNewRequest("card.location.mode"); - if (req != NULL) { - if (mode[0] == '\0') { - mode = "-"; - } - JAddStringToObject(req, "mode", mode); - JAddNumberToObject(req, "seconds", seconds); - success = NoteRequest(req); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Get the current service configuration information, unsuppressed. - @param productBuf (out) a buffer to populate with the ProductUID from the - response. - @param productBufLen The length of the ProductUID buffer. - @param serviceBuf (out) a buffer to populate with the host url from the - response. - @param serviceBufLen The length of the host buffer. - @param deviceBuf (out) a buffer to populate with the DeviceUID from the - response. - @param deviceBufLen The length of the DeviceUID buffer. - @param snBuf (out) a buffer to populate with the Product Serial Number - from the response. - @param snBufLen The length of the Serial Number buffer. - @returns boolean. `true` if the service configuration was obtained. -*/ -/**************************************************************************/ -bool NoteGetServiceConfig(char *productBuf, int productBufLen, char *serviceBuf, int serviceBufLen, char *deviceBuf, int deviceBufLen, char *snBuf, int snBufLen) -{ - serviceConfigTimer = 0; - return NoteGetServiceConfigST(productBuf, productBufLen, serviceBuf, serviceBufLen, deviceBuf, deviceBufLen, snBuf, snBufLen); -} - -//**************************************************************************/ -/*! - @brief Get the current service configuration information, with - a supression timer. - @param productBuf (out) a buffer to populate with the ProductUID from the - response. - @param productBufLen The length of the ProductUID buffer. - @param serviceBuf (out) a buffer to populate with the host url from the - response. - @param serviceBufLen The length of the host buffer. - @param deviceBuf (out) a buffer to populate with the DeviceUID from the - response. - @param deviceBufLen The length of the DeviceUID buffer. - @param snBuf (out) a buffer to populate with the Product Serial Number - from the response. - @param snBufLen The length of the Serial Number buffer. - @returns boolean. `true` if the service configuration was obtained. -*/ -/**************************************************************************/ -bool NoteGetServiceConfigST(char *productBuf, int productBufLen, char *serviceBuf, int serviceBufLen, char *deviceBuf, int deviceBufLen, char *snBuf, int snBufLen) -{ - bool success = false; - - // Use cache except for a rare refresh - if (scProduct[0] == '\0' || scDevice[0] == '\0' || timerExpiredSecs(&serviceConfigTimer, 4*60*60)) { - J *rsp = NoteRequestResponse(NoteNewRequest("hub.get")); - if (rsp != NULL) { - success = !NoteResponseError(rsp); - if (success) { - strlcpy(scProduct, JGetString(rsp, "product"), sizeof(scProduct)); - strlcpy(scService, JGetString(rsp, "host"), sizeof(scService)); - strlcpy(scDevice, JGetString(rsp, "device"), sizeof(scDevice)); - strlcpy(scSN, JGetString(rsp, "sn"), sizeof(scSN)); - } - NoteDeleteResponse(rsp); - } - } else { - success = true; - } - - // Done - if (productBuf != NULL) { - strlcpy(productBuf, scProduct, productBufLen); - } - if (serviceBuf != NULL) { - strlcpy(serviceBuf, scService, serviceBufLen); - } - if (deviceBuf != NULL) { - strlcpy(deviceBuf, scDevice, deviceBufLen); - } - if (snBuf != NULL) { - strlcpy(snBuf, scSN, snBufLen); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Get Status of the Notecard, unsuppressed. - @param statusBuf (out) a buffer to populate with the Notecard status - from the response. - @param statusBufLen The length of the status buffer. - @param bootTime (out) The Notecard boot time. - @param retUSB (out) Whether the Notecard is powered over USB. - @param retSignals (out) Whether the Notecard has a network signal. - @returns boolean. `true` if the card status was obtained. -*/ -/**************************************************************************/ -bool NoteGetStatus(char *statusBuf, int statusBufLen, JTIME *bootTime, bool *retUSB, bool *retSignals) -{ - statusTimer = 0; - return NoteGetStatusST(statusBuf, statusBufLen, bootTime, retUSB, retSignals); -} - -//**************************************************************************/ -/*! - @brief Get Status of the Notecard, with a suppression timer. - @param statusBuf (out) a buffer to populate with the Notecard status - from the response. - @param statusBufLen The length of the status buffer. - @param bootTime (out) The Notecard boot time. - @param retUSB (out) Whether the Notecard is powered over USB. - @param retSignals (out) Whether the Notecard has a network signal. - @returns boolean. `true` if the card status was obtained. -*/ -/**************************************************************************/ -bool NoteGetStatusST(char *statusBuf, int statusBufLen, JTIME *bootTime, bool *retUSB, bool *retSignals) -{ - bool success = false; - static char lastStatus[128] = {0}; - static JTIME lastBootTime = 0; - static bool lastUSB = false; - static bool lastSignals = false; - - // Refresh if it's time to do so - if (timerExpiredSecs(&statusTimer, suppressionTimerSecs)) { - J *rsp = NoteRequestResponse(NoteNewRequest("card.status")); - if (rsp != NULL) { - success = !NoteResponseError(rsp); - if (success) { - strlcpy(lastStatus, JGetString(rsp, "status"), sizeof(lastStatus)); - lastBootTime = JGetInt(rsp, "time"); - lastUSB = JGetBool(rsp, "usb"); - if (JGetBool(rsp, "connected")) { - lastSignals = (JGetInt(rsp, "signals") > 0); - } else { - lastSignals = false; - } - } - NoteDeleteResponse(rsp); - } - } else { - success = true; - } - - // Done - if (statusBuf != NULL) { - strlcpy(statusBuf, lastStatus, statusBufLen); - } - if (bootTime != NULL) { - *bootTime = lastBootTime; - } - if (retUSB != NULL) { - *retUSB = lastUSB; - } - if (retSignals != NULL) { - *retSignals = lastSignals; - } - - return success; -} - -/*! - @brief Save the current state and use `card.attn` to set a host - sleep interval. - - @param desc An optional binary payload to keep in memory while the host - sleeps. - @param seconds The duration to sleep. - @param modes Optional list of additional `card.attn` modes. - - @returns `true` if the cmd is sent without error. The Notecard does not reply - to commands so a `true` return value does not guarantee - that the sleep request was received and processed by the Notecard. -*/ -bool NotePayloadSaveAndSleep(NotePayloadDesc *desc, uint32_t seconds, const char *modes) -{ - char *stateB64 = NULL; - - // If specified, encode - if (desc->data != NULL && desc->length != 0) { - - // Get length that it will be when converted to base64 - unsigned b64Len = JB64EncodeLen(desc->length); - - // Convert the state to base64 - stateB64 = _Malloc(b64Len); - if (stateB64 == NULL) { - return false; - } - - // Encode the state buffer into a string - JB64Encode(stateB64, (const char *) desc->data, desc->length); - - } - - // Sleep - bool success = NoteSleep(stateB64, seconds, modes); - - // Free the temp buffer - if (stateB64 != NULL) { - _Free(stateB64); - } - - // Done - return success; - -} - -//**************************************************************************/ -/*! - @brief Save the current state and use `card.attn` to set a host - sleep interval. - @param stateb64 A base64 payload to keep in memory while the host sleeps. - @param seconds The duration to sleep. - @param modes Optional list of additional `card.attn` modes. - @returns boolean. `true` if the cmd is sent without error. The Notecard - does not reply to `cmd` so a `true` return value does not guarantee - that the sleep request was received and processed by the Notecard. -*/ -/**************************************************************************/ -bool NoteSleep(char *stateb64, uint32_t seconds, const char *modes) -{ - bool success = false; - - // Trace - NOTE_C_LOG_INFO("ABOUT TO SLEEP\n"); - - // Use a Command rather than a Request so that the Notecard doesn't try to - // send a response back to us (host), which would cause a communications - // error on the Notecard end. - NOTE_C_LOG_INFO("requesting sleep\n"); - J *req = NoteNewCommand("card.attn"); - if (req != NULL) { - // Add the base64 item in a wonderful way that doesn't strdup the - // huge string - if (stateb64 != NULL) { - J *stringReferenceItem = JCreateStringReference(stateb64); - if (stringReferenceItem != NULL) { - JAddItemToObject(req, "payload", stringReferenceItem); - } - } - char modestr[64]; - strlcpy(modestr, "sleep", sizeof(modestr)); - if (modes != NULL) { - strlcat(modestr, ",", sizeof(modestr)); - strlcat(modestr, modes, sizeof(modestr)); - } - JAddStringToObject(req, "mode", modestr); - JAddNumberToObject(req, "seconds", seconds); - - // Note that since we use cmd and not req a true return value from - // NoteRequest means only that the cmd was sent without error. - success = NoteRequest(req); - } - - // Trace - NOTE_C_LOG_ERROR("DIDN'T SLEEP\n"); - - // Done - return success; -} - -//**************************************************************************/ -/*! - @brief Wake the module by restoring state into a state buffer of a - specified length, and fail if it isn't available or isn't that length. - @param stateLen A length of the state payload buffer to return to the host. - @param state (out) The in-memory payload to return to the host. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteWake(int stateLen, void *state) -{ - NotePayloadDesc desc; - bool success = NotePayloadRetrieveAfterSleep(&desc); - if (!success) { - return false; - } - if (desc.length != (uint32_t)stateLen) { - NotePayloadFree(&desc); - return false; - } - memcpy(state, desc.data, stateLen); - NotePayloadFree(&desc); - return true; -} - -/*! - @brief Wake the module by restoring state into a state buffer, returning - its length, and fail if it isn't available. - - @param desc [out] Payload descriptor to hold the retrieved. - payload. - - @returns True if the request was successful, false otherwise. - */ -bool NotePayloadRetrieveAfterSleep(NotePayloadDesc *desc) -{ - - // Initialize payload descriptor - if (desc != NULL) { - memset(desc, 0, sizeof(NotePayloadDesc)); - } - - // Send the Notecard a request to retrieve the saved state - J *req = NoteNewRequest("card.attn"); - if (req == NULL) { - return false; - } - JAddBoolToObject(req, "start", true); - J *rsp = NoteRequestResponse(req); - if (rsp == NULL) { - return false; - } - if (NoteResponseError(rsp)) { - NoteDeleteResponse(rsp); - return false; - } - - // Note the current time, if the field is present - JTIME seconds = JGetInt(rsp, "time"); - if (seconds != 0) { - setTime(seconds); - } - - // If we didn't expect any state to be restored, we're done - if (desc == NULL) { - NoteDeleteResponse(rsp); - return true; - } - - // Exit if no payload, knowing that we expected one - char *payload = JGetString(rsp, "payload"); - if (payload[0] == '\0') { - NoteDeleteResponse(rsp); - return false; - } - - // Allocate a buffer for the payload. (We can't decode in place because we - // can't risk overwriting memory if the actual payload is even slightly - // different.) - uint32_t allocLen = JB64DecodeLen(payload); - uint8_t *p = (uint8_t *) _Malloc(allocLen); - if (p == NULL) { - NoteDeleteResponse(rsp); - return false; - } - uint32_t actualLen = (uint32_t) JB64Decode((char *)p, payload); - - // Fill out the payload descriptor - desc->data = p; - desc->alloc = allocLen; - desc->length = actualLen; - - // State restored - NOTE_C_LOG_INFO("AWAKENED SUCCESSFULLY\n"); - NoteDeleteResponse(rsp); - return true; -} - -//**************************************************************************/ -/*! - @brief Restore the module to factory settings. - @param deleteConfigSettings Whether to delete current configuration - settings on the Notecard during the reset. - @returns boolean. `true` if reset was successful. -*/ -/**************************************************************************/ -bool NoteFactoryReset(bool deleteConfigSettings) -{ - bool success = false; - - // Perform the restore-to-factor-settings transaction - J *req = NoteNewRequest("card.restore"); - if (req != NULL) { - JAddBoolToObject(req, "delete", deleteConfigSettings); - success = NoteRequest(req); - } - - // Exit if it didn't work - if (!success) { - return false; - } - - // Wait for serial to stabilize after it reboots - _DelayMs(5000); - NOTE_C_LOG_INFO("CARD RESTORED\n"); - - // Reset the Notecard - while (!NoteReset()) { - _DelayMs(5000); - } - - // Success - return true; - -} - -//**************************************************************************/ -/*! - @brief Set the Notecard Product UID. - @param productID The ProductUID to set on the Notecard. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteSetProductID(const char *productID) -{ - bool success = false; - J *req = NoteNewRequest("hub.set"); - if (req != NULL) { - if (productID[0] == '\0') { - JAddStringToObject(req, "product", "-"); - } else { - JAddStringToObject(req, "product", productID); - } - success = NoteRequest(req); - } - // Flush cache so that service config is re-fetched - serviceConfigTimer = 0; - return success; -} - -//**************************************************************************/ -/*! - @brief Set the Notecard Serial Number. - @param sn The Serial Number to set on the Notecard. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteSetSerialNumber(const char *sn) -{ - bool success = false; - J *req = NoteNewRequest("hub.set"); - if (req != NULL) { - if (sn[0] == '\0') { - JAddStringToObject(req, "sn", "-"); - } else { - JAddStringToObject(req, "sn", sn); - } - success = NoteRequest(req); - } - // Flush cache so that service config is re-fetched - serviceConfigTimer = 0; - return success; -} - -//**************************************************************************/ -/*! - @brief Set the Notecard upload mode and interval. - @param uploadMode The upload mode (for instance, `continuous`, - or `periodic`). - @param uploadMinutes The max number of minutes to wait between Notehub - uploads. - @param align Flag to specify that uploads should be grouped within the - specified period, rather than counting the number of minutes - from first modified. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteSetUploadMode(const char *uploadMode, int uploadMinutes, bool align) -{ - bool success = false; - J *req = NoteNewRequest("hub.set"); - if (req != NULL) { - JAddStringToObject(req, "mode", uploadMode); - if (uploadMinutes != 0) { - JAddNumberToObject(req, "outbound", uploadMinutes); - // Setting this flag aligns uploads to be grouped within the period, - // rather than counting the number of minutes from "first modified". - JAddBoolToObject(req, "align", align); - } - success = NoteRequest(req); - } - return success; - -} - -//**************************************************************************/ -/*! - @brief Set the Notecard upload mode, download mode and interval. - @param uploadMode The upload mode (for instance, `continuous`, - or `periodic`). - @param uploadMinutes The max number of minutes to wait between Notehub - uploads. - @param downloadMinutes The max number of minutes to wait between Notehub - downloads. - @param align Flag to specify that uploads should be grouped within the - specified period, rather than counting the number of minutes - from first modified. - @param sync Setting this flag when mode is `continuous` causes an - immediate sync when a file is modified on the service side. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteSetSyncMode(const char *uploadMode, int uploadMinutes, int downloadMinutes, bool align, bool sync) -{ - bool success = false; - J *req = NoteNewRequest("hub.set"); - if (req != NULL) { - JAddStringToObject(req, "mode", uploadMode); - if (uploadMinutes != 0) { - JAddNumberToObject(req, "outbound", uploadMinutes); - // Setting this flag aligns uploads to be grouped within the period, - // rather than counting the number of minutes from "first modified". - JAddBoolToObject(req, "align", align); - } - if (downloadMinutes != 0) { - JAddNumberToObject(req, "inbound", downloadMinutes); - } - // Setting this flag when mode is "continuous" causes an immediate sync - // when a file is modified on the service side via HTTP - JAddBoolToObject(req, "sync", sync); - success = NoteRequest(req); - } - return success; - -} - -//**************************************************************************/ -/*! - @brief Set a template for a Notefile. - @param target The Notefile on which to set a template. - @param body The template body. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteTemplate(const char *target, J *body) -{ - J *req = NoteNewRequest("note.template"); - if (req == NULL) { - JDelete(body); - return false; - } - JAddStringToObject(req, "file", target); - JAddItemToObject(req, "body", body); - return NoteRequest(req); -} - -//**************************************************************************/ -/*! - @brief Add a Note to a Notefile with `note.add`. Body is freed, regardless - of success. - @param target The Notefile on which to set a template. - @param body The template body. - @param urgent Whether to perform an immediate sync after the Note - is added. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteAdd(const char *target, J *body, bool urgent) -{ - - // Initiate the request - J *req = NoteNewRequest("note.add"); - if (req == NULL) { - JDelete(body); - return false; - } - - // Add the target notefile and body to the request. Note that - // JAddItemToObject passes ownership of the object to req - JAddStringToObject(req, "file", target); - JAddItemToObject(req, "body", body); - - // Initiate sync NOW if it's urgent - if (urgent) { - JAddBoolToObject(req, "start", true); - } - - // Perform the transaction - return NoteRequest(req); - -} - -//**************************************************************************/ -/*! - @brief Send a body to a route using an HTTP request. - Body is freed, regardless of success. - @param method HTTP method to use, `get`, `post` or `put`. - @param routeAlias The Notehub Route alias. - @param notefile The Notefile name. - @param body The request JSON body. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteSendToRoute(const char *method, const char *routeAlias, char *notefile, J *body) -{ - - // Create the new event - J *req = NoteNewRequest("note.event"); - if (req == NULL) { - JDelete(body); - return false; - } - - // Add the body item and the Notefile name - JAddItemToObject(req, "body", body); - JAddStringToObject(req, "file", notefile); - - // Perform the transaction to convert it to an event - J *rsp = NoteRequestResponse(req); - if (rsp == NULL) { - return false; - } - - // Exit if error - if (NoteResponseError(rsp)) { - NoteDeleteResponse(rsp); - return false; - } - - // Extract the event, which we'll use as the body for the next transaction - body = JDetachItemFromObject(rsp, "body"); - NoteDeleteResponse(rsp); - - // Create the web transaction - char request[32]; - strlcpy(request, "web.", sizeof(request)); - strlcat(request, method, sizeof(request)); - req = NoteNewRequest(request); - if (req == NULL) { - JDelete(body); - return false; - } - - // Add the body, and the alias of the route on the notehub, hard-wired here - JAddItemToObject(req, "body", body); - JAddStringToObject(req, "route", routeAlias); - - // Perform the transaction - return NoteRequest(req); - -} - -//**************************************************************************/ -/*! - @brief Get the voltage of the Notecard. - @param voltage (out) The Notecard voltage. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteGetVoltage(JNUMBER *voltage) -{ - bool success = false; - *voltage = 0.0; - J *rsp = NoteRequestResponse(NoteNewRequest("card.voltage")); - if (rsp != NULL) { - if (!NoteResponseError(rsp)) { - *voltage = JGetNumber(rsp, "value"); - success = true; - } - NoteDeleteResponse(rsp); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Get the temperature of the Notecard. - @param temp (out) The Notecard temperature. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteGetTemperature(JNUMBER *temp) -{ - bool success = false; - *temp = 0.0; - J *rsp = NoteRequestResponse(NoteNewRequest("card.temp")); - if (rsp != NULL) { - if (!NoteResponseError(rsp)) { - *temp = JGetNumber(rsp, "value"); - success = true; - } - NoteDeleteResponse(rsp); - } - return success; -} - -//**************************************************************************/ -/*! - @brief Get the Notecard contact info. - @param nameBuf (out) The contact name buffer. - @param nameBufLen The length of the contact name buffer. - @param orgBuf (out) The contact organization buffer. - @param orgBufLen The length of the contact organization buffer. - @param roleBuf (out) The contact role buffer. - @param roleBufLen The length of the contact role buffer. - @param emailBuf (out) The contact email buffer. - @param emailBufLen The length of the contact email buffer. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteGetContact(char *nameBuf, int nameBufLen, char *orgBuf, int orgBufLen, char *roleBuf, int roleBufLen, char *emailBuf, int emailBufLen) -{ - bool success = false; - - if (nameBuf != NULL) { - *nameBuf = '\0'; - } - if (orgBuf != NULL) { - *orgBuf = '\0'; - } - if (roleBuf != NULL) { - *roleBuf = '\0'; - } - if (emailBuf != NULL) { - *emailBuf = '\0'; - } - - J *rsp = NoteRequestResponse(NoteNewRequest("card.contact")); - if (rsp != NULL) { - success = !NoteResponseError(rsp); - if (success) { - if (nameBuf != NULL) { - strlcpy(nameBuf, JGetString(rsp, "name"), nameBufLen); - } - if (orgBuf != NULL) { - strlcpy(orgBuf, JGetString(rsp, "org"), orgBufLen); - } - if (roleBuf != NULL) { - strlcpy(roleBuf, JGetString(rsp, "role"), roleBufLen); - } - if (emailBuf != NULL) { - strlcpy(emailBuf, JGetString(rsp, "email"), emailBufLen); - } - } - NoteDeleteResponse(rsp); - } - - return success; -} - -//**************************************************************************/ -/*! - @brief Set the Notecard contact info. - @param nameBuf (in) The contact name buffer. - @param orgBuf (in) The contact organization buffer. - @param roleBuf (in) The contact role buffer. - @param emailBuf (in) The contact email buffer. - @returns boolean. `true` if request was successful. -*/ -/**************************************************************************/ -bool NoteSetContact(char *nameBuf, char *orgBuf, char *roleBuf, char *emailBuf) -{ - J *req = NoteNewRequest("card.contact"); - if (req == NULL) { - return false; - } - if (nameBuf != NULL) { - JAddStringToObject(req, "name", nameBuf); - } - if (orgBuf != NULL) { - JAddStringToObject(req, "org", orgBuf); - } - if (roleBuf != NULL) { - JAddStringToObject(req, "role", roleBuf); - } - if (emailBuf != NULL) { - JAddStringToObject(req, "email", emailBuf); - } - return NoteRequest(req); -} - -// A simple suppression timer based on a millisecond system clock. This clock is -// reset to 0 after boot and every wake. This returns true if the specified -// interval has elapsed, in seconds, and it updates the timer if it expires so -// that we will go another period. -NOTE_C_STATIC bool timerExpiredSecs(uint32_t *timer, uint32_t periodSecs) -{ - bool expired = false; - - // If the timer went backward, we've expired regardless of the interval - uint32_t prev = *timer; - uint32_t now = _GetMs(); - if (now < prev) { - prev = 0; - } - - // If never initialized, it's expired - if (prev == 0 || now >= prev+(periodSecs*1000)) { - expired = true; - *timer = now; - } - - return expired; - -} - -//**************************************************************************/ -/*! - @brief Periodically show Notecard sync status, returning `true` if - something was displayed - @param pollFrequencyMs The time to wait, in milliseconds, between - polling for sync status. - @param maxLevel The Synclog level to monitor. - @returns boolean. `true` if a sync status message was displayed. -*/ -/**************************************************************************/ -bool NoteDebugSyncStatus(int pollFrequencyMs, int maxLevel) -{ - - // Suppress polls so as to not overwhelm the notecard - static uint32_t lastCommStatusPollMs = 0; - if (lastCommStatusPollMs != 0 && (_GetMs() - lastCommStatusPollMs) < (uint32_t) pollFrequencyMs) { - return false; - } - - // Get the next queued status note - J *req = NoteNewRequest("note.get"); - if (req == NULL) { - return false; - } - JAddStringToObject(req, "file", "_synclog.qi"); - JAddBoolToObject(req, "delete", true); - NoteSuspendTransactionDebug(); - J *rsp = NoteRequestResponse(req); - NoteResumeTransactionDebug(); - if (rsp != NULL) { - - // If an error is returned, this means that no response is pending. Note - // that it's expected that this might return either a "note does not - // exist" error if there are no pending inbound notes, or a "file does - // not exist" error if the inbound queue hasn't yet been created on the - // service. - if (NoteResponseError(rsp)) { - // Only stop polling quickly if we don't receive anything - lastCommStatusPollMs = _GetMs(); - NoteDeleteResponse(rsp); - return false; - } - - // Get the note's body - J *body = JGetObject(rsp, "body"); - if (body != NULL) { - if (maxLevel < 0 || JGetInt(body, "level") <= maxLevel) { - _Debug("[SYNC] "); - _Debug(JGetString(body, "subsystem")); - _Debug(" "); - _Debugln(JGetString(body, "text")); - } - } - - // Done with this response - NoteDeleteResponse(rsp); - return true; - } - - return false; - -} - -// A general purpose, super nonperformant, but accurate way of figuring out how -// much memory is available, while exercising the allocator to ensure that it -// competently deals with adjacent block coalescing on free. -typedef struct objHeader_s { - struct objHeader_s *prev; - int length; -} objHeader; - -//**************************************************************************/ -/*! - @brief Obtain the amount of free memory available on the Notecard. - @returns The number of bytes of memory available on the Notecard. -*/ -/**************************************************************************/ -uint32_t NoteMemAvailable(void) -{ - - // Allocate progressively smaller and smaller chunks - objHeader *lastObj = NULL; - static long int maxsize = 35000; - for (long int i=maxsize; i>=(long int)sizeof(objHeader); i=i-sizeof(objHeader)) { - for (long int j=0;; j++) { - objHeader *thisObj; - thisObj = (objHeader *) _Malloc(i); - if (thisObj == NULL) { - break; - } - thisObj->prev = lastObj; - thisObj->length = i; - lastObj = thisObj; - } - } - - // Free the objects backwards - long int lastLength = 0; - long int lastLengthCount = 0; - uint32_t total = 0; - while (lastObj != NULL) { - if (lastObj->length != lastLength) { - lastLength = lastObj->length; - lastLengthCount = 1; - } else { - lastLengthCount++; - } - objHeader *thisObj = lastObj; - lastObj = lastObj->prev; - total += thisObj->length; - _Free(thisObj); - } - - return total; - -} - -//**************************************************************************/ -/*! - @brief Create a desc from a buffer, or initialize a new to-be-allocated desc - if the buf is null - @param desc Pointer to the payload descriptor - @param buf Pointer to the buffer to initialize the desc with (or NULL) - @param buflen Length of the buffer to initialize the desc with (or 0) -*/ -/**************************************************************************/ -void NotePayloadSet(NotePayloadDesc *desc, uint8_t *buf, uint32_t buflen) -{ - desc->data = buf; - desc->alloc = buflen; - desc->length = buflen; -} - -//**************************************************************************/ -/*! - @brief Free the payload pointed to by the descriptor - @param desc Pointer to the payload descriptor -*/ -/**************************************************************************/ -void NotePayloadFree(NotePayloadDesc *desc) -{ - if (desc->data != NULL) { - _Free(desc->data); - } - desc->data = NULL; - desc->alloc = 0; - desc->length = 0; -} - -//**************************************************************************/ -/*! - @brief Add a segment to the specified binary with 4-character type code - @param desc Pointer to the payload descriptor - @param segtype Pointer to the 4-character payload identifier - @param data Pointer to the data segment to be appended - @param len Length of data segment - @returns boolean. `true` if named segment is appended successfully -*/ -/**************************************************************************/ -bool NotePayloadAddSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *data, uint32_t len) -{ - uint32_t alloc = 512; - uint32_t hlen = len + NP_SEGHDR_LEN; - if (hlen > alloc) { - alloc += hlen; - } - if (desc->data == NULL) { - uint8_t *base = _Malloc(alloc); - if (base == NULL) { - return false; - } - uint8_t *p = base; - memcpy(p, segtype, NP_SEGTYPE_LEN); - p += NP_SEGTYPE_LEN; - memcpy(p, &len, NP_SEGLEN_LEN); - p += NP_SEGLEN_LEN; - memcpy(p, data, len); - desc->data = base; - desc->alloc = alloc; - desc->length = hlen; - } else if ((desc->alloc - desc->length) < hlen) { - uint8_t *base = _Malloc(desc->alloc + alloc); - if (base == NULL) { - return false; - } - uint8_t *p = base; - memcpy(p, desc->data, desc->length); - p += desc->length; - memcpy(p, segtype, NP_SEGTYPE_LEN); - p += NP_SEGTYPE_LEN; - memcpy(p, &len, NP_SEGLEN_LEN); - p += NP_SEGLEN_LEN; - memcpy(p, data, len); - _Free(desc->data); - desc->data = base; - desc->alloc = desc->alloc + alloc; - desc->length += hlen; - } else { - uint8_t *p = desc->data + desc->length; - memcpy(p, segtype, NP_SEGTYPE_LEN); - p += NP_SEGTYPE_LEN; - memcpy(p, &len, NP_SEGLEN_LEN); - p += NP_SEGLEN_LEN; - memcpy(p, data, len); - desc->length += hlen; - } - return true; -} - -//**************************************************************************/ -/*! - @brief Find and copy a named segment from a segmented payload - @param desc Pointer to the payload descriptor - @param segtype Pointer to the 4-character payload identifier - @param pdata Buffer to copy found segment to if return is true - @param len The expected length of the returned segment - @returns boolean. `true` if named segment is restored successfully -*/ -/**************************************************************************/ -bool NotePayloadGetSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t len) -{ - uint8_t *data; - uint32_t datalen; - bool success = NotePayloadFindSegment(desc, segtype, &data, &datalen); - if (success && datalen == len) { - memcpy(pdata, data, len); - return true; - } - return false; -} - -//**************************************************************************/ -/*! - @brief Find a named segment within a segmented payload - @param desc Pointer to the payload descriptor - @param segtype Pointer to the 4-character payload identifier - @param pdata Pointer to the found segment if return is true - @param plen Pointer to the returned segment length if return is true - @returns boolean. `true` if named segment is found -*/ -/**************************************************************************/ -bool NotePayloadFindSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t *plen) -{ - - // Preset returns - * (uint8_t **) pdata = NULL; - *plen = 0; - - // Locate the segment - uint8_t *p = desc->data; - uint32_t left = desc->length; - if (p == NULL) { - return false; - } - while (left >= NP_SEGHDR_LEN) { - uint32_t len; - memcpy(&len, p + NP_SEGTYPE_LEN, sizeof(len)); - if (memcmp(p, segtype, NP_SEGTYPE_LEN) == 0) { - *plen = len; - * (uint8_t **) pdata = p + NP_SEGHDR_LEN; - return true; - } - len += NP_SEGHDR_LEN; - p += len; - if (len > left) { - left = 0; - } else { - left -= len; - } - } - return false; -} - -void NoteTurboIO(bool enable) -{ - (void)enable; - - NOTE_C_LOG_WARN("NoteTurboIO is deprecated and has no effect."); -} diff --git a/src/note-c/n_hooks.c b/src/note-c/n_hooks.c deleted file mode 100644 index 3e00d0e..0000000 --- a/src/note-c/n_hooks.c +++ /dev/null @@ -1,911 +0,0 @@ -/*! - * @file n_hooks.c - * - * Hooks allow libraries dependent on note-c to provide platform- or - * MCU-specific functions for common functions like I2C locking/unlocking, - * memory allocation and freeing, delays, and communicating with the Notecard - * over I2C and Serial. Using these hooks, note-c is able to manage Notecard - * transaction logic, and defer to platform functionality, when needed. - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - */ - -#include -#include -#include -#include "n_lib.h" - -//**************************************************************************/ -/*! - @brief Show malloc operations for debugging in very low mem environments. -*/ -/**************************************************************************/ -#define NOTE_SHOW_MALLOC false -#if NOTE_SHOW_MALLOC -#include -void *malloc_show(size_t len); -#endif - -// Which I/O port to use -#define interfaceNone 0 -#define interfaceSerial 1 -#define interfaceI2C 2 - -// Externalized Hooks -//**************************************************************************/ -/*! - @brief Hook for the calling platform's debug interface, if any. -*/ -/**************************************************************************/ -debugOutputFn hookDebugOutput = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's I2C lock function. -*/ -/**************************************************************************/ -mutexFn hookLockI2C = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's I2C unlock function. -*/ -/**************************************************************************/ -mutexFn hookUnlockI2C = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's Notecard lock function. -*/ -/**************************************************************************/ -mutexFn hookLockNote = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's Notecard lock function. -*/ -/**************************************************************************/ -mutexFn hookUnlockNote = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's transaction initiation function. -*/ -/**************************************************************************/ -txnStartFn hookTransactionStart = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's transaction completion function. -*/ -/**************************************************************************/ -txnStopFn hookTransactionStop = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's memory allocation function. -*/ -/**************************************************************************/ -mallocFn hookMalloc = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's memory free function. -*/ -/**************************************************************************/ -freeFn hookFree = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's delay function. -*/ -/**************************************************************************/ -delayMsFn hookDelayMs = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's millis timing function. -*/ -/**************************************************************************/ -getMsFn hookGetMs = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's current active interface. Value is - one of: - - interfaceNone = 0 (default) - - interfaceSerial = 1 - - interfaceI2C = 2 -*/ -/**************************************************************************/ -uint32_t hookActiveInterface = interfaceNone; - -//**************************************************************************/ -/*! - @brief Hook for the calling platform's Serial reset function. -*/ -/**************************************************************************/ -serialResetFn hookSerialReset = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's Serial transmit function. -*/ -/**************************************************************************/ -serialTransmitFn hookSerialTransmit = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's Serial data available function. -*/ -/**************************************************************************/ -serialAvailableFn hookSerialAvailable = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's Serial receive function. -*/ -/**************************************************************************/ -serialReceiveFn hookSerialReceive = NULL; - -//**************************************************************************/ -/*! - @brief Hook for the calling platform's I2C address. -*/ -/**************************************************************************/ -uint32_t i2cAddress = 0; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's I2C maximum segment size, in bytes. -*/ -/**************************************************************************/ -uint32_t i2cMax = 0; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's I2C reset function. -*/ -/**************************************************************************/ -i2cResetFn hookI2CReset = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's transmit function. -*/ -/**************************************************************************/ -i2cTransmitFn hookI2CTransmit = NULL; -//**************************************************************************/ -/*! - @brief Hook for the calling platform's I2C receive function. -*/ -/**************************************************************************/ -i2cReceiveFn hookI2CReceive = NULL; - -// Internal hooks -typedef bool (*nNoteResetFn) (void); -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; -static nReceiveFn notecardChunkedReceive = NULL; -static nTransmitFn notecardChunkedTransmit = NULL; - -/*! - @brief Set the default memory and timing hooks if they aren't already set - @param mallocfn The default memory allocation `malloc` - function to use. - @param freefn The default memory free - function to use. - @param delayfn The default delay function to use. - @param millisfn The default 'millis' function to use. - */ -void NoteSetFnDefault(mallocFn mallocfn, freeFn freefn, delayMsFn delayfn, getMsFn millisfn) -{ - if (hookMalloc == NULL) { - hookMalloc = mallocfn; - } - if (hookFree == NULL) { - hookFree = freefn; - } - if (hookDelayMs == NULL) { - hookDelayMs = delayfn; - } - if (hookGetMs == NULL) { - hookGetMs = millisfn; - } -} - -/*! - @brief Set the platform-specific memory and timing hooks. - - @param mallocHook The platform-specific memory allocation function (i.e. - `malloc`). - @param freeHook The platform-specific memory free function (i.e. `free`). - @param delayMsHook The platform-specific millisecond delay function. - @param getMsHook The platform-specific millisecond counter function. -*/ -void NoteSetFn(mallocFn mallocHook, freeFn freeHook, delayMsFn delayMsHook, - getMsFn getMsHook) -{ - hookMalloc = mallocHook; - hookFree = freeHook; - hookDelayMs = delayMsHook; - hookGetMs = getMsHook; -} - -//**************************************************************************/ -/*! - @brief Set the platform-specific debug output function. - @param fn A function pointer to call for debug output. -*/ -/**************************************************************************/ -void NoteSetFnDebugOutput(debugOutputFn fn) -{ - hookDebugOutput = fn; -} - -//**************************************************************************/ -/*! - @brief Determine if a debug output function has been set. - @returns A boolean indicating whether a debug ouput function was - provided. -*/ -/**************************************************************************/ -bool noteIsDebugOutputActive(void) -{ - return hookDebugOutput != NULL; -} - -//**************************************************************************/ -/*! - @brief Set the platform-specific transaction initiation/completion fn's - @param startFn The platform-specific transaction initiation function to use. - @param stopFn The platform-specific transaction completion function to use. - to use. -*/ -/**************************************************************************/ -void NoteSetFnTransaction(txnStartFn startFn, txnStopFn stopFn) -{ - hookTransactionStart = startFn; - hookTransactionStop = stopFn; -} - -//**************************************************************************/ -/*! - @brief Set the platform-specific mutex functions for I2C and the - Notecard. - @param lockI2Cfn The platform-specific I2C lock function to use. - @param unlockI2Cfn The platform-specific I2C unlock function to use. - @param lockNotefn The platform-specific Notecard lock function to use. - @param unlockNotefn The platform-specific Notecard unlock function - to use. -*/ -/**************************************************************************/ -void NoteSetFnMutex(mutexFn lockI2Cfn, mutexFn unlockI2Cfn, mutexFn lockNotefn, mutexFn unlockNotefn) -{ - hookLockI2C = lockI2Cfn; - hookUnlockI2C = unlockI2Cfn; - hookLockNote = lockNotefn; - hookUnlockNote = unlockNotefn; -} - -/*! - @brief Set the platform-specific mutex functions for I2C. - - @param lockI2Cfn The platform-specific I2C lock function. - @param unlockI2Cfn The platform-specific I2C unlock function. - */ -void NoteSetFnI2CMutex(mutexFn lockI2Cfn, mutexFn unlockI2Cfn) -{ - hookLockI2C = lockI2Cfn; - hookUnlockI2C = unlockI2Cfn; -} - -/*! - @brief Set the platform-specific mutex functions for the Notecard. - - @param lockFn The platform-specific Notecard lock function. - @param unlockFn The platform-specific Notecard unlock function. - */ -void NoteSetFnNoteMutex(mutexFn lockFn, mutexFn unlockFn) -{ - hookLockNote = lockFn; - hookUnlockNote = unlockFn; -} - -/*! - @brief Set the platform-specific hooks for communicating with the Notecard over - serial. - - @param resetFn The platform-specific serial reset function. - @param transmitFn The platform-specific serial transmit function. - @param availFn The platform-specific serial available function. - @param receiveFn The platform-specific serial receive function. -*/ -void NoteSetFnSerial(serialResetFn resetFn, serialTransmitFn transmitFn, - serialAvailableFn availFn, serialReceiveFn receiveFn) -{ - hookActiveInterface = interfaceSerial; - - hookSerialReset = resetFn; - hookSerialTransmit = transmitFn; - hookSerialAvailable = availFn; - hookSerialReceive = receiveFn; - - notecardReset = serialNoteReset; - notecardTransaction = serialNoteTransaction; - notecardChunkedReceive = serialChunkedReceive; - notecardChunkedTransmit = serialChunkedTransmit; -} - -/*! - @brief Set the platform-specific hooks for communicating with the Notecard over - I2C, as well as the I2C address of the Notecard and maximum transmission - size. - - @param notecardAddr The I2C address of the Notecard. Pass 0 to use the default - address. - @param maxTransmitSize The max number of bytes to send to the Notecard in a - single I2C segment. Pass 0 to use the default maximum transmission - size. - @param resetFn The platform-specific I2C reset function. - @param transmitFn The platform-specific I2C transmit function. - @param receiveFn The platform-specific I2C receive function. - */ -void NoteSetFnI2C(uint32_t notecardAddr, uint32_t maxTransmitSize, - i2cResetFn resetFn, i2cTransmitFn transmitFn, - i2cReceiveFn receiveFn) -{ - i2cAddress = notecardAddr; - i2cMax = maxTransmitSize; - - hookActiveInterface = interfaceI2C; - - hookI2CReset = resetFn; - hookI2CTransmit = transmitFn; - hookI2CReceive = receiveFn; - - notecardReset = i2cNoteReset; - notecardTransaction = i2cNoteTransaction; - notecardChunkedReceive = i2cChunkedReceive; - notecardChunkedTransmit = i2cChunkedTransmit; -} - -//**************************************************************************/ -/*! - @brief Set the platform-specific communications method to be disabled -*/ -/**************************************************************************/ -void NoteSetFnDisabled(void) -{ - - hookActiveInterface = interfaceNone; - - notecardReset = NULL; - notecardTransaction = NULL; - notecardChunkedReceive = NULL; - notecardChunkedTransmit = NULL; - -} - -// Runtime hook wrappers - -//**************************************************************************/ -/*! - @brief Write a number to the debug stream and output a newline. - @param line A debug string for output. - @param n The number to write. -*/ -/**************************************************************************/ -void NoteDebugIntln(const char *line, int n) -{ - if (line != NULL) { - _Debug(line); - } - char str[16]; - JItoA(n, str); - _Debug(str); - _Debug(c_newline); -} - -//**************************************************************************/ -/*! - @brief Write text to the debug stream and output a newline. - @param line A debug string for output. -*/ -/**************************************************************************/ -void NoteDebugln(const char *line) -{ - _Debug(line); - _Debug(c_newline); -} - -//**************************************************************************/ -/*! - @brief Write to the debug stream. - @param line A debug string for output. -*/ -/**************************************************************************/ -void NoteDebug(const char *line) -{ -#ifndef NOTE_NODEBUG - if (hookDebugOutput != NULL) { - hookDebugOutput(line); - } -#endif -} - -//**************************************************************************/ -/*! - @brief Write the message to the debug stream, if the level is less than - or equal to NOTE_C_LOG_LEVEL. Otherwise, the message is dropped. - @param level The log level of the message. See the NOTE_C_LOG_LEVEL_* macros - in note.h for possible values. - @param msg The debug message. -*/ -/**************************************************************************/ -void NoteDebugWithLevel(uint8_t level, const char *msg) -{ -#ifndef NOTE_NODEBUG - - if (level > NOTE_C_LOG_LEVEL) { - return; - } - - _Debug(msg); - -#endif // !NOTE_NODEBUG -} - -//**************************************************************************/ -/*! - @brief Same as NoteDebugWithLevel, but add a newline at the end. - @param level The log level of the message. See the NOTE_C_LOG_LEVEL_* macros - in note.h for possible values. - @param msg The debug message. -*/ -/**************************************************************************/ -void NoteDebugWithLevelLn(uint8_t level, const char *msg) -{ - _DebugWithLevel(level, msg); - _DebugWithLevel(level, c_newline); -} - -//**************************************************************************/ -/*! - @brief Get the current milliseconds value from the platform-specific - hook. - @returns The current milliseconds value. -*/ -/**************************************************************************/ -uint32_t NoteGetMs(void) -{ - if (hookGetMs == NULL) { - return 0; - } - return hookGetMs(); -} - -//**************************************************************************/ -/*! - @brief Delay milliseconds using the platform-specific hook. - @param ms the milliseconds delay value. -*/ -/**************************************************************************/ -void NoteDelayMs(uint32_t ms) -{ - if (hookDelayMs != NULL) { - hookDelayMs(ms); - } -} - -#if NOTE_SHOW_MALLOC || !defined(NOTE_C_LOW_MEM) -//**************************************************************************/ -/*! - @brief Convert number to a hex string - @param n the number - @param p the buffer to return it into -*/ -/**************************************************************************/ -void n_htoa32(uint32_t n, char *p) -{ - int i; - for (i=0; i<8; i++) { - uint32_t nibble = (n >> 28) & 0xff; - n = n << 4; - if (nibble >= 10) { - *p++ = 'A' + (nibble-10); - } else { - *p++ = '0' + nibble; - } - } - *p = '\0'; -} -#endif - -#if NOTE_SHOW_MALLOC -//**************************************************************************/ -/*! - @brief If set for low-memory platforms, show a malloc call. - @param len the number of bytes of memory allocated by the last call. -*/ -/**************************************************************************/ -void *malloc_show(size_t len) -{ - char str[16]; - JItoA(len, str); - hookDebugOutput("malloc "); - hookDebugOutput(str); - void *p = hookMalloc(len); - if (p == NULL) { - hookDebugOutput("FAIL"); - } else { - n_htoa32((uint32_t)p, str); - hookDebugOutput(str); - } - return p; -} -#endif - -//**************************************************************************/ -/*! - @brief Allocate a memory chunk using the platform-specific hook. - @param size the number of bytes to allocate. -*/ -/**************************************************************************/ -void *NoteMalloc(size_t size) -{ - if (hookMalloc == NULL) { - return NULL; - } -#if NOTE_SHOW_MALLOC - return malloc_show(size); -#else - return hookMalloc(size); -#endif -} - -//**************************************************************************/ -/*! - @brief Free memory using the platform-specific hook. - @param p A pointer to the memory address to free. -*/ -/**************************************************************************/ -void NoteFree(void *p) -{ - if (hookFree != NULL) { -#if NOTE_SHOW_MALLOC - char str[16]; - n_htoa32((uint32_t)p, str); - hookDebugOutput("free"); - hookDebugOutput(str); -#endif - hookFree(p); - } -} - -//**************************************************************************/ -/*! - @brief Lock the I2C bus using the platform-specific hook. -*/ -/**************************************************************************/ -void NoteLockI2C(void) -{ - if (hookLockI2C != NULL) { - hookLockI2C(); - } -} - -//**************************************************************************/ -/*! - @brief Unlock the I2C bus using the platform-specific hook. -*/ -/**************************************************************************/ -void NoteUnlockI2C(void) -{ - if (hookUnlockI2C != NULL) { - hookUnlockI2C(); - } -} - -//**************************************************************************/ -/*! - @brief Lock the Notecard using the platform-specific hook. -*/ -/**************************************************************************/ -void noteLockNote(void) -{ - if (hookLockNote != NULL) { - hookLockNote(); - } -} - -//**************************************************************************/ -/*! - @brief Unlock the Notecard using the platform-specific hook. -*/ -/**************************************************************************/ -void noteUnlockNote(void) -{ - if (hookUnlockNote != NULL) { - hookUnlockNote(); - } -} - -//**************************************************************************/ -/*! - @brief Indicate that we're initiating a transaction using the platform-specific hook. -*/ -/**************************************************************************/ -bool noteTransactionStart(uint32_t timeoutMs) -{ - if (hookTransactionStart != NULL) { - return hookTransactionStart(timeoutMs); - } - return true; -} - -//**************************************************************************/ -/*! - @brief Indicate that we've completed a transaction using the platform-specific hook. -*/ -/**************************************************************************/ -void noteTransactionStop(void) -{ - if (hookTransactionStop != NULL) { - hookTransactionStop(); - } -} - -//**************************************************************************/ -/*! - @brief Get the active interface's name - @returns A string -*/ -/**************************************************************************/ -const char *noteActiveInterface(void) -{ - switch (hookActiveInterface) { - case interfaceSerial: - return "serial"; - case interfaceI2C: - return "i2c"; - } - return "unknown"; -} - -//**************************************************************************/ -/*! - @brief Reset the Serial bus using the platform-specific hook. - @returns A boolean indicating whether the Serial bus was reset successfully. -*/ -/**************************************************************************/ -bool noteSerialReset(void) -{ - if (hookActiveInterface == interfaceSerial && hookSerialReset != NULL) { - return hookSerialReset(); - } - return true; -} - -//**************************************************************************/ -/*! - @brief Transmit bytes over Serial using the platform-specific hook. - @param text The bytes to transmit. - @param len The length of bytes. - @param flush `true` to flush the bytes upon transmit. -*/ -/**************************************************************************/ -void noteSerialTransmit(uint8_t *text, size_t len, bool flush) -{ - if (hookActiveInterface == interfaceSerial && hookSerialTransmit != NULL) { - hookSerialTransmit(text, len, flush); - } -} - -//**************************************************************************/ -/*! - @brief Determine if Serial bus is available using the platform-specific - hook. - @returns A boolean indicating whether the Serial bus is available to read. -*/ -/**************************************************************************/ -bool noteSerialAvailable(void) -{ - if (hookActiveInterface == interfaceSerial && hookSerialAvailable != NULL) { - return hookSerialAvailable(); - } - return false; -} - -//**************************************************************************/ -/*! - @brief Obtain a character from the Serial bus using the platform-specific - hook. - @returns A character from the Serial bus. -*/ -/**************************************************************************/ -char noteSerialReceive(void) -{ - if (hookActiveInterface == interfaceSerial && hookSerialReceive != NULL) { - return hookSerialReceive(); - } - return 0; -} - -//**************************************************************************/ -/*! - @brief Reset the I2C bus using the platform-specific hook. - @returns A boolean indicating whether the I2C bus was reset successfully. -*/ -/**************************************************************************/ -bool noteI2CReset(uint16_t DevAddress) -{ - if (hookActiveInterface == interfaceI2C && hookI2CReset != NULL) { - return hookI2CReset(DevAddress); - } - return true; -} - -//**************************************************************************/ -/*! - @brief Transmit bytes over I2C using the platform-specific hook. - @param DevAddress the I2C address for transmission. - @param pBuffer The bytes to transmit. - @param Size The length of bytes. - @returns A c-string from the platform-specific hook, or an error string - if the bus is not active. -*/ -/**************************************************************************/ -const char *noteI2CTransmit(uint16_t DevAddress, uint8_t* pBuffer, uint16_t Size) -{ - if (hookActiveInterface == interfaceI2C && hookI2CTransmit != NULL) { - return hookI2CTransmit(DevAddress, pBuffer, Size); - } - return "i2c not active"; -} - -//**************************************************************************/ -/*! - @brief Receive bytes from I2C using the platform-specific hook. - @param DevAddress the I2C address for transmission. - @param pBuffer (out) A buffer in which to place received bytes. - @param Size The length of bytes. - @param available (out) The number of bytes left to read. - @returns A c-string from the platform-specific hook, or an error string - if the bus is not active. -*/ -/**************************************************************************/ -const char *noteI2CReceive(uint16_t DevAddress, uint8_t* pBuffer, uint16_t Size, uint32_t *available) -{ - if (hookActiveInterface == interfaceI2C && hookI2CReceive != NULL) { - return hookI2CReceive(DevAddress, pBuffer, Size, available); - } - return "i2c not active"; -} - -//**************************************************************************/ -/*! - @brief Get the I2C address of the Notecard. - @returns The current I2C address. -*/ -/**************************************************************************/ -uint32_t NoteI2CAddress(void) -{ - if (i2cAddress == 0) { - return NOTE_I2C_ADDR_DEFAULT; - } - return i2cAddress; -} - -//**************************************************************************/ -/*! - @brief Set the I2C address for communication with the Notecard. - @param i2caddress the I2C address to use for the Notecard. -*/ -/**************************************************************************/ -void NoteSetI2CAddress(uint32_t i2caddress) -{ - i2cAddress = i2caddress; -} - -//**************************************************************************/ -/*! - @brief Determine the maximum number of bytes for each segment of - data sent to the Notecard over I2C. - @returns A 32-bit integer of the maximum number of bytes per I2C segment. -*/ -/**************************************************************************/ -uint32_t NoteI2CMax(void) -{ - // Many Arduino libraries (such as ESP32) have a limit less than 32, so if the max isn't specified - // we must assume the worst and segment the I2C messages into very tiny chunks. - if (i2cMax == 0) { - return NOTE_I2C_MAX_DEFAULT; - } - // Note design specs - if (i2cMax > NOTE_I2C_MAX_MAX) { - i2cMax = NOTE_I2C_MAX_MAX; - } - return i2cMax; -} - - -//**************************************************************************/ -/*! - @brief Perform a hard reset on the Notecard using the platform-specific - hook. - @returns A boolean indicating whether the Notecard has been reset successfully. -*/ -/**************************************************************************/ -bool noteHardReset(void) -{ - if (notecardReset == NULL) { - return true; - } - return notecardReset(); -} - - -//**************************************************************************/ -/*! - @brief Perform a JSON request to the Notecard using the currently-set - platform hook. - - @param request A string containing the JSON request object, which MUST BE - terminated with a newline character. - @param reqLen the string length of the JSON request. - @param response [out] A c-string buffer that will contain the newline ('\n') - terminated JSON response from the Notercard. If NULL, no response - will be captured. - @param timeoutMs The maximum amount of time, in milliseconds, to wait - for data to arrive. Passing zero (0) disables the timeout. - - @returns NULL if successful, or an error string if the transaction failed - or the hook has not been set. -*/ -/**************************************************************************/ -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"; - } - return notecardTransaction(request, reqLen, response, timeoutMs); -} - -/**************************************************************************/ -/*! - @brief Receive bytes over from the Notecard using the currently-set - platform hook. - @param buffer A buffer to receive bytes into. - @param size (in/out) - - (in) The size of the buffer in bytes. - - (out) The length of the received data in bytes. - @param delay Respect standard processing delays. - @param timeoutMs The maximum amount of time, in milliseconds, to wait - for data to arrive. Passing zero (0) disables the timeout. - @param available (in/out) - - (in) The amount of bytes to request. Sending zero (0) will - initiate a priming query when using the I2C interface. - - (out) The amount of bytes unable to fit into the provided buffer. - @returns A c-string with an error, or `NULL` if no error ocurred. -*/ -/**************************************************************************/ -const char *noteChunkedReceive(uint8_t *buffer, uint32_t *size, bool delay, - uint32_t timeoutMs, uint32_t *available) -{ - if (notecardChunkedReceive == NULL || hookActiveInterface == interfaceNone) { - return "i2c or serial interface must be selected"; - } - return notecardChunkedReceive(buffer, size, delay, timeoutMs, available); -} - -/**************************************************************************/ -/*! - @brief Transmit bytes over to the Notecard using the currently-set - platform hook. - @param buffer A buffer of bytes to transmit. - @param size The count of bytes in the buffer to send - @param delay Respect standard processing delays. - @returns A c-string with an error, or `NULL` if no error ocurred. -*/ -/**************************************************************************/ -const char *noteChunkedTransmit(uint8_t *buffer, uint32_t size, bool delay) -{ - if (notecardChunkedTransmit == NULL || hookActiveInterface == interfaceNone) { - return "i2c or serial interface must be selected"; - } - return notecardChunkedTransmit(buffer, size, delay); -} diff --git a/src/note-c/n_i2c.c b/src/note-c/n_i2c.c deleted file mode 100644 index deb2269..0000000 --- a/src/note-c/n_i2c.c +++ /dev/null @@ -1,466 +0,0 @@ -/*! - * @file n_i2c.c - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - */ - -#include - -#include "n_lib.h" - -#ifdef NOTE_C_TEST -#include "test_static.h" -#else -#define NOTE_C_STATIC static -#endif - -// Forwards -NOTE_C_STATIC void delayIO(void); -NOTE_C_STATIC const char * i2cNoteQueryLength(uint32_t * available, uint32_t timeoutMs); - -/**************************************************************************/ -/*! - @brief We've noticed that there's an instability in some cards' - implementations of I2C, and as a result we introduce an intentional - delay before each and every I2C I/O.The timing was computed - empirically based on a number of commercial devices. -*/ -/**************************************************************************/ -NOTE_C_STATIC void delayIO(void) -{ - if (!cardTurboIO) { - _DelayMs(6); - } -} - -/**************************************************************************/ -/*! - @brief Query the Notecard for the length of cached data. - - @details It is necessary to send a priming I2C transaction to understand - the amount of data the Notecard is prepared to send before an - I2C read request can be issued. -*/ -/**************************************************************************/ -NOTE_C_STATIC const char * i2cNoteQueryLength(uint32_t * available, - uint32_t timeoutMs) -{ - uint8_t dummy_buffer = 0; - - 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) { - NOTE_C_LOG_ERROR(err); - return err; - } - - // If we've timed out, return an error - if (timeoutMs && _GetMs() - startMs >= timeoutMs) { - const char *err = ERRSTR("timeout: no response from Notecard {io}", c_iotimeout); - NOTE_C_LOG_ERROR(err); - return err; - } - } - return NULL; -} - -/**************************************************************************/ -/*! - @brief Given a JSON string, perform an I2C transaction with the Notecard. - - @param request A string containing the JSON request object, which MUST BE - terminated with a newline character. - @param reqLen the string length of the JSON request. - @param response [out] A c-string buffer that will contain the newline ('\n') - terminated JSON response from the Notercard. If NULL, no response - will be captured. - @param timeoutMs The maximum amount of time, in milliseconds, to wait - for data to arrive. Passing zero (0) disables the timeout. - - @returns a c-string with an error, or `NULL` if no error occurred. -*/ -/**************************************************************************/ -const char *i2cNoteTransaction(const char *request, size_t reqLen, char **response, uint32_t timeoutMs) -{ - const char *err = NULL; - - // Lock over the entire transaction - _LockI2C(); - - err = i2cChunkedTransmit((uint8_t *)request, reqLen, true); - if (err) { - _UnlockI2C(); - return err; - } - - // If no reply expected, we're done - if (response == NULL) { - _UnlockI2C(); - return NULL; - } - - delayIO(); - - // Allocate a buffer for input, noting that we always put the +1 in the - // alloc so we can be assured that it can be null-terminated. This must be - // the case because json parsing requires a null-terminated string. - uint32_t available = 0; - err = i2cNoteQueryLength(&available, timeoutMs); - if (err) { - NOTE_C_LOG_ERROR(ERRSTR("failed to query Notecard", c_err)); - _UnlockI2C(); - return err; - } - size_t jsonbufAllocLen = (ALLOC_CHUNK * ((available / ALLOC_CHUNK) + ((available % ALLOC_CHUNK) > 0))); - uint8_t *jsonbuf = NULL; - if (jsonbufAllocLen) { - jsonbuf = (uint8_t *)_Malloc(jsonbufAllocLen + 1); - if (jsonbuf == NULL) { - const char *err = ERRSTR("transaction: jsonbuf malloc failed", c_mem); - NOTE_C_LOG_ERROR(err); - _UnlockI2C(); - return err; - } - } - - // Receive the Notecard response - uint32_t jsonbufLen = 0; - do { - uint32_t jsonbufAvailLen = (jsonbufAllocLen - jsonbufLen); - - // Append into the json buffer - const char *err = i2cChunkedReceive((uint8_t *)(jsonbuf + jsonbufLen), &jsonbufAvailLen, true, (CARD_INTRA_TRANSACTION_TIMEOUT_SEC * 1000), &available); - if (err) { - if (jsonbuf) { - _Free(jsonbuf); - } - NOTE_C_LOG_ERROR(ERRSTR("error occured during receive", c_iobad)); - _UnlockI2C(); - return err; - } - jsonbufLen += jsonbufAvailLen; - - if (available) { - // When more bytes are available than we have buffer to accommodate - // (i.e. overflow), then we allocate blocks of size `ALLOC_CHUNK` to - // reduce heap fragmentation. - // NOTE: We always put the +1 in the allocation so we can be assured - // that it can be null-terminated, because the json parser requires - // a null-terminated string. - jsonbufAllocLen += (ALLOC_CHUNK * ((available / ALLOC_CHUNK) + ((available % ALLOC_CHUNK) > 0))); - uint8_t *jsonbufNew = (uint8_t *)_Malloc(jsonbufAllocLen + 1); - if (jsonbufNew == NULL) { - const char *err = ERRSTR("transaction: jsonbuf grow malloc failed", c_mem); - NOTE_C_LOG_ERROR(err); - if (jsonbuf) { - _Free(jsonbuf); - } - _UnlockI2C(); - return err; - } - if (jsonbuf) { - memcpy(jsonbufNew, jsonbuf, jsonbufLen); - _Free(jsonbuf); - } - jsonbuf = jsonbufNew; - NOTE_C_LOG_DEBUG("additional receive buffer chunk allocated"); - } - } while (available); - - // Done with the bus - _UnlockI2C(); - - // Null-terminate it, using the +1 space that we'd allocated in the buffer - if (jsonbuf) { - jsonbuf[jsonbufLen] = '\0'; - } - - // Return it - *response = (char *)jsonbuf; - return NULL; -} - -//**************************************************************************/ -/*! - @brief Initialize or re-initialize the I2C subsystem, returning false if - anything fails. - - @returns a boolean. `true` if the reset was successful, `false`, if not. -*/ -/**************************************************************************/ -bool i2cNoteReset(void) -{ - bool notecardReady = false; - - // Claim the I2C bus - _LockI2C(); - NOTE_C_LOG_DEBUG("resetting I2C interface..."); - - // Reset the I2C subsystem and exit if failure - _DelayMs(CARD_REQUEST_I2C_SEGMENT_DELAY_MS); - notecardReady = _I2CReset(_I2CAddress()); - if (!notecardReady) { - NOTE_C_LOG_ERROR(ERRSTR("error encountered during I2C reset hook execution", c_err)); - _UnlockI2C(); - return false; - } - delayIO(); - - // The guaranteed behavior for robust resyncing is to send two newlines - // and wait for two echoed blank lines in return. - for (size_t retries = 0; retries < CARD_RESET_SYNC_RETRIES ; ++retries) { - // Send a newline to the module to clean out request/response processing - // NOTE: This MUST always be `\n` and not `\r\n`, because there are some - // versions of the Notecard firmware will not respond to `\r\n` - // after communicating over I2C. - const char *transmitErr = _I2CTransmit(_I2CAddress(), (uint8_t *)"\n", 1); - // If we get a failure on transmitting the `\n`, it means that the - // Notecard isn't present. - if (transmitErr) { - NOTE_C_LOG_ERROR(transmitErr); - NOTE_C_LOG_ERROR(ERRSTR("error encountered during I2C transmit hook execution", c_err)); - _DelayMs(CARD_REQUEST_I2C_NACK_WAIT_MS); - notecardReady = false; - continue; - } - - // Wait for the Notecard to respond with a carriage return and newline - _DelayMs(CARD_REQUEST_I2C_SEGMENT_DELAY_MS); - - // Determine if I2C data is available - // set initial state of variable to perform query - uint16_t chunkLen = 0; - - // Content flags to determine if reset conditions are met. - bool somethingFound = false; - bool nonControlCharFound = false; - - // Read I2C data for at least `CARD_RESET_DRAIN_MS` continuously - for (uint32_t startMs = _GetMs() ; (_GetMs() - startMs) < CARD_RESET_DRAIN_MS ;) { - - // Read the next chunk of available data - uint32_t available = 0; - uint8_t buffer[ALLOC_CHUNK] = {0}; - chunkLen = (chunkLen > sizeof(buffer)) ? sizeof(buffer) : chunkLen; - chunkLen = (chunkLen > _I2CMax()) ? _I2CMax() : chunkLen; - const char *err = _I2CReceive(_I2CAddress(), buffer, chunkLen, &available); - if (err) { - // We have received a hardware or protocol level error. - // Introduce delay to relieve system stress. - NOTE_C_LOG_ERROR(err); - NOTE_C_LOG_ERROR(ERRSTR("error encountered during I2C receive hook execution", c_err)); - _DelayMs(CARD_REQUEST_I2C_SEGMENT_DELAY_MS); - notecardReady = false; - continue; - } - - // Set content flags - if (chunkLen) { - somethingFound = true; - // The Notecard responds to a bare `\n` with `\r\n`. If we get - // any other characters back, it means the host and Notecard - // aren't synced up yet and we need to transmit `\n` again. - for (size_t i = 0; i < chunkLen ; ++i) { - char ch = buffer[i]; - if (ch != '\n' && ch != '\r') { - nonControlCharFound = true; - // Reset the timer with each non-control character - startMs = _GetMs(); - } - } - } - - // Read the minimum of the available bytes left to read and what - // will fit into a 16-bit unsigned value (_I2CReceive takes the - // buffer size as a uint16_t). - chunkLen = (available > 0xFFFF) ? 0xFFFF : available; - - _DelayMs(CARD_REQUEST_I2C_CHUNK_DELAY_MS); - } - - // If characters were received and they were ONLY `\r` or `\n`, - // then the Notecard has been successfully reset. - if (!somethingFound || nonControlCharFound) { - notecardReady = false; - if (somethingFound) { - NOTE_C_LOG_WARN(ERRSTR("unrecognized data from notecard", c_iobad)); - } else { - NOTE_C_LOG_ERROR(ERRSTR("notecard not responding", c_iobad)); - - // Reset the I2C subsystem and exit if failure - if (!_I2CReset(_I2CAddress())) { - NOTE_C_LOG_ERROR(ERRSTR("error encountered during I2C reset hook execution", c_err)); - break; - } - delayIO(); - } - } else { - notecardReady = true; - break; - } - - NOTE_C_LOG_DEBUG("retrying I2C interface reset...") - } - - // Done with the I2C bus - _UnlockI2C(); - - // Done - return notecardReady; -} - -/**************************************************************************/ -/*! - @brief Receive bytes over I2C from the Notecard. - - @param buffer A buffer to receive bytes into. - @param size (in/out) - - (in) The size of the buffer in bytes. - - (out) The length of the received data in bytes. - @param delay Respect standard processing delays. - @param timeoutMs The maximum amount of time, in milliseconds, to wait for - serial data to arrive. Passing zero (0) disables the timeout. - @param available (in/out) - - (in) The amount of bytes to request. Sending zero (0) will - initiate a priming query when using the I2C interface. - - (out) The amount of bytes unable to fit into the provided buffer. - - @returns A c-string with an error, or `NULL` if no error ocurred. -*/ -/**************************************************************************/ -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; - uint32_t startMs = _GetMs(); - - // Request all available bytes, up to the maximum request size - requested = (*available > 0xFFFF) ? 0xFFFF : *available; - requested = (requested > _I2CMax()) ? _I2CMax() : requested; - - for (bool eop = false ; !overflow ; overflow = ((received + requested) > *size)) { - - // Read a chunk of data from I2C - // The first read will request zero bytes to query the amount of data - // available to receive from the Notecard. - const char *err = _I2CReceive(_I2CAddress(), (buffer + received), requested, available); - if (err) { - *size = received; - NOTE_C_LOG_ERROR(err); - return err; - } - - // Add requested bytes to received total - received += requested; - - // Once we've received any character, we will no longer wait patiently - if (requested != 0) { - timeoutMs = (CARD_INTRA_TRANSACTION_TIMEOUT_SEC * 1000); - startMs = _GetMs(); - } - - // Request all available bytes, up to the maximum request size - requested = (*available > 0xFFFF) ? 0xFFFF : *available; - requested = (requested > _I2CMax()) ? _I2CMax() : requested; - - // Look for end-of-packet marker - if (received > 0 && !eop) { - eop = (buffer[received-1] == '\n'); - } - - // If the last byte of the chunk is `\n`, then we have received a - // complete message. However, everything pending from the Notecard must - // be pulled. This loop will only exit when a newline is received AND - // there are no more bytes available from the Notecard, OR if the buffer - // is full and cannot receive more bytes (i.e. overflow condition). - if (*available && eop) { - NOTE_C_LOG_WARN(ERRSTR("received newline before all data was received", c_iobad)); - }; - - // If there's something available on the Notecard for us to receive, do it - if (*available > 0) { - continue; - } - - // If there's nothing available AND we've received a newline, we're done - if (eop) { - break; - } - - // Exit on timeout - if (timeoutMs && (_GetMs() - startMs >= timeoutMs)) { - *size = received; - if (received) { - NOTE_C_LOG_ERROR(ERRSTR("received only partial reply before timeout", c_iobad)); - } - return ERRSTR("timeout: transaction incomplete {io}", c_iotimeout); - } - - // Delay, simply waiting for the Note to process the request - if (delay) { - _DelayMs(50); - } - } - - *size = received; - return NULL; -} - -/**************************************************************************/ -/*! - @brief Transmit bytes over I2C to the Notecard. - - @param buffer A buffer of bytes to transmit. - @param size The count of bytes in the buffer to send - @param delay Respect standard processing delays. - - @returns A c-string with an error, or `NULL` if no error ocurred. -*/ -/**************************************************************************/ -const char *i2cChunkedTransmit(uint8_t *buffer, uint32_t size, bool delay) -{ - // Transmit the request in chunks, but also in segments so as not to - // overwhelm the notecard's interrupt buffers - const char *estr; - uint8_t *chunk = buffer; - uint16_t sentInSegment = 0; - while (size > 0) { - // Constrain chunkLen to fit into 16 bits (_I2CTransmit takes the buffer - // size as a uint16_t). - uint16_t chunkLen = (size > 0xFFFF) ? 0xFFFF : size; - // Constrain chunkLen to be <= _I2CMax(). - chunkLen = (chunkLen > _I2CMax()) ? _I2CMax() : chunkLen; - - delayIO(); - estr = _I2CTransmit(_I2CAddress(), chunk, chunkLen); - if (estr != NULL) { - _I2CReset(_I2CAddress()); - NOTE_C_LOG_ERROR(estr); - return estr; - } - chunk += chunkLen; - size -= chunkLen; - sentInSegment += chunkLen; - if (sentInSegment > CARD_REQUEST_I2C_SEGMENT_MAX_LEN) { - sentInSegment = 0; - if (delay) { - _DelayMs(CARD_REQUEST_I2C_SEGMENT_DELAY_MS); - } - } - if (delay) { - _DelayMs(CARD_REQUEST_I2C_CHUNK_DELAY_MS); - } - } - - return NULL; -} diff --git a/src/note-c/n_lib.h b/src/note-c/n_lib.h deleted file mode 100644 index 60d59b7..0000000 --- a/src/note-c/n_lib.h +++ /dev/null @@ -1,240 +0,0 @@ -/*! - * @file n_lib.h - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - */ - -#pragma once - -#include -#include - -#include "note.h" - -// C-callable functions -#ifdef __cplusplus -extern "C" { -#endif - -/**************************************************************************/ -/*! - @brief How long to wait for the card for any given transaction. -*/ -/**************************************************************************/ -#define CARD_INTER_TRANSACTION_TIMEOUT_SEC 30 -#define CARD_INTRA_TRANSACTION_TIMEOUT_SEC 1 - -// The notecard is a real-time device that has a fixed size interrupt buffer. -// We can push data at it far, far faster than it can process it, therefore we -// push it in segments with a pause between each segment. - -/**************************************************************************/ -/*! - @brief The max length, in bytes, of each request segment when using I2C. -*/ -/**************************************************************************/ -#define CARD_REQUEST_I2C_SEGMENT_MAX_LEN 250 -/**************************************************************************/ -/*! - @brief The delay, in miliseconds, of each request when using I2C. -*/ -/**************************************************************************/ -#define CARD_REQUEST_I2C_SEGMENT_DELAY_MS 250 -/**************************************************************************/ -/*! - @brief The delay, in miliseconds, between each request chunk when using I2C. -*/ -/**************************************************************************/ -#define CARD_REQUEST_I2C_CHUNK_DELAY_MS 20 -/**************************************************************************/ -/*! - @brief The delay, in miliseconds, to wait after receiving a NACK I2C. -*/ -/**************************************************************************/ -#define CARD_REQUEST_I2C_NACK_WAIT_MS 1000 -/**************************************************************************/ -/*! - @brief The max length, in bytes, of each request segment when using Serial. -*/ -/**************************************************************************/ -#define CARD_REQUEST_SERIAL_SEGMENT_MAX_LEN 250 -/**************************************************************************/ -/*! - @brief The delay, in miliseconds, of each request when using Serial. -*/ -/**************************************************************************/ -#define CARD_REQUEST_SERIAL_SEGMENT_DELAY_MS 250 -/**************************************************************************/ -/*! - @brief The time, in miliseconds, to drain incoming messages. -*/ -/**************************************************************************/ -#define CARD_RESET_DRAIN_MS 500 -/**************************************************************************/ -/*! - @brief The number of times we will retry a request before giving up. -*/ -/**************************************************************************/ -#define CARD_REQUEST_RETRIES_ALLOWED 5 -/**************************************************************************/ -/*! - @brief The number of times we will retry getting in sync before giving up. -*/ -/**************************************************************************/ -#define CARD_RESET_SYNC_RETRIES 10 -/**************************************************************************/ -/*! - @brief Memory allocation chunk size. -*/ -/**************************************************************************/ -#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, uint32_t timeoutMs); -bool i2cNoteReset(void); -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, 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, uint32_t timeoutMs, uint32_t *available); -const char *serialChunkedTransmit(uint8_t *buffer, uint32_t size, bool delay); - -// Hooks -void noteLockNote(void); -void noteUnlockNote(void); -bool noteTransactionStart(uint32_t timeoutMs); -void noteTransactionStop(void); -const char *noteActiveInterface(void); -bool noteSerialReset(void); -void noteSerialTransmit(uint8_t *, size_t, bool); -bool noteSerialAvailable(void); -char noteSerialReceive(void); -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, 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); - -// Utilities -void n_htoa32(uint32_t n, char *p); -void n_htoa16(uint16_t n, unsigned char *p); -uint64_t n_atoh(char *p, int maxLen); - -// COBS Helpers -uint32_t cobsDecode(uint8_t *ptr, uint32_t length, uint8_t eop, uint8_t *dst); -uint32_t cobsEncode(uint8_t *ptr, uint32_t length, uint8_t eop, uint8_t *dst); -uint32_t cobsEncodedLength(const uint8_t *ptr, uint32_t length); -uint32_t cobsEncodedMaxLength(uint32_t length); -uint32_t cobsGuaranteedFit(uint32_t bufLen); - -// Turbo I/O mode -extern bool cardTurboIO; - -// Constants, a global optimization to save static string memory -extern const char *c_null; -#define c_null_len 4 - -extern const char *c_false; -#define c_false_len 5 - -extern const char *c_true; -#define c_true_len 4 - -extern const char *c_nullstring; -#define c_nullstring_len 0 - -extern const char *c_newline; -#define c_newline_len 2 - -extern const char *c_mem; -#define c_mem_len 3 - -extern const char *c_iotimeout; -#define c_iotimeout_len 12 - -extern const char *c_err; -#define c_err_len 3 - -extern const char *c_req; -#define c_req_len 3 - -extern const char *c_cmd; -#define c_cmd_len 3 - -extern const char *c_bad; -#define c_bad_len 3 - -extern const char *c_iobad; -#define c_iobad_len 8 - -extern const char *c_ioerr; -#define c_ioerr_len 4 - -extern const char *c_unsupported; -#define c_unsupported_len 15 - -extern const char *c_badbinerr; -#define c_badbinerr_len 9 - -// Readability wrappers. Anything starting with _ is simply calling the wrapper -// function. -#define _LockNote noteLockNote -#define _UnlockNote noteUnlockNote -#define _TransactionStart noteTransactionStart -#define _TransactionStop noteTransactionStop -#define _SerialReset noteSerialReset -#define _SerialTransmit noteSerialTransmit -#define _SerialAvailable noteSerialAvailable -#define _SerialReceive noteSerialReceive -#define _I2CReset noteI2CReset -#define _I2CTransmit noteI2CTransmit -#define _I2CReceive noteI2CReceive -#define _Reset noteHardReset -#define _Transaction noteJSONTransaction -#define _ChunkedReceive noteChunkedReceive -#define _ChunkedTransmit noteChunkedTransmit -#define _Malloc NoteMalloc -#define _Free NoteFree -#define _GetMs NoteGetMs -#define _DelayMs NoteDelayMs -#define _LockI2C NoteLockI2C -#define _UnlockI2C NoteUnlockI2C -#define _I2CAddress NoteI2CAddress -#define _I2CMax NoteI2CMax -#ifdef NOTE_NODEBUG -#define _Debug(x) -#define _Debugln(x) -#define _DebugIntln(x, y) -#define _DebugWithLevel(x, y) -#define _DebugWithLevelLn(x, y) -#else -#define _Debug(x) NoteDebug(x) -#define _Debugln(x) NoteDebugln(x) -#define _DebugIntln(x, y) NoteDebugIntln(x, y) -#define _DebugWithLevel(x, y) NoteDebugWithLevel(x, y) -#define _DebugWithLevelLn(x, y) NoteDebugWithLevelLn(x, y) -#endif - -// End of C-callable functions -#ifdef __cplusplus -} -#endif diff --git a/src/note-c/n_md5.c b/src/note-c/n_md5.c deleted file mode 100644 index 739fd7a..0000000 --- a/src/note-c/n_md5.c +++ /dev/null @@ -1,321 +0,0 @@ -/* - * https://opensource.apple.com/source/cvs/cvs-19/cvs/lib/md5.c - * - * This code implements the MD5 message-digest algorithm. - * The algorithm is due to Ron Rivest. This code was - * written by Colin Plumb in 1993, no copyright is claimed. - * This code is in the public domain; do with it what you wish. - * - * Equivalent code is available from RSA Data Security, Inc. - * This code has been tested against that, and is equivalent, - * except that you don't need to include two pages of legalese - * with every copy. - * - * To compute the message digest of a chunk of bytes, declare an - * MD5Context structure, pass it to NoteMD5Init, call NoteMD5Update as - * needed on buffers full of bytes, and then call NoteMD5Final, which - * will fill a supplied 16-byte array with the digest. - */ - -/* This code was modified in 1997 by Jim Kingdon of Cyclic Software to - not require an integer type which is exactly 32 bits. This work - draws on the changes for the same purpose by Tatu Ylonen - as part of SSH, but since I didn't actually use - that code, there is no copyright issue. I hereby disclaim - copyright in any changes I have made; this code remains in the - public domain. */ - -#include -#include "n_lib.h" - -// Forwards -void n_htoa8(unsigned char n, unsigned char *p); -static void putu32 (unsigned long data, unsigned char *addr); -static unsigned long getu32 (const unsigned char *addr); - -/* Little-endian byte-swapping routines. Note that these do not - depend on the size of datatypes such as unsigned long, nor do they require - us to detect the endianness of the machine we are running on. It - is possible they should be macros for speed, but I would be - surprised if they were a performance bottleneck for MD5. */ - -static unsigned long getu32 (const unsigned char *addr) -{ - return (((((unsigned long)addr[3] << 8) | addr[2]) << 8) - | addr[1]) << 8 | addr[0]; -} - -static void putu32 (unsigned long data, unsigned char *addr) -{ - addr[0] = (unsigned char)data; - addr[1] = (unsigned char)(data >> 8); - addr[2] = (unsigned char)(data >> 16); - addr[3] = (unsigned char)(data >> 24); -} - -/* Convert an 8-bit number to 2 hex digits, null-terminating it */ -void n_htoa8(unsigned char n, unsigned char *p) -{ - unsigned char nibble = (n >> 4) & 0xf; - if (nibble >= 10) { - *p++ = 'a' + (nibble-10); - } else { - *p++ = '0' + nibble; - } - nibble = n & 0xf; - if (nibble >= 10) { - *p++ = 'a' + (nibble-10); - } else { - *p++ = '0' + nibble; - } - *p = '\0'; -} - -/* - * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious - * initialization constants. - */ -void NoteMD5Init(NoteMD5Context *ctx) -{ - memset(ctx, 0, sizeof(NoteMD5Context)); - - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - - ctx->bits[0] = 0; - ctx->bits[1] = 0; -} - -/* - * Update context to reflect the concatenation of another buffer full - * of bytes. - */ -void NoteMD5Update(NoteMD5Context *ctx, unsigned char const *buf, unsigned long len) -{ - unsigned long t; - - /* Update bitcount */ - - t = ctx->bits[0]; - if ((ctx->bits[0] = (t + ((unsigned long)len << 3)) & 0xffffffff) < t) { - ctx->bits[1]++; /* Carry from low to high */ - } - ctx->bits[1] += len >> 29; - - t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ - - /* Handle any leading odd-sized chunks */ - - if ( t ) { - unsigned char *p = ctx->in + t; - - t = 64-t; - if (len < t) { - memcpy(p, buf, len); - return; - } - memcpy(p, buf, t); - NoteMD5Transform(ctx->buf, ctx->in); - buf += t; - len -= t; - } - - /* Process data in 64-byte chunks */ - - while (len >= 64) { - memcpy(ctx->in, buf, 64); - NoteMD5Transform(ctx->buf, ctx->in); - buf += 64; - len -= 64; - } - - /* Handle any remaining bytes of data. */ - - memcpy(ctx->in, buf, len); -} - -/* - * Final wrapup - pad to 64-byte boundary with the bit pattern - * 1 0* (64-bit count of bits processed, MSB-first) - */ -void NoteMD5Final(unsigned char *digest, NoteMD5Context *ctx) -{ - unsigned count; - unsigned char *p; - - /* Compute number of bytes mod 64 */ - count = (ctx->bits[0] >> 3) & 0x3F; - - /* Set the first char of padding to 0x80. This is safe since there is - always at least one byte free */ - p = ctx->in + count; - *p++ = 0x80; - - /* Bytes of padding needed to make 64 bytes */ - count = 64 - 1 - count; - - /* Pad out to 56 mod 64 */ - if (count < 8) { - /* Two lots of padding: Pad the first block to 64 bytes */ - memset(p, 0, count); - NoteMD5Transform(ctx->buf, ctx->in); - - /* Now fill the next block with 56 bytes */ - memset(ctx->in, 0, 56); - } else { - /* Pad block to 56 bytes */ - memset(p, 0, count-8); - } - - /* Append length in bits and transform */ - putu32(ctx->bits[0], ctx->in + 56); - putu32(ctx->bits[1], ctx->in + 60); - - NoteMD5Transform(ctx->buf, ctx->in); - putu32(ctx->buf[0], digest); - putu32(ctx->buf[1], digest + 4); - putu32(ctx->buf[2], digest + 8); - putu32(ctx->buf[3], digest + 12); - memset(ctx, 0, sizeof(NoteMD5Context)); /* In case it's sensitive */ -} - -/* The four core functions - F1 is optimized somewhat */ - -/* #define F1(x, y, z) (x & y | ~x & z) */ -#define F1(x, y, z) (z ^ (x & (y ^ z))) -#define F2(x, y, z) F1(z, x, y) -#define F3(x, y, z) (x ^ y ^ z) -#define F4(x, y, z) (y ^ (x | ~z)) - -/* This is the central step in the MD5 algorithm. */ -#define MD5STEP(f, w, x, y, z, data, s) \ - ( w += f(x, y, z) + data, w &= 0xffffffff, w = w<>(32-s), w += x ) - -/* - * The core of the MD5 algorithm, this alters an existing MD5 hash to - * reflect the addition of 16 longwords of new data. NoteMD5Update blocks - * the data and converts bytes into longwords for this routine. - */ -void NoteMD5Transform(unsigned long buf[4], const unsigned char inraw[64]) -{ - register unsigned long a, b, c, d; - unsigned long in[16]; - int i; - - for (i = 0; i < 16; ++i) { - in[i] = getu32 (inraw + 4 * i); - } - - a = buf[0]; - b = buf[1]; - c = buf[2]; - d = buf[3]; - - MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); - MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); - MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); - MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); - MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); - MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); - MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); - MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); - MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); - MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); - MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); - MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); - MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); - MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); - MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); - MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); - - MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); - MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); - MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); - MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); - MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); - MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); - MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); - MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); - MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); - MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); - MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); - MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); - MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); - MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); - MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); - MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); - - MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); - MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); - MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); - MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); - MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); - MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); - MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); - MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); - MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); - MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); - MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); - MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); - MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); - MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); - MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); - MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); - - MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); - MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); - MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); - MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); - MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); - MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); - MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); - MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); - MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); - MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); - MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); - MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); - MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); - MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); - MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); - MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); - - buf[0] += a; - buf[1] += b; - buf[2] += c; - buf[3] += d; - -} - -// Hash data and return its binary hash into an NOTE_MD5_HASH_SIZE buffer -void NoteMD5Hash(unsigned char* data, unsigned long len, unsigned char *retHash) -{ - NoteMD5Context context; - NoteMD5Init(&context); - NoteMD5Update(&context, data, len); - NoteMD5Final(retHash, &context); -} - -// Hash data and return it as a string into a buffer that is at least (16*2)+1 in length -void NoteMD5HashString(unsigned char *data, unsigned long len, char *strbuf, unsigned long buflen) -{ - unsigned char hash[NOTE_MD5_HASH_SIZE]; - NoteMD5Hash(data, len, hash); - char hashstr[NOTE_MD5_HASH_SIZE*3] = {0}; - for (int i=0; iLICENSE - * file. - * - */ - -#include -#include -#include -#include "n_lib.h" - -// Externalized Hooks -//**************************************************************************/ -/*! - @brief Hook for the calling platform's debug interface, if any. -*/ -/**************************************************************************/ -extern debugOutputFn hookDebugOutput; - -//**************************************************************************/ -/*! - @brief Write a formatted string to the debug output. - @param format A format string for output. - @param ... One or more values to interpolate into the format string. -*/ -/**************************************************************************/ -void NoteDebugf(const char *format, ...) -{ -#ifndef NOTE_NODEBUG - if (hookDebugOutput != NULL) { - char line[256]; - va_list args; - va_start(args, format); - vsnprintf(line, sizeof(line), format, args); - va_end(args); - hookDebugOutput(line); - } -#endif -} - -//**************************************************************************/ -/*! - @brief Write a formatted string to the debug output. - @param format A format string for output. - @param ... One or more values to interpolate into the format string. - @note. Do NOT use this in a memory-constrained environment (vsnprintf is large) -*/ -/**************************************************************************/ -#ifndef NOTE_LOMEM -bool NotePrintf(const char *format, ...) -{ - char line[256]; - va_list args; - va_start(args, format); - vsnprintf(line, sizeof(line), format, args); - va_end(args); - return NotePrint(line); -} -#endif diff --git a/src/note-c/n_request.c b/src/note-c/n_request.c deleted file mode 100644 index 9226d17..0000000 --- a/src/note-c/n_request.c +++ /dev/null @@ -1,954 +0,0 @@ -/*! - @file n_request.c - - Written by Ray Ozzie and Blues Inc. team. - - Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - governed by licenses granted by the copyright holder including that found in - the - LICENSE - file. - */ - -#include "n_lib.h" - -#ifdef NOTE_C_TEST -#include "test_static.h" -#else -#define NOTE_C_STATIC static -#endif - -// For flow tracing -static int suppressShowTransactions = 0; - -// Flag that gets set whenever an error occurs that should force a reset -static bool resetRequired = true; - -// CRC data -#ifndef NOTE_C_LOW_MEM -static uint16_t lastRequestSeqno = 0; -#define CRC_FIELD_LENGTH 22 // ,"crc":"SSSS:CCCCCCCC" -#define CRC_FIELD_NAME_OFFSET 1 -#define CRC_FIELD_NAME_TEST "\"crc\":\"" -NOTE_C_STATIC int32_t crc32(const void* data, size_t length); -NOTE_C_STATIC char * crcAdd(char *json, uint16_t seqno); -NOTE_C_STATIC bool crcError(char *json, uint16_t shouldBeSeqno); - -static bool notecardSupportsCrc = false; -#endif - -/*! - @internal - - @brief Create a JSON object containing an error message. - - Create a dynamically allocated `J` object containing a single string field - "err" whose value is the passed in error message. - - @param id The "id" from the original request that resulted in an error - @param errmsg The error message. - - @returns A `J` object with the "err" field populated. - */ -NOTE_C_STATIC J * errDoc(uint32_t id, const char *errmsg) -{ - J *rspdoc = JCreateObject(); - if (rspdoc != NULL) { - JAddStringToObject(rspdoc, c_err, errmsg); - JAddStringToObject(rspdoc, "src", "note-c"); - if (id) { - JAddIntToObject(rspdoc, "id", id); - } - if (suppressShowTransactions == 0) { - _DebugWithLevel(NOTE_C_LOG_LEVEL_ERROR, "[ERROR] "); - _DebugWithLevel(NOTE_C_LOG_LEVEL_ERROR, "{\"err\":\""); - _DebugWithLevel(NOTE_C_LOG_LEVEL_ERROR, errmsg); - _DebugWithLevelLn(NOTE_C_LOG_LEVEL_ERROR, "\",\"src\":\"note-c\"}"); - } - } - - return rspdoc; -} - -/*! - @brief Suppress showing transaction details. - */ -void NoteSuspendTransactionDebug(void) -{ - suppressShowTransactions++; -} - -/*! - @brief Resume showing transaction details. - */ -void NoteResumeTransactionDebug(void) -{ - suppressShowTransactions--; -} - -/*! - @brief Create a new JSON request. - - Creates a dynamically allocated `J` object with one field `"req"` whose value - is the passed in request string. - - @param request The name of the request, for example `hub.set`. - - @returns A `J` object with the "req" field populated. - */ -J *NoteNewRequest(const char *request) -{ - J *reqdoc = JCreateObject(); - if (reqdoc != NULL) { - JAddStringToObject(reqdoc, c_req, request); - } - return reqdoc; -} - -/*! - @brief Create a new JSON command. - - Create a dynamically allocated `J` object with one field `"cmd"` whose value is - the passed in request string. The difference between a command and a request is - that the Notecard does not send a response to commands, only to requests. - - @param request The name of the command (e.g. `card.attn`). - - @returns A `J` object with the "cmd" field populated. - - */ -J *NoteNewCommand(const char *request) -{ - J *reqdoc = JCreateObject(); - if (reqdoc != NULL) { - JAddStringToObject(reqdoc, c_cmd, request); - } - return reqdoc; -} - -/*! - @brief Send a request to the Notecard. - - The passed in request object is always freed, regardless of if the request was - successful or not. The response from the Notecard, if any, is freed and not - returned to the caller. - - @param req Pointer to a `J` request object. - - @returns `true` if successful and `false` if an error occurs (e.g. out of - memory or the response from the Notecard has an "err" field). If req - is a command rather than a request, a `true` return value indicates - that the command was sent without error. However, since the Notecard - sends no response to commands, it does not guarantee that the - command was received and processed by the Notecard. - - @see `NoteRequestResponse` if you need to work with the response. - */ -bool NoteRequest(J *req) -{ - J *rsp = NoteRequestResponse(req); - if (rsp == NULL) { - return false; - } - - // Check for a transaction error, and exit - bool success = JIsNullString(rsp, c_err); - JDelete(rsp); - - return success; -} - -/*! - @brief Send a request to the Notecard, retrying it until it succeeds or it - times out. - - The passed in request object is always freed, regardless of if the request was - successful or not. The response from the Notecard, if any, is freed and not - returned to the caller. - - @param req Pointer to a `J` request object. - @param timeoutSeconds Time limit for retires, in seconds, if there is no - response, or if the response contains an I/O error. - - @returns `true` if successful and `false` if an error occurs (e.g. out of - memory or the response from the Notecard has an "err" field). - - @see `NoteRequestResponseWithRetry` if you need to work with the response. - */ -bool NoteRequestWithRetry(J *req, uint32_t timeoutSeconds) -{ - J *rsp = NoteRequestResponseWithRetry(req, timeoutSeconds); - // If there is no response return false - if (rsp == NULL) { - return false; - } - - // Check for a transaction error, and exit - bool success = JIsNullString(rsp, c_err); - JDelete(rsp); - - return success; -} - -/*! - @brief Send a request to the Notecard and return the response. - - The passed in request object is always freed, regardless of if the request was - successful or not. - - @param req Pointer to a `J` request object. - - @returns A `J` object with the response or NULL if there was an error sending - the request. - - @see `NoteResponseError` to check the response for errors. - */ -J *NoteRequestResponse(J *req) -{ - // Exit if null request. This allows safe execution of the form - // NoteRequestResponse(NoteNewRequest("xxx")) - if (req == NULL) { - return NULL; - } - // Execute the transaction - J *rsp = NoteTransaction(req); - if (rsp == NULL) { - JDelete(req); - return NULL; - } - // Free the request and exit - JDelete(req); - return rsp; -} - -/*! - @brief Send a request to the Notecard, retrying it until it succeeds or it - times out, and return the response. - - The passed in request object is always freed, regardless of if the request was - successful or not. - - @param req Pointer to a `J` request object. - @param timeoutSeconds Time limit for retires, in seconds, if there is no - response, or if the response contains an I/O error. - - @returns A `J` object with the response or NULL if there was an error sending - the request. - - @see `NoteResponseError` to check the response for errors. - */ -J *NoteRequestResponseWithRetry(J *req, uint32_t timeoutSeconds) -{ - // Exit if null request. This allows safe execution of the form - // NoteRequestResponse(NoteNewRequest("xxx")) - if (req == NULL) { - return NULL; - } - - J *rsp; - - // Calculate expiry time in milliseconds - uint32_t startMs = _GetMs(); - uint32_t timeoutMs = timeoutSeconds * 1000; - - while(true) { - // Execute the transaction - rsp = NoteTransaction(req); - - // Loop if there is no response, or if there is an io error - if ((rsp == NULL) || (JContainsString(rsp, c_err, c_ioerr) && !JContainsString(rsp, c_err, c_unsupported))) { - - // Free error response - if (rsp != NULL) { - JDelete(rsp); - rsp = NULL; - } - } else { - - // Exit loop on non-null response without io error - break; - } - - // Exit loop on timeout - if (_GetMs() - startMs >= timeoutMs) { - break; - } - } - - // Free the request - JDelete(req); - - // Return the response - return rsp; -} - -/*! - @brief Send a request to the Notecard and return the response. - - Unlike `NoteRequestResponse`, this function expects the request to be a valid - JSON C-string, rather than a `J` object. The string is expected to be - newline-terminated, otherwise the call produces undefined behavior. The - response is returned as a dynamically allocated JSON C-string. The response - string is verbatim what was sent by the Notecard, which IS newline-terminated. - The caller is responsible for freeing the response string. If the request was a - command (i.e. it uses "cmd" instead of "req"), this function returns NULL, - because the Notecard does not send a response to commands. - - @param reqJSON A valid newline-terminated JSON C-string containing the request. - - @returns A newline-terminated JSON C-string with the response, or NULL - if there was no response or if there was an error. - - @note When a "cmd" is sent, it is not possible to determine if an error occurred. -*/ -char * NoteRequestResponseJSON(const char *reqJSON) -{ - uint32_t transactionTimeoutMs = (CARD_INTER_TRANSACTION_TIMEOUT_SEC * 1000); - char *rspJSON = NULL; - - if (reqJSON == NULL) { - return NULL; - } - - // Make sure that we get access to the Notecard before transacting. - if (!_TransactionStart(transactionTimeoutMs)) { - return NULL; - } - - _LockNote(); - - // Manually tokenize the string to search for multiple embedded commands (cannot use strtok) - for (;;) { - const char *endPtr; - const char * const newlinePtr = strchr(reqJSON, '\n'); - - // If string is not newline-terminated, then allocate a new string and terminate it - if (NULL == newlinePtr) { - // All JSON strings should be newline-terminated to meet the specification, however - // this is required to ensure backward compatibility with the previous implementation. - const size_t tempLen = strlen(reqJSON); - if (0 == tempLen) { - NOTE_C_LOG_ERROR(ERRSTR("request: jsonbuf zero length", c_bad)); - break; - } - - NOTE_C_LOG_WARN(ERRSTR("Memory allocation due to malformed request (not newline-terminated)", c_bad)); - char * const temp = _Malloc(tempLen + 2); // +2 for newline and null-terminator - if (temp == NULL) { - NOTE_C_LOG_ERROR(ERRSTR("request: jsonbuf malloc failed", c_mem)); - break; - } - - memcpy(temp, reqJSON, tempLen); - temp[tempLen] = '\n'; - temp[tempLen + 1] = '\0'; - reqJSON = temp; - endPtr = &temp[tempLen]; - } else { - endPtr = newlinePtr; - } - const size_t reqLen = ((endPtr - reqJSON) + 1); - - bool isCmd = false; - if (strstr(reqJSON, "\"cmd\":") != NULL) { - // Only call `JParse()` after verifying the provided request - // appears to contain a command (i.e. we find `"cmd":`). - J *jsonObj = JParse(reqJSON); - if (!jsonObj) { - // Invalid JSON. - if (NULL == newlinePtr) { - _Free((void *)reqJSON); - } - break; - } - isCmd = JIsPresent(jsonObj, "cmd"); - JDelete(jsonObj); - } - - if (!isCmd) { - const char *errstr = _Transaction(reqJSON, reqLen, &rspJSON, transactionTimeoutMs); - if (errstr != NULL) { - NOTE_C_LOG_ERROR(errstr); - uint32_t id = 0; - if (reqJSON != NULL) { - J *req = JParse(reqJSON); - if (req != NULL) { - id = JGetInt(req, "id"); - JDelete(req); - } - } - J *errdoc = errDoc(id, errstr); - if (errdoc != NULL) { - char *errdocJSON = JPrintUnformatted(errdoc); - JDelete(errdoc); - if (errdocJSON != NULL) { - uint32_t errdocJSONLen = strlen(errdocJSON); - rspJSON = (char *) _Malloc(errdocJSONLen+2); - if (rspJSON != NULL) { - memcpy(rspJSON, errdocJSON, errdocJSONLen); - rspJSON[errdocJSONLen++] = '\n'; - rspJSON[errdocJSONLen] = '\0'; - } - _Free((void *)errdocJSON); - } - } - } - if (NULL == newlinePtr) { - _Free((void *)reqJSON); - } - break; - } else { - // If it's a command, the Notecard will not respond, so we pass NULL for - // the response parameter. - const char *errstr = _Transaction(reqJSON, reqLen, NULL, transactionTimeoutMs); - reqJSON = (endPtr + 1); - if (errstr != NULL) { - NOTE_C_LOG_ERROR(errstr); - } - } - - // Clean up if we allocated a new string - if (NULL == newlinePtr) { - _Free((void *)reqJSON); - } - } - - _UnlockNote(); - _TransactionStop(); - - return rspJSON; -} - -/*! - @brief Send a request to the Notecard and return the response. - - This function doesn't free the passed in request object. The caller is - responsible for freeing it. - - @param req Pointer to a `J` request object. - - @returns A `J` object with the response or NULL if there was an error sending - the request. - - @see `NoteResponseError` to check the response for errors. - */ -J *NoteTransaction(J *req) -{ - return noteTransactionShouldLock(req, true); -} - -/**************************************************************************/ -/*! - @brief Same as `NoteTransaction`, but takes an additional parameter that - indicates if the Notecard should be locked. - @param req - The `J` cJSON request object. - @param lockNotecard - Set to `true` if the Notecard should be locked and `false` otherwise. - @returns a `J` cJSON object with the response, or NULL if there is - insufficient memory. -*/ -/**************************************************************************/ -J *noteTransactionShouldLock(J *req, bool lockNotecard) -{ - // Validate in case of memory failure of the requestor - if (req == NULL) { - return NULL; - } - - // Make sure that we get access to the notecard hardware before we begin - if (!_TransactionStart(CARD_INTER_TRANSACTION_TIMEOUT_SEC * 1000)) { - return NULL; - } - - // Determine the request or command type - const char *reqType = JGetString(req, "req"); - const char *cmdType = JGetString(req, "cmd"); - - // Add the user agent object only when we're doing a hub.set and only when we're - // specifying the product UID. The intent is that we only piggyback user agent - // data when the host is initializing the Notecard, as opposed to every time - // the host does a hub.set to change mode. -#ifndef NOTE_DISABLE_USER_AGENT - if (!JIsPresent(req, "body") && (strcmp(reqType, "hub.set") == 0) && JIsPresent(req, "product")) { - J *body = NoteUserAgent(); - if (body != NULL) { - JAddItemToObject(req, "body", body); - } - } -#endif - - // Determine whether or not a response will be expected, by virtue of "cmd" being present - bool noResponseExpected = (reqType[0] == '\0' && cmdType[0] != '\0'); - - // If a reset of the module is required for any reason, do it now. - // We must do this before acquiring lock. - if (resetRequired) { - if (!NoteReset()) { - _TransactionStop(); - return NULL; - } - } - - if (lockNotecard) { - _LockNote(); - } - - // Extract the ID of the request so that errors can be returned with the same ID - uint32_t id = JGetInt(req, "id"); - - // Serialize the JSON request - char *json = JPrintUnformatted(req); - if (json == NULL) { - J *errRsp = errDoc(id, ERRSTR("can't convert to JSON", c_bad)); - if (lockNotecard) { - _UnlockNote(); - } - _TransactionStop(); - return errRsp; - } - - // If it is a request (as opposed to a command), include a CRC so that the - // request might be retried if it is received in a corrupted state. (We can - // only do this on requests because for cmd's there is no 'response channel' - // 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. - uint8_t lastRequestRetries = 0; -#ifndef NOTE_C_LOW_MEM - bool lastRequestCrcAdded = false; - if (!noResponseExpected) { - char *newJson = crcAdd(json, lastRequestSeqno); - if (newJson != NULL) { - JFree(json); - json = newJson; - lastRequestCrcAdded = true; - } - } -#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 - // the payload and network conditions. Therefore, it's possible for - // these transactions to timeout prematurely. - // - // The algorithm below, executes the following logic: - // - If the request is a `note.add`, set the timeout value to the - // value of the "milliseconds" parameter, if it exists. If it - // doesn't, use the "seconds" parameter. If that doesn't exist, - // use the standard timeout of `CARD_INTER_TRANSACTION_TIMEOUT_SEC`. - // - 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. - uint32_t transactionTimeoutMs = (CARD_INTER_TRANSACTION_TIMEOUT_SEC * 1000); - - // Interrogate the request - if (JContainsString(req, (reqType ? "req" : "cmd"), "note.add")) { - if (JIsPresent(req, "milliseconds")) { - NOTE_C_LOG_DEBUG("Using `milliseconds` parameter value for " - "timeout."); - transactionTimeoutMs = JGetInt(req, "milliseconds"); - } else if (JIsPresent(req, "seconds")) { - NOTE_C_LOG_DEBUG("Using `seconds` parameter value for timeout."); - transactionTimeoutMs = (JGetInt(req, "seconds") * 1000); - } - } else if (JContainsString(req, (reqType ? "req" : "cmd"), "web.")) { - NOTE_C_LOG_DEBUG("web.* request received."); - if (JIsPresent(req, "milliseconds")) { - NOTE_C_LOG_DEBUG("Using `milliseconds` parameter value for " - "timeout."); - transactionTimeoutMs = JGetInt(req, "milliseconds"); - } else if (JIsPresent(req, "seconds")) { - NOTE_C_LOG_DEBUG("Using `seconds` parameter value for timeout."); - transactionTimeoutMs = (JGetInt(req, "seconds") * 1000); - } else { - NOTE_C_LOG_DEBUG("No `milliseconds` or `seconds` parameter " - "provided. Defaulting to 90-second timeout."); - transactionTimeoutMs = (90 * 1000); - } - } - - // If we're performing retries, this is where we come back to - const char *errStr = NULL; - char *responseJSON = NULL; - J *rsp = NULL; - while (true) { - // If no retry possibility, break out - if (lastRequestRetries > CARD_REQUEST_RETRIES_ALLOWED) { - break; - } else { - // free on retry - JDelete(rsp); - } - - // reset variables - rsp = NULL; - responseJSON = NULL; - - // Trace - if (suppressShowTransactions == 0) { - NOTE_C_LOG_INFO(json); - } - - // Swap NULL-terminator for newline-terminator - const size_t jsonLen = strlen(json); - json[jsonLen] = '\n'; - - // Perform the transaction - if (noResponseExpected) { - errStr = _Transaction(json, (jsonLen + 1), NULL, transactionTimeoutMs); - break; - } - errStr = _Transaction(json, (jsonLen + 1), &responseJSON, transactionTimeoutMs); - - // Swap newline-terminator for NULL-terminator - json[jsonLen] = '\0'; - - // If there's an I/O error on the transaction, retry - if (errStr != NULL) { - JFree(responseJSON); - resetRequired = !_Reset(); - lastRequestRetries++; - NOTE_C_LOG_WARN(ERRSTR("retrying I/O error detected by host", c_iobad)); - _DelayMs(500); - 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. - if (lastRequestCrcAdded && crcError(responseJSON, lastRequestSeqno)) { - JFree(responseJSON); - errStr = "crc error {io}"; - lastRequestRetries++; - NOTE_C_LOG_ERROR(ERRSTR("CRC error on response", c_iobad)); - _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); - bool isBadBin = false; - bool isIoError = false; - if (rsp != NULL) { - isBadBin = JContainsString(rsp, c_err, c_badbinerr); - isIoError = JContainsString(rsp, c_err, c_ioerr) && !JContainsString(rsp, c_err, c_unsupported); - } else { - // Failed to parse response as JSON - if (responseJSON == NULL) { - NOTE_C_LOG_ERROR(ERRSTR("response expected, but response is NULL.", c_ioerr)); - } else { -#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); -#else - NOTE_C_LOG_ERROR(c_ioerr); -#endif - } - isIoError = true; - } - if (isIoError || isBadBin) { - NOTE_C_LOG_ERROR(JGetString(rsp, c_err)); - JFree(responseJSON); - - if (isBadBin) { - errStr = ERRSTR("notecard binary i/o error {bad-bin}{io}", c_badbinerr); - NOTE_C_LOG_DEBUG("{bad-bin} is not elibigle for retry"); - break; - } else { - errStr = ERRSTR("notecard i/o error {io}", c_ioerr); - lastRequestRetries++; - NOTE_C_LOG_WARN(ERRSTR("retrying I/O error detected by notecard", c_iobad)); - _DelayMs(500); - continue; - } - } - - // Transaction completed - break; - } - - // Bump the request sequence number now that we've processed this request, success or error -#ifndef NOTE_C_LOW_MEM - lastRequestSeqno++; -#endif - - // Free the original serialized JSON request - JFree(json); - - // If error, queue up a reset - if (errStr != NULL) { - JDelete(rsp); - NoteResetRequired(); - J *errRsp = errDoc(id, errStr); - if (lockNotecard) { - _UnlockNote(); - } - _TransactionStop(); - return errRsp; - } - - // Exit with a blank object (with no err field) if no response expected - if (noResponseExpected) { - if (lockNotecard) { - _UnlockNote(); - } - _TransactionStop(); - return JCreateObject(); - } - - // Debug - if (suppressShowTransactions == 0) { - NOTE_C_LOG_INFO(responseJSON); - } - - // Discard the buffer now that it has been logged - JFree(responseJSON); - - if (lockNotecard) { - _UnlockNote(); - } - - _TransactionStop(); - - // Done - return rsp; - -} - -/*! - @brief Mark that a reset will be required before doing further I/O on a given - port. - */ -void NoteResetRequired(void) -{ - resetRequired = true; -} - -/*! - @brief Initialize or re-initialize the module. - - @returns True if the reset was successful and false if not. - */ -bool NoteReset(void) -{ - _LockNote(); - resetRequired = !_Reset(); - _UnlockNote(); - return !resetRequired; -} - -/*! - @brief Check to see if a Notecard error is present in a JSON string. - - Only Notecard errors enclosed in `{}` (e.g. `{io}` for an I/O error) are - guaranteed by the API. - - @param errstr The string to check for errors. - @param errtype The error substring to search for in errstr. - - @returns `true` if errstr contains errtype and `false` otherwise. - */ -bool NoteErrorContains(const char *errstr, const char *errtype) -{ - return (strstr(errstr, errtype) != NULL); -} - -/*! - @brief Clean error strings out of the specified buffer. - - Notecard errors are enclosed in {} (e.g. {io} for an I/O error). This - function takes the input string and removes all errors from it, meaning it - removes any substrings matching the pattern {some error string}, including the - braces. - - @param begin A C-string to to clean of errors. - */ -void NoteErrorClean(char *begin) -{ - while (true) { - char *end = &begin[strlen(begin)+1]; - char *beginBrace = strchr(begin, '{'); - if (beginBrace == NULL) { - break; - } - if (beginBrace>begin && *(beginBrace-1) == ' ') { - beginBrace--; - } - char *endBrace = strchr(beginBrace, '}'); - if (endBrace == NULL) { - break; - } - char *afterBrace = endBrace + 1; - memmove(beginBrace, afterBrace, end-afterBrace); - } -} - -#ifndef NOTE_C_LOW_MEM - -/*! - @brief Convert a hex string to a 64-bit unsigned integer. - - @param p The hex string to convert. - @param maxLen The length of the hex string. - - @returns The converted number. - */ -uint64_t n_atoh(char *p, int maxLen) -{ - uint64_t n = 0; - char *ep = p+maxLen; - while (p < ep) { - char ch = *p++; - bool digit = (ch >= '0' && ch <= '9'); - bool lcase = (ch >= 'a' && ch <= 'f'); - bool space = (ch == ' '); - bool ucase = (ch >= 'A' && ch <= 'F'); - if (!digit && !lcase && !space && !ucase) { - break; - } - n *= 16; - if (digit) { - n += ch - '0'; - } else if (lcase) { - n += 10 + (ch - 'a'); - } else if (ucase) { - n += 10 + (ch - 'A'); - } - } - return (n); -} - -static uint32_t lut[16] = { - 0x00000000, 0x1DB71064, 0x3B6E20C8, 0x26D930AC, 0x76DC4190, 0x6B6B51F4, - 0x4DB26158, 0x5005713C, 0xEDB88320, 0xF00F9344, 0xD6D6A3E8, 0xCB61B38C, - 0x9B64C2B0, 0x86D3D2D4, 0xA00AE278, 0xBDBDF21C -}; - -/*! - @brief Compute the CRC32 of the passed in buffer. - - Small lookup-table half-byte CRC32 algorithm. See - https://create.stephan-brumme.com/crc32/#half-byte - - @param data The buffer. - @param length The length of the buffer. - - @returns The CRC32 of the buffer. - */ -NOTE_C_STATIC int32_t crc32(const void* data, size_t length) -{ - uint32_t previousCrc32 = 0; - uint32_t crc = ~previousCrc32; - unsigned char* current = (unsigned char*) data; - while (length--) { - crc = lut[(crc ^ *current ) & 0x0F] ^ (crc >> 4); - crc = lut[(crc ^ (*current >> 4)) & 0x0F] ^ (crc >> 4); - current++; - } - return ~crc; -} - -/*! - @brief Append a "crc" field to the passed in JSON buffer. - - The "crc" field has a value of the form "SSSS:CCCCCCCC", where SSSS is the - passed in sequence number and CCCCCCCC is the CRC32. - - @param json The JSON buffer to both add the CRC32 to and to compute the - CRC32 over. - @param seqno A 16-bit sequence number to include as a part of the CRC. - - @returns A dynamically-allocated version of the passed in buffer with the CRC - field added or NULL on error. - */ -NOTE_C_STATIC char *crcAdd(char *json, uint16_t seqno) -{ - - // Allocate a block the size of the input json plus the size of - // the field to be added. Note that the input JSON ends in '"}' and - // this will be replaced with a combination of 4 hex digits of the - // seqno plus 8 hex digits of the CRC32, and the '}' will be - // transformed into ',"crc":"SSSS:CCCCCCCC"}' where SSSS is the - // seqno and CCCCCCCC is the crc32. Note that the comma is - // replaced with a space if the input json doesn't contain - // any fields, so that we always return compliant JSON. - size_t jsonLen = strlen(json); - // Minimum JSON is "{}" and must end with a closing "}". - if (jsonLen < 2 || json[jsonLen-1] != '}') { - return NULL; - } - char *newJson = (char *) _Malloc(jsonLen+CRC_FIELD_LENGTH+1); - if (newJson == NULL) { - return NULL; - } - bool isEmptyObject = (memchr(json, ':', jsonLen) == NULL); - size_t newJsonLen = jsonLen-1; - memcpy(newJson, json, newJsonLen); - newJson[newJsonLen++] = (isEmptyObject ? ' ' : ','); // Replace } - newJson[newJsonLen++] = '"'; // +1 - newJson[newJsonLen++] = 'c'; // +2 - newJson[newJsonLen++] = 'r'; // +3 - newJson[newJsonLen++] = 'c'; // +4 - newJson[newJsonLen++] = '"'; // +5 - newJson[newJsonLen++] = ':'; // +6 - newJson[newJsonLen++] = '"'; // +7 - n_htoa16(seqno, (uint8_t *) &newJson[newJsonLen]); - newJsonLen += 4; // +11 - newJson[newJsonLen++] = ':'; // +12 - n_htoa32(crc32(json, jsonLen), &newJson[newJsonLen]); - newJsonLen += 8; // +20 - newJson[newJsonLen++] = '"'; // +21 - newJson[newJsonLen++] = '}'; // +22 == CRC_FIELD_LENGTH - newJson[newJsonLen] = '\0'; // null-terminated as it came in - return newJson; -} - -/*! - @brief Check the passed in JSON for CRC and sequence number errors. - - If the calculated CRC32 doesn't match what's in the buffer, that's an error. If - the sequence number in the buffer doesn't match shouldBeSeqno, that's an error. - If there is no CRC field in the buffer, that's not an error UNLESS this - function has been given a buffer with a CRC field before. In this case, the - lack of a CRC field is an error. - - @param json The JSON buffer for which the CRC should be checked. Note that the - CRC is stripped from the input JSON regardless of whether or not there - was an error. - @param shouldBeSeqno The expected sequence number. - - @returns `true` if there's an error and `false` otherwise. - */ -NOTE_C_STATIC bool crcError(char *json, uint16_t shouldBeSeqno) -{ - // Strip off any crlf for crc calculation - size_t jsonLen = strlen(json); - while (jsonLen > 0 && json[jsonLen-1] <= ' ') { - jsonLen--; - } - // Minimum valid JSON is "{}" (2 bytes) and must end with a closing "}". - if (jsonLen < CRC_FIELD_LENGTH+2 || json[jsonLen-1] != '}') { - return false; - } - // See if it has a compliant CRC field - size_t fieldOffset = ((jsonLen-1) - CRC_FIELD_LENGTH); - if (memcmp(&json[fieldOffset+CRC_FIELD_NAME_OFFSET], CRC_FIELD_NAME_TEST, sizeof(CRC_FIELD_NAME_TEST)-1) != 0) { - // If we've seen a CRC before, we should see one every time - return notecardSupportsCrc ? true : false; - } - // If we get here, we've seen at least one CRC from the Notecard, so we should expect it. - notecardSupportsCrc = true; - char *p = &json[fieldOffset + CRC_FIELD_NAME_OFFSET + (sizeof(CRC_FIELD_NAME_TEST)-1)]; - uint16_t actualSeqno = (uint16_t) n_atoh(p, 4); - uint32_t actualCrc32 = (uint32_t) n_atoh(p+5, 8); - json[fieldOffset++] = '}'; - json[fieldOffset] = '\0'; - uint32_t shouldBeCrc32 = crc32(json, fieldOffset); - return (shouldBeSeqno != actualSeqno || shouldBeCrc32 != actualCrc32); -} - -#endif // !NOTE_C_LOW_MEM diff --git a/src/note-c/n_serial.c b/src/note-c/n_serial.c deleted file mode 100644 index ede4189..0000000 --- a/src/note-c/n_serial.c +++ /dev/null @@ -1,316 +0,0 @@ -/*! - * @file n_serial.c - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - */ - -#include - -#include "n_lib.h" - -/**************************************************************************/ -/*! - @brief Given a JSON string, perform a serial transaction with the Notecard. - - @param request A string containing the JSON request object, which MUST BE - terminated with a newline character. - @param reqLen the string length of the JSON request. - @param response [out] A c-string buffer that will contain the newline ('\n') - terminated JSON response from the Notercard. If NULL, no response - will be captured. - @param timeoutMs The maximum amount of time, in milliseconds, to wait - for data to arrive. Passing zero (0) disables the timeout. - - @returns a c-string with an error, or `NULL` if no error occurred. -*/ -/**************************************************************************/ -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. - reqLen--; // remove newline - if (request[reqLen - 1] == '\r') { - reqLen--; // remove carriage return if it exists - } - - const char *err = serialChunkedTransmit((uint8_t *)request, reqLen, true); - if (err) { - NOTE_C_LOG_ERROR(err); - return err; - } - - // Append the carriage return and newline to the transaction. - _SerialTransmit((uint8_t *)c_newline, c_newline_len, true); - - // If no reply expected, we're done - if (response == NULL) { - return NULL; - } - - // Wait for something to become available, processing timeout errors - // up-front because the json parse operation immediately following is - // subject to the serial port timeout. We'd like more flexibility in - // max timeout and ultimately in our error handling. - for (const uint32_t startMs = _GetMs(); !_SerialAvailable(); ) { - if (timeoutMs && (_GetMs() - startMs) >= timeoutMs) { - NOTE_C_LOG_DEBUG(ERRSTR("reply to request didn't arrive from module in time", c_iotimeout)); - return ERRSTR("transaction timeout {io}", c_iotimeout); - } - if (!cardTurboIO) { - _DelayMs(10); - } - } - - // Allocate a buffer for input, noting that we always put the +1 in the - // alloc so we can be assured that it can be null-terminated. This must be - // the case because json parsing requires a null-terminated string. - uint32_t available = 0; - uint32_t jsonbufAllocLen = ALLOC_CHUNK; - uint8_t *jsonbuf = (uint8_t *)_Malloc(jsonbufAllocLen + 1); - if (jsonbuf == NULL) { - const char *err = ERRSTR("transaction: jsonbuf malloc failed", c_mem); - NOTE_C_LOG_ERROR(err); - return err; - } - - // Receive the Notecard response - uint32_t jsonbufLen = 0; - do { - uint32_t jsonbufAvailLen = (jsonbufAllocLen - jsonbufLen); - - // Append into the json buffer - const char *err = serialChunkedReceive((uint8_t *)(jsonbuf + jsonbufLen), &jsonbufAvailLen, true, (CARD_INTRA_TRANSACTION_TIMEOUT_SEC * 1000), &available); - if (err) { - _Free(jsonbuf); - NOTE_C_LOG_ERROR(ERRSTR("error occured during receive", c_iobad)); - return err; - } - jsonbufLen += jsonbufAvailLen; - - if (available) { - // When more bytes are available than we have buffer to accommodate - // (i.e. overflow), then we allocate blocks of size `ALLOC_CHUNK` to - // reduce heap fragmentation. - // NOTE: We always put the +1 in the allocation so we can be assured - // that it can be null-terminated, because the json parser requires - // a null-terminated string. - jsonbufAllocLen += (ALLOC_CHUNK * ((available / ALLOC_CHUNK) + ((available % ALLOC_CHUNK) > 0))); - uint8_t *jsonbufNew = (uint8_t *)_Malloc(jsonbufAllocLen + 1); - if (jsonbufNew == NULL) { - const char *err = ERRSTR("transaction: jsonbuf grow malloc failed", c_mem); - NOTE_C_LOG_ERROR(err); - _Free(jsonbuf); - return err; - } - memcpy(jsonbufNew, jsonbuf, jsonbufLen); - _Free(jsonbuf); - jsonbuf = jsonbufNew; - NOTE_C_LOG_DEBUG("additional receive buffer chunk allocated"); - } - } while (available); - - // Null-terminate it, using the +1 space that we'd allocated in the buffer - jsonbuf[jsonbufLen] = '\0'; - - // Return it - *response = (char *)jsonbuf; - return NULL; -} - -//**************************************************************************/ -/*! - @brief Initialize or re-initialize the Serial bus, returning false if - anything fails. - - @returns a boolean. `true` if the reset was successful, `false`, if not. -*/ -/**************************************************************************/ -bool serialNoteReset(void) -{ - NOTE_C_LOG_DEBUG("resetting Serial interface..."); - - // Reset the Serial subsystem and exit if failure - _DelayMs(CARD_REQUEST_SERIAL_SEGMENT_DELAY_MS); - if (!_SerialReset()) { - NOTE_C_LOG_ERROR(ERRSTR("unable to reset Serial interface.", c_err)); - return false; - } - - // The guaranteed behavior for robust resyncing is to send two newlines - // and wait for two echoed blank lines in return. - bool notecardReady = false; - for (size_t retries = 0; retries < CARD_RESET_SYNC_RETRIES ; ++retries) { - - // Send a newline to the module to clean out request/response processing - // NOTE: This MUST always be `\n` and not `\r\n`, because there are some - // versions of the Notecard firmware will not respond to `\r\n` - // after communicating over I2C. - _SerialTransmit((uint8_t *)"\n", 1, true); - - // Drain all communications for 500ms - bool somethingFound = false; - bool nonControlCharFound = false; - - // Read Serial data for at least CARD_RESET_DRAIN_MS continously - for (uint32_t startMs = _GetMs() ; (_GetMs() - startMs) < CARD_RESET_DRAIN_MS ;) { - // Determine if Serial data is available - while (_SerialAvailable()) { - somethingFound = true; - // The Notecard responds to a bare `\n` with `\r\n`. If we get - // any other characters back, it means the host and Notecard - // aren't synced up yet and we need to transmit `\n` again. - char ch = _SerialReceive(); - if (ch != '\n' && ch != '\r') { - nonControlCharFound = true; - // Reset the timer with each non-control character - startMs = _GetMs(); - } - } - _DelayMs(1); - } - - // If all we got back is newlines, we're ready - if (somethingFound && !nonControlCharFound) { - notecardReady = true; - break; - } - - NOTE_C_LOG_ERROR(somethingFound ? ERRSTR("unrecognized data from notecard", c_iobad) : ERRSTR("notecard not responding", c_iobad)); - - _DelayMs(CARD_RESET_DRAIN_MS); - if (!_SerialReset()) { - NOTE_C_LOG_ERROR(ERRSTR("unable to reset Serial interface.", c_err)); - return false; - } - - NOTE_C_LOG_DEBUG("retrying Serial interface reset.") - } - - // Done - return notecardReady; -} - -/**************************************************************************/ -/*! - @brief Receive bytes over Serial from the Notecard. - - @param buffer A buffer to receive bytes into. - @param size (in/out) - - (in) The size of the buffer in bytes. - - (out) The length of the received data in bytes. - @param delay Respect standard processing delays. - @param timeoutMs The maximum amount of time, in milliseconds, to wait for - serial data to arrive. Passing zero (0) disables the timeout. - @param available (out) The amount of bytes unable to fit into the provided buffer. - - @returns A c-string with an error, or `NULL` if no error ocurred. -*/ -/**************************************************************************/ -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); - uint32_t startMs = _GetMs(); - for (bool eop = false ; !overflow && !eop ;) { - while (!_SerialAvailable()) { - if (timeoutMs && (_GetMs() - startMs >= timeoutMs)) { - *size = received; - if (received) { - NOTE_C_LOG_ERROR(ERRSTR("received only partial reply before timeout", c_iobad)); - } - return ERRSTR("timeout: transaction incomplete {io}",c_iotimeout); - } - // Yield while awaiting the first byte (lazy). After the first byte, - // start to spin for the remaining bytes (greedy). - if (delay && received == 0) { - _DelayMs(1); - } - } - - // Once we've received any character, we will no longer wait patiently - timeoutMs = (CARD_INTRA_TRANSACTION_TIMEOUT_SEC * 1000); - startMs = _GetMs(); - - // Receive the next character - char ch = _SerialReceive(); - - // Place into the buffer - buffer[received++] = ch; - - // Look for end-of-packet marker - eop = (ch == '\n'); - - // Check overflow condition - overflow = ((received >= *size) && !eop); - if (overflow) { - // We haven't received a newline, so we're not done with this - // packet. If the newline never comes, for whatever reason, when - // this function is called again, we'll timeout. We don't just - // use _SerialAvailable to set *available here because we're - // typically reading faster than the serial buffer fills, and so - // _SerialAvailable may return 0. - *available = 1; - break; - } else { - *available = 0; - } - } - - // Return it - *size = received; - return NULL; -} - -/**************************************************************************/ -/*! - @brief Transmit bytes over serial to the Notecard. - - @param buffer A buffer of bytes to transmit. - @param size The count of bytes in the buffer to send. - @param delay Respect standard processing delays. - - @returns A c-string with an error, or `NULL` if no error ocurred. -*/ -/**************************************************************************/ -const char *serialChunkedTransmit(uint8_t *buffer, uint32_t size, bool delay) -{ - // Transmit the request in segments so as not to overwhelm the Notecard's - // interrupt buffers - uint32_t segOff = 0; - uint32_t segLeft = size; - - if (sizeof(size_t) != 4) { // Give the compiler a hint to eliminate the code - // Ensure truncation does not occur on 16-bit microcontrollers - const size_t castSize = (size_t)size; - if (castSize != size) { - const char *err = ERRSTR("Cannot transmit provided size; limit to `size_t`", c_iobad); - NOTE_C_LOG_ERROR(err); - return err; - } - } - - while (true) { - size_t segLen = segLeft; - if (segLen > CARD_REQUEST_SERIAL_SEGMENT_MAX_LEN) { - segLen = CARD_REQUEST_SERIAL_SEGMENT_MAX_LEN; - } - _SerialTransmit(&buffer[segOff], segLen, false); - segOff += segLen; - segLeft -= segLen; - if (segLeft == 0) { - break; - } - if (delay) { - _DelayMs(CARD_REQUEST_SERIAL_SEGMENT_DELAY_MS); - } - } - - return NULL; -} diff --git a/src/note-c/n_str.c b/src/note-c/n_str.c deleted file mode 100644 index 5f4423b..0000000 --- a/src/note-c/n_str.c +++ /dev/null @@ -1,99 +0,0 @@ -/*! - * @file n_str.c - * - * $OpenBSD: strlcpy.c,v 1.10 2005/08/08 08:05:37 espie Exp $ - * $OpenBSD: strlcat.c,v 1.12 2005/03/30 20:13:52 otto Exp $ - * - * Copyright (c) 1998 Todd C. Miller - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include "note.h" - -/* - * Copy src to string dst of size siz. At most siz-1 characters - * will be copied. Always NUL terminates (unless siz == 0). - * Returns strlen(src); if retval >= siz, truncation occurred. - */ -#if defined(_MSC_VER) -size_t strlcpy(char *dst, const char *src, size_t siz) -#else -__attribute__((weak)) size_t strlcpy(char *dst, const char *src, size_t siz) -#endif -{ - char *d = dst; - const char *s = src; - size_t n = siz; - - /* Copy as many bytes as will fit */ - if (n != 0) { - while (--n != 0) { - if ((*d++ = *s++) == '\0') { - break; - } - } - } - - /* Not enough room in dst, add NUL and traverse rest of src */ - if (n == 0) { - if (siz != 0) { - *d = '\0'; /* NUL-terminate dst */ - } - while (*s++) - ; - } - - return(s - src - 1); /* count does not include NUL */ -} - -/* - * Appends src to string dst of size siz (unlike strncat, siz is the - * full size of dst, not space left). At most siz-1 characters - * will be copied. Always NUL terminates (unless siz <= strlen(dst)). - * Returns strlen(src) + MIN(siz, strlen(initial dst)). - * If retval >= siz, truncation occurred. - */ -#if defined(_MSC_VER) -size_t strlcat(char *dst, const char *src, size_t siz) -#else -__attribute__((weak)) size_t strlcat(char *dst, const char *src, size_t siz) -#endif -{ - char *d = dst; - const char *s = src; - size_t n = siz; - size_t dlen; - - /* Find the end of dst and adjust bytes left but don't go past end */ - while (n-- != 0 && *d != '\0') { - d++; - } - dlen = d - dst; - n = siz - dlen; - - if (n == 0) { - return(dlen + strlen(s)); - } - while (*s != '\0') { - if (n != 1) { - *d++ = *s; - n--; - } - s++; - } - *d = '\0'; - - return(dlen + (s - src)); /* count does not include NUL */ -} diff --git a/src/note-c/n_ua.c b/src/note-c/n_ua.c deleted file mode 100644 index f4a46ac..0000000 --- a/src/note-c/n_ua.c +++ /dev/null @@ -1,209 +0,0 @@ -/*! - * @file n_ua.c - * - * Written by Ray Ozzie and Blues Inc. team. - * - * Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - * governed by licenses granted by the copyright holder including that found in - * the - * LICENSE - * file. - * - */ - -#ifndef NOTE_C_LOW_MEM - -#include "n_lib.h" - -// Override-able statics -static char *n_agent = (char *) ("note-c" " " NOTE_C_VERSION); -static int n_cpu_cores = 0; -static int n_cpu_mem = 0; -static int n_cpu_mhz = 0; -static char *n_cpu_vendor = NULL; -static char *n_os_family = NULL; -static char *n_os_name = NULL; -static char *n_os_platform = NULL; -static char *n_os_version = NULL; - -#if defined(ARDUINO_ARCH_ARC32) -static char *n_cpu_name = (char *) "arc32"; -#elif defined(ARDUINO_ARCH_AVR) -static char *n_cpu_name = (char *) "avr"; -#elif defined(ARDUINO_ARCH_ESP32) -static char *n_cpu_name = (char *) "esp32"; -#elif defined(ARDUINO_ARCH_ESP8266) -static char *n_cpu_name = (char *) "esp8266"; -#elif defined(ARDUINO_ARCH_MEGAAVR) -static char *n_cpu_name = (char *) "megaavr"; -#elif defined(ARDUINO_ARCH_NRF52840) -static char *n_cpu_name = (char *) "nrf52840"; -#elif defined(ARDUINO_ARCH_NRF52) -static char *n_cpu_name = (char *) "nrf52"; -#elif defined(ARDUINO_ARCH_NRF51) -static char *n_cpu_name = (char *) "nrf51"; -#elif defined(ARDUINO_ARCH_PIC32) -static char *n_cpu_name = (char *) "pic32"; -#elif defined(ARDUINO_ARCH_SAMD) -static char *n_cpu_name = (char *) "samd"; -#elif defined(ARDUINO_ARCH_SAM) -static char *n_cpu_name = (char *) "sam"; -#elif defined(ARDUINO_ARCH_SPRESENSE) -static char *n_cpu_name = (char *) "spresence"; -#elif defined(ARDUINO_ARCH_STM32F0) -static char *n_cpu_name = (char *) "stm32f0"; -#elif defined(ARDUINO_ARCH_STM32F1) -static char *n_cpu_name = (char *) "stm32f1"; -#elif defined(ARDUINO_ARCH_STM32F4) -static char *n_cpu_name = (char *) "stm32f4"; -#elif defined(ARDUINO_ARCH_STM32G0) -static char *n_cpu_name = (char *) "stm32g0"; -#elif defined(ARDUINO_SWAN_R5) -static char *n_cpu_name = (char *) "swan_r5"; -#elif defined(ARDUINO_ARCH_STM32L4) -static char *n_cpu_name = (char *) "stm32l4"; -#elif defined(ARDUINO_ARCH_STM32U5) -static char *n_cpu_name = (char *) "stm32u5"; -#elif defined(ARDUINO_ARCH_STM32) -static char *n_cpu_name = (char *) "stm32"; -#else -static char *n_cpu_name = NULL; -#endif - -/**************************************************************************/ -/*! - @brief Override-able method to add more data to the user agent object - @returns a `J` cJSON object with the user agent object. -*/ -/**************************************************************************/ -#if defined(_MSC_VER) -void NoteUserAgentUpdate(J *ua) -#else -__attribute__((weak)) void NoteUserAgentUpdate(J *ua) -#endif -{ - ((void)ua); // avoid compiler warning -} - -/**************************************************************************/ -/*! - @brief Override-able method to return user agent object - @returns a `J` cJSON object with the user agent object. -*/ -/**************************************************************************/ -#if defined(_MSC_VER) -J *NoteUserAgent() -#else -__attribute__((weak)) J *NoteUserAgent() -#endif -{ - - J *ua = JCreateObject(); - if (ua == NULL) { - return ua; - } - -#if defined(__cplusplus) -#define PLUS " c++" -#else -#define PLUS "" -#endif - -#if defined(__ICCARM__) - char *compiler = (char *) ("iar arm" PLUS " " NOTE_C_STRINGIZE(__VER__)); -#elif defined(__IAR_SYSTEMS_ICC__) - char *compiler = (char *) ("iar" PLUS " " NOTE_C_STRINGIZE(__VER__)); -#elif defined(__clang__) - char *compiler = (char *) ("clang" PLUS " " __VERSION__); -#elif defined(__ATOLLIC__) && defined(__GNUC__) - char *compiler = (char *) ("atollic gcc" PLUS " " __VERSION__); -#elif defined(__GNUC__) - char *compiler = (char *) ("gcc" PLUS " " __VERSION__); -#elif defined(_MSC_FULL_VER) - char *compiler = (char *) ("msc" PLUS " " _MSC_FULL_VER); -#elif defined(__STDC_VERSION___) - char *compiler = (char *) ("STDC" PLUS " " __STDC_VERSION__); -#else - char *compiler = (char *) ("unknown" PLUS " " __VERSION__) -#endif - - JAddStringToObject(ua, "agent", n_agent); - JAddStringToObject(ua, "compiler", compiler); - JAddStringToObject(ua, "req_interface", noteActiveInterface()); - - // Add CPU Details - if (n_cpu_cores != 0) { - JAddNumberToObject(ua, "cpu_cores", n_cpu_cores); - } - if (n_cpu_mem != 0) { - JAddNumberToObject(ua, "cpu_mem", n_cpu_mem); - } - if (n_cpu_mhz != 0) { - JAddNumberToObject(ua, "cpu_mhz", n_cpu_mhz); - } - if (n_cpu_name != NULL) { - JAddStringToObject(ua, "cpu_name", n_cpu_name); - } - if (n_cpu_vendor != NULL) { - JAddStringToObject(ua, "cpu_vendor", n_cpu_vendor); - } - - // Add Operating System Details - if (n_os_family != NULL) { - JAddStringToObject(ua, "os_family", n_os_family); - } - if (n_os_name != NULL) { - JAddStringToObject(ua, "os_name", n_os_name); - } - if (n_os_platform != NULL) { - JAddStringToObject(ua, "os_platform", n_os_platform); - } - if (n_os_version != NULL) { - JAddStringToObject(ua, "os_version", n_os_version); - } - - // Add more data to the UA from a higher level - NoteUserAgentUpdate(ua); - - return ua; - -} - -/**************************************************************************/ -/*! - @brief Set key UA fields from a higher level library context -*/ -/**************************************************************************/ -void NoteSetUserAgent(char *agent) -{ - n_agent = agent; -} - -/**************************************************************************/ -/*! - @brief Set key UA fields from a higher level library context -*/ -/**************************************************************************/ -void NoteSetUserAgentOS(char *os_name, char *os_platform, char *os_family, char *os_version) -{ - n_os_family = os_family; - n_os_name = os_name; - n_os_platform = os_platform; - n_os_version = os_version; -} - -/**************************************************************************/ -/*! - @brief Set key UA fields from a higher level library context -*/ -/**************************************************************************/ -void NoteSetUserAgentCPU(int cpu_mem, int cpu_mhz, int cpu_cores, char *cpu_vendor, char *cpu_name) -{ - n_cpu_cores = cpu_cores; - n_cpu_mem = cpu_mem; - n_cpu_mhz = cpu_mhz; - n_cpu_name = cpu_name; - n_cpu_vendor = cpu_vendor; -} - -#endif diff --git a/src/note-c/note.h b/src/note-c/note.h deleted file mode 100644 index 045c5d5..0000000 --- a/src/note-c/note.h +++ /dev/null @@ -1,566 +0,0 @@ -/*! - @file note.h - - Written by Ray Ozzie and Blues Inc. team. - - Copyright (c) 2019 Blues Inc. MIT License. Use of this source code is - governed by licenses granted by the copyright holder including that found in - the - LICENSE - file. - - This library bundles the cJSON - JSON parser library. - */ - -#pragma once - -// In case they're not yet defined -#include -#include -#include -#include - -#define _NOTE_C_STRINGIZE(x) #x -#define NOTE_C_STRINGIZE(x) _NOTE_C_STRINGIZE(x) - -#define NOTE_C_VERSION_MAJOR 2 -#define NOTE_C_VERSION_MINOR 2 -#define NOTE_C_VERSION_PATCH 1 - -#define NOTE_C_VERSION NOTE_C_STRINGIZE(NOTE_C_VERSION_MAJOR) "." NOTE_C_STRINGIZE(NOTE_C_VERSION_MINOR) "." NOTE_C_STRINGIZE(NOTE_C_VERSION_PATCH) - -// 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_C_LOW_MEM -#endif -#elif defined(__FLT_MAX_EXP__) && defined(__DBL_MAX_EXP__) -#if (__FLT_MAX_EXP__ == __DBL_MAX_EXP__) -#define NOTE_C_LOW_MEM -#endif -#else -#error What are floating point exponent length symbols for this compiler? -#endif - -// 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) -#else -#define ERRSTR(x,y) (x) -#endif - -/*! - @brief The floating point type used for JSON handling in note-c. - */ -#ifdef NOTE_C_TEST_SINGLE_PRECISION -typedef float JNUMBER; -#else -typedef double JNUMBER; -#endif - -/*! - @brief The signed integer type used for JSON handling in note-c. - */ -typedef int64_t JINTEGER; -#define JINTEGER_MIN INT64_MIN -#define JINTEGER_MAX INT64_MAX - -/*! - @brief The unsigned integer type used for JSON handling in note-c. - */ -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 JUINTEGER JTIME; - -// C-callable functions -#ifdef __cplusplus -extern "C" { -#endif - -// cJSON wrappers -#include "n_cjson.h" - -// Notecard hook functions - -/*! - @typedef mutexFn - - @brief The type for the various mutex (i.e. lock/unlock) hooks. - */ -typedef void (*mutexFn) (void); -/*! - @typedef mallocFn - - @brief The type for the memory allocation hook. - - @param size The number of bytes to allocate. - - @returns A pointer to the newly allocated memory or NULL on failure. - */ -typedef void * (*mallocFn) (size_t size); -/*! - @typedef freeFn - - @brief The type for the memory freeing hook. - - @param mem Pointer to the memory to free. - */ -typedef void (*freeFn) (void * mem); -/*! - @typedef delayMsFn - - @brief The type for the millisecond delay hook. - - @param ms The number of milliseconds to delay for. - */ -typedef void (*delayMsFn) (uint32_t ms); -/*! - @typedef getMsFn - - @brief The type for the millisecond counter hook. - - @returns The value of the millisecond counter. - */ -typedef uint32_t (*getMsFn) (void); -typedef size_t (*debugOutputFn) (const char *text); - -/*! - @typedef serialResetFn - - @brief The type for the serial reset hook. - - This hook is used to reset the serial peripheral used to communicate with the - Notecard. - - @returns `true` on success and `false` on failure. - */ -typedef bool (*serialResetFn) (void); -/*! - @typedef serialTransmitFn - - @brief The type for the serial transmit hook. - - @param txBuf A buffer of bytes to transmit to the Notecard. - @param txBufSize The size, in bytes, of `txBuf`. - @param flush If true, flush the serial peripheral's transmit buffer. - */ -typedef void (*serialTransmitFn) (uint8_t *txBuf, size_t txBufSize, bool flush); -/*! - @typedef serialAvailableFn - - @brief The type for the serial available hook. - - @return `true` if there's data to read and `false` otherwise. - */ -typedef bool (*serialAvailableFn) (void); - -/*! - @typedef serialReceiveFn - - @brief The type for the serial receive hook. - - @return The received byte. - */ -typedef char (*serialReceiveFn) (void); -/*! - @typedef i2cResetFn - - @brief The type for the I2C reset hook. - - This hook is used to reset the I2C peripheral used to communicate with the - Notecard. - - @param address The I2C address of the Notecard. - */ -typedef bool (*i2cResetFn) (uint16_t address); -/*! - @typedef i2cTransmitFn - - @brief The type for the I2C transmit hook. - - This hook is used to send a buffer of bytes to the Notecard. - - @param address The I2C address of the Notecard to transmit the data to. - @param txBuf A buffer of bytes to transmit to the Notecard. - @param txBufSize The size, in bytes, of `txBuf`. - - @returns NULL on success and an error string on failure. - */ -typedef const char * (*i2cTransmitFn) (uint16_t address, uint8_t* txBuf, - uint16_t txBufSize); -/*! - @typedef i2cReceiveFn - - @brief The type for the I2C receive hook. - - This hook is used to receive a buffer of bytes from the Notecard. - - @param address The I2C address of the Notecard sending the data to receive. - @param rxBuf A buffer to hold the data received from the Notecard. - @param rxBufSize The size, in bytes, of rxBuf. - @param available The number of bytes remaining to be received, if any. - - @returns NULL on success and an error string on failure. - */ -typedef const char * (*i2cReceiveFn) (uint16_t address, uint8_t* rxBuf, - uint16_t rxBufSize, uint32_t *available); -typedef bool (*txnStartFn) (uint32_t timeoutMs); -typedef void (*txnStopFn) (void); - -// External API -bool NoteReset(void); -void NoteResetRequired(void); -#define NoteNewBody JCreateObject -#define NoteAddBodyToObject(a, b) JAddItemToObject(a, "body", b) -#define NoteGetBody(a) JGetObject(a, "body") -J *NoteNewRequest(const char *request); -J *NoteNewCommand(const char *request); -J *NoteRequestResponse(J *req); -J *NoteRequestResponseWithRetry(J *req, uint32_t timeoutSeconds); -char * NoteRequestResponseJSON(const char *reqJSON); -void NoteSuspendTransactionDebug(void); -void NoteResumeTransactionDebug(void); -#define SYNCSTATUS_LEVEL_MAJOR 0 -#define SYNCSTATUS_LEVEL_MINOR 1 -#define SYNCSTATUS_LEVEL_DETAILED 2 -#define SYNCSTATUS_LEVEL_ALGORITHMIC 3 -#define SYNCSTATUS_LEVEL_ALL -1 -bool NoteDebugSyncStatus(int pollFrequencyMs, int maxLevel); -bool NoteRequest(J *req); -bool NoteRequestWithRetry(J *req, uint32_t timeoutSeconds); -/*! - @brief Check if the Notecard response contains an error. - - @param rsp The response to check. - - @returns `true` if there's an error and `false` if there's not. - */ -#define NoteResponseError(rsp) (!JIsNullString(rsp, "err")) -#define NoteResponseErrorContains(rsp, errstr) (JContainsString(rsp, "err", errstr)) -/*! - @brief Free a response from the Notecard. - - @param rsp The response to free. - */ -#define NoteDeleteResponse(rsp) JDelete(rsp) -J *NoteTransaction(J *req); -bool NoteErrorContains(const char *errstr, const char *errtype); -void NoteErrorClean(char *errbuf); -void NoteSetFnDebugOutput(debugOutputFn fn); -void NoteSetFnTransaction(txnStartFn startFn, txnStopFn stopFn); -void NoteSetFnMutex(mutexFn lockI2Cfn, mutexFn unlockI2Cfn, mutexFn lockNotefn, - mutexFn unlockNotefn); -void NoteSetFnI2CMutex(mutexFn lockI2Cfn, mutexFn unlockI2Cfn); -void NoteSetFnNoteMutex(mutexFn lockFn, mutexFn unlockFn); -void NoteSetFnDefault(mallocFn mallocfn, freeFn freefn, delayMsFn delayfn, - getMsFn millisfn); -void NoteSetFn(mallocFn mallocHook, freeFn freeHook, delayMsFn delayMsHook, - getMsFn getMsHook); -void NoteSetFnSerial(serialResetFn resetFn, serialTransmitFn transmitFn, - serialAvailableFn availFn, serialReceiveFn receiveFn); -void NoteSetFnI2C(uint32_t notecardAddr, uint32_t maxTransmitSize, - i2cResetFn resetFn, i2cTransmitFn transmitFn, - i2cReceiveFn receiveFn); -void NoteSetFnDisabled(void); -void NoteSetI2CAddress(uint32_t i2caddress); - -// The Notecard, whose default I2C address is below, uses a serial-to-i2c -// protocol whose "byte count" must fit into a single byte and which must not -// include a 2-byte header field. This is why the maximum that can be -// transmitted by note-c in a single I2C I/O is 255 - 2 = 253 bytes. - -/*! - @brief The default I2C address of the Notecard. - */ -#define NOTE_I2C_ADDR_DEFAULT 0x17 - -// Serial-to-i2c protocol header size in bytes -#ifndef NOTE_I2C_HEADER_SIZE -#define NOTE_I2C_HEADER_SIZE 2 -#endif - -// Maximum bytes capable of being transmitted in a single read/write operation -#ifndef NOTE_I2C_MAX_MAX -#define NOTE_I2C_MAX_MAX (UCHAR_MAX - NOTE_I2C_HEADER_SIZE) -#endif - -// In ARDUINO implementations, which to date is the largest use of this library, -// the Wire package is implemented in a broad variety of ways by different -// vendors. The default implementation has a mere 32-byte static I2C buffer, -// which means that the maximum to be transmitted in a single I/O (given our -// 2-byte serial-to-i2c protocol header) is 30 bytes. However, if we know -// the specific platform (such as STM32DUINO) we can relax this restriction. -#if defined(NOTE_I2C_MAX_DEFAULT) -// user is overriding it at compile time -#elif defined(ARDUINO_ARCH_STM32) -// we know that stm32duino dynamically allocates I/O buffer -#define NOTE_I2C_MAX_DEFAULT NOTE_I2C_MAX_MAX -#else -// default to what's known to be safe for all Arduino implementations -/*! - @brief The maximum number of bytes to request from or transmit to the Notecard - in a single chunk. - */ -#define NOTE_I2C_MAX_DEFAULT 30 -#endif - -// User agent -J *NoteUserAgent(void); -void NoteUserAgentUpdate(J *ua); -void NoteSetUserAgent(char *agent); -void NoteSetUserAgentOS(char *os_name, char *os_platform, char *os_family, char *os_version); -void NoteSetUserAgentCPU(int cpu_mem, int cpu_mhz, int cpu_cores, char *cpu_vendor, char *cpu_name); - -// Calls to the functions set above -void NoteDebug(const char *message); -void NoteDebugln(const char *message); -void NoteDebugIntln(const char *message, int n); -void NoteDebugf(const char *format, ...); - -#define NOTE_C_LOG_LEVEL_ERROR 0 -#define NOTE_C_LOG_LEVEL_WARN 1 -#define NOTE_C_LOG_LEVEL_INFO 2 -#define NOTE_C_LOG_LEVEL_DEBUG 3 - -void NoteDebugWithLevel(uint8_t level, const char *msg); -void NoteDebugWithLevelLn(uint8_t level, const char *msg); - -#ifdef NOTE_C_LOG_SHOW_FILE_AND_LINE -#define NOTE_C_LOG_FILE_AND_LINE(lvl) do { \ - NoteDebugWithLevel(lvl, __FILE__ ":" NOTE_C_STRINGIZE(__LINE__) " "); \ -} while (0); -#else -#define NOTE_C_LOG_FILE_AND_LINE(lvl) -#endif - -#define NOTE_C_LOG_ERROR(msg) do { \ - NOTE_C_LOG_FILE_AND_LINE(NOTE_C_LOG_LEVEL_ERROR); \ - NoteDebugWithLevel(NOTE_C_LOG_LEVEL_ERROR, "[ERROR] "); \ - NoteDebugWithLevelLn(NOTE_C_LOG_LEVEL_ERROR, msg); \ -} while (0); - -#define NOTE_C_LOG_WARN(msg) do { \ - NOTE_C_LOG_FILE_AND_LINE(NOTE_C_LOG_LEVEL_WARN); \ - NoteDebugWithLevel(NOTE_C_LOG_LEVEL_WARN, "[WARN] "); \ - NoteDebugWithLevelLn(NOTE_C_LOG_LEVEL_WARN, msg); \ -} while (0); - -#define NOTE_C_LOG_INFO(msg) do { \ - NOTE_C_LOG_FILE_AND_LINE(NOTE_C_LOG_LEVEL_INFO); \ - NoteDebugWithLevel(NOTE_C_LOG_LEVEL_INFO, "[INFO] "); \ - NoteDebugWithLevelLn(NOTE_C_LOG_LEVEL_INFO, msg); \ -} while (0); - -#define NOTE_C_LOG_DEBUG(msg) do { \ - NOTE_C_LOG_FILE_AND_LINE(NOTE_C_LOG_LEVEL_DEBUG); \ - NoteDebugWithLevel(NOTE_C_LOG_LEVEL_DEBUG, "[DEBUG] "); \ - NoteDebugWithLevelLn(NOTE_C_LOG_LEVEL_DEBUG, msg); \ -} while (0); - -// The max log level for NoteDebugWithLevel is only configurable at -// compile-time, via NOTE_C_LOG_LEVEL. -#ifndef NOTE_C_LOG_LEVEL -#define NOTE_C_LOG_LEVEL NOTE_C_LOG_LEVEL_INFO -#endif - -void *NoteMalloc(size_t size); -void NoteFree(void *); -uint32_t NoteGetMs(void); -void NoteDelayMs(uint32_t ms); -void NoteLockI2C(void); -void NoteUnlockI2C(void); -uint32_t NoteI2CAddress(void); -uint32_t NoteI2CMax(void); -uint32_t NoteMemAvailable(void); -bool NotePrint(const char *text); -void NotePrintln(const char *line); -bool NotePrintf(const char *format, ...); - -// String helpers to help encourage the world to abandon the horribly-error-prone strn* -size_t strlcpy(char *dst, const char *src, size_t siz); -size_t strlcat(char *dst, const char *src, size_t siz); - -// JSON helpers -void JInit(void); -void JCheck(void); -bool JIsPresent(J *json, const char *field); -char *JGetString(J *json, const char *field); -JNUMBER JGetNumber(J *json, const char *field); -J *JGetArray(J *json, const char *field); -J *JGetObject(J *json, const char *field); -JINTEGER JGetInt(J *json, const char *field); -bool JGetBool(J *json, const char *field); -JNUMBER JNumberValue(J *item); -char *JStringValue(J *item); -bool JBoolValue(J *item); -JINTEGER JIntValue(J *item); -bool JIsNullString(J *json, const char *field); -bool JIsExactString(J *json, const char *field, const char *teststr); -bool JContainsString(J *json, const char *field, const char *substr); -bool JAddBinaryToObject(J *json, const char *fieldName, const void *binaryData, uint32_t binaryDataLen); -bool JGetBinaryFromObject(J *json, const char *fieldName, uint8_t **retBinaryData, uint32_t *retBinaryDataLen); -const char *JGetItemName(const J * item); -char *JAllocString(uint8_t *buffer, uint32_t len); -const char *JType(J *item); - -#define JTYPE_NOT_PRESENT 0 -#define JTYPE_BOOL_TRUE 1 -#define JTYPE_BOOL_FALSE 2 -#define JTYPE_BOOL JTYPE_BOOL_TRUE -#define JTYPE_NULL 3 -#define JTYPE_NUMBER_ZERO 4 -#define JTYPE_NUMBER 5 -#define JTYPE_STRING_BLANK 6 -#define JTYPE_STRING_ZERO 7 -#define JTYPE_STRING_NUMBER 8 -#define JTYPE_STRING_BOOL_TRUE 9 -#define JTYPE_STRING_BOOL_FALSE 10 -#define JTYPE_STRING 11 -#define JTYPE_OBJECT 12 -#define JTYPE_ARRAY 13 -int JGetType(J *json, const char *field); -int JGetItemType(J *item); -int JBaseItemType(int type); -#define JGetObjectItemName(j) (j->string) - -// Helper functions for apps that wish to limit their C library dependencies -#define JNTOA_PRECISION (16) -#define JNTOA_MAX (44) -char * JNtoA(JNUMBER f, char * buf, int precision); -JNUMBER JAtoN(const char *string, char **endPtr); -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); -int JB64Decode(char * plain_dst, const char *coded_src); - -// MD5 Helper functions -typedef struct { - unsigned long buf[4]; - unsigned long bits[2]; - unsigned char in[64]; -} NoteMD5Context; -#define NOTE_MD5_HASH_SIZE 16 -#define NOTE_MD5_HASH_STRING_SIZE (((NOTE_MD5_HASH_SIZE)*2)+1) -void NoteMD5Init(NoteMD5Context *ctx); -void NoteMD5Update(NoteMD5Context *ctx, unsigned char const *buf, unsigned long len); -void NoteMD5Final(unsigned char *digest, NoteMD5Context *ctx); -void NoteMD5Transform(unsigned long buf[4], const unsigned char inraw[64]); -void NoteMD5Hash(unsigned char* data, unsigned long len, unsigned char *retHash); -void NoteMD5HashString(unsigned char *data, unsigned long len, char *strbuf, unsigned long buflen); -void NoteMD5HashToString(unsigned char *hash, char *strbuf, unsigned long buflen); - -// High-level helper functions that are both useful and serve to show developers -// how to call the API -uint32_t NoteBinaryCodecDecode(const uint8_t *encData, uint32_t encDataLen, - uint8_t *decBuf, uint32_t decBufSize); -uint32_t NoteBinaryCodecEncode(const uint8_t *decData, uint32_t decDataLen, - uint8_t *encBuf, uint32_t encBufSize); -uint32_t NoteBinaryCodecMaxDecodedLength(uint32_t bufferSize); -uint32_t NoteBinaryCodecMaxEncodedLength(uint32_t unencodedLength); -const char * NoteBinaryStoreDecodedLength(uint32_t *len); -const char * NoteBinaryStoreEncodedLength(uint32_t *len); -const char * NoteBinaryStoreReceive(uint8_t *buffer, uint32_t bufLen, - uint32_t decodedOffset, uint32_t decodedLen); -const char * NoteBinaryStoreReset(void); -const char * NoteBinaryStoreTransmit(uint8_t *unencodedData, uint32_t unencodedLen, - uint32_t bufLen, uint32_t notecardOffset); -uint32_t NoteSetSTSecs(uint32_t secs); -bool NoteTimeValid(void); -bool NoteTimeValidST(void); -JTIME NoteTime(void); -JTIME NoteTimeST(void); -void NoteTimeRefreshMins(uint32_t mins); -void NoteTimeSet(JTIME secondsUTC, int offset, char *zone, char *country, char *area); -bool NoteLocalTimeST(uint16_t *retYear, uint8_t *retMonth, uint8_t *retDay, uint8_t *retHour, uint8_t *retMinute, uint8_t *retSecond, char **retWeekday, char **retZone); -bool NoteRegion(char **retCountry, char **retArea, char **retZone, int *retZoneOffset); -bool NoteLocationValid(char *errbuf, uint32_t errbuflen); -bool NoteLocationValidST(char *errbuf, uint32_t errbuflen); -void NoteTurboIO(bool enable); -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, JINTEGER defaultVal); -bool NoteIsConnected(void); -bool NoteIsConnectedST(void); -bool NoteGetNetStatus(char *statusBuf, int statusBufLen); -bool NoteGetVersion(char *versionBuf, int versionBufLen); -bool NoteGetLocation(JNUMBER *retLat, JNUMBER *retLon, JTIME *time, char *statusBuf, int statusBufLen); -bool NoteSetLocation(JNUMBER lat, JNUMBER lon); -bool NoteClearLocation(void); -bool NoteGetLocationMode(char *modeBuf, int modeBufLen); -bool NoteSetLocationMode(const char *mode, uint32_t seconds); -bool NoteGetServiceConfig(char *productBuf, int productBufLen, char *serviceBuf, int serviceBufLen, char *deviceBuf, int deviceBufLen, char *snBuf, int snBufLen); -bool NoteGetServiceConfigST(char *productBuf, int productBufLen, char *serviceBuf, int serviceBufLen, char *deviceBuf, int deviceBufLen, char *snBuf, int snBufLen); -bool NoteGetStatus(char *statusBuf, int statusBufLen, JTIME *bootTime, bool *retUSB, bool *retSignals); -bool NoteGetStatusST(char *statusBuf, int statusBufLen, JTIME *bootTime, bool *retUSB, bool *retSignals); -bool NoteSleep(char *stateb64, uint32_t seconds, const char *modes); -bool NoteWake(int stateLen, void *state); -bool NoteFactoryReset(bool deleteConfigSettings); -bool NoteSetSerialNumber(const char *sn); -bool NoteSetProductID(const char *productID); -bool NoteSetUploadMode(const char *uploadMode, int uploadMinutes, bool align); -bool NoteSetSyncMode(const char *uploadMode, int uploadMinutes, int downloadMinutes, bool align, bool sync); -#define NoteSend NoteAdd -bool NoteAdd(const char *target, J *body, bool urgent); -bool NoteSendToRoute(const char *method, const char *routeAlias, char *notefile, J *body); -bool NoteGetVoltage(JNUMBER *voltage); -bool NoteGetTemperature(JNUMBER *temp); -bool NoteGetContact(char *nameBuf, int nameBufLen, char *orgBuf, int orgBufLen, char *roleBuf, int roleBufLen, char *emailBuf, int emailBufLen); -bool NoteSetContact(char *nameBuf, char *orgBuf, char *roleBuf, char *emailBuf); - -// Definitions necessary for payload descriptor -#define NP_SEGTYPE_LEN 4 -#define NP_SEGLEN_LEN sizeof(uint32_t) -#define NP_SEGHDR_LEN (NP_SEGTYPE_LEN + NP_SEGLEN_LEN) -typedef struct { - uint8_t *data; - uint32_t alloc; - uint32_t length; -} NotePayloadDesc; -bool NotePayloadSaveAndSleep(NotePayloadDesc *desc, uint32_t seconds, const char *modes); -bool NotePayloadRetrieveAfterSleep(NotePayloadDesc *desc); -void NotePayloadSet(NotePayloadDesc *desc, uint8_t *buf, uint32_t buflen); -void NotePayloadFree(NotePayloadDesc *desc); -bool NotePayloadAddSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t plen); -bool NotePayloadFindSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t *plen); -bool NotePayloadGetSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t len); - -// Hard-wired constants used to specify field types when creating note templates -#define TBOOL true // bool -#define TINT8 11 // 1-byte signed integer -#define TINT16 12 // 2-byte signed integer -#define TINT24 13 // 3-byte signed integer -#define TINT32 14 // 4-byte signed integer -#define TINT64 18 // 8-byte signed integer (note-c support depends upon platform) -#define TUINT8 21 // 1-byte unsigned integer (requires notecard firmware >= build 14444) -#define TUINT16 22 // 2-byte unsigned integer (requires notecard firmware >= build 14444) -#define TUINT24 23 // 3-byte unsigned integer (requires notecard firmware >= build 14444) -#define TUINT32 24 // 4-byte unsigned integer (requires notecard firmware >= build 14444) -#define TFLOAT16 12.1 // 2-byte IEEE 754 floating point -#define TFLOAT32 14.1 // 4-byte IEEE 754 floating point (a.k.a. "float") -#define TFLOAT64 18.1 // 8-byte IEEE 754 floating point (a.k.a. "double") -#define TSTRING(N) _NOTE_C_STRINGIZE(N) // UTF-8 text of N bytes maximum (fixed-length reserved buffer) -#define TSTRINGV _NOTE_C_STRINGIZE(0) // variable-length string -bool NoteTemplate(const char *notefileID, J *templateBody); - -// End of C-callable functions -#ifdef __cplusplus -} -#endif From 5392f614b592945794cff4294b33303084e46e99 Mon Sep 17 00:00:00 2001 From: "Zachary J. Fields" Date: Tue, 25 Feb 2025 14:59:12 +0000 Subject: [PATCH 2/4] Squashed 'src/note-c/' content from commit 1503daa git-subtree-dir: src/note-c git-subtree-split: 1503daa65a201ae1aed3ae462a8a2ee5d6657d60 --- .devcontainer/devcontainer.json | 40 + .github/actions/load-ci-image/action.yml | 17 + .github/workflows/ci.yml | 212 + .github/workflows/md5srv-tests.yml | 40 + .github/workflows/notecard-binary-tests.yml | 292 + .github/workflows/publish_docs_site.yml | 40 + .gitignore | 13 + .vscode/extensions.json | 9 + .vscode/tasks.json | 124 + CMakeLists.txt | 152 + CODE_OF_CONDUCT.md | 7 + CONTRIBUTING.md | 69 + Dockerfile | 97 + LICENSE | 19 + README.md | 162 + assets/blues_logo_no_text.png | Bin 0 -> 4854 bytes docs/CMakeLists.txt | 26 + docs/Doxyfile.in | 2820 +++++++ docs/_extensions/doxyrunner.py | 399 + docs/api_reference.rst | 237 + docs/calling_the_notecard_api.rst | 81 + docs/conf.py | 70 + docs/getting_started.rst | 20 + docs/index.rst | 18 + docs/library_initialization.rst | 343 + docs/ports.rst | 28 + n_atof.c | 292 + n_b64.c | 200 + n_cjson.c | 2823 +++++++ n_cjson.h | 332 + n_cjson_helpers.c | 694 ++ n_cobs.c | 161 + n_const.c | 30 + n_ftoa.c | 510 ++ n_helpers.c | 2569 +++++++ n_hooks.c | 1111 +++ n_i2c.c | 460 ++ n_lib.h | 253 + n_md5.c | 321 + n_printf.c | 66 + n_request.c | 995 +++ n_serial.c | 316 + n_str.c | 99 + n_ua.c | 209 + note.h | 593 ++ scripts/build_docs.sh | 34 + scripts/check_libc_dependencies.sh | 105 + scripts/git_hooks/pre-push | 23 + scripts/run_astyle.sh | 43 + scripts/run_cppcheck.sh | 94 + scripts/run_md5srv.sh | 3 + scripts/run_ngrok.sh | 55 + scripts/run_unit_tests.sh | 91 + test/CMakeLists.txt | 221 + test/README.md | 185 + .../lib/notecard_binary/NotecardBinary.cpp | 15 + .../lib/notecard_binary/NotecardBinary.h | 1294 ++++ .../lib/notecard_binary/NotecardComms.cpp | 154 + .../lib/notecard_binary/NotecardComms.h | 55 + .../lib/notecard_binary/defines.txt | 5 + .../lib/notecard_binary/small_img.cpp | 4921 ++++++++++++ .../lib/notecard_binary/small_img.h | 10 + test/hitl/card.binary/platformio.ini | 45 + test/hitl/card.binary/src/main.cpp | 19 + test/hitl/card.binary/test/LOCAL_TESTING.md | 50 + test/hitl/card.binary/test/README.md | 55 + .../test/test_binary_generators.cpp | 91 + .../card.binary/test/test_binary_generators.h | 62 + .../card.binary/test/test_card_binary.cpp | 477 ++ test/hitl/card.binary/test/test_card_binary.h | 4 + test/hitl/card.binary/test/test_main.cpp | 39 + .../hitl/card.binary/test/test_unity_util.cpp | 37 + test/hitl/card.binary/test/test_unity_util.h | 34 + test/hitl/card.binary/wait_for_test_port.py | 16 + test/hitl/scripts/md5srv.bats | 260 + test/hitl/scripts/md5srv.py | 345 + test/hitl/scripts/note.json | 37 + test/include/fff.h | 6649 +++++++++++++++++ test/include/test_static.h | 25 + test/include/time_mocks.h | 24 + test/src/JAddBinaryToObject_test.cpp | 86 + test/src/JAllocString_test.cpp | 62 + test/src/JAtoI_test.cpp | 57 + test/src/JAtoN_test.cpp | 64 + test/src/JBaseItemType_test.cpp | 103 + test/src/JBoolValue_test.cpp | 53 + test/src/JContainsString_test.cpp | 71 + test/src/JGetArray_test.cpp | 56 + test/src/JGetBinaryFromObject_test.cpp | 90 + test/src/JGetBool_test.cpp | 56 + test/src/JGetInt_test.cpp | 56 + test/src/JGetItemName_test.cpp | 50 + test/src/JGetNumber_test.cpp | 56 + test/src/JGetObject_test.cpp | 56 + test/src/JGetString_test.cpp | 69 + test/src/JGetType_test.cpp | 154 + test/src/JIntValue_test.cpp | 49 + test/src/JIsExactString_test.cpp | 74 + test/src/JIsNullString_test.cpp | 64 + test/src/JIsPresent_test.cpp | 53 + test/src/JItoA_test.cpp | 118 + test/src/JNtoA_test.cpp | 69 + test/src/JNumberValue_test.cpp | 49 + test/src/JPrintUnformatted_test.cpp | 66 + test/src/JSON_number_handling_test.cpp | 417 ++ test/src/JStringValue_test.cpp | 49 + test/src/JType_test.cpp | 63 + test/src/NoteAdd_test.cpp | 75 + test/src/NoteBinaryCodecDecode_test.cpp | 91 + test/src/NoteBinaryCodecEncode_test.cpp | 92 + .../NoteBinaryCodecMaxDecodedLength_test.cpp | 56 + .../NoteBinaryCodecMaxEncodedLength_test.cpp | 56 + .../src/NoteBinaryStoreDecodedLength_test.cpp | 135 + .../src/NoteBinaryStoreEncodedLength_test.cpp | 133 + test/src/NoteBinaryStoreReceive_test.cpp | 245 + test/src/NoteBinaryStoreReset_test.cpp | 87 + test/src/NoteBinaryStoreTransmit_test.cpp | 407 + test/src/NoteClearLocation_test.cpp | 65 + test/src/NoteDebugSyncStatus_test.cpp | 170 + test/src/NoteDebug_test.cpp | 127 + test/src/NoteDebugf_test.cpp | 49 + test/src/NoteErrorClean_test.cpp | 50 + test/src/NoteFactoryReset_test.cpp | 85 + test/src/NoteGetContact_test.cpp | 110 + test/src/NoteGetEnvNumber_test.cpp | 70 + test/src/NoteGetEnv_test.cpp | 102 + test/src/NoteGetFnDebugOutput_test.cpp | 52 + test/src/NoteGetFnI2CMutex_test.cpp | 89 + test/src/NoteGetFnI2C_test.cpp | 146 + test/src/NoteGetFnMutex_test.cpp | 127 + test/src/NoteGetFnNoteMutex_test.cpp | 88 + test/src/NoteGetFnSerial_test.cpp | 129 + test/src/NoteGetFnTransaction_test.cpp | 90 + test/src/NoteGetFn_test.cpp | 128 + test/src/NoteGetI2CAddress_test.cpp | 54 + test/src/NoteGetLocationMode_test.cpp | 67 + test/src/NoteGetLocation_test.cpp | 72 + test/src/NoteGetNetStatus_test.cpp | 67 + test/src/NoteGetServiceConfig_test.cpp | 99 + test/src/NoteGetStatus_test.cpp | 98 + test/src/NoteGetTemperature_test.cpp | 81 + test/src/NoteGetVersion_test.cpp | 67 + test/src/NoteGetVoltage_test.cpp | 81 + test/src/NoteIsConnected_test.cpp | 69 + test/src/NoteLocalTimeST_test.cpp | 100 + test/src/NoteLocationValid_test.cpp | 79 + test/src/NoteNewCommand_test.cpp | 56 + test/src/NoteNewRequest_test.cpp | 56 + .../NotePayloadRetrieveAfterSleep_test.cpp | 143 + test/src/NotePayloadSaveAndSleep_test.cpp | 76 + test/src/NotePayload_test.cpp | 145 + test/src/NotePrint_test.cpp | 86 + test/src/NotePrintf_test.cpp | 53 + test/src/NotePrintln_test.cpp | 58 + test/src/NoteRegion_test.cpp | 101 + test/src/NoteRequestResponseJSON_test.cpp | 273 + .../src/NoteRequestResponseWithRetry_test.cpp | 229 + test/src/NoteRequestResponse_test.cpp | 67 + test/src/NoteRequestWithRetry_test.cpp | 122 + test/src/NoteRequest_test.cpp | 70 + test/src/NoteReset_test.cpp | 58 + test/src/NoteResponseError_test.cpp | 51 + test/src/NoteSendToRoute_test.cpp | 106 + test/src/NoteSerialHooks_test.cpp | 61 + test/src/NoteSetContact_test.cpp | 75 + test/src/NoteSetEnvDefaultNumber_test.cpp | 58 + test/src/NoteSetEnvDefault_test.cpp | 67 + test/src/NoteSetFnI2CMutex_test.cpp | 58 + test/src/NoteSetFnI2C_test.cpp | 119 + test/src/NoteSetFnMutex_test.cpp | 78 + test/src/NoteSetFnNoteMutex_test.cpp | 54 + test/src/NoteSetFnSerial_test.cpp | 112 + test/src/NoteSetFn_test.cpp | 93 + test/src/NoteSetLocationMode_test.cpp | 78 + test/src/NoteSetLocation_test.cpp | 68 + test/src/NoteSetProductID_test.cpp | 75 + test/src/NoteSetSerialNumber_test.cpp | 75 + test/src/NoteSetSyncMode_test.cpp | 98 + test/src/NoteSetUploadMode_test.cpp | 80 + test/src/NoteSleep_test.cpp | 80 + test/src/NoteTemplate_test.cpp | 68 + test/src/NoteTimeSet_test.cpp | 84 + test/src/NoteTime_test.cpp | 105 + test/src/NoteTransactionHooks_test.cpp | 59 + test/src/NoteTransaction_test.cpp | 488 ++ test/src/NoteUserAgent_test.cpp | 100 + test/src/NoteWake_test.cpp | 79 + test/src/_crcAdd_test.cpp | 71 + test/src/_crcError_test.cpp | 164 + test/src/_i2cChunkedReceive_test.cpp | 418 ++ test/src/_i2cChunkedTransmit_test.cpp | 145 + test/src/_i2cNoteQueryLength_test.cpp | 152 + test/src/_i2cNoteReset_test.cpp | 509 ++ test/src/_i2cNoteTransaction_test.cpp | 263 + test/src/_j_tolower_test.cpp | 73 + test/src/_serialChunkedReceive_test.cpp | 302 + test/src/_serialChunkedTransmit_test.cpp | 156 + test/src/_serialNoteReset_test.cpp | 135 + test/src/_serialNoteTransaction_test.cpp | 356 + 199 files changed, 45950 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/actions/load-ci-image/action.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/md5srv-tests.yml create mode 100644 .github/workflows/notecard-binary-tests.yml create mode 100644 .github/workflows/publish_docs_site.yml create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 assets/blues_logo_no_text.png create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 docs/_extensions/doxyrunner.py create mode 100644 docs/api_reference.rst create mode 100644 docs/calling_the_notecard_api.rst create mode 100644 docs/conf.py create mode 100644 docs/getting_started.rst create mode 100644 docs/index.rst create mode 100644 docs/library_initialization.rst create mode 100644 docs/ports.rst create mode 100644 n_atof.c create mode 100644 n_b64.c create mode 100644 n_cjson.c create mode 100644 n_cjson.h create mode 100644 n_cjson_helpers.c create mode 100644 n_cobs.c create mode 100644 n_const.c create mode 100644 n_ftoa.c create mode 100644 n_helpers.c create mode 100644 n_hooks.c create mode 100644 n_i2c.c create mode 100644 n_lib.h create mode 100644 n_md5.c create mode 100644 n_printf.c create mode 100644 n_request.c create mode 100644 n_serial.c create mode 100644 n_str.c create mode 100644 n_ua.c create mode 100644 note.h create mode 100755 scripts/build_docs.sh create mode 100755 scripts/check_libc_dependencies.sh create mode 100755 scripts/git_hooks/pre-push create mode 100755 scripts/run_astyle.sh create mode 100755 scripts/run_cppcheck.sh create mode 100755 scripts/run_md5srv.sh create mode 100755 scripts/run_ngrok.sh create mode 100755 scripts/run_unit_tests.sh create mode 100644 test/CMakeLists.txt create mode 100644 test/README.md create mode 100644 test/hitl/card.binary/lib/notecard_binary/NotecardBinary.cpp create mode 100644 test/hitl/card.binary/lib/notecard_binary/NotecardBinary.h create mode 100644 test/hitl/card.binary/lib/notecard_binary/NotecardComms.cpp create mode 100644 test/hitl/card.binary/lib/notecard_binary/NotecardComms.h create mode 100644 test/hitl/card.binary/lib/notecard_binary/defines.txt create mode 100644 test/hitl/card.binary/lib/notecard_binary/small_img.cpp create mode 100644 test/hitl/card.binary/lib/notecard_binary/small_img.h create mode 100644 test/hitl/card.binary/platformio.ini create mode 100644 test/hitl/card.binary/src/main.cpp create mode 100644 test/hitl/card.binary/test/LOCAL_TESTING.md create mode 100644 test/hitl/card.binary/test/README.md create mode 100644 test/hitl/card.binary/test/test_binary_generators.cpp create mode 100644 test/hitl/card.binary/test/test_binary_generators.h create mode 100644 test/hitl/card.binary/test/test_card_binary.cpp create mode 100644 test/hitl/card.binary/test/test_card_binary.h create mode 100644 test/hitl/card.binary/test/test_main.cpp create mode 100644 test/hitl/card.binary/test/test_unity_util.cpp create mode 100644 test/hitl/card.binary/test/test_unity_util.h create mode 100644 test/hitl/card.binary/wait_for_test_port.py create mode 100644 test/hitl/scripts/md5srv.bats create mode 100755 test/hitl/scripts/md5srv.py create mode 100644 test/hitl/scripts/note.json create mode 100644 test/include/fff.h create mode 100644 test/include/test_static.h create mode 100644 test/include/time_mocks.h create mode 100644 test/src/JAddBinaryToObject_test.cpp create mode 100644 test/src/JAllocString_test.cpp create mode 100644 test/src/JAtoI_test.cpp create mode 100644 test/src/JAtoN_test.cpp create mode 100644 test/src/JBaseItemType_test.cpp create mode 100644 test/src/JBoolValue_test.cpp create mode 100644 test/src/JContainsString_test.cpp create mode 100644 test/src/JGetArray_test.cpp create mode 100644 test/src/JGetBinaryFromObject_test.cpp create mode 100644 test/src/JGetBool_test.cpp create mode 100644 test/src/JGetInt_test.cpp create mode 100644 test/src/JGetItemName_test.cpp create mode 100644 test/src/JGetNumber_test.cpp create mode 100644 test/src/JGetObject_test.cpp create mode 100644 test/src/JGetString_test.cpp create mode 100644 test/src/JGetType_test.cpp create mode 100644 test/src/JIntValue_test.cpp create mode 100644 test/src/JIsExactString_test.cpp create mode 100644 test/src/JIsNullString_test.cpp create mode 100644 test/src/JIsPresent_test.cpp create mode 100644 test/src/JItoA_test.cpp create mode 100644 test/src/JNtoA_test.cpp create mode 100644 test/src/JNumberValue_test.cpp create mode 100644 test/src/JPrintUnformatted_test.cpp create mode 100644 test/src/JSON_number_handling_test.cpp create mode 100644 test/src/JStringValue_test.cpp create mode 100644 test/src/JType_test.cpp create mode 100644 test/src/NoteAdd_test.cpp create mode 100644 test/src/NoteBinaryCodecDecode_test.cpp create mode 100644 test/src/NoteBinaryCodecEncode_test.cpp create mode 100644 test/src/NoteBinaryCodecMaxDecodedLength_test.cpp create mode 100644 test/src/NoteBinaryCodecMaxEncodedLength_test.cpp create mode 100644 test/src/NoteBinaryStoreDecodedLength_test.cpp create mode 100644 test/src/NoteBinaryStoreEncodedLength_test.cpp create mode 100644 test/src/NoteBinaryStoreReceive_test.cpp create mode 100644 test/src/NoteBinaryStoreReset_test.cpp create mode 100644 test/src/NoteBinaryStoreTransmit_test.cpp create mode 100644 test/src/NoteClearLocation_test.cpp create mode 100644 test/src/NoteDebugSyncStatus_test.cpp create mode 100644 test/src/NoteDebug_test.cpp create mode 100644 test/src/NoteDebugf_test.cpp create mode 100644 test/src/NoteErrorClean_test.cpp create mode 100644 test/src/NoteFactoryReset_test.cpp create mode 100644 test/src/NoteGetContact_test.cpp create mode 100644 test/src/NoteGetEnvNumber_test.cpp create mode 100644 test/src/NoteGetEnv_test.cpp create mode 100644 test/src/NoteGetFnDebugOutput_test.cpp create mode 100644 test/src/NoteGetFnI2CMutex_test.cpp create mode 100644 test/src/NoteGetFnI2C_test.cpp create mode 100644 test/src/NoteGetFnMutex_test.cpp create mode 100644 test/src/NoteGetFnNoteMutex_test.cpp create mode 100644 test/src/NoteGetFnSerial_test.cpp create mode 100644 test/src/NoteGetFnTransaction_test.cpp create mode 100644 test/src/NoteGetFn_test.cpp create mode 100644 test/src/NoteGetI2CAddress_test.cpp create mode 100644 test/src/NoteGetLocationMode_test.cpp create mode 100644 test/src/NoteGetLocation_test.cpp create mode 100644 test/src/NoteGetNetStatus_test.cpp create mode 100644 test/src/NoteGetServiceConfig_test.cpp create mode 100644 test/src/NoteGetStatus_test.cpp create mode 100644 test/src/NoteGetTemperature_test.cpp create mode 100644 test/src/NoteGetVersion_test.cpp create mode 100644 test/src/NoteGetVoltage_test.cpp create mode 100644 test/src/NoteIsConnected_test.cpp create mode 100644 test/src/NoteLocalTimeST_test.cpp create mode 100644 test/src/NoteLocationValid_test.cpp create mode 100644 test/src/NoteNewCommand_test.cpp create mode 100644 test/src/NoteNewRequest_test.cpp create mode 100644 test/src/NotePayloadRetrieveAfterSleep_test.cpp create mode 100644 test/src/NotePayloadSaveAndSleep_test.cpp create mode 100644 test/src/NotePayload_test.cpp create mode 100644 test/src/NotePrint_test.cpp create mode 100644 test/src/NotePrintf_test.cpp create mode 100644 test/src/NotePrintln_test.cpp create mode 100644 test/src/NoteRegion_test.cpp create mode 100644 test/src/NoteRequestResponseJSON_test.cpp create mode 100644 test/src/NoteRequestResponseWithRetry_test.cpp create mode 100644 test/src/NoteRequestResponse_test.cpp create mode 100644 test/src/NoteRequestWithRetry_test.cpp create mode 100644 test/src/NoteRequest_test.cpp create mode 100644 test/src/NoteReset_test.cpp create mode 100644 test/src/NoteResponseError_test.cpp create mode 100644 test/src/NoteSendToRoute_test.cpp create mode 100644 test/src/NoteSerialHooks_test.cpp create mode 100644 test/src/NoteSetContact_test.cpp create mode 100644 test/src/NoteSetEnvDefaultNumber_test.cpp create mode 100644 test/src/NoteSetEnvDefault_test.cpp create mode 100644 test/src/NoteSetFnI2CMutex_test.cpp create mode 100644 test/src/NoteSetFnI2C_test.cpp create mode 100644 test/src/NoteSetFnMutex_test.cpp create mode 100644 test/src/NoteSetFnNoteMutex_test.cpp create mode 100644 test/src/NoteSetFnSerial_test.cpp create mode 100644 test/src/NoteSetFn_test.cpp create mode 100644 test/src/NoteSetLocationMode_test.cpp create mode 100644 test/src/NoteSetLocation_test.cpp create mode 100644 test/src/NoteSetProductID_test.cpp create mode 100644 test/src/NoteSetSerialNumber_test.cpp create mode 100644 test/src/NoteSetSyncMode_test.cpp create mode 100644 test/src/NoteSetUploadMode_test.cpp create mode 100644 test/src/NoteSleep_test.cpp create mode 100644 test/src/NoteTemplate_test.cpp create mode 100644 test/src/NoteTimeSet_test.cpp create mode 100644 test/src/NoteTime_test.cpp create mode 100644 test/src/NoteTransactionHooks_test.cpp create mode 100644 test/src/NoteTransaction_test.cpp create mode 100644 test/src/NoteUserAgent_test.cpp create mode 100644 test/src/NoteWake_test.cpp create mode 100644 test/src/_crcAdd_test.cpp create mode 100644 test/src/_crcError_test.cpp create mode 100644 test/src/_i2cChunkedReceive_test.cpp create mode 100644 test/src/_i2cChunkedTransmit_test.cpp create mode 100644 test/src/_i2cNoteQueryLength_test.cpp create mode 100644 test/src/_i2cNoteReset_test.cpp create mode 100644 test/src/_i2cNoteTransaction_test.cpp create mode 100644 test/src/_j_tolower_test.cpp create mode 100644 test/src/_serialChunkedReceive_test.cpp create mode 100644 test/src/_serialChunkedTransmit_test.cpp create mode 100644 test/src/_serialNoteReset_test.cpp create mode 100644 test/src/_serialNoteTransaction_test.cpp diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..6e0aa22 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,40 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.205.2/containers/docker-existing-dockerfile +{ + "name": "Note-C Development Environment Dockerfile", + + // Sets the run context to one level up instead of the .devcontainer folder. + "context": "..", + + // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. + "image": "ghcr.io/blues/note_c_ci:latest", + // "dockerFile": "../Dockerfile", + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "matepek.vscode-catch2-test-adapter", + "ms-vscode.cpptools", + "shardulm94.trailing-spaces", + "twxs.cmake" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Uncomment the next line to run commands after the container is created - for example installing curl. + // "postCreateCommand": "apt-get update && apt-get install -y curl", + + // Uncomment when using a ptrace-based debugger like C++, Go, and Rust + // "runArgs": [ + // "--device=/dev/bus/usb/" + // ], + + // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker. + // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], + + // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "blues" +} diff --git a/.github/actions/load-ci-image/action.yml b/.github/actions/load-ci-image/action.yml new file mode 100644 index 0000000..596335a --- /dev/null +++ b/.github/actions/load-ci-image/action.yml @@ -0,0 +1,17 @@ +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@v4 + 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/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..44da283 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,212 @@ +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@v4 + + # 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@v4 + + - 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@v4 + 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@v4 + + - 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@v4 + + - 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@v4 + + - 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: Run tests + run: | + docker run --rm --volume $(pwd):/note-c/ \ + --workdir /note-c/ \ + --entrypoint ./scripts/run_unit_tests.sh \ + --user root \ + 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_low_mem_unit_tests: + runs-on: ubuntu-latest + if: ${{ always() }} + needs: [build_ci_docker_image] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - 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: Run tests with NOTE_LOWMEM defined + run: | + docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/run_unit_tests.sh ghcr.io/blues/note_c_ci:latest --mem-check --low-mem --single-precision + + run_astyle: + runs-on: ubuntu-latest + if: ${{ always() }} + needs: [build_ci_docker_image] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - 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: 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 + + run_cppcheck: + runs-on: ubuntu-latest + if: ${{ always() }} + needs: [build_ci_docker_image] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - 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: Run cppcheck + run: | + docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/run_cppcheck.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/.github/workflows/md5srv-tests.yml b/.github/workflows/md5srv-tests.yml new file mode 100644 index 0000000..5da2365 --- /dev/null +++ b/.github/workflows/md5srv-tests.yml @@ -0,0 +1,40 @@ +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 + steps: + - name: Setup Bats and bats libs + id: setup-bats + uses: bats-core/bats-action@3.0.0 + with: + bats-install: true + file-install: false + detik-install: false + - name: Checkout + uses: actions/checkout@v4 + - name: Run Tests + env: + BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }} + run: | + cd ${{env.MD5SRV_DIR}} + bats -p --print-output-on-failure . + - name: Re-run Tests + if: failure() + env: + BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }} + run: | + cd ${{env.MD5SRV_DIR}} + $HOME/.local/bin/bats -p --print-output-on-failure -x . diff --git a/.github/workflows/notecard-binary-tests.yml b/.github/workflows/notecard-binary-tests.yml new file mode 100644 index 0000000..15a409a --- /dev/null +++ b/.github/workflows/notecard-binary-tests.yml @@ -0,0 +1,292 @@ +name: note-c card.binary HIL tests + +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: ubuntu-latest + defaults: + run: + shell: bash + env: + MD5SRV_PORT: 9178 + NOTEHUB: "notehub.io" + NOTEHUB_API: "api.notefile.net" + NOTEHUB_PROJECT_UID: "app:458d7b93-8e19-45f8-b030-fb96d03eb1cc" + NOTEHUB_PRODUCT_UID: "com.blues.hitl" + 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 + steps: + - name: Connect to Tailscale + uses: tailscale/github-action@v2 + with: + oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} + oauth-secret: ${{ secrets.TS_OAUTH_CLIENT_SECRET }} + tags: tag:ci + + # Needed for asyncio.TaskGroup, which the card_client code uses. + - name: Set up Python 3.11 + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Checkout note-c repo + uses: actions/checkout@v4 + + - name: Checkout hil_lab repo + uses: actions/checkout@v4 + with: + repository: blues/hil_lab + ref: master + path: hil_lab + # Since hil_lab is a private repo, we need to authenticate. This uses + # the "deploy keys" approach described here: + # https://stackoverflow.com/a/70283191 + ssh-key: ${{ secrets.HIL_LAB_CLONE_PRIV_KEY }} + + - name: Generate MD5 Server Token + run: | + [ -n "$MD5SRV_TOKEN" ] || echo "MD5SRV_TOKEN=`uuidgen`" >> $GITHUB_ENV + + # gdb-multiarch: We used gdb to remotely flash the test firmware onto the + # Swan attached to the Notestation. Apparently "regular" gdb (i.e. + # installed via apt install gdb) can't cope with the fact that the target + # is ARM. + # ngrok: Used to get a public IP for the MD5 server. + # jq: Used for JSON parsing. + - name: Install apt dependencies + run: | + curl -sSL https://ngrok-agent.s3.amazonaws.com/ngrok.asc | \ + sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null + echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | \ + sudo tee /etc/apt/sources.list.d/ngrok.list + + sudo apt update + sudo apt install gdb-multiarch ngrok jq + + # We need socat 1.7.4.4 because prior to that there are some issues with + # setting the baud rate properly. socat is used by the card_client code + # for various tunnels (e.g. Swan USB port, Swan OpenOCD server, etc.). + - name: Install socat 1.7.4.4 + run: | + wget http://www.dest-unreach.org/socat/download/socat-1.7.4.4.tar.gz + tar xvf socat-1.7.4.4.tar.gz + cd socat-1.7.4.4 + ./configure + make -j + sudo make install + + - name: Install PlatformIO dependencies + run: | + python -m pip install --upgrade pip + 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 + run: | + mkdir md5srv-files + ./scripts/run_md5srv.sh + + - name: Start ngrok + run: | + ngrok config add-authtoken ${{ secrets.NGROK_AUTH_TOKEN }} + ./scripts/run_ngrok.sh + + - name: Check MD5 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` + if [ "$code" -ge "500" ]; then + echo "5xx error ($code) from tunnel." + exit 1 + else + echo "MD5 server is available." + fi + + - 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=${{ secrets.NOTEHUB_HIL_CLIENT_ID }} \ + -d client_secret=${{ secrets.NOTEHUB_HIL_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' + run: | + # ?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 "Notehub HTTP route request: $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 "Notehub HTTP route: $route" + route_uid=`echo $route | jq -r .uid` + if [ -n "$route_uid" ]; then + echo "NOTEHUB_HTTP_ROUTE_UID=$route_uid" >> $GITHUB_ENV + else + echo "Failed to create or parse Notehub HTTP route." + exit 1 + fi + + - name: Create Notehub proxy route + if: env.CREATE_NOTEHUB_ROUTES!='false' + run: | + 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 "Notehub proxy route: $route" + route_uid=`echo "$route" | jq -r .uid` + if [ -n $route_uid ]; then + echo "NOTEHUB_PROXY_ROUTE_UID=$route_uid" >> $GITHUB_ENV + echo "NOTEHUB_PROXY_ROUTE_ALIAS=$ALIAS" >> $GITHUB_ENV + else + echo "Failed to create or parse Notehub proxy route." + exit 1 + fi + + - name: Build and upload test firmware and run tests + run: | + cd hil_lab/ + pip install -r requirements.txt + + # For now, we're hardcoding the Notestation to + # los-angeles-notestation-1, which is the only one we've set up to run + # these tests. + cd notestation/ + nohup python -m core.card_client \ + --mcu-debug \ + --notestation los-angeles-notestation-1 \ + --work-dir /tmp &> card_client.log & + + PID=$! + RES_FILE=/tmp/ns_reservation.json + + timeout=600 # 10 minutes in seconds + interval=1 # Check every second + elapsed=0 + + # If we aren't able to reserve a Notestation after 10 minutes, or if + # the card_client fails, bail. + while [ ! -f $RES_FILE ]; do + sleep $interval + elapsed=$((elapsed + interval)) + + # Check if the Python process is still running + if ! kill -0 $PID 2>/dev/null; then + echo "Error: Process $PID has terminated unexpectedly." + echo "$(cat card_client.log)" + exit 1 + fi + + if [ $elapsed -ge $timeout ]; then + echo "Timeout reached: $RES_FILE did not appear." + kill $PID 2>/dev/null + exit 1 + fi + done + + echo "Notestation reserved." + + # Set these environment variables, which are read in platformio.ini in + # order to flash the Swan with the test firmware. + export MCU_GDB_SERVER_IP="$(jq -r '.notestation' $RES_FILE)" + export MCU_GDB_SERVER_PORT="$(jq -r '.mcu_openocd.gdb' $RES_FILE)" + export GDB_CMD="gdb-multiarch" + + if [ -z "$MCU_GDB_SERVER_IP" ] || [ "$MCU_GDB_SERVER_IP" == "null" ]; then + echo "Error: MCU_GDB_SERVER_IP is empty or not defined." + kill $PID 2>/dev/null + exit 1 + fi + + if [ -z "$MCU_GDB_SERVER_PORT" ] || [ "$MCU_GDB_SERVER_PORT" -eq 0 ]; then + echo "Error: MCU_GDB_SERVER_PORT is empty or zero." + kill $PID 2>/dev/null + exit 1 + fi + + 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" + cd $GITHUB_WORKSPACE/$PIO_PROJECT_DIR + + # Run the tests. It's important that we provided --no-reset here. If + # we don't, PlatformIO tries to fiddle with DTR and RTS, and that + # causes an exception because the serial port for the Swan isn't a + # local serial port. It's a virtual serial device hooked up to the + # Notestation over TCP, and from there it's connected to the actual + # Swan USB port. Trying to do DTR/RTS on the port causes an ioctl + # error, because PlatformIO is expecting a genuine serial device that + # plays nicely with the ioctl stuff it wants to use. + platformio test -v -e debug \ + --no-reset \ + --json-output-path test.json \ + --junit-output-path test.xml + + kill $PID 2>/dev/null + + - 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 + + - name: Cleanup Notehub proxy route + if: always() + run: | + if [ "$DELETE_NOTEHUB_ROUTES" == "true" ] && [ -n "$NOTEHUB_PROXY_ROUTE_UID" ]; then + echo "Deleting Notehub proxy route." + 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" + fi + + - name: Cleanup Notehub HTTP route + if: always() + run: | + if [ "$DELETE_NOTEHUB_ROUTES" == "true" ] && [ -n "$NOTEHUB_HTTP_ROUTE_UID" ]; then + echo "Deleting Notehub HTTP route." + 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" + fi diff --git a/.github/workflows/publish_docs_site.yml b/.github/workflows/publish_docs_site.yml new file mode 100644 index 0000000..b367659 --- /dev/null +++ b/.github/workflows/publish_docs_site.yml @@ -0,0 +1,40 @@ +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/.gitignore b/.gitignore new file mode 100644 index 0000000..d6056bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +Doxyfile.bak +latex/ +html/ +__pycache__/ + +# VS Code workspace files +*.code-workspace +*.orig +settings.json + +# Development Artifacts +cppcheck_output.txt diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..363da33 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "unwantedRecommendations": [ + "ms-vscode.cmake-tools", + "ms-vscode.cpptools-extension-pack", + "ms-azuretools.vscode-docker" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..b00c48c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,124 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Note-C: Compile and Run ALL Tests", + "type": "cppbuild", + "command": "./scripts/run_unit_tests.sh", + "args": [], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Note-C: Compile and Run ALL Tests (with coverage)", + "type": "cppbuild", + "command": "./scripts/run_unit_tests.sh", + "args": [ + "--coverage", + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": false + } + }, + { + "label": "Note-C: Compile and Run ALL Tests (with coverage and memory check)", + "type": "cppbuild", + "command": "./scripts/run_unit_tests.sh", + "args": [ + "--coverage", + "--mem-check" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": false + } + }, + { + "label": "Note-C: Run `astyle` Formatter", + "type": "shell", + "command": "./scripts/run_astyle.sh", + "args": [], + "options": { + "cwd": "${workspaceFolder}", + "env": { + "LC_ALL": "C" + } + }, + "problemMatcher": [], + "group": { + "kind": "none" + } + }, + { + "label": "Note-C: Generate Coverage HTML", + "type": "shell", + "command": "genhtml lcov.info -o tmp", + "args": [], + "options": { + "cwd": "${workspaceFolder}/build/test/coverage", + "env": { + "LC_ALL": "C" + } + }, + "problemMatcher": [], + "group": { + "kind": "none" + }, + "dependsOn": [ + "Note-C: Compile and Run ALL Tests (with coverage)" + ] + }, + { + "label": "Note-C: Generate Documentation", + "type": "shell", + "command": "./scripts/build_docs.sh", + "args": [], + "options": { + "cwd": "${workspaceFolder}", + "env": { + "LC_ALL": "C" + } + }, + "problemMatcher": [], + "group": { + "kind": "none" + } + }, + { + "label": "Note-C: Run Static Analysis", + "type": "shell", + "command": "${workspaceFolder}/scripts/run_cppcheck.sh", + "options": { + "cwd": "${workspaceFolder}", + "env": { + "LC_ALL": "C" + } + }, + "problemMatcher": [ + "$gcc" + ], + "group": "test" + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..eccdc7f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,152 @@ +cmake_minimum_required(VERSION 3.20) +cmake_policy(SET CMP0095 NEW) + +if ("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}") + message(FATAL_ERROR "In-source builds are not allowed. + Please create a build directory and use `cmake ..` inside it. + NOTE: cmake will now create CMakeCache.txt and CMakeFiles/*. + You must delete them, or cmake will refuse to work.") +endif() + +project(note_c) + +# Automatically ignore CMake build directory. +if(NOT EXISTS ${PROJECT_BINARY_DIR}/.gitignore) + file(WRITE ${PROJECT_BINARY_DIR}/.gitignore "*") +endif() + +option(NOTE_C_BUILD_DOCS "Build docs." OFF) +option(NOTE_C_BUILD_TESTS "Build tests." ON) +option(NOTE_C_COVERAGE "Compile for test NOTE_C_COVERAGE reporting." OFF) +option(NOTE_C_LOW_MEM "Build the library tailored for low memory usage." OFF) +option(NOTE_C_MEM_CHECK "Run tests with Valgrind." OFF) +option(NOTE_C_NO_LIBC "Build the library without linking against libc, generating errors for any undefined symbols." OFF) +option(NOTE_C_SHOW_MALLOC "Build the library with flags required to log memory usage." OFF) +option(NOTE_C_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) +target_sources( + note_c + PRIVATE + ${NOTE_C_SRC_DIR}/n_atof.c + ${NOTE_C_SRC_DIR}/n_b64.c + ${NOTE_C_SRC_DIR}/n_cjson.c + ${NOTE_C_SRC_DIR}/n_cjson_helpers.c + ${NOTE_C_SRC_DIR}/n_cobs.c + ${NOTE_C_SRC_DIR}/n_const.c + ${NOTE_C_SRC_DIR}/n_ftoa.c + ${NOTE_C_SRC_DIR}/n_helpers.c + ${NOTE_C_SRC_DIR}/n_hooks.c + ${NOTE_C_SRC_DIR}/n_i2c.c + ${NOTE_C_SRC_DIR}/n_md5.c + ${NOTE_C_SRC_DIR}/n_printf.c + ${NOTE_C_SRC_DIR}/n_request.c + ${NOTE_C_SRC_DIR}/n_serial.c + ${NOTE_C_SRC_DIR}/n_str.c +) +target_compile_options( + note_c + PRIVATE + -Wall + -Wextra + -Wpedantic + -Werror + -Og + -ggdb + PUBLIC + -m32 + -mfpmath=sse + -msse2 +) +target_include_directories( + note_c + PUBLIC ${NOTE_C_SRC_DIR} +) +target_link_directories( + note_c + PUBLIC + /lib32 + /usr/lib32 + /usr/lib/gcc/x86_64-linux-gnu/12/32 +) +target_link_options( + note_c + PUBLIC + -m32 + -Wl,-melf_i386 +) + +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 + PRIVATE + -nostdlib + -nodefaultlibs + LINKER:--no-undefined + ) +endif() + +if(NOTE_C_SHOW_MALLOC) + target_compile_definitions( + note_c + PUBLIC + NOTE_C_SHOW_MALLOC + ) +endif() + +if(NOTE_C_SINGLE_PRECISION) + target_compile_definitions( + note_c + PUBLIC + NOTE_C_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/). + # We also need to set MEMORYCHECK_COMMAND_OPTIONS before including this. + # See: https://stackoverflow.com/a/60741757 + if(NOTE_C_MEM_CHECK) + # Go ahead and make sure we can find valgrind while we're here. + find_program(VALGRIND valgrind REQUIRED) + message(STATUS "Found valgrind: ${VALGRIND}") + set(MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --error-exitcode=1") + endif(NOTE_C_MEM_CHECK) + include(CTest) + + target_compile_definitions( + note_c + PUBLIC + NOTE_C_TEST + ) + target_include_directories( + note_c + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/test/include + ) + + add_subdirectory(test) +endif(NOTE_C_BUILD_TESTS) + +if(NOTE_C_BUILD_DOCS) + add_subdirectory(docs) +endif(NOTE_C_BUILD_DOCS) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..50838cf --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,7 @@ +# Code of conduct + +By participating in this project, you agree to abide by the +[Blues Inc code of conduct][1]. + +[1]: https://blues.github.io/opensource/code-of-conduct + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0d32823 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,69 @@ +# Contributing to blues/note-c + +We love pull requests from everyone. By participating in this project, you +agree to abide by the Blues Inc [code of conduct]. + +Here are some ways *you* can contribute: + +* by using alpha, beta, and prerelease versions +* by reporting bugs +* by suggesting new features +* by writing or editing documentation +* by writing specifications +* by writing code ( **no patch is too small** : fix typos, add comments, +clean up inconsistent whitespace ) +* by refactoring code +* by closing [issues][] +* by reviewing patches + +[issues]: https://github.com/blues/note-c/issues + +## Submitting an Issue + +* We use the [GitHub issue tracker][issues] to track bugs and features. +* Before submitting a bug report or feature request, check to make sure it + hasn't + already been submitted. +* When submitting a bug report, please include a [Gist][] that includes a stack + trace and any details that may be necessary to reproduce the bug, including + your gem version, Ruby version, and operating system. Ideally, a bug report + should include a pull request with failing specs. + +[gist]: https://gist.github.com/ + +## Library Development + +Review our [contribution guidelines](./CONTRIBUTING.md) and [developer +documentation](./test/README.md) for setting up your environment and +configuring your toolchain. + +## Cleaning up issues + +* Issues that have no response from the submitter will be closed after 30 days. +* Issues will be closed once they're assumed to be fixed or answered. If the + maintainer is wrong, it can be opened again. +* If your issue is closed by mistake, please understand and explain the issue. + We will happily reopen the issue. + +## Submitting a Pull Request +1. [Fork][fork] the [official repository][repo]. +2. [Create a topic branch.][branch] +3. Implement your feature or bug fix. +4. Add, commit, and push your changes. +5. [Submit a pull request.][pr] + +## Notes +* Please add tests if you changed code. Contributions without tests won't be +* accepted. If you don't know how to add tests, please put in a PR and leave a +* comment asking for help. We love helping! + +[repo]: https://github.com/blues/note-c/tree/master +[fork]: https://help.github.com/articles/fork-a-repo/ +[branch]: +https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/ +[pr]: https://help.github.com/articles/creating-a-pull-request-from-a-fork/ + +Inspired by: +https://github.com/thoughtbot/factory_bot/blob/master/CONTRIBUTING.md + +[code of conduct]: https://blues.github.io/opensource/code-of-conduct diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..334df54 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,97 @@ +# Container with dependencies necessary to run note-c unit tests. + +# Build development environment +# $ docker build . --tag note_c_run_unit_tests + +# Launch development environment (mount source root as /note-c/) +# $ docker run --rm --volume $(pwd)/../../../:/note-c/ --workdir /note-c/ note_c_run_unit_tests + +# Global Argument(s) +ARG DEBIAN_FRONTEND="noninteractive" +ARG UID=1000 +ARG USER="blues" + +# POSIX compatible (Linux/Unix) base image. +FROM --platform=linux/amd64 debian:stable-slim + +# Import Global Argument(s) +ARG DEBIAN_FRONTEND +ARG UID +ARG USER + +# Local Argument(s) + +# Local Environment Variable(s) +ENV LC_ALL="C.UTF-8" + +# Create Non-Root User +RUN ["dash", "-c", "\ + addgroup \ + --gid ${UID} \ + \"${USER}\" \ + && adduser \ + --disabled-password \ + --gecos \"\" \ + --ingroup \"${USER}\" \ + --uid ${UID} \ + \"${USER}\" \ + && usermod \ + --append \ + --groups \"dialout,plugdev\" \ + \"${USER}\" \ +"] + +# Add 32-bit binaries to the index. +RUN ["dash", "-c", "\ + dpkg --add-architecture i386 \ +"] + +# Install whatever dependencies we can via apt-get. +RUN ["dash", "-c", "\ + apt-get update --quiet \ + && apt-get install --assume-yes --no-install-recommends --quiet \ + astyle \ + ca-certificates \ + curl \ + g++ \ + g++-multilib \ + gcc \ + gcc-multilib \ + gdb \ + git \ + lcov \ + libc6-dbg:i386 \ + make \ + nano \ + python3-pip \ + python3-sphinx \ + cppcheck \ + valgrind \ + && pip install --break-system-packages \ + breathe \ + sphinx-rtd-theme \ + && apt-get clean \ + && apt-get purge \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + "] + +# Download and install Doxygen 1.9.8. Currently, the version of Doxygen that +# gets installed via apt-get doesn't have support FAIL_ON_WARNINGS_PRINT for the +# WARN_AS_ERROR config option (added in 1.9.7). +RUN ["dash", "-c", "\ + curl -LO https://github.com/doxygen/doxygen/releases/download/Release_1_9_8/doxygen-1.9.8.linux.bin.tar.gz \ + && tar xf doxygen-1.9.8.linux.bin.tar.gz \ + && cd doxygen-1.9.8 \ + && make INSTALL=/usr install \ + && cd .. \ + && rm doxygen-1.9.8.linux.bin.tar.gz \ + && rm -rf doxygen-1.9.8 \ +"] + +# Download and install CMake v3.25.1. We need CMake v3.20+ in order to get the +# ctest --test-dir option used by run_unit_tests.sh. +RUN ["dash", "-c", "\ + curl -LO https://github.com/Kitware/CMake/releases/download/v3.25.1/cmake-3.25.1-linux-x86_64.tar.gz \ + && tar xf cmake-3.25.1-linux-x86_64.tar.gz --strip-components=1 -C /usr \ + && rm cmake-3.25.1-linux-x86_64.tar.gz \ +"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aed1af8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2019 Blues Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a29089 --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +[![Coverage Status][coverage badge]][coverage details] + +# note-c + +The note-c C library for communicating with the +[Blues Wireless][blues] Notecard via serial or I²C. + +This library allows you to control a Notecard by writing a C +or C++ program. Your program may programmatically configure Notecard and send +Notes to [Notehub.io][notehub]. + +This library is used by the [note-arduino library][note-arduino], which includes +it as a git subtree. + +## API Documentation + +The API documentation for this library can be found [here][note-c API docs]. + +## Logging Control + +`note-c` provides a comprehensive and flexible logging functionality. + +To activate logging, you must provide a callback for logging via +`hookDebugOutput()`. The callback takes the following form: + +```c +typedef size_t (*debugOutputFn) (const char *text); +``` + +The callback is responsible for taking a character array (C-style string) and +returning the number of bytes processed (written out) as confirmation. The +exact implementation will be up to the user who provided the function pointer, +but its presence will active logging in the library. + +### Library Logging + +#### Log Levels + +`note-c` provides for four (4) levels of logging. Here they are listed from +most severe to most verbose: + +0. `NOTE_C_LOG_LEVEL_ERROR` +1. `NOTE_C_LOG_LEVEL_WARN` +2. `NOTE_C_LOG_LEVEL_INFO` +3. `NOTE_C_LOG_LEVEL_DEBUG` + +By default, `note-c` logs at `NOTE_C_LOG_LEVEL_INFO`. + +#### Default Logging Behavior + +To modify the default behavior, you may specify the desired log level at compile +time, as follows: + +```sh +-DNOTE_C_LOG_LEVEL=0 +``` + +_**NOTE:** In the example above, you will notice we used zero (`0`), instead of +`NOTE_C_LOG_LEVEL_ERROR`. This is because the warning constants are internal to +the library, and not available in the context of the command line._ + +Here, we have decided to show only the most severe (i.e. `[ERROR]`) logs. +Alternatively, you may set the level to any of the values listed above. + +#### Dynamic Logging Behavior + +In the previous section, we discussed setting the base (or default) logging +behavior for the library. However, you may also set the log level dynamically, +during runtime, by using the `NoteSetLogLevel()` API. + +```c +NoteSetLogLevel(NOTE_C_LOG_LEVEL_WARN); +``` + +### Notecard Sync Logging (`[SYNC]`) + +Tangential to the standard logging behavior, `note-c` also provides a helper +function to invoke/extract synchronization logs from the Notecard. + +- `NoteDebugSyncStatus()` + +Instead of toggling features inside the library, this helper functions sends a +request to the Notecard to inquire about its synchronization status and logs +those details. + +The function is designed to be called in a loop and throttled by a parameter. +See [the documentation page][NoteDebugSyncStatus] for more information. + +## Versioning + +The `note-c` versioning scheme is a variant of [Semantic +Versioning](https://semver.org/). + +Below is a high-level overview of the major/minor/patch versions: + +- Major Version: Signals incompatible API changes. +- Minor Version: Signals added functionality in a backward compatible manner. +- Patch Version: Signals backward compatible bug fixes. + +Beyond the SemVer foundation, Blues has imposed additional requirements for a +version to be considered valid: + +- Major/minor/patch versions SHALL NOT be zero. +- For anything other than major version, version numbers MUST NOT contain +EITHER leading zeroes OR trailing zeroes (e.g. version `1.10.2` is invalid). + +> Example version progression: +> +> `1.8.1`, `1.9.1`, `1.9.2`, `1.11.1`, `1.11.2`, `1.11.3`, `1.12.1`, `2.1.1` + +These additional constraints have been observed to help disambiguate versions +and reduce support burden. + +### Version Artifacts + +The version can be referenced/tested programmatically via the following +preprocessor defined integers found in `note.h`: + +- `NOTE_C_VERSION_MAJOR` +- `NOTE_C_VERSION_MINOR` +- `NOTE_C_VERSION_PATCH` + +The version may also be logged via the preprocessor defined string literal, +`NOTE_C_VERSION`. + +## Contributing + +We love issues, fixes, and pull requests from everyone. By participating in this +project, you agree to abide by the Blues Inc [code of conduct]. + +For details on contributions we accept and the process for contributing, see our +[contribution guide](CONTRIBUTING.md). + +## More Information + +For additional Notecard SDKs and Libraries, see: + +- [note-arduino][note-arduino] for Arduino support +- [note-python][note-python] for Python +- [note-go][note-go] for Go + +## To learn more about Blues Wireless, the Notecard and Notehub, see: + +- [blues.com](https://blues.io) +- [notehub.io][notehub] +- [wireless.dev](https://wireless.dev) + +## License + +Copyright (c) 2019 Blues Inc. Released under the MIT license. See +[LICENSE](LICENSE) for details. + +[blues]: https://blues.com +[code of conduct]: https://blues.github.io/opensource/code-of-conduct +[coverage badge]: https://coveralls.io/repos/github/blues/note-c/badge.svg?branch=master +[coverage details]: https://coveralls.io/github/blues/note-c?branch=master +[NoteDebugSyncStatus]: https://blues.github.io/note-c/api_reference.html#c.NoteDebugSyncStatus +[notehub]: https://notehub.io +[note-arduino]: https://github.com/blues/note-arduino +[note-c API docs]: https://blues.github.io/note-c/index.html +[note-go]: https://github.com/blues/note-go +[note-python]: https://github.com/blues/note-python diff --git a/assets/blues_logo_no_text.png b/assets/blues_logo_no_text.png new file mode 100644 index 0000000000000000000000000000000000000000..1598330dc4425ac054a88a67169f90daa12cac4c GIT binary patch literal 4854 zcmVEX>4Tx04R}tkv&MmKpe$iQ$;Bi2djv6$WWc^q9Ts93Pq?8YK2xEOfLO`CJjl8 zi=*ILaPVWX>fqw6tAnc`2!4RLx;QDiNQwVT3N2zhIPS;0dyl(!fY2y2&FYE)nqD3?VEt%b1g-Bs|C0J$!tC`-Nh*&Iiu+qV-XllgM#1U1~DPPFA zta9Gstd(o5bx;1nU`}6I<~q$$B(R7jND!f*h7!uCB1)@HiiH&I$36Tbj$a~|Laq`R zITlcX2HEk0|H1EWt^Cxan-q)#-7mKNF$VPP0?oQ@e;?a+^91le16O+6UugidpQP8@ zTI2}m-v%zO+nTZmT6?KN00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru=L#DY7B{r@IOYHV5c5eyK~#9!?VWkB-c@76_QTrC`74RaHdrlU>uwZ#iea9|8(daj!Zb_J3edLa5aVakENKTs0=Ho)S zr9G2yX%lkFH02@E&s8fV4Zw92BR8t(@}{2?YSY z8;174HU)SbKv%#r(dKi@bgXQ}H|u8*bUkST56+zfnAmjkA;4p} z@-p?zVce;_y}&W=5bbs;;mVf|)=-0A0)K)_PAz~x9a0Cn{9oV$v4bRbfD8hDR?wc~5bzu>YjruW5?6IS zh7BhJ=in+r&IM-TD#sVGCs_a-DB>5OKk#J1#>eY`ZvvMA0}|5p0PjdkfZr8N=a2iv zokGzUc(@=2dI9)b;5`}X|Gl^}JuL;X!H@m+Kw$@%@2o{Jbew``nhh5kA^s`7#$d=7)n9*U>WSrMPAH9Bp-Vu1cSW zOFqkO=k^@xms!A1Gqc%2GRy)VM_wuGPXn_N9Y1a>GP2(*WOPkKBd{zZ#a@dpi|LFj zaClV&;4qkcG`GM8aT4qqnC4aT>CGgDB||0JlKH&NBT*o!j< z67+*-tUr)9C#aO2xN{LAF0+Ac3Hrm0RtlL6>`zP?8-Ql{su}MBUQWy>_F_~hJGJv2 zF9N5@N6i_JJ7S!YZ#)oN36~|NfK|9N+ae9dklvkY~Dtw$Gqk%OE z`AG|rP9c6eu{no^RcQ~mQrb|wK6HbJl1@6wHciO0nYiyr#AkK@r-W?mmlA2{0aPkK zC(gs|O{L&FUkugA5q`-mg&lHj0_Mje(%5m|4Gas>z&jH80xXuVWy@C*@u4q;Sdn-m zfrdSba;tKtgsZHX!sg+~qRl;C4%Ib^sEoiJKd|ACn@HacnN?@pU zc{L5NzCZA*fekdl_fQIo^0FFttRW=dFmB`hy?yahv8{!)k+5~ECIMR&1l`J3ud7fD z!WHndYTqGxS(VYcD*K|q!>Uzs{vON9z{9PN8@KP)N?*7E+bqmwfxGn|$muJVdjr~| z0arTI^2kI*%rQ2``2meF-A_X0^p&r0%?xOldB*LVRmsbK)9^(f4%~Y@E@!Xme!!4e z*4etdlD&^t4Zl($*Wkx%Ik#&2gxpM=X=tAZGe1ZD9Sa+)`h@B z^J}BFt?BN?{=iW2ACI8;*X7_4Kq zbo`i>PI{qJ3?g=QPbNpA?pTGZw;Y?HxI1s&5g=Aq2HYoK5Q+K5{Bi*@&*)|DmmdfP zNO{%)FUb!?C#(h5rw|~cfzu6MWT|{Wbc03e$z)(u#{ijb)OZDmNYzKCb_|fI1~1SC z{8IK8-LMRIEY>tYrWw4zvxMIq6!~+2uo>RT_8=={d(lId8$5Gb#{hY^0z@c4-o_d2 z0Wt!&dl;MNWp~j-RvJ8WI54ykn`_MW;A$Ziox>5@IT2UJv&d}34m-YuXPp&5iur=8{o>tkbALRLEL`XEv9xxnq2z>@70znXx7O zmU?71$=)J^02$T3iWQs9vbV_KqdKyLOH_=_HrZQL$o6{bL0V;RQ6ammdXT}9A9@J4 zrY%Gz?8WUX#bi*4_?t|7WNXn2+V3}yNxu?tDpYi&O|}-9_)b%a@I}DN4Y7pC!FT#8 zK!kdbZURII(FrAkFM5;IE_(Y#kKJP_KtxV7Sp-O1!M_4RJxH(T%O&D(z6?j#poz+9 zApZ3gZ6)B1$Pev{O_6MoH$!oEyu{?q5;l2JqhxPUAr?usyGqz3fsK{DMTLwpcxJ0r zfJ~OXMTJ-t7ujhw#8QBW90H`Zgv~*eX4zY0@KN1S!saOBnX$O!|>B^ z>)J)HjWrSYeS~Keosw06G~kZhTFgV5BlliS=qyUD=pR{^@4^+xi_NeSu$%ZNwE)hM zpNSqa!{C{_fxYeN?iTmJ&6C|l!oIS;eQXD=R&H$O%kH9wTxjsj)g1%m1%nqj7Z@Ph zi*6WbG{kDL?m_w)eHlbvybqh}9adQf2*5(wUnJ@yZ|gyJ0b3LxB0&}yJaZF%>!V}Q z28(;({?LmM<(Pua{h80S|G_#y06ro=5S?(f(Q_3bA`3oj^xU$s&<9sk`nZ9_xTn3z zsBJ}*7{wE27`5&7jvYtG9^@#n%qU|2QXflk5}!1B?k|92@OXFsV4cy}@7V~AZ_)gr zYKd{bSTq&vKLt# z@OQrkJgH8g*E|Wl8u0gj4ZFI{|5?fE+-&17)!cxJn2y`?QIAlW9L1gC3B`rR?VDN2 z%QoPS99q5X^1xq`d*$#I%fAM+M+5LH!?vyK%}amY*dQMbXpjMZSy_(XGg%1?3TTf{ z7`Lxii$mjZ8$wnuy#;sNG8B`5t#bZuWvka!C0TkBp6TSKx`+1ob3c z$|}N_{QQ=28}F;;u^SO`>9Rlo3q@g=-HxG%lIU^yT*@jDP z3CfM6vyQ)+uz5nFIwk-wC*&_t%1iaZ<>;i+=$CMbl_D)h11~1zC(nD|jlv(1UQ*o{ z`D8MT#XgPXWVtA|5*|uS0c(kyDj~`<5&QhTljVmNiZ~s+TE3mh%bK{GPsleW<`a8> z)2tNoY0^u;+l8)nvtW*2Sxn6zuD4W533q-xHKiOtn~5>G8n-u*u6O02&^~?)m_yNm*MRG^(p8oQ z+)YoLGqL5*{SYt<#wI?AD_09x=Sxm-A@J*r?6(D%9GTsi^%2M+j5!(g z{v9`kv$fer$gg7IF&`+G)=^-*pS1Thx+({CRqo3F>kJj&F3$niP{aC32{8b;7FW44 zSNnXnD8~5bjE!&{*bV#x?(&`7nF0JG?n>;OZStKWeXSZv@BBzL^;{Q|P@Yc@n1b8j zTU!ttJV?aTfwAa^D~w#E+O{v@cIOum_AZl383-)&J;nNh>3k^>vmqJ`47U`6Lx4vL+LKhm5pVE) ze#!Ev|NA~(h5)CK(GGFIp5uzx!I8o^xoC3|Ntn&s0rmQ2X!0u(as){^d6;7EK~+Q_ z;M;Nr$w~4r8d|=co6$v@5|`?8yTYM7SK^Y- zPhwb5x~Mb&e?^*QP9;4r2l^QjEb3My-i<3KDu>HUo&nC3`^M`{uEkyHE9XmF_V`U; zl~yuf0&u^aFj@Jb<{D{mF?kmsD4INvjxr>5T@Gq`5BVoGoJyNPz-^kJM3q)t zg5eOkUp@@>f%3W>IGF6mWjhR$yXDIW;ETTdms8h9wgR6AhReN*Lz7<`ZjpocTGsm| z+W~T?;?jVtj&VQGCa3RK+JO6j51<|Jx?>D*E3i(1@D5pvyPS2j+(ZDJY#h%XC~y{)ou$(F$z9UDdqW51m%~ zUQZ}Ml4A%bicC)i#`zVYksYKzZpXi`@12JF|GU?J