diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ce21e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Ignore output directories +/out/ +/install/ \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..6be3796 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,22 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "windowsSdkVersion": "10.0.18362.0", + "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Tools/MSVC/14.25.28610/bin/Hostx64/x64/cl.exe", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "msvc-x64", + "configurationProvider": "vector-of-bool.cmake-tools" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dae7c0c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch for Azure Sphere High-Level Applications (gdb)", + "type": "azurespheredbg", + "request": "launch", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "targetCore": "HLCore", + "partnerComponents": [], + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8ae2d77 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "cmake.generator": "Ninja", + "cmake.buildDirectory": "${workspaceRoot}/out/${buildType}-${command:azuresphere.AzureSphereTargetApiSet}", + "cmake.buildToolArgs": [ "-v" ], + "cmake.configureSettings": { + "CMAKE_TOOLCHAIN_FILE": "${command:azuresphere.AzureSphereSdkDir}/CMakeFiles/AzureSphereToolchain.cmake", + "AZURE_SPHERE_TARGET_API_SET": "latest-lts" + }, + "cmake.configureOnOpen": true, + "files.associations": { + "xutility": "c", + "xlocale": "c", + "ostream": "c" + } +} \ No newline at end of file diff --git a/AS5600.c b/AS5600.c new file mode 100644 index 0000000..7faad0b --- /dev/null +++ b/AS5600.c @@ -0,0 +1,296 @@ + + +#include +#include "AS5600.h" +#include +#include "Wire.h" +static int i2cFd = -1; +int _ic2Id; +int ang, lang = 0; + +void AS5600_open(int ic2Id) +{ + /* set i2c address */ + _ic2Id = ic2Id; +TwoWire(ic2Id); +begin2(); + //_ams5600_Address = 0x36; + /* load register values*/ + /* c++ class forbids pre loading of variables */ + _zmco = 0x00; + _zpos_hi = 0x01; + _zpos_lo = 0x02; + _mpos_hi = 0x03; + _mpos_lo = 0x04; + _mang_hi = 0x05; + _mang_lo = 0x06; + _conf_hi = 0x07; + _conf_lo = 0x08; + _raw_ang_hi = 0x0c; + _raw_ang_lo = 0x0d; + _ang_hi = 0x0e; + _ang_lo = 0x0f; + _stat = 0x0b; + _agc = 0x1a; + _mag_hi = 0x1b; + _mag_lo = 0x1c; + _burn = 0xff; + // i2cFd = I2CMaster_Open(_ams5600_Address); + //i2cFd = I2CMaster_Open(AVNET_AESMS_ISU0_I2C); + i2cFd = I2CMaster_Open(_ic2Id); + if (i2cFd == -1) + { + Log_Debug("ERROR: I2CMaster_Open: errno=%d (%s)\n", errno, strerror(errno)); + return; + } + + int result; + result = I2CMaster_SetBusSpeed(i2cFd, I2C_BUS_SPEED_STANDARD); + + //result = I2CMaster_SetTimeout(i2cFd, 100); + //result = I2CMaster_SetDefaultTargetAddress(i2cFd, _ams5600_Address); + Log_Debug("I2C Result %s\n", result); +} + +/* mode = 0, output PWM, mode = 1 output analog (full range from 0% to 100% between GND and VDD*/ +void setOutPut(uint8_t mode) +{ + uint8_t config_status; + config_status = readOneByte(_conf_lo); + if (mode == 1) + { + config_status = config_status & 0xcf; + } + else + { + config_status = config_status & 0xef; + } + writeOneByte(_conf_lo, lowByte(config_status)); +} + +uint8_t getAddress() +{ + return _ams5600_Address; +} + +word setMaxAngle(word newMaxAngle) +{ + word retVal; + if (newMaxAngle == -1) + { + _maxAngle = getRawAngle(); + } + else + _maxAngle = newMaxAngle; + + writeOneByte(_mang_hi, highByte(_maxAngle)); + delay(2); + writeOneByte(_mang_lo, lowByte(_maxAngle)); + delay(2); + + retVal = readTwoBytes(_mang_hi, _mang_lo); + return retVal; +} + +word getMaxAngle() +{ + return readTwoBytes(_mang_hi, _mang_lo); +} + +word setStartPosition(word startAngle) +{ + if (startAngle == -1) + { + _rawStartAngle = getRawAngle(); + } + else + _rawStartAngle = startAngle; + + writeOneByte(_zpos_hi, highByte(_rawStartAngle)); + delay(2); + writeOneByte(_zpos_lo, lowByte(_rawStartAngle)); + delay(2); + _zPosition = readTwoBytes(_zpos_hi, _zpos_lo); + + return (_zPosition); +} + +word getStartPosition() +{ + return readTwoBytes(_zpos_hi, _zpos_lo); +} + +word setEndPosition(word endAngle) +{ + if (endAngle == -1) + _rawEndAngle = getRawAngle(); + else + _rawEndAngle = endAngle; + + writeOneByte(_mpos_hi, highByte(_rawEndAngle)); + delay(2); + writeOneByte(_mpos_lo, lowByte(_rawEndAngle)); + delay(2); + _mPosition = readTwoBytes(_mpos_hi, _mpos_lo); + + return (_mPosition); +} + +word getEndPosition() +{ + word retVal = readTwoBytes(_mpos_hi, _mpos_lo); + return retVal; +} + +word getRawAngle() +{ + return readTwoBytes(_raw_ang_hi, _raw_ang_lo); +} + +word getScaledAngle() +{ + return readTwoBytes(_ang_hi, _ang_lo); +} + +uint8_t detectMagnet() +{ + uint8_t magStatus; + uint8_t retVal = 0; + + magStatus = readOneByte(_stat); + + if (magStatus & 0x20) + retVal = 1; + + return retVal; +} + +uint8_t getMagnetStrength() +{ + uint8_t magStatus; + uint8_t retVal = 0; + + magStatus = readOneByte(_stat); + if (detectMagnet() == 1) + { + retVal = 2; /*just right */ + if (magStatus & 0x10) + retVal = 1; /*to weak */ + else if (magStatus & 0x08) + retVal = 3; /*to strong */ + } + + return retVal; +} + +uint8_t getAgc() +{ + return readOneByte(_agc); +} + +word getMagnitude() +{ + return readTwoBytes(_mag_hi, _mag_lo); +} + +uint8_t getBurnCount() +{ + return readOneByte(_zmco); +} + +uint8_t burnAngle() +{ + uint8_t retVal = 1; + _zPosition = getStartPosition(); + _mPosition = getEndPosition(); + _maxAngle = getMaxAngle(); + + if (detectMagnet() == 1) + { + if (getBurnCount() < 3) + { + if ((_zPosition == 0) && (_mPosition == 0)) + retVal = -3; + else + writeOneByte(_burn, 0x80); + } + else + retVal = -2; + } + else + retVal = -1; + + return retVal; +} + +uint8_t burnMaxAngleAndConfig() +{ + uint8_t retVal = 1; + _maxAngle = getMaxAngle(); + + if (getBurnCount() == 0) + { + if (_maxAngle * 0.087 < 18) + retVal = -2; + else + writeOneByte(_burn, 0x40); + } + else + retVal = -1; + + return retVal; +} + +uint8_t readOneByte(uint8_t in_adr) +{ + uint8_t retVal; + //ssize_t transferredBytes = + I2CMaster_WriteThenRead(i2cFd, _ams5600_Address, &in_adr, sizeof(in_adr), &retVal, sizeof(retVal)); + return retVal; +} + + +word readTwoBytes(int in_adr_hi, int in_adr_lo) +{ + word retVal = -1; + + /* Read Low Byte */ + beginTransmission(_ams5600_Address); + wire_write(in_adr_lo); + endTransmission(true); + requestFrom(_ams5600_Address, 1); + while(wire_available() == 0); + int low = wire_read(); + + /* Read High Byte */ + beginTransmission(_ams5600_Address); + wire_write(in_adr_hi); + endTransmission(true); + requestFrom(_ams5600_Address, 1); + + while(wire_available() == 0); + + word high = wire_read(); + + high = high << 8; + retVal = high | low; + + return retVal; +} + + +void writeOneByte(int adr_in, int dat_in) +{ + beginTransmission(_ams5600_Address); + wire_write(adr_in); + wire_write(dat_in); + endTransmission(true); +} + +float convertRawAngleToDegrees(word newAngle) +{ + /* Raw data reports 0 - 4095 segments, which is 0.087 of a degree */ + float retVal = newAngle * 0.087; + ang = retVal; + return ang; +} \ No newline at end of file diff --git a/AS5600.h b/AS5600.h new file mode 100644 index 0000000..e9b7e51 --- /dev/null +++ b/AS5600.h @@ -0,0 +1,83 @@ +#ifndef AMS_5600_h +#define AMS_5600_h + +//#include +//#include + +// AMS_5600 +//{ +// public: + + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; + + +typedef uint8_t byte; +typedef uint16_t word; + + + + +void AS5600_open(int ic2Id); +uint8_t getAddress(void); + +word setMaxAngle(word newMaxAngle); +word getMaxAngle(void); + +word setStartPosition(word startAngle); +word getStartPosition(void); + +word setEndPosition(word endAngle); +word getEndPosition(void); + +word getRawAngle(void); +word getScaledAngle(void); + +uint8_t detectMagnet(void); +uint8_t getMagnetStrength(void); +uint8_t getAgc(void); +word getMagnitude(void); + +uint8_t getBurnCount(void); +uint8_t burnAngle(void); +uint8_t burnMaxAngleAndConfig(void); +void setOutPut(uint8_t mode); + +//private: + +static const uint8_t _ams5600_Address =0x36; +word _rawStartAngle; +word _zPosition; +word _rawEndAngle; +word _mPosition; +word _maxAngle; + +/* Registers */ +uint8_t _zmco; +uint8_t _zpos_hi; /*zpos[11:8] high nibble START POSITION */ +uint8_t _zpos_lo; /*zpos[7:0] */ +uint8_t _mpos_hi; /*mpos[11:8] high nibble STOP POSITION */ +uint8_t _mpos_lo; /*mpos[7:0] */ +uint8_t _mang_hi; /*mang[11:8] high nibble MAXIMUM ANGLE */ +uint8_t _mang_lo; /*mang[7:0] */ +uint8_t _conf_hi; +uint8_t _conf_lo; +uint8_t _raw_ang_hi; +uint8_t _raw_ang_lo; +uint8_t _ang_hi; +uint8_t _ang_lo; +uint8_t _stat; +uint8_t _agc; +uint8_t _mag_hi; +uint8_t _mag_lo; +uint8_t _burn; + +uint8_t readOneByte(uint8_t in_adr); +word readTwoBytes(int in_adr_hi, int in_adr_lo); +//word readTwoBytes2(uint8_t in_adr_hi, uint8_t in_adr_lo); +//void writeOneByte(uint8_t adr_in, uint8_t dat_in); +void writeOneByte(int adr_in, int dat_in); +float convertRawAngleToDegrees(word newAngle); +//}; +#endif \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cd5a50b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.10) + +project(AzureIoT C) + +azsphere_configure_tools(TOOLS_REVISION "20.04") +azsphere_configure_api(TARGET_API_SET "5") + +add_executable(${PROJECT_NAME} main.c eventloop_timer_utilities.c parson.c AS5600.c wire.c) +target_include_directories(${PROJECT_NAME} PUBLIC ${AZURE_SPHERE_API_SET_DIR}/usr/include/azureiot +${PROJECT_NAME}/include +C:/Users/rlisiecki/.platformio/packages/framework-azure/arduino/core +C:/Users/rlisiecki/.platformio/packages/framework-azure/arduino/variants/avnet_aesms_mt3620 +C:/Users/rlisiecki/.platformio/packages/framework-azure/arduino/libraries +C:/Users/rlisiecki/.platformio/packages/framework-azure/arduino/arduino) +target_compile_definitions(${PROJECT_NAME} PUBLIC AZURE_IOT_HUB_CONFIGURED) +target_link_libraries(${PROJECT_NAME} m azureiot applibs pthread gcc_s c) + +azsphere_target_hardware_definition(${PROJECT_NAME} TARGET_DIRECTORY "../../Hardware/avnet_aesms_mt3620" TARGET_DEFINITION "avnet_aesms_mt3620.json") + +find_program(POWERSHELL powershell.exe) + +if (POWERSHELL) + # Run validate_manifest script during build + add_custom_target(ValidateManifest ALL + COMMAND ${POWERSHELL} -ExecutionPolicy Bypass -NoProfile -NonInteractive -File ${CMAKE_SOURCE_DIR}/script/validate_manifest.ps1 + DEPENDS ${CMAKE_SOURCE_DIR}/app_manifest.json) +else() + # Warn users without PowerShell to update their manifest + add_custom_target(ValidateManifest ALL + COMMAND echo "Please ensure that you have updated app_manifest.json as described in IoTCentral.md or IoTHub.md, as appropriate." + DEPENDS ${CMAKE_SOURCE_DIR}/app_manifest.json) +endif() + +add_dependencies(ValidateManifest ${PROJECT_NAME}) + +azsphere_target_add_image_package(${PROJECT_NAME}) diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..90d86fa --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,47 @@ +{ + "environments": [ + { + "environment": "AzureSphere" + } + ], + "configurations": [ + { + "name": "ARM-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ + "AzureSphere" + ], + "buildRoot": "${projectDir}\\out\\${name}", + "installRoot": "${projectDir}\\install\\${name}", + "cmakeToolchain": "${env.AzureSphereDefaultSDKDir}CMakeFiles\\AzureSphereToolchain.cmake", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "AZURE_SPHERE_TARGET_API_SET", + "value": "latest-lts" + } + ] + }, + { + "name": "ARM-Release", + "generator": "Ninja", + "configurationType": "Release", + "inheritEnvironments": [ + "AzureSphere" + ], + "buildRoot": "${projectDir}\\out\\${name}", + "installRoot": "${projectDir}\\install\\${name}", + "cmakeToolchain": "${env.AzureSphereDefaultSDKDir}CMakeFiles\\AzureSphereToolchain.cmake", + "buildCommandArgs": "-v", + "ctestCommandArgs": "", + "variables": [ + { + "name": "AZURE_SPHERE_TARGET_API_SET", + "value": "latest-lts" + } + ] + } + ] +} \ No newline at end of file diff --git a/Wire.c b/Wire.c new file mode 100644 index 0000000..6cf4913 --- /dev/null +++ b/Wire.c @@ -0,0 +1,246 @@ +/* + Created on: 01.01.2019 + Author: Georgi Angelov + http://www.wizio.eu/ + https://github.com/Wiz-IO + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified: Junxiao Shi + */ + +//#include +#include +#include +#include +#include "avnet_aesms_mt3620.h" +#include + +//#define min(a, b) ((a) < (b) ? (a) : (b)) +//#define max(a, b) ((a) > (b) ? (a) : (b)) + +//#include "Arduino.h" +#include "Wire.h" +#include + +//#include +#define DEBUG_I2C +//Serial.printf + +static const size_t DEFAULT_BUFFER_LIMIT = 32; + +TwoWire(int port_id) +{ + fd = -1; + id = port_id; + rxBuffer = NULL; + rxBufferLength = 0; + rxBufferLimit = 0; + txAddress = 0; + txBuffer = NULL; + txBufferLength = 0; + txBufferLimit = 0; +} + +TwoWireCtor() +{ + end(); +} + +void begin2() +{ + if (fd >= 0) + { + DEBUG_I2C("[I2C] already open\n"); + return; + } + changeBufferLimits(DEFAULT_BUFFER_LIMIT, DEFAULT_BUFFER_LIMIT); + fd = I2CMaster_Open(id); + DEBUG_I2C("[I2C] I2CMaster_Open = %d\n", fd); +} + +void end() +{ + if (fd >= 0) + { + //close(fd); + fd = -1; + } + // if (rxBuffer != nullptr) { + //// delete[] rxBuffer; + // rxBufferLimit = 0; + // } + // if (txBuffer != nullptr) { + // delete[] txBuffer; + // txBufferLimit = 0; + // } +} + +void setClock(uint32_t frequency) +{ + int rc = I2CMaster_SetBusSpeed(fd, I2C_BUS_SPEED_STANDARD); + if (rc != 0) + { + DEBUG_I2C("[ERROR] I2CMaster_SetBusSpeed\n"); + } +} + +uint8_t requestFrom2(uint8_t address, uint8_t quantity, uint8_t sendStop) +{ + if (txBufferLength > 0 && address != txAddress) + { + endTransmission(true); + } + + quantity = min(quantity, rxBufferLimit); + size_t res = -1; + if (txBufferLength > 0) + { + res = I2CMaster_WriteThenRead(fd, address, + txBuffer, txBufferLength, rxBuffer, quantity); + DEBUG_I2C("[I2C] I2CMaster_WriteThenRead = %d, errno = %d\n", res, errno); + } + else + { + res = I2CMaster_Read(fd, address, rxBuffer, quantity); + DEBUG_I2C("[I2C] I2CMaster_Read = %d, errno = %d\n", res, errno); + } + + rxBufferIndex = 0; + rxBufferLength = res < 0 ? 0 : res - txBufferLength; + txBufferLength = 0; + return rxBufferLength; +} + +void beginTransmission(uint8_t address) +{ + txAddress = address; + txBufferLength = 0; +} + +uint8_t endTransmission(uint8_t sendStop) +{ + if (!sendStop) + { + return 0; + } + + int res = I2CMaster_Write(fd, txAddress, + txBuffer, txBufferLength); + DEBUG_I2C("[I2C] I2CMaster_Write = %d, errno = %d\n", res, errno); + txBufferLength = 0; + + if (res < 0) + { + switch (errno) + { + case ENXIO: + return 3; + default: + return 4; + } + } + return 0; +} + +uint8_t endTransmission2(void) +{ + return endTransmission(true); +} + +size_t wire_write(uint8_t data) +{ + if (txBufferLength >= txBufferLimit) + { + return 0; + } + txBuffer[txBufferLength++] = data; + return 1; +} + +size_t wire_write2(const uint8_t *data, size_t quantity) +{ + size_t count = 0; + for (size_t i = 0; i < quantity; ++i) + { + count += wire_write(data[i]); + } + return count; +} + +int wire_available(void) +{ + return rxBufferLength - rxBufferIndex; +} + +int wire_read(void) +{ + int value = -1; + if (rxBufferIndex < rxBufferLength) + { + value = rxBuffer[rxBufferIndex]; + ++rxBufferIndex; + } + return value; +} + +int peek(void) +{ + int value = -1; + if (rxBufferIndex < rxBufferLength) + { + value = rxBuffer[rxBufferIndex]; + } + return value; +} + +void begin(uint8_t address) +{ + begin2(); +} + +uint8_t requestFrom(uint8_t address, uint8_t quantity) +{ + return requestFrom2(address, quantity, (uint8_t) true); +} + +void changeBufferLimits(size_t rxLimit, size_t txLimit) +{ + rxLimit = max(1, rxLimit); + txLimit = max(1, txLimit); + + if (rxBufferLimit != rxLimit) + { + if (rxBuffer != NULL) { + //delete[] rxBuffer; + free(rxBuffer); + } + rxBufferLimit = rxLimit; + // rxBuffer = new uint8_t[rxBufferLimit]; + rxBuffer= malloc(rxBufferLimit); + rxBufferIndex = 0; + rxBufferLength = 0; + } + + if (txBufferLimit != txLimit) + { + if (txBuffer != NULL) { + //delete[] txBuffer; + free(txBuffer); + } + txBufferLimit = txLimit; + //txBuffer = new uint8_t[txBufferLimit]; + txBuffer = malloc(txBufferLimit); + txBufferLength = 0; + } +} diff --git a/Wire.h b/Wire.h new file mode 100644 index 0000000..4f3fac7 --- /dev/null +++ b/Wire.h @@ -0,0 +1,50 @@ +#ifndef TwoWire_h +#define TwoWire_h + +#include +#include + +extern uint8_t TWBR; + +#define BUFFER_LENGTH 32 + +int id; +int fd; +uint8_t *rxBuffer; +size_t rxBufferIndex; +size_t rxBufferLength; +size_t rxBufferLimit; +uint8_t txAddress; +uint8_t *txBuffer; +size_t txBufferLength; +size_t txBufferLimit; + +TwoWire(int port_id); +TwoWireCtor(); + +void end(); +void begin2(); +void begin(uint8_t); +void setClock(uint32_t); +void beginTransmission(uint8_t); +uint8_t endTransmission2(void); +uint8_t endTransmission(uint8_t); +uint8_t requestFrom(uint8_t, uint8_t); +uint8_t requestFrom2(uint8_t, uint8_t, uint8_t); +size_t wire_write(uint8_t); +size_t wire_write2(const uint8_t *data, size_t quantity); +int wire_available(void); +int wire_read(void); +int peek(void); +void flush(void) ; + +/** \brief Change buffer size limits. + * \param rxLimit read buffer size. + * \param txLimit write buffer size. + */ +void changeBufferLimits(size_t rxLimit, size_t txLimit); + +//extern TwoWire Wire; +//extern TwoWire Wire1; + +#endif diff --git a/app_manifest.json b/app_manifest.json new file mode 100644 index 0000000..b4a7879 --- /dev/null +++ b/app_manifest.json @@ -0,0 +1,30 @@ +{ + "SchemaVersion": 1, + "Name": "AzureIoT", + "ComponentId": "819255ff-8640-41fd-aea7-f85d34c491d5", + "EntryPoint": "/bin/app", + "CmdArgs": [ + "0ne001155C9" + ], + "Capabilities": { + "I2cMaster": [ "$AVNET_AESMS_ISU0_I2C" ,"$AVNET_AESMS_ISU1_I2C", "$AVNET_AESMS_ISU2_I2C"], + "AllowedConnections": [ + "global.azure-devices-provisioning.net", + "iotc-10dfbf89-f26b-4ad3-872f-00299ff73e05.azure-devices.net", + "iotc-1a4dc635-8bd3-40e8-9f01-289e75326d02.azure-devices.net", + "iotc-e5836402-2faf-4da3-9441-a10c18ea51e6.azure-devices.net", + "iotc-f52c02c3-4b59-4030-b386-32e93fac13ed.azure-devices.net", + "iotc-c79cf6a9-eda9-4169-8b83-0378a39fa115.azure-devices.net", + "iotc-85fce218-9935-4a7e-8eb1-b764537f6088.azure-devices.net", + "iotc-730a4890-4bef-47b9-9ed6-b7191833d380.azure-devices.net", + "iotc-9364826d-a98a-47ea-b1f5-96b769364ee9.azure-devices.net", + "iotc-74e3d74f-7215-403c-b64d-3b5153bbabb9.azure-devices.net", + "iotc-fcaf1d93-d68c-45e5-ad72-47e21db0506d.azure-devices.net" + ], + "WifiConfig": true, + "Gpio": [ "$AVNET_AESMS_PIN12_GPIO9", "$AVNET_AESMS_PIN14_GPIO12" ], + + "DeviceAuthentication": "5764d973-dc52-4302-8a43-5ad7d0252197" + }, + "ApplicationType": "Default" + } \ No newline at end of file diff --git a/applibs_versions.h b/applibs_versions.h new file mode 100644 index 0000000..a22a7b4 --- /dev/null +++ b/applibs_versions.h @@ -0,0 +1,28 @@ +/* Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +#pragma once + +/// +/// This identifier should be defined before including any of the networking-related header files. +/// It indicates which version of the Wi-Fi data structures the application uses. +/// +#define NETWORKING_STRUCTS_VERSION 1 + +/// +/// This identifier must be defined before including any of the Wi-Fi related header files. +/// It indicates which version of the Wi-Fi data structures the application uses. +/// +#define WIFICONFIG_STRUCTS_VERSION 1 + +/// +/// This identifier must be defined before including any of the UART-related header files. +/// It indicates which version of the UART data structures the application uses. +/// +#define UART_STRUCTS_VERSION 1 + +/// +/// This identifier must be defined before including any of the SPI-related header files. +/// It indicates which version of the SPI data structures the application uses. +/// +#define SPI_STRUCTS_VERSION 1 \ No newline at end of file diff --git a/eventloop_timer_utilities.c b/eventloop_timer_utilities.c new file mode 100644 index 0000000..0fe546a --- /dev/null +++ b/eventloop_timer_utilities.c @@ -0,0 +1,139 @@ +/* Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "eventloop_timer_utilities.h" + +static int SetTimerPeriod(int timerFd, const struct timespec *initial, + const struct timespec *repeat); + +static int SetTimerPeriod(int timerFd, const struct timespec *initial, + const struct timespec *repeat) +{ + static const struct timespec nullTimeSpec = {.tv_sec = 0, .tv_nsec = 0}; + struct itimerspec newValue = {.it_value = initial ? *initial : nullTimeSpec, + .it_interval = repeat ? *repeat : nullTimeSpec}; + + if (timerfd_settime(timerFd, /* flags */ 0, &newValue, /* old_value */ NULL) == -1) { + Log_Debug("ERROR: Could not set timer period: %s (%d).\n", strerror(errno), errno); + return -1; + } + + return 0; +} + +struct EventLoopTimer { + EventLoop *eventLoop; + EventLoopTimerHandler handler; + int fd; + EventRegistration *registration; +}; + +// This satisfies the EventLoopIoCallback signature. +static void TimerCallback(EventLoop *el, int fd, EventLoop_IoEvents events, void *context) +{ + EventLoopTimer *timer = (EventLoopTimer *)context; + + timer->handler(timer); +} + +EventLoopTimer *CreateEventLoopPeriodicTimer(EventLoop *eventLoop, EventLoopTimerHandler handler, + const struct timespec *period) +{ + if (handler == NULL) { + errno = EINVAL; + return NULL; + } + + EventLoopTimer *timer = malloc(sizeof(EventLoopTimer)); + if (timer == NULL) { + return NULL; + } + + timer->eventLoop = eventLoop; + timer->handler = handler; + + // Initialize to unused values in case have to clean up partially initialized object. + timer->fd = -1; + timer->registration = NULL; + + timer->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + if (timer->fd == -1) { + Log_Debug("ERROR: Unable to create timer: %s (%d).\n", strerror(errno), errno); + goto failed; + } + + if (SetTimerPeriod(timer->fd, /* initial */ period, /* repeat */ period) == -1) { + goto failed; + } + + timer->registration = + EventLoop_RegisterIo(eventLoop, timer->fd, EventLoop_Input, TimerCallback, timer); + if (timer->registration == NULL) { + Log_Debug("ERROR: Unable to register timer event: %s (%d).\n", strerror(errno), errno); + goto failed; + } + + return timer; + +failed: + DisposeEventLoopTimer(timer); + return NULL; +} + +EventLoopTimer *CreateEventLoopDisarmedTimer(EventLoop *eventLoop, EventLoopTimerHandler handler) +{ + return CreateEventLoopPeriodicTimer(eventLoop, handler, NULL); +} + +void DisposeEventLoopTimer(EventLoopTimer *timer) +{ + if (timer == NULL) { + return; + } + + EventLoop_UnregisterIo(timer->eventLoop, timer->registration); + + if (timer->fd != -1) { + close(timer->fd); + } + + free(timer); +} + +int ConsumeEventLoopTimerEvent(EventLoopTimer *timer) +{ + uint64_t timerData = 0; + + if (read(timer->fd, &timerData, sizeof(timerData)) == -1) { + Log_Debug("ERROR: Could not read timerfd %s (%d).\n", strerror(errno), errno); + return -1; + } + + return 0; +} + +int SetEventLoopTimerPeriod(EventLoopTimer *timer, const struct timespec *period) +{ + return SetTimerPeriod(timer->fd, /* initial */ period, /* period */ period); +} + +int SetEventLoopTimerOneShot(EventLoopTimer *timer, const struct timespec *delay) +{ + return SetTimerPeriod(timer->fd, /* initial */ delay, /* repeat */ NULL); +} + +int DisarmEventLoopTimer(EventLoopTimer *timer) +{ + return SetTimerPeriod(timer->fd, /* initial */ NULL, /* repeat */ NULL); +} diff --git a/eventloop_timer_utilities.h b/eventloop_timer_utilities.h new file mode 100644 index 0000000..fc763c7 --- /dev/null +++ b/eventloop_timer_utilities.h @@ -0,0 +1,102 @@ +/* Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +#pragma once +#include + +#include + +#include + +/// +/// Opaque handle. Obtain via +/// or and dispose of via +/// . +/// +typedef struct EventLoopTimer EventLoopTimer; + +/// +/// Applications implement a function with this signature to be +/// notified when a timer expires. +/// +/// The timer which has expired. +/// +/// +typedef void (*EventLoopTimerHandler)(EventLoopTimer *timer); + +/// +/// Create a periodic timer which is invoked on the event loop. The timer +/// will begin firing immediately. +/// +/// Event loop to which the timer will be added. +/// Callback to invoke when the timer expires. +/// Timer period. +/// On success, pointer to new EventLoopTimer, which should be disposed of +/// with . On failure, returns NULL, with more +/// information available in errno.. +EventLoopTimer *CreateEventLoopPeriodicTimer(EventLoop *eventLoop, EventLoopTimerHandler handler, + const struct timespec *period); + +/// +/// Create a disarmed timer. After the timer has been allocated, call +/// or +/// to arm the timer. +/// +/// Event loop to which the timer will be added. +/// Callback to invoke when the timer expires. +/// On success, pointer to new EventLoopTimer, which should be disposed of +/// with . On failure, returns NULL, with more +/// information available in errno.. +EventLoopTimer *CreateEventLoopDisarmedTimer(EventLoop *eventLoop, EventLoopTimerHandler handler); + +/// +/// Dispose of a timer which was allocated with +/// or . +/// It is safe to call this function with a NULL pointer. +/// +/// Successfully allocated event loop timer, or NULL. +void DisposeEventLoopTimer(EventLoopTimer *timer); + +/// +/// The timer callback should call this function to consume the timer event. +/// +/// Successfully allocated timer. +/// 0 on success, -1 on failure, in which case errno contains more information. +int ConsumeEventLoopTimerEvent(EventLoopTimer *timer); + +/// +/// Change the timer's period. This function should only be called to change an existing +/// timer's period. It does not have to be called to set the initial period - that is +/// handled by . +/// +/// Timer previously allocated with +/// or . +/// New timer period. +/// 0 on success, -1 on failure, in which case errno contains more information. +/// +/// +int SetEventLoopTimerPeriod(EventLoopTimer *timer, const struct timespec *period); + +/// +/// Set the timer to expire one after a specified period. +/// +/// 0 on succcess, -1 on failure, in which case errno contains more information. +/// Timer previously allocated with +/// or . +/// Period to wait before timer expires. +/// 0 on success, -1 on failure, in which case errno contains more +/// information. +/// +/// +int SetEventLoopTimerOneShot(EventLoopTimer *timer, const struct timespec *delay); + +/// +/// Disarm an existing event loop timer. +/// +/// Timer previously allocated with +/// or . +/// 0 on success; -1 on failure, in which case errno contains more +/// information. +/// +/// +int DisarmEventLoopTimer(EventLoopTimer *timer); diff --git a/launch.vs.json b/launch.vs.json new file mode 100644 index 0000000..0ec8351 --- /dev/null +++ b/launch.vs.json @@ -0,0 +1,21 @@ +{ + "version": "0.2.1", + "defaults": {}, + "configurations": [ + { + "type": "azurespheredbg", + "name": "GDB Debugger (HLCore)", + "project": "CMakeLists.txt", + "inheritEnvironments": [ + "AzureSphere" + ], + "customLauncher": "AzureSphereLaunchOptions", + "workingDirectory": "${workspaceRoot}", + "applicationPath": "${debugInfo.target}", + "imagePath": "${debugInfo.targetImage}", + "targetCore": "HLCore", + "targetApiSet": "${env.AzureSphereTargetApiSet}", + "partnerComponents": [] + } + ] +} \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..7b7324d --- /dev/null +++ b/main.c @@ -0,0 +1,763 @@ +/* Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the MIT License. */ + +// This sample C application demonstrates how to interface Azure Sphere devices with Azure IoT +// services. Using the Azure IoT SDK C APIs, it shows how to: +// 1. Use Device Provisioning Service (DPS) to connect to Azure IoT Hub/Central with +// certificate-based authentication +// 2. Use Device Twin to upload simulated temperature measurements, upload button press events and +// receive a desired LED state from Azure IoT Hub/Central +// 3. Use Direct Methods to receive a "Trigger Alarm" command from Azure IoT Hub/Central + +// You will need to provide four pieces of information to use this application, all of which are set +// in the app_manifest.json. +// 1. The Scope Id for the Device Provisioning Service - DPS (set in 'CmdArgs') +// 2. The Tenant Id obtained from 'azsphere tenant show-selected' (set in 'DeviceAuthentication') +// 3. The Azure DPS Global endpoint address 'global.azure-devices-provisioning.net' +// (set in 'AllowedConnections') +// 4. The Azure IoT Hub Endpoint address(es) that DPS is configured to direct this device to (set in +// 'AllowedConnections') + +#include +#include +#include +#include +#include +#include +#include +#include + +// applibs_versions.h defines the API struct versions to use for applibs APIs. +#include "applibs_versions.h" +#include +#include +#include +#include +#include + +// By default, this sample targets hardware that follows the MT3620 Reference +// Development Board (RDB) specification, such as the MT3620 Dev Kit from +// Seeed Studio. +// +// To target different hardware, you'll need to update CMakeLists.txt. See +// https://github.com/Azure/azure-sphere-samples/tree/master/Hardware for more details. +// +// This #include imports the sample_hardware abstraction from that hardware definition. +#include + +#include "eventloop_timer_utilities.h" + +// Azure IoT SDK +#include +#include +#include +#include +#include +#include + +#include "AS5600.h" +//AMS_5600 ams5600; + +/// +/// Exit codes for this application. These are used for the +/// application exit code. They must all be between zero and 255, +/// where zero is reserved for successful termination. +/// +typedef enum +{ + ExitCode_Success = 0, + + ExitCode_TermHandler_SigTerm = 1, + + ExitCode_Main_EventLoopFail = 2, + + ExitCode_ButtonTimer_Consume = 3, + + ExitCode_AzureTimer_Consume = 4, + + ExitCode_Init_EventLoop = 5, + ExitCode_Init_MessageButton = 6, + ExitCode_Init_OrientationButton = 7, + ExitCode_Init_TwinStatusLed = 8, + ExitCode_Init_ButtonPollTimer = 9, + ExitCode_Init_AzureTimer = 10, + + ExitCode_IsButtonPressed_GetValue = 11 +} ExitCode; + +static volatile sig_atomic_t exitCode = ExitCode_Success; + +#include "parson.h" // used to parse Device Twin messages. + +// Azure IoT defines. +static char *scopeId; // ScopeId for DPS +static IOTHUB_DEVICE_CLIENT_LL_HANDLE iothubClientHandle = NULL; +static const int keepalivePeriodSeconds = 20; +static bool iothubAuthenticated = false; + +// Function declarations +static void SendEventCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void *context); +static void DeviceTwinCallback(DEVICE_TWIN_UPDATE_STATE updateState, const unsigned char *payload, + size_t payloadSize, void *userContextCallback); +static void TwinReportState(const char *jsonState); +static void ReportedStateCallback(int result, void *context); +static int DeviceMethodCallback(const char *methodName, const unsigned char *payload, + size_t payloadSize, unsigned char **response, size_t *responseSize, + void *userContextCallback); +static const char *GetReasonString(IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason); +static const char *GetAzureSphereProvisioningResultString( + AZURE_SPHERE_PROV_RETURN_VALUE provisioningResult); +static void SendTelemetry(const char *jsonMessage); +static void SetupAzureClient(void); +static void SendRotationData(void); +static void ButtonPollTimerEventHandler(EventLoopTimer *timer); +static bool IsButtonPressed(int fd, GPIO_Value_Type *oldState); +static void AzureTimerEventHandler(EventLoopTimer *timer); + +// Initialization/Cleanup +static ExitCode InitPeripheralsAndHandlers(void); +static void CloseFdAndPrintError(int fd, const char *fdName); +static void ClosePeripheralsAndHandlers(void); + +// File descriptors - initialized to invalid value +// Button +static int sendMessageButtonGpioFd = -1; + +// LED +static int deviceTwinStatusLedGpioFd = -1; + +// Timer / polling +static EventLoop *eventLoop = NULL; +static EventLoopTimer *buttonPollTimer = NULL; +static EventLoopTimer *azureTimer = NULL; + +// Azure IoT poll periods +static const int AzureIoTDefaultPollPeriodSeconds = 1; // poll azure iot every second +static const int AzureIoTPollPeriodsPerTelemetry = 1; // only send telemetry 1/5 of polls +static const int AzureIoTMinReconnectPeriodSeconds = 60; // back off when reconnecting +static const int AzureIoTMaxReconnectPeriodSeconds = 10 * 60; // back off limit + +static int azureIoTPollPeriodSeconds = -1; +static int telemetryCount = 0; + +// State variables +static GPIO_Value_Type sendMessageButtonState = GPIO_Value_High; +static bool statusLedOn = false; + +/// +/// Signal handler for termination requests. This handler must be async-signal-safe. +/// +static void TerminationHandler(int signalNumber) +{ + // Don't use Log_Debug here, as it is not guaranteed to be async-signal-safe. + exitCode = ExitCode_TermHandler_SigTerm; +} + +/// +/// Main entry point for this sample. +/// + +int main(int argc, char *argv[]) +{ + Log_Debug("Azure IoT Application starting.\n"); + //AMS_5600(); + AS5600_open(AVNET_AESMS_ISU2_I2C); + + // 3 SCL GPIO37_MOSI2_RTS2_SCL2 + // 4 SDA GPIO38_MISO2_RXD2_SDA2 + + bool isNetworkingReady = false; + if ((Networking_IsNetworkingReady(&isNetworkingReady) == -1) || !isNetworkingReady) + { + Log_Debug("WARNING: Network is not ready. Device cannot connect until network is ready.\n"); + } + + if (argc > 1) + { + scopeId = argv[1]; + Log_Debug("Using Azure IoT DPS Scope ID %s\n", scopeId); + } + else + { + Log_Debug("ScopeId needs to be set in the app_manifest CmdArgs\n"); + return -1; + } + + exitCode = InitPeripheralsAndHandlers(); + + // Main loop + while (exitCode == ExitCode_Success) + { + + // check if rotation is greater or less than last check + // did it complete a full rotation? + + EventLoop_Run_Result result = EventLoop_Run(eventLoop, -1, true); + // Continue if interrupted by signal, e.g. due to breakpoint being set. + if (result == EventLoop_Run_Failed && errno != EINTR) + { + exitCode = ExitCode_Main_EventLoopFail; + } + } + + ClosePeripheralsAndHandlers(); + + Log_Debug("Application exiting.\n"); + + return exitCode; +} + +/// +/// Button timer event: Check the status of the button +/// +static void ButtonPollTimerEventHandler(EventLoopTimer *timer) +{ + if (ConsumeEventLoopTimerEvent(timer) != 0) + { + exitCode = ExitCode_ButtonTimer_Consume; + return; + } + + if (IsButtonPressed(sendMessageButtonGpioFd, &sendMessageButtonState)) + { + SendTelemetry("{\"ButtonPress\" : \"True\"}"); + } +} + +/// +/// Azure timer event: Check connection status and send telemetry +/// +static void AzureTimerEventHandler(EventLoopTimer *timer) +{ + if (ConsumeEventLoopTimerEvent(timer) != 0) + { + exitCode = ExitCode_AzureTimer_Consume; + return; + } + + bool isNetworkReady = false; + if (Networking_IsNetworkingReady(&isNetworkReady) != -1) + { + if (isNetworkReady && !iothubAuthenticated) + { + SetupAzureClient(); + } + } + else + { + Log_Debug("Failed to get Network state\n"); + } + + if (iothubAuthenticated) + { + telemetryCount++; + if (telemetryCount == AzureIoTPollPeriodsPerTelemetry) + { + telemetryCount = 0; + SendRotationData(); + } + IoTHubDeviceClient_LL_DoWork(iothubClientHandle); + } +} + +/// +/// Set up SIGTERM termination handler, initialize peripherals, and set up event handlers. +/// +/// +/// ExitCode_Success if all resources were allocated successfully; otherwise another +/// ExitCode value which indicates the specific failure. +/// +static ExitCode InitPeripheralsAndHandlers(void) +{ + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + action.sa_handler = TerminationHandler; + sigaction(SIGTERM, &action, NULL); + + eventLoop = EventLoop_Create(); + if (eventLoop == NULL) + { + Log_Debug("Could not create event loop.\n"); + return ExitCode_Init_EventLoop; + } + + // Open SAMPLE_BUTTON_1 GPIO as input + Log_Debug("Opening AVNET_AESMS_PIN14_GPIO12 as input.\n"); + sendMessageButtonGpioFd = GPIO_OpenAsInput(AVNET_AESMS_PIN14_GPIO12); + if (sendMessageButtonGpioFd == -1) + { + Log_Debug("ERROR: Could not open AVNET_AESMS_PIN14_GPIO12: %s (%d).\n", strerror(errno), errno); + return ExitCode_Init_MessageButton; + } + + // AVNET_AESMS_PIN12_GPIO9 is used to show Device Twin settings state + Log_Debug("Opening AVNET_AESMS_PIN12_GPIO9 as output.\n"); + deviceTwinStatusLedGpioFd = + GPIO_OpenAsOutput(AVNET_AESMS_PIN12_GPIO9, GPIO_OutputMode_PushPull, GPIO_Value_High); + if (deviceTwinStatusLedGpioFd == -1) + { + Log_Debug("ERROR: Could not open AVNET_AESMS_PIN12_GPIO9: %s (%d).\n", strerror(errno), errno); + return ExitCode_Init_TwinStatusLed; + } + + // Set up a timer to poll for button events. + static const struct timespec buttonPressCheckPeriod = {.tv_sec = 0, .tv_nsec = 1000 * 1000}; + buttonPollTimer = CreateEventLoopPeriodicTimer(eventLoop, &ButtonPollTimerEventHandler, + &buttonPressCheckPeriod); + if (buttonPollTimer == NULL) + { + return ExitCode_Init_ButtonPollTimer; + } + + azureIoTPollPeriodSeconds = AzureIoTDefaultPollPeriodSeconds; + //struct timespec azureTelemetryPeriod = {.tv_sec = azureIoTPollPeriodSeconds, .tv_nsec = 0}; + struct timespec azureTelemetryPeriod = {.tv_sec = 0, .tv_nsec = 500000000}; + azureTimer = + CreateEventLoopPeriodicTimer(eventLoop, &AzureTimerEventHandler, &azureTelemetryPeriod); + if (azureTimer == NULL) + { + return ExitCode_Init_AzureTimer; + } + + return ExitCode_Success; +} + +/// +/// Closes a file descriptor and prints an error on failure. +/// +/// File descriptor to close +/// File descriptor name to use in error message +static void CloseFdAndPrintError(int fd, const char *fdName) +{ + if (fd >= 0) + { + int result = close(fd); + if (result != 0) + { + Log_Debug("ERROR: Could not close fd %s: %s (%d).\n", fdName, strerror(errno), errno); + } + } +} + +/// +/// Close peripherals and handlers. +/// +static void ClosePeripheralsAndHandlers(void) +{ + DisposeEventLoopTimer(buttonPollTimer); + DisposeEventLoopTimer(azureTimer); + EventLoop_Close(eventLoop); + + Log_Debug("Closing file descriptors\n"); + + // Leave the LEDs off + if (deviceTwinStatusLedGpioFd >= 0) + { + GPIO_SetValue(deviceTwinStatusLedGpioFd, GPIO_Value_High); + } + + CloseFdAndPrintError(sendMessageButtonGpioFd, "SendMessageButton"); + CloseFdAndPrintError(deviceTwinStatusLedGpioFd, "StatusLed"); +} + +/// +/// Callback when the Azure IoT connection state changes. +/// This can indicate that a new connection attempt has succeeded or failed. +/// It can also indicate than an existing connection has expired due to SAS token expiry. +/// +static void ConnectionStatusCallback(IOTHUB_CLIENT_CONNECTION_STATUS result, + IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, + void *userContextCallback) +{ + iothubAuthenticated = (result == IOTHUB_CLIENT_CONNECTION_AUTHENTICATED); + Log_Debug("Azure IoT connection status: %s\n", GetReasonString(reason)); + if (iothubAuthenticated) + { + // Send static device twin properties when connection is established + TwinReportState( + "{\"manufacturer\":\"Microsoft\",\"model\":\"Azure Sphere Sample Device\"}"); + } +} + +/// +/// Sets up the Azure IoT Hub connection (creates the iothubClientHandle) +/// When the SAS Token for a device expires the connection needs to be recreated +/// which is why this is not simply a one time call. +/// +static void SetupAzureClient(void) +{ + if (iothubClientHandle != NULL) + { + IoTHubDeviceClient_LL_Destroy(iothubClientHandle); + } + + AZURE_SPHERE_PROV_RETURN_VALUE provResult = + IoTHubDeviceClient_LL_CreateWithAzureSphereDeviceAuthProvisioning(scopeId, 10000, + &iothubClientHandle); + Log_Debug("IoTHubDeviceClient_LL_CreateWithAzureSphereDeviceAuthProvisioning returned '%s'.\n", + GetAzureSphereProvisioningResultString(provResult)); + + if (provResult.result != AZURE_SPHERE_PROV_RESULT_OK) + { + + // If we fail to connect, reduce the polling frequency, starting at + // AzureIoTMinReconnectPeriodSeconds and with a backoff up to + // AzureIoTMaxReconnectPeriodSeconds + if (azureIoTPollPeriodSeconds == AzureIoTDefaultPollPeriodSeconds) + { + azureIoTPollPeriodSeconds = AzureIoTMinReconnectPeriodSeconds; + } + else + { + azureIoTPollPeriodSeconds *= 2; + if (azureIoTPollPeriodSeconds > AzureIoTMaxReconnectPeriodSeconds) + { + azureIoTPollPeriodSeconds = AzureIoTMaxReconnectPeriodSeconds; + } + } + + struct timespec azureTelemetryPeriod = {azureIoTPollPeriodSeconds, 0}; + SetEventLoopTimerPeriod(azureTimer, &azureTelemetryPeriod); + + Log_Debug("ERROR: Failed to create IoTHub Handle - will retry in %i seconds.\n", + azureIoTPollPeriodSeconds); + return; + } + + // Successfully connected, so make sure the polling frequency is back to the default + azureIoTPollPeriodSeconds = AzureIoTDefaultPollPeriodSeconds; + struct timespec azureTelemetryPeriod = {.tv_sec = azureIoTPollPeriodSeconds, .tv_nsec = 0}; + SetEventLoopTimerPeriod(azureTimer, &azureTelemetryPeriod); + + iothubAuthenticated = true; + + if (IoTHubDeviceClient_LL_SetOption(iothubClientHandle, OPTION_KEEP_ALIVE, + &keepalivePeriodSeconds) != IOTHUB_CLIENT_OK) + { + Log_Debug("ERROR: Failure setting Azure IoT Hub client option \"%s\".\n", + OPTION_KEEP_ALIVE); + return; + } + + IoTHubDeviceClient_LL_SetDeviceTwinCallback(iothubClientHandle, DeviceTwinCallback, NULL); + IoTHubDeviceClient_LL_SetDeviceMethodCallback(iothubClientHandle, DeviceMethodCallback, NULL); + IoTHubDeviceClient_LL_SetConnectionStatusCallback(iothubClientHandle, ConnectionStatusCallback, + NULL); +} + +/// +/// Callback invoked when a Direct Method is received from Azure IoT Hub. +/// +static int DeviceMethodCallback(const char *methodName, const unsigned char *payload, + size_t payloadSize, unsigned char **response, size_t *responseSize, + void *userContextCallback) +{ + int result; + char *responseString; + + Log_Debug("Received Device Method callback: Method name %s.\n", methodName); + + if (strcmp("TriggerAlarm", methodName) == 0) + { + // Output alarm using Log_Debug + Log_Debug(" ----- ALARM TRIGGERED! -----\n"); + responseString = "\"Alarm Triggered\""; // must be a JSON string (in quotes) + result = 200; + } + else + { + // All other method names are ignored + responseString = "{}"; + result = -1; + } + + // if 'response' is non-NULL, the Azure IoT library frees it after use, so copy it to heap + *responseSize = strlen(responseString); + *response = malloc(*responseSize); + memcpy(*response, responseString, *responseSize); + return result; +} + +/// +/// Callback invoked when a Device Twin update is received from Azure IoT Hub. +/// +static void DeviceTwinCallback(DEVICE_TWIN_UPDATE_STATE updateState, const unsigned char *payload, + size_t payloadSize, void *userContextCallback) +{ + size_t nullTerminatedJsonSize = payloadSize + 1; + char *nullTerminatedJsonString = (char *)malloc(nullTerminatedJsonSize); + if (nullTerminatedJsonString == NULL) + { + Log_Debug("ERROR: Could not allocate buffer for twin update payload.\n"); + abort(); + } + + // Copy the provided buffer to a null terminated buffer. + memcpy(nullTerminatedJsonString, payload, payloadSize); + // Add the null terminator at the end. + nullTerminatedJsonString[nullTerminatedJsonSize - 1] = 0; + + JSON_Value *rootProperties = NULL; + rootProperties = json_parse_string(nullTerminatedJsonString); + if (rootProperties == NULL) + { + Log_Debug("WARNING: Cannot parse the string as JSON content.\n"); + goto cleanup; + } + + JSON_Object *rootObject = json_value_get_object(rootProperties); + JSON_Object *desiredProperties = json_object_dotget_object(rootObject, "desired"); + if (desiredProperties == NULL) + { + desiredProperties = rootObject; + } + + // The desired properties should have a "StatusLED" object + JSON_Object *LEDState = json_object_dotget_object(desiredProperties, "StatusLED"); + if (LEDState != NULL) + { + // ... with a "value" field which is a Boolean + int statusLedValue = json_object_get_boolean(LEDState, "value"); + if (statusLedValue != -1) + { + statusLedOn = statusLedValue == 1; + GPIO_SetValue(deviceTwinStatusLedGpioFd, + statusLedOn ? GPIO_Value_Low : GPIO_Value_High); + } + } + + // Report current status LED state + if (statusLedOn) + { + TwinReportState("{\"StatusLED\":{\"value\":true}}"); + } + else + { + TwinReportState("{\"StatusLED\":{\"value\":false}}"); + } + +cleanup: + // Release the allocated memory. + json_value_free(rootProperties); + free(nullTerminatedJsonString); +} + +/// +/// Converts the Azure IoT Hub connection status reason to a string. +/// +static const char *GetReasonString(IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason) +{ + static char *reasonString = "unknown reason"; + switch (reason) + { + case IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN: + reasonString = "IOTHUB_CLIENT_CONNECTION_EXPIRED_SAS_TOKEN"; + break; + case IOTHUB_CLIENT_CONNECTION_DEVICE_DISABLED: + reasonString = "IOTHUB_CLIENT_CONNECTION_DEVICE_DISABLED"; + break; + case IOTHUB_CLIENT_CONNECTION_BAD_CREDENTIAL: + reasonString = "IOTHUB_CLIENT_CONNECTION_BAD_CREDENTIAL"; + break; + case IOTHUB_CLIENT_CONNECTION_RETRY_EXPIRED: + reasonString = "IOTHUB_CLIENT_CONNECTION_RETRY_EXPIRED"; + break; + case IOTHUB_CLIENT_CONNECTION_NO_NETWORK: + reasonString = "IOTHUB_CLIENT_CONNECTION_NO_NETWORK"; + break; + case IOTHUB_CLIENT_CONNECTION_COMMUNICATION_ERROR: + reasonString = "IOTHUB_CLIENT_CONNECTION_COMMUNICATION_ERROR"; + break; + case IOTHUB_CLIENT_CONNECTION_OK: + reasonString = "IOTHUB_CLIENT_CONNECTION_OK"; + break; + } + return reasonString; +} + +/// +/// Converts AZURE_SPHERE_PROV_RETURN_VALUE to a string. +/// +static const char *GetAzureSphereProvisioningResultString( + AZURE_SPHERE_PROV_RETURN_VALUE provisioningResult) +{ + switch (provisioningResult.result) + { + case AZURE_SPHERE_PROV_RESULT_OK: + return "AZURE_SPHERE_PROV_RESULT_OK"; + case AZURE_SPHERE_PROV_RESULT_INVALID_PARAM: + return "AZURE_SPHERE_PROV_RESULT_INVALID_PARAM"; + case AZURE_SPHERE_PROV_RESULT_NETWORK_NOT_READY: + return "AZURE_SPHERE_PROV_RESULT_NETWORK_NOT_READY"; + case AZURE_SPHERE_PROV_RESULT_DEVICEAUTH_NOT_READY: + return "AZURE_SPHERE_PROV_RESULT_DEVICEAUTH_NOT_READY"; + case AZURE_SPHERE_PROV_RESULT_PROV_DEVICE_ERROR: + return "AZURE_SPHERE_PROV_RESULT_PROV_DEVICE_ERROR"; + case AZURE_SPHERE_PROV_RESULT_GENERIC_ERROR: + return "AZURE_SPHERE_PROV_RESULT_GENERIC_ERROR"; + default: + return "UNKNOWN_RETURN_VALUE"; + } +} + +/// +/// Sends telemetry to Azure IoT Hub +/// +static void SendTelemetry(const char *jsonMessage) +{ + Log_Debug("Sending Azure IoT Hub telemetry: %s.\n", jsonMessage); + + bool isNetworkingReady = false; + if ((Networking_IsNetworkingReady(&isNetworkingReady) == -1) || !isNetworkingReady) + { + Log_Debug("WARNING: Cannot send Azure IoT Hub telemetry because the network is not up.\n"); + return; + } + + IOTHUB_MESSAGE_HANDLE messageHandle = IoTHubMessage_CreateFromString(jsonMessage); + + if (messageHandle == 0) + { + Log_Debug("ERROR: unable to create a new IoTHubMessage.\n"); + return; + } + + if (IoTHubDeviceClient_LL_SendEventAsync(iothubClientHandle, messageHandle, SendEventCallback, + /*&callback_param*/ NULL) != IOTHUB_CLIENT_OK) + { + Log_Debug("ERROR: failure requesting IoTHubClient to send telemetry event.\n"); + } + else + { + Log_Debug("INFO: IoTHubClient accepted the telemetry event for delivery.\n"); + } + + IoTHubMessage_Destroy(messageHandle); +} + +/// +/// Callback invoked when the Azure IoT Hub send event request is processed. +/// +static void SendEventCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void *context) +{ + Log_Debug("INFO: Azure IoT Hub send telemetry event callback: status code %d.\n", result); +} + +/// +/// Enqueues a report containing Device Twin reported properties. The report is not sent +/// immediately, but it is sent on the next invocation of IoTHubDeviceClient_LL_DoWork(). +/// +static void TwinReportState(const char *jsonState) +{ + if (iothubClientHandle == NULL) + { + Log_Debug("ERROR: Azure IoT Hub client not initialized.\n"); + } + else + { + if (IoTHubDeviceClient_LL_SendReportedState( + iothubClientHandle, (const unsigned char *)jsonState, strlen(jsonState), + ReportedStateCallback, NULL) != IOTHUB_CLIENT_OK) + { + Log_Debug("ERROR: Azure IoT Hub client error when reporting state '%s'.\n", jsonState); + } + else + { + Log_Debug("INFO: Azure IoT Hub client accepted request to report state '%s'.\n", + jsonState); + } + } +} + +/// +/// Callback invoked when the Device Twin report state request is processed by Azure IoT Hub +/// client. +/// +static void ReportedStateCallback(int result, void *context) +{ + Log_Debug("INFO: Azure IoT Hub Device Twin reported state callback: status code %d.\n", result); +} + +#define TELEMETRY_BUFFER_SIZE 100 + +enum RollDirection +{ + Forward = 0, + Backward = 1 +} rollDirection; + +enum RollDirection previousDirection; +float rotationHistory[4]; +float delta = 0; +float lastDegrees = 0; +int fullRotationCount = 0; +void SendRotationData(void) +{ + float rotation = convertRawAngleToDegrees(getRawAngle()); + if (rotation != lastDegrees) + { + rotationHistory[3] = rotationHistory[2]; // 2 290 340.. 10 2 340 + rotationHistory[2] = rotationHistory[1]; + rotationHistory[1] = rotationHistory[0]; + rotationHistory[0] = rotation; + delta = abs(lastDegrees - rotationHistory[0]); + if ((rotationHistory[1] > rotationHistory[2]) && (rotationHistory[2] > rotationHistory[3])) + { + rollDirection = Forward; + } + else if ((rotationHistory[1] < rotationHistory[2]) && (rotationHistory[2] < rotationHistory[3])) + { + rollDirection = Backward; + } + + if (rollDirection != previousDirection) + { + // someone is rolling it backwards! + } + + previousDirection = rollDirection; + + if ((delta > 200) && (rotationHistory[0] < rotationHistory[1]) && (rotationHistory[1] > rotationHistory[2]) && (rotationHistory[2] > rotationHistory[3])) + { + // one full rotation? + fullRotationCount++; + } + + char telemetryBuffer[TELEMETRY_BUFFER_SIZE]; + int len = snprintf(telemetryBuffer, TELEMETRY_BUFFER_SIZE, "{\"Degrees\":\"%3.2f\",\"Rotation\":\"%i\"}", rotationHistory[0], fullRotationCount); + + if (len < 0 || len >= TELEMETRY_BUFFER_SIZE) + { + Log_Debug("ERROR: Cannot write telemetry to buffer.\n"); + return; + } + SendTelemetry(telemetryBuffer); + } + lastDegrees = rotation; +} + +/// +/// Check whether a given button has just been pressed. +/// +/// The button file descriptor +/// Old state of the button (pressed or released) +/// true if pressed, false otherwise +static bool IsButtonPressed(int fd, GPIO_Value_Type *oldState) +{ + bool isButtonPressed = false; + GPIO_Value_Type newState; + int result = GPIO_GetValue(fd, &newState); + if (result != 0) + { + Log_Debug("ERROR: Could not read button GPIO: %s (%d).\n", strerror(errno), errno); + exitCode = ExitCode_IsButtonPressed_GetValue; + } + else + { + // Button is pressed if it is low and different than last known state. + isButtonPressed = (newState != *oldState) && (newState == GPIO_Value_Low); + *oldState = newState; + } + + return isButtonPressed; +} diff --git a/parson.c b/parson.c new file mode 100644 index 0000000..8864bb5 --- /dev/null +++ b/parson.c @@ -0,0 +1,2202 @@ +/* + This source code comes from Git repository + https://github.com/kgabis/parson at commit id 4f3eaa6 + Patched to avoid any usage of fopen(), and removed implicit + cast warnings by making them explicit. +*/ + +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2017 Krzysztof Gabis + + 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. +*/ +#ifdef _MSC_VER +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif /* _CRT_SECURE_NO_WARNINGS */ +#endif /* _MSC_VER */ + +#include "parson.h" + +#include +#include +#include +#include +#include +#include + +/* Apparently sscanf is not implemented in some "standard" libraries, so don't use it, if you + * don't have to. */ +#define sscanf THINK_TWICE_ABOUT_USING_SSCANF + +#define STARTING_CAPACITY 16 +#define MAX_NESTING 2048 + +#define FLOAT_FORMAT "%1.17g" /* do not increase precision without incresing NUM_BUF_SIZE */ +/* double printed with "%1.17g" shouldn't be longer than 25 bytes so let's use 64 */ +#define NUM_BUF_SIZE 64 + +#define SIZEOF_TOKEN(a) (sizeof(a) - 1) +#define SKIP_CHAR(str) ((*str)++) +#define SKIP_WHITESPACES(str) \ + while (isspace((unsigned char)(**str))) { \ + SKIP_CHAR(str); \ + } +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#undef malloc +#undef free + +static JSON_Malloc_Function parson_malloc = malloc; +static JSON_Free_Function parson_free = free; + +#define IS_CONT(b) (((unsigned char)(b)&0xC0) == 0x80) /* is utf-8 continuation byte */ + +/* Type definitions */ +typedef union json_value_value { + char *string; + double number; + JSON_Object *object; + JSON_Array *array; + int boolean; + int null; +} JSON_Value_Value; + +struct json_value_t { + JSON_Value *parent; + JSON_Value_Type type; + JSON_Value_Value value; +}; + +struct json_object_t { + JSON_Value *wrapping_value; + char **names; + JSON_Value **values; + size_t count; + size_t capacity; +}; + +struct json_array_t { + JSON_Value *wrapping_value; + JSON_Value **items; + size_t count; + size_t capacity; +}; + +/* Various */ +static void remove_comments(char *string, const char *start_token, const char *end_token); +static char *parson_strndup(const char *string, size_t n); +static char *parson_strdup(const char *string); +static int hex_char_to_int(char c); +static int parse_utf16_hex(const char *string, unsigned int *result); +static int num_bytes_in_utf8_sequence(unsigned char c); +static int verify_utf8_sequence(const unsigned char *string, int *len); +static int is_valid_utf8(const char *string, size_t string_len); +static int is_decimal(const char *string, size_t length); + +/* JSON Object */ +static JSON_Object *json_object_init(JSON_Value *wrapping_value); +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value); +static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, + JSON_Value *value); +static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity); +static JSON_Value *json_object_getn_value(const JSON_Object *object, const char *name, + size_t name_len); +static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, + int free_value); +static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, + int free_value); +static void json_object_free(JSON_Object *object); + +/* JSON Array */ +static JSON_Array *json_array_init(JSON_Value *wrapping_value); +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value); +static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity); +static void json_array_free(JSON_Array *array); + +/* JSON Value */ +static JSON_Value *json_value_init_string_no_copy(char *string); + +/* Parser */ +static JSON_Status skip_quotes(const char **string); +static int parse_utf16(const char **unprocessed, char **processed); +static char *process_string(const char *input, size_t len); +static char *get_quoted_string(const char **string); +static JSON_Value *parse_object_value(const char **string, size_t nesting); +static JSON_Value *parse_array_value(const char **string, size_t nesting); +static JSON_Value *parse_string_value(const char **string); +static JSON_Value *parse_boolean_value(const char **string); +static JSON_Value *parse_number_value(const char **string); +static JSON_Value *parse_null_value(const char **string); +static JSON_Value *parse_value(const char **string, size_t nesting); + +/* Serialization */ +static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, + char *num_buf); +static int json_serialize_string(const char *string, char *buf); +static int append_indent(char *buf, int level); +static int append_string(char *buf, const char *string); + +/* Various */ +static char *parson_strndup(const char *string, size_t n) +{ + char *output_string = (char *)parson_malloc(n + 1); + if (!output_string) { + return NULL; + } + output_string[n] = '\0'; + strncpy(output_string, string, n); + return output_string; +} + +static char *parson_strdup(const char *string) +{ + return parson_strndup(string, strlen(string)); +} + +static int hex_char_to_int(char c) +{ + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return -1; +} + +static int parse_utf16_hex(const char *s, unsigned int *result) +{ + int x1, x2, x3, x4; + if (s[0] == '\0' || s[1] == '\0' || s[2] == '\0' || s[3] == '\0') { + return 0; + } + x1 = hex_char_to_int(s[0]); + x2 = hex_char_to_int(s[1]); + x3 = hex_char_to_int(s[2]); + x4 = hex_char_to_int(s[3]); + if (x1 == -1 || x2 == -1 || x3 == -1 || x4 == -1) { + return 0; + } + *result = (unsigned int)((x1 << 12) | (x2 << 8) | (x3 << 4) | x4); + return 1; +} + +static int num_bytes_in_utf8_sequence(unsigned char c) +{ + if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) { + return 0; + } else if ((c & 0x80) == 0) { /* 0xxxxxxx */ + return 1; + } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */ + return 2; + } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */ + return 3; + } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */ + return 4; + } + return 0; /* won't happen */ +} + +static int verify_utf8_sequence(const unsigned char *string, int *len) +{ + unsigned int cp = 0; + *len = num_bytes_in_utf8_sequence(string[0]); + + if (*len == 1) { + cp = string[0]; + } else if (*len == 2 && IS_CONT(string[1])) { + cp = string[0] & 0x1F; + cp = (cp << 6) | (string[1] & 0x3F); + } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) { + cp = ((unsigned char)string[0]) & 0xF; + cp = (cp << 6) | (string[1] & 0x3F); + cp = (cp << 6) | (string[2] & 0x3F); + } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) { + cp = string[0] & 0x7; + cp = (cp << 6) | (string[1] & 0x3F); + cp = (cp << 6) | (string[2] & 0x3F); + cp = (cp << 6) | (string[3] & 0x3F); + } else { + return 0; + } + + /* overlong encodings */ + if ((cp < 0x80 && *len > 1) || (cp < 0x800 && *len > 2) || (cp < 0x10000 && *len > 3)) { + return 0; + } + + /* invalid unicode */ + if (cp > 0x10FFFF) { + return 0; + } + + /* surrogate halves */ + if (cp >= 0xD800 && cp <= 0xDFFF) { + return 0; + } + + return 1; +} + +static int is_valid_utf8(const char *string, size_t string_len) +{ + int len = 0; + const char *string_end = string + string_len; + while (string < string_end) { + if (!verify_utf8_sequence((const unsigned char *)string, &len)) { + return 0; + } + string += len; + } + return 1; +} + +static int is_decimal(const char *string, size_t length) +{ + if (length > 1 && string[0] == '0' && string[1] != '.') { + return 0; + } + if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.') { + return 0; + } + while (length--) { + if (strchr("xX", string[length])) { + return 0; + } + } + return 1; +} + +static void remove_comments(char *string, const char *start_token, const char *end_token) +{ + int in_string = 0, escaped = 0; + size_t i; + char *ptr = NULL, current_char; + size_t start_token_len = strlen(start_token); + size_t end_token_len = strlen(end_token); + if (start_token_len == 0 || end_token_len == 0) { + return; + } + while ((current_char = *string) != '\0') { + if (current_char == '\\' && !escaped) { + escaped = 1; + string++; + continue; + } else if (current_char == '\"' && !escaped) { + in_string = !in_string; + } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) { + for (i = 0; i < start_token_len; i++) { + string[i] = ' '; + } + string = string + start_token_len; + ptr = strstr(string, end_token); + if (!ptr) { + return; + } + for (i = 0; i < ((size_t)(ptr - string) + end_token_len); i++) { + string[i] = ' '; + } + string = ptr + end_token_len - 1; + } + escaped = 0; + string++; + } +} + +/* JSON Object */ +static JSON_Object *json_object_init(JSON_Value *wrapping_value) +{ + JSON_Object *new_obj = (JSON_Object *)parson_malloc(sizeof(JSON_Object)); + if (new_obj == NULL) { + return NULL; + } + new_obj->wrapping_value = wrapping_value; + new_obj->names = (char **)NULL; + new_obj->values = (JSON_Value **)NULL; + new_obj->capacity = 0; + new_obj->count = 0; + return new_obj; +} + +static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) +{ + if (name == NULL) { + return JSONFailure; + } + return json_object_addn(object, name, strlen(name), value); +} + +static JSON_Status json_object_addn(JSON_Object *object, const char *name, size_t name_len, + JSON_Value *value) +{ + size_t index = 0; + if (object == NULL || name == NULL || value == NULL) { + return JSONFailure; + } + if (json_object_getn_value(object, name, name_len) != NULL) { + return JSONFailure; + } + if (object->count >= object->capacity) { + size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY); + if (json_object_resize(object, new_capacity) == JSONFailure) { + return JSONFailure; + } + } + index = object->count; + object->names[index] = parson_strndup(name, name_len); + if (object->names[index] == NULL) { + return JSONFailure; + } + value->parent = json_object_get_wrapping_value(object); + object->values[index] = value; + object->count++; + return JSONSuccess; +} + +static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity) +{ + char **temp_names = NULL; + JSON_Value **temp_values = NULL; + + if ((object->names == NULL && object->values != NULL) || + (object->names != NULL && object->values == NULL) || new_capacity == 0) { + return JSONFailure; /* Shouldn't happen */ + } + temp_names = (char **)parson_malloc(new_capacity * sizeof(char *)); + if (temp_names == NULL) { + return JSONFailure; + } + temp_values = (JSON_Value **)parson_malloc(new_capacity * sizeof(JSON_Value *)); + if (temp_values == NULL) { + parson_free(temp_names); + return JSONFailure; + } + if (object->names != NULL && object->values != NULL && object->count > 0) { + memcpy(temp_names, object->names, object->count * sizeof(char *)); + memcpy(temp_values, object->values, object->count * sizeof(JSON_Value *)); + } + parson_free(object->names); + parson_free(object->values); + object->names = temp_names; + object->values = temp_values; + object->capacity = new_capacity; + return JSONSuccess; +} + +static JSON_Value *json_object_getn_value(const JSON_Object *object, const char *name, + size_t name_len) +{ + size_t i, name_length; + for (i = 0; i < json_object_get_count(object); i++) { + name_length = strlen(object->names[i]); + if (name_length != name_len) { + continue; + } + if (strncmp(object->names[i], name, name_len) == 0) { + return object->values[i]; + } + } + return NULL; +} + +static JSON_Status json_object_remove_internal(JSON_Object *object, const char *name, + int free_value) +{ + size_t i = 0, last_item_index = 0; + if (object == NULL || json_object_get_value(object, name) == NULL) { + return JSONFailure; + } + last_item_index = json_object_get_count(object) - 1; + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + parson_free(object->names[i]); + if (free_value) { + json_value_free(object->values[i]); + } + if (i != last_item_index) { /* Replace key value pair with one from the end */ + object->names[i] = object->names[last_item_index]; + object->values[i] = object->values[last_item_index]; + } + object->count -= 1; + return JSONSuccess; + } + } + return JSONFailure; /* No execution path should end here */ +} + +static JSON_Status json_object_dotremove_internal(JSON_Object *object, const char *name, + int free_value) +{ + JSON_Value *temp_value = NULL; + JSON_Object *temp_object = NULL; + const char *dot_pos = strchr(name, '.'); + if (dot_pos == NULL) { + return json_object_remove_internal(object, name, free_value); + } + temp_value = json_object_getn_value(object, name, (size_t)(dot_pos - name)); + if (json_value_get_type(temp_value) != JSONObject) { + return JSONFailure; + } + temp_object = json_value_get_object(temp_value); + return json_object_dotremove_internal(temp_object, dot_pos + 1, free_value); +} + +static void json_object_free(JSON_Object *object) +{ + size_t i; + for (i = 0; i < object->count; i++) { + parson_free(object->names[i]); + json_value_free(object->values[i]); + } + parson_free(object->names); + parson_free(object->values); + parson_free(object); +} + +/* JSON Array */ +static JSON_Array *json_array_init(JSON_Value *wrapping_value) +{ + JSON_Array *new_array = (JSON_Array *)parson_malloc(sizeof(JSON_Array)); + if (new_array == NULL) { + return NULL; + } + new_array->wrapping_value = wrapping_value; + new_array->items = (JSON_Value **)NULL; + new_array->capacity = 0; + new_array->count = 0; + return new_array; +} + +static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) +{ + if (array->count >= array->capacity) { + size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY); + if (json_array_resize(array, new_capacity) == JSONFailure) { + return JSONFailure; + } + } + value->parent = json_array_get_wrapping_value(array); + array->items[array->count] = value; + array->count++; + return JSONSuccess; +} + +static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) +{ + JSON_Value **new_items = NULL; + if (new_capacity == 0) { + return JSONFailure; + } + new_items = (JSON_Value **)parson_malloc(new_capacity * sizeof(JSON_Value *)); + if (new_items == NULL) { + return JSONFailure; + } + if (array->items != NULL && array->count > 0) { + memcpy(new_items, array->items, array->count * sizeof(JSON_Value *)); + } + parson_free(array->items); + array->items = new_items; + array->capacity = new_capacity; + return JSONSuccess; +} + +static void json_array_free(JSON_Array *array) +{ + size_t i; + for (i = 0; i < array->count; i++) { + json_value_free(array->items[i]); + } + parson_free(array->items); + parson_free(array); +} + +/* JSON Value */ +static JSON_Value *json_value_init_string_no_copy(char *string) +{ + JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONString; + new_value->value.string = string; + return new_value; +} + +/* Parser */ +static JSON_Status skip_quotes(const char **string) +{ + if (**string != '\"') { + return JSONFailure; + } + SKIP_CHAR(string); + while (**string != '\"') { + if (**string == '\0') { + return JSONFailure; + } else if (**string == '\\') { + SKIP_CHAR(string); + if (**string == '\0') { + return JSONFailure; + } + } + SKIP_CHAR(string); + } + SKIP_CHAR(string); + return JSONSuccess; +} + +static int parse_utf16(const char **unprocessed, char **processed) +{ + unsigned int cp, lead, trail; + int parse_succeeded = 0; + char *processed_ptr = *processed; + const char *unprocessed_ptr = *unprocessed; + unprocessed_ptr++; /* skips u */ + parse_succeeded = parse_utf16_hex(unprocessed_ptr, &cp); + if (!parse_succeeded) { + return JSONFailure; + } + if (cp < 0x80) { + processed_ptr[0] = (char)cp; /* 0xxxxxxx */ + } else if (cp < 0x800) { + processed_ptr[0] = (char)(((cp >> 6) & 0x1F) | 0xC0); /* 110xxxxx */ + processed_ptr[1] = (char)(((cp)&0x3F) | 0x80); /* 10xxxxxx */ + processed_ptr += 1; + } else if (cp < 0xD800 || cp > 0xDFFF) { + processed_ptr[0] = (char)(((cp >> 12) & 0x0F) | 0xE0); /* 1110xxxx */ + processed_ptr[1] = (char)(((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */ + processed_ptr[2] = (char)(((cp)&0x3F) | 0x80); /* 10xxxxxx */ + processed_ptr += 2; + } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */ + lead = cp; + unprocessed_ptr += + 4; /* should always be within the buffer, otherwise previous sscanf would fail */ + if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u') { + return JSONFailure; + } + parse_succeeded = parse_utf16_hex(unprocessed_ptr, &trail); + if (!parse_succeeded || trail < 0xDC00 || + trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */ + return JSONFailure; + } + cp = ((((lead - 0xD800) & 0x3FF) << 10) | ((trail - 0xDC00) & 0x3FF)) + 0x010000; + processed_ptr[0] = (char)((((cp >> 18) & 0x07) | 0xF0)); /* 11110xxx */ + processed_ptr[1] = (char)((((cp >> 12) & 0x3F) | 0x80)); /* 10xxxxxx */ + processed_ptr[2] = (char)((((cp >> 6) & 0x3F) | 0x80)); /* 10xxxxxx */ + processed_ptr[3] = (char)((((cp)&0x3F) | 0x80)); /* 10xxxxxx */ + processed_ptr += 3; + } else { /* trail surrogate before lead surrogate */ + return JSONFailure; + } + unprocessed_ptr += 3; + *processed = processed_ptr; + *unprocessed = unprocessed_ptr; + return JSONSuccess; +} + +/* Copies and processes passed string up to supplied length. +Example: "\u006Corem ipsum" -> lorem ipsum */ +static char *process_string(const char *input, size_t len) +{ + const char *input_ptr = input; + size_t initial_size = (len + 1) * sizeof(char); + size_t final_size = 0; + char *output = NULL, *output_ptr = NULL, *resized_output = NULL; + output = (char *)parson_malloc(initial_size); + if (output == NULL) { + goto error; + } + output_ptr = output; + while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) { + if (*input_ptr == '\\') { + input_ptr++; + switch (*input_ptr) { + case '\"': + *output_ptr = '\"'; + break; + case '\\': + *output_ptr = '\\'; + break; + case '/': + *output_ptr = '/'; + break; + case 'b': + *output_ptr = '\b'; + break; + case 'f': + *output_ptr = '\f'; + break; + case 'n': + *output_ptr = '\n'; + break; + case 'r': + *output_ptr = '\r'; + break; + case 't': + *output_ptr = '\t'; + break; + case 'u': + if (parse_utf16(&input_ptr, &output_ptr) == JSONFailure) { + goto error; + } + break; + default: + goto error; + } + } else if ((unsigned char)*input_ptr < 0x20) { + goto error; /* 0x00-0x19 are invalid characters for json string + (http://www.ietf.org/rfc/rfc4627.txt) */ + } else { + *output_ptr = *input_ptr; + } + output_ptr++; + input_ptr++; + } + *output_ptr = '\0'; + /* resize to new length */ + final_size = (size_t)(output_ptr - output) + 1; + /* todo: don't resize if final_size == initial_size */ + resized_output = (char *)parson_malloc(final_size); + if (resized_output == NULL) { + goto error; + } + memcpy(resized_output, output, final_size); + parson_free(output); + return resized_output; +error: + parson_free(output); + return NULL; +} + +/* Return processed contents of a string between quotes and + skips passed argument to a matching quote. */ +static char *get_quoted_string(const char **string) +{ + const char *string_start = *string; + size_t string_len = 0; + JSON_Status status = skip_quotes(string); + if (status != JSONSuccess) { + return NULL; + } + string_len = (size_t)(*string - string_start - 2); /* length without quotes */ + return process_string(string_start + 1, string_len); +} + +static JSON_Value *parse_value(const char **string, size_t nesting) +{ + if (nesting > MAX_NESTING) { + return NULL; + } + SKIP_WHITESPACES(string); + switch (**string) { + case '{': + return parse_object_value(string, nesting + 1); + case '[': + return parse_array_value(string, nesting + 1); + case '\"': + return parse_string_value(string); + case 'f': + case 't': + return parse_boolean_value(string); + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return parse_number_value(string); + case 'n': + return parse_null_value(string); + default: + return NULL; + } +} + +static JSON_Value *parse_object_value(const char **string, size_t nesting) +{ + JSON_Value *output_value = NULL, *new_value = NULL; + JSON_Object *output_object = NULL; + char *new_key = NULL; + output_value = json_value_init_object(); + if (output_value == NULL) { + return NULL; + } + if (**string != '{') { + json_value_free(output_value); + return NULL; + } + output_object = json_value_get_object(output_value); + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + if (**string == '}') { /* empty object */ + SKIP_CHAR(string); + return output_value; + } + while (**string != '\0') { + new_key = get_quoted_string(string); + if (new_key == NULL) { + json_value_free(output_value); + return NULL; + } + SKIP_WHITESPACES(string); + if (**string != ':') { + parson_free(new_key); + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + new_value = parse_value(string, nesting); + if (new_value == NULL) { + parson_free(new_key); + json_value_free(output_value); + return NULL; + } + if (json_object_add(output_object, new_key, new_value) == JSONFailure) { + parson_free(new_key); + json_value_free(new_value); + json_value_free(output_value); + return NULL; + } + parson_free(new_key); + SKIP_WHITESPACES(string); + if (**string != ',') { + break; + } + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + } + SKIP_WHITESPACES(string); + if (**string != '}' || /* Trim object after parsing is over */ + json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + return output_value; +} + +static JSON_Value *parse_array_value(const char **string, size_t nesting) +{ + JSON_Value *output_value = NULL, *new_array_value = NULL; + JSON_Array *output_array = NULL; + output_value = json_value_init_array(); + if (output_value == NULL) { + return NULL; + } + if (**string != '[') { + json_value_free(output_value); + return NULL; + } + output_array = json_value_get_array(output_value); + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + if (**string == ']') { /* empty array */ + SKIP_CHAR(string); + return output_value; + } + while (**string != '\0') { + new_array_value = parse_value(string, nesting); + if (new_array_value == NULL) { + json_value_free(output_value); + return NULL; + } + if (json_array_add(output_array, new_array_value) == JSONFailure) { + json_value_free(new_array_value); + json_value_free(output_value); + return NULL; + } + SKIP_WHITESPACES(string); + if (**string != ',') { + break; + } + SKIP_CHAR(string); + SKIP_WHITESPACES(string); + } + SKIP_WHITESPACES(string); + if (**string != ']' || /* Trim array after parsing is over */ + json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) { + json_value_free(output_value); + return NULL; + } + SKIP_CHAR(string); + return output_value; +} + +static JSON_Value *parse_string_value(const char **string) +{ + JSON_Value *value = NULL; + char *new_string = get_quoted_string(string); + if (new_string == NULL) { + return NULL; + } + value = json_value_init_string_no_copy(new_string); + if (value == NULL) { + parson_free(new_string); + return NULL; + } + return value; +} + +static JSON_Value *parse_boolean_value(const char **string) +{ + size_t true_token_size = SIZEOF_TOKEN("true"); + size_t false_token_size = SIZEOF_TOKEN("false"); + if (strncmp("true", *string, true_token_size) == 0) { + *string += true_token_size; + return json_value_init_boolean(1); + } else if (strncmp("false", *string, false_token_size) == 0) { + *string += false_token_size; + return json_value_init_boolean(0); + } + return NULL; +} + +static JSON_Value *parse_number_value(const char **string) +{ + char *end; + double number = 0; + errno = 0; + number = strtod(*string, &end); + if (errno || !is_decimal(*string, (size_t)(end - *string))) { + return NULL; + } + *string = end; + return json_value_init_number(number); +} + +static JSON_Value *parse_null_value(const char **string) +{ + size_t token_size = SIZEOF_TOKEN("null"); + if (strncmp("null", *string, token_size) == 0) { + *string += token_size; + return json_value_init_null(); + } + return NULL; +} + +/* Serialization */ +#define APPEND_STRING(str) \ + do { \ + written = append_string(buf, (str)); \ + if (written < 0) { \ + return -1; \ + } \ + if (buf != NULL) { \ + buf += written; \ + } \ + written_total += written; \ + } while (0) + +#define APPEND_INDENT(level) \ + do { \ + written = append_indent(buf, (level)); \ + if (written < 0) { \ + return -1; \ + } \ + if (buf != NULL) { \ + buf += written; \ + } \ + written_total += written; \ + } while (0) + +static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, + char *num_buf) +{ + const char *key = NULL, *string = NULL; + JSON_Value *temp_value = NULL; + JSON_Array *array = NULL; + JSON_Object *object = NULL; + size_t i = 0, count = 0; + double num = 0.0; + int written = -1, written_total = 0; + + switch (json_value_get_type(value)) { + case JSONArray: + array = json_value_get_array(value); + count = json_array_get_count(array); + APPEND_STRING("["); + if (count > 0 && is_pretty) { + APPEND_STRING("\n"); + } + for (i = 0; i < count; i++) { + if (is_pretty) { + APPEND_INDENT(level + 1); + } + temp_value = json_array_get_value(array, i); + written = json_serialize_to_buffer_r(temp_value, buf, level + 1, is_pretty, num_buf); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + if (i < (count - 1)) { + APPEND_STRING(","); + } + if (is_pretty) { + APPEND_STRING("\n"); + } + } + if (count > 0 && is_pretty) { + APPEND_INDENT(level); + } + APPEND_STRING("]"); + return written_total; + case JSONObject: + object = json_value_get_object(value); + count = json_object_get_count(object); + APPEND_STRING("{"); + if (count > 0 && is_pretty) { + APPEND_STRING("\n"); + } + for (i = 0; i < count; i++) { + key = json_object_get_name(object, i); + if (key == NULL) { + return -1; + } + if (is_pretty) { + APPEND_INDENT(level + 1); + } + written = json_serialize_string(key, buf); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + APPEND_STRING(":"); + if (is_pretty) { + APPEND_STRING(" "); + } + temp_value = json_object_get_value(object, key); + written = json_serialize_to_buffer_r(temp_value, buf, level + 1, is_pretty, num_buf); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + if (i < (count - 1)) { + APPEND_STRING(","); + } + if (is_pretty) { + APPEND_STRING("\n"); + } + } + if (count > 0 && is_pretty) { + APPEND_INDENT(level); + } + APPEND_STRING("}"); + return written_total; + case JSONString: + string = json_value_get_string(value); + if (string == NULL) { + return -1; + } + written = json_serialize_string(string, buf); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + return written_total; + case JSONBoolean: + if (json_value_get_boolean(value)) { + APPEND_STRING("true"); + } else { + APPEND_STRING("false"); + } + return written_total; + case JSONNumber: + num = json_value_get_number(value); + if (buf != NULL) { + num_buf = buf; + } + written = sprintf(num_buf, FLOAT_FORMAT, num); + if (written < 0) { + return -1; + } + if (buf != NULL) { + buf += written; + } + written_total += written; + return written_total; + case JSONNull: + APPEND_STRING("null"); + return written_total; + case JSONError: + return -1; + default: + return -1; + } +} + +static int json_serialize_string(const char *string, char *buf) +{ + size_t i = 0, len = strlen(string); + char c = '\0'; + int written = -1, written_total = 0; + APPEND_STRING("\""); + for (i = 0; i < len; i++) { + c = string[i]; + switch (c) { + case '\"': + APPEND_STRING("\\\""); + break; + case '\\': + APPEND_STRING("\\\\"); + break; + case '/': + APPEND_STRING("\\/"); + break; /* to make json embeddable in xml\/html */ + case '\b': + APPEND_STRING("\\b"); + break; + case '\f': + APPEND_STRING("\\f"); + break; + case '\n': + APPEND_STRING("\\n"); + break; + case '\r': + APPEND_STRING("\\r"); + break; + case '\t': + APPEND_STRING("\\t"); + break; + case '\x00': + APPEND_STRING("\\u0000"); + break; + case '\x01': + APPEND_STRING("\\u0001"); + break; + case '\x02': + APPEND_STRING("\\u0002"); + break; + case '\x03': + APPEND_STRING("\\u0003"); + break; + case '\x04': + APPEND_STRING("\\u0004"); + break; + case '\x05': + APPEND_STRING("\\u0005"); + break; + case '\x06': + APPEND_STRING("\\u0006"); + break; + case '\x07': + APPEND_STRING("\\u0007"); + break; + /* '\x08' duplicate: '\b' */ + /* '\x09' duplicate: '\t' */ + /* '\x0a' duplicate: '\n' */ + case '\x0b': + APPEND_STRING("\\u000b"); + break; + /* '\x0c' duplicate: '\f' */ + /* '\x0d' duplicate: '\r' */ + case '\x0e': + APPEND_STRING("\\u000e"); + break; + case '\x0f': + APPEND_STRING("\\u000f"); + break; + case '\x10': + APPEND_STRING("\\u0010"); + break; + case '\x11': + APPEND_STRING("\\u0011"); + break; + case '\x12': + APPEND_STRING("\\u0012"); + break; + case '\x13': + APPEND_STRING("\\u0013"); + break; + case '\x14': + APPEND_STRING("\\u0014"); + break; + case '\x15': + APPEND_STRING("\\u0015"); + break; + case '\x16': + APPEND_STRING("\\u0016"); + break; + case '\x17': + APPEND_STRING("\\u0017"); + break; + case '\x18': + APPEND_STRING("\\u0018"); + break; + case '\x19': + APPEND_STRING("\\u0019"); + break; + case '\x1a': + APPEND_STRING("\\u001a"); + break; + case '\x1b': + APPEND_STRING("\\u001b"); + break; + case '\x1c': + APPEND_STRING("\\u001c"); + break; + case '\x1d': + APPEND_STRING("\\u001d"); + break; + case '\x1e': + APPEND_STRING("\\u001e"); + break; + case '\x1f': + APPEND_STRING("\\u001f"); + break; + default: + if (buf != NULL) { + buf[0] = c; + buf += 1; + } + written_total += 1; + break; + } + } + APPEND_STRING("\""); + return written_total; +} + +static int append_indent(char *buf, int level) +{ + int i; + int written = -1, written_total = 0; + for (i = 0; i < level; i++) { + APPEND_STRING(" "); + } + return written_total; +} + +static int append_string(char *buf, const char *string) +{ + if (buf == NULL) { + return (int)strlen(string); + } + return sprintf(buf, "%s", string); +} + +#undef APPEND_STRING +#undef APPEND_INDENT + +/* Parser API */ +JSON_Value *json_parse_string(const char *string) +{ + if (string == NULL) { + return NULL; + } + if (string[0] == '\xEF' && string[1] == '\xBB' && string[2] == '\xBF') { + string = string + 3; /* Support for UTF-8 BOM */ + } + return parse_value((const char **)&string, 0); +} + +JSON_Value *json_parse_string_with_comments(const char *string) +{ + JSON_Value *result = NULL; + char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL; + string_mutable_copy = parson_strdup(string); + if (string_mutable_copy == NULL) { + return NULL; + } + remove_comments(string_mutable_copy, "/*", "*/"); + remove_comments(string_mutable_copy, "//", "\n"); + string_mutable_copy_ptr = string_mutable_copy; + result = parse_value((const char **)&string_mutable_copy_ptr, 0); + parson_free(string_mutable_copy); + return result; +} + +/* JSON Object API */ + +JSON_Value *json_object_get_value(const JSON_Object *object, const char *name) +{ + if (object == NULL || name == NULL) { + return NULL; + } + return json_object_getn_value(object, name, strlen(name)); +} + +const char *json_object_get_string(const JSON_Object *object, const char *name) +{ + return json_value_get_string(json_object_get_value(object, name)); +} + +double json_object_get_number(const JSON_Object *object, const char *name) +{ + return json_value_get_number(json_object_get_value(object, name)); +} + +JSON_Object *json_object_get_object(const JSON_Object *object, const char *name) +{ + return json_value_get_object(json_object_get_value(object, name)); +} + +JSON_Array *json_object_get_array(const JSON_Object *object, const char *name) +{ + return json_value_get_array(json_object_get_value(object, name)); +} + +int json_object_get_boolean(const JSON_Object *object, const char *name) +{ + return json_value_get_boolean(json_object_get_value(object, name)); +} + +JSON_Value *json_object_dotget_value(const JSON_Object *object, const char *name) +{ + const char *dot_position = strchr(name, '.'); + if (!dot_position) { + return json_object_get_value(object, name); + } + object = + json_value_get_object(json_object_getn_value(object, name, (size_t)(dot_position - name))); + return json_object_dotget_value(object, dot_position + 1); +} + +const char *json_object_dotget_string(const JSON_Object *object, const char *name) +{ + return json_value_get_string(json_object_dotget_value(object, name)); +} + +double json_object_dotget_number(const JSON_Object *object, const char *name) +{ + return json_value_get_number(json_object_dotget_value(object, name)); +} + +JSON_Object *json_object_dotget_object(const JSON_Object *object, const char *name) +{ + return json_value_get_object(json_object_dotget_value(object, name)); +} + +JSON_Array *json_object_dotget_array(const JSON_Object *object, const char *name) +{ + return json_value_get_array(json_object_dotget_value(object, name)); +} + +int json_object_dotget_boolean(const JSON_Object *object, const char *name) +{ + return json_value_get_boolean(json_object_dotget_value(object, name)); +} + +size_t json_object_get_count(const JSON_Object *object) +{ + return object ? object->count : 0; +} + +const char *json_object_get_name(const JSON_Object *object, size_t index) +{ + if (object == NULL || index >= json_object_get_count(object)) { + return NULL; + } + return object->names[index]; +} + +JSON_Value *json_object_get_value_at(const JSON_Object *object, size_t index) +{ + if (object == NULL || index >= json_object_get_count(object)) { + return NULL; + } + return object->values[index]; +} + +JSON_Value *json_object_get_wrapping_value(const JSON_Object *object) +{ + return object->wrapping_value; +} + +int json_object_has_value(const JSON_Object *object, const char *name) +{ + return json_object_get_value(object, name) != NULL; +} + +int json_object_has_value_of_type(const JSON_Object *object, const char *name, JSON_Value_Type type) +{ + JSON_Value *val = json_object_get_value(object, name); + return val != NULL && json_value_get_type(val) == type; +} + +int json_object_dothas_value(const JSON_Object *object, const char *name) +{ + return json_object_dotget_value(object, name) != NULL; +} + +int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, + JSON_Value_Type type) +{ + JSON_Value *val = json_object_dotget_value(object, name); + return val != NULL && json_value_get_type(val) == type; +} + +/* JSON Array API */ +JSON_Value *json_array_get_value(const JSON_Array *array, size_t index) +{ + if (array == NULL || index >= json_array_get_count(array)) { + return NULL; + } + return array->items[index]; +} + +const char *json_array_get_string(const JSON_Array *array, size_t index) +{ + return json_value_get_string(json_array_get_value(array, index)); +} + +double json_array_get_number(const JSON_Array *array, size_t index) +{ + return json_value_get_number(json_array_get_value(array, index)); +} + +JSON_Object *json_array_get_object(const JSON_Array *array, size_t index) +{ + return json_value_get_object(json_array_get_value(array, index)); +} + +JSON_Array *json_array_get_array(const JSON_Array *array, size_t index) +{ + return json_value_get_array(json_array_get_value(array, index)); +} + +int json_array_get_boolean(const JSON_Array *array, size_t index) +{ + return json_value_get_boolean(json_array_get_value(array, index)); +} + +size_t json_array_get_count(const JSON_Array *array) +{ + return array ? array->count : 0; +} + +JSON_Value *json_array_get_wrapping_value(const JSON_Array *array) +{ + return array->wrapping_value; +} + +/* JSON Value API */ +JSON_Value_Type json_value_get_type(const JSON_Value *value) +{ + return value ? value->type : JSONError; +} + +JSON_Object *json_value_get_object(const JSON_Value *value) +{ + return json_value_get_type(value) == JSONObject ? value->value.object : NULL; +} + +JSON_Array *json_value_get_array(const JSON_Value *value) +{ + return json_value_get_type(value) == JSONArray ? value->value.array : NULL; +} + +const char *json_value_get_string(const JSON_Value *value) +{ + return json_value_get_type(value) == JSONString ? value->value.string : NULL; +} + +double json_value_get_number(const JSON_Value *value) +{ + return json_value_get_type(value) == JSONNumber ? value->value.number : 0; +} + +int json_value_get_boolean(const JSON_Value *value) +{ + return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1; +} + +JSON_Value *json_value_get_parent(const JSON_Value *value) +{ + return value ? value->parent : NULL; +} + +void json_value_free(JSON_Value *value) +{ + switch (json_value_get_type(value)) { + case JSONObject: + json_object_free(value->value.object); + break; + case JSONString: + parson_free(value->value.string); + break; + case JSONArray: + json_array_free(value->value.array); + break; + default: + break; + } + parson_free(value); +} + +JSON_Value *json_value_init_object(void) +{ + JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONObject; + new_value->value.object = json_object_init(new_value); + if (!new_value->value.object) { + parson_free(new_value); + return NULL; + } + return new_value; +} + +JSON_Value *json_value_init_array(void) +{ + JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONArray; + new_value->value.array = json_array_init(new_value); + if (!new_value->value.array) { + parson_free(new_value); + return NULL; + } + return new_value; +} + +JSON_Value *json_value_init_string(const char *string) +{ + char *copy = NULL; + JSON_Value *value; + size_t string_len = 0; + if (string == NULL) { + return NULL; + } + string_len = strlen(string); + if (!is_valid_utf8(string, string_len)) { + return NULL; + } + copy = parson_strndup(string, string_len); + if (copy == NULL) { + return NULL; + } + value = json_value_init_string_no_copy(copy); + if (value == NULL) { + parson_free(copy); + } + return value; +} + +JSON_Value *json_value_init_number(double number) +{ + JSON_Value *new_value = NULL; + if ((number * 0.0) != 0.0) { /* nan and inf test */ + return NULL; + } + new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); + if (new_value == NULL) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONNumber; + new_value->value.number = number; + return new_value; +} + +JSON_Value *json_value_init_boolean(int boolean) +{ + JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONBoolean; + new_value->value.boolean = boolean ? 1 : 0; + return new_value; +} + +JSON_Value *json_value_init_null(void) +{ + JSON_Value *new_value = (JSON_Value *)parson_malloc(sizeof(JSON_Value)); + if (!new_value) { + return NULL; + } + new_value->parent = NULL; + new_value->type = JSONNull; + return new_value; +} + +JSON_Value *json_value_deep_copy(const JSON_Value *value) +{ + size_t i = 0; + JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL; + const char *temp_string = NULL, *temp_key = NULL; + char *temp_string_copy = NULL; + JSON_Array *temp_array = NULL, *temp_array_copy = NULL; + JSON_Object *temp_object = NULL, *temp_object_copy = NULL; + + switch (json_value_get_type(value)) { + case JSONArray: + temp_array = json_value_get_array(value); + return_value = json_value_init_array(); + if (return_value == NULL) { + return NULL; + } + temp_array_copy = json_value_get_array(return_value); + for (i = 0; i < json_array_get_count(temp_array); i++) { + temp_value = json_array_get_value(temp_array, i); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONObject: + temp_object = json_value_get_object(value); + return_value = json_value_init_object(); + if (return_value == NULL) { + return NULL; + } + temp_object_copy = json_value_get_object(return_value); + for (i = 0; i < json_object_get_count(temp_object); i++) { + temp_key = json_object_get_name(temp_object, i); + temp_value = json_object_get_value(temp_object, temp_key); + temp_value_copy = json_value_deep_copy(temp_value); + if (temp_value_copy == NULL) { + json_value_free(return_value); + return NULL; + } + if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) { + json_value_free(return_value); + json_value_free(temp_value_copy); + return NULL; + } + } + return return_value; + case JSONBoolean: + return json_value_init_boolean(json_value_get_boolean(value)); + case JSONNumber: + return json_value_init_number(json_value_get_number(value)); + case JSONString: + temp_string = json_value_get_string(value); + if (temp_string == NULL) { + return NULL; + } + temp_string_copy = parson_strdup(temp_string); + if (temp_string_copy == NULL) { + return NULL; + } + return_value = json_value_init_string_no_copy(temp_string_copy); + if (return_value == NULL) { + parson_free(temp_string_copy); + } + return return_value; + case JSONNull: + return json_value_init_null(); + case JSONError: + return NULL; + default: + return NULL; + } +} + +size_t json_serialization_size(const JSON_Value *value) +{ + char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do + it only once */ + int res = json_serialize_to_buffer_r(value, NULL, 0, 0, num_buf); + return res < 0 ? 0 : (size_t)(res + 1); +} + +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) +{ + int written = -1; + size_t needed_size_in_bytes = json_serialization_size(value); + if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { + return JSONFailure; + } + written = json_serialize_to_buffer_r(value, buf, 0, 0, NULL); + if (written < 0) { + return JSONFailure; + } + return JSONSuccess; +} + +char *json_serialize_to_string(const JSON_Value *value) +{ + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size(value); + char *buf = NULL; + if (buf_size_bytes == 0) { + return NULL; + } + buf = (char *)parson_malloc(buf_size_bytes); + if (buf == NULL) { + return NULL; + } + serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +size_t json_serialization_size_pretty(const JSON_Value *value) +{ + char num_buf[NUM_BUF_SIZE]; /* recursively allocating buffer on stack is a bad idea, so let's do + it only once */ + int res = json_serialize_to_buffer_r(value, NULL, 0, 1, num_buf); + return res < 0 ? 0 : (size_t)(res + 1); +} + +JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, + size_t buf_size_in_bytes) +{ + int written = -1; + size_t needed_size_in_bytes = json_serialization_size_pretty(value); + if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) { + return JSONFailure; + } + written = json_serialize_to_buffer_r(value, buf, 0, 1, NULL); + if (written < 0) { + return JSONFailure; + } + return JSONSuccess; +} + +char *json_serialize_to_string_pretty(const JSON_Value *value) +{ + JSON_Status serialization_result = JSONFailure; + size_t buf_size_bytes = json_serialization_size_pretty(value); + char *buf = NULL; + if (buf_size_bytes == 0) { + return NULL; + } + buf = (char *)parson_malloc(buf_size_bytes); + if (buf == NULL) { + return NULL; + } + serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes); + if (serialization_result == JSONFailure) { + json_free_serialized_string(buf); + return NULL; + } + return buf; +} + +void json_free_serialized_string(char *string) +{ + parson_free(string); +} + +JSON_Status json_array_remove(JSON_Array *array, size_t ix) +{ + size_t to_move_bytes = 0; + if (array == NULL || ix >= json_array_get_count(array)) { + return JSONFailure; + } + json_value_free(json_array_get_value(array, ix)); + to_move_bytes = (json_array_get_count(array) - 1 - ix) * sizeof(JSON_Value *); + memmove(array->items + ix, array->items + ix + 1, to_move_bytes); + array->count -= 1; + return JSONSuccess; +} + +JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) +{ + if (array == NULL || value == NULL || value->parent != NULL || + ix >= json_array_get_count(array)) { + return JSONFailure; + } + json_value_free(json_array_get_value(array, ix)); + value->parent = json_array_get_wrapping_value(array); + array->items[ix] = value; + return JSONSuccess; +} + +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char *string) +{ + JSON_Value *value = json_value_init_string(string); + if (value == NULL) { + return JSONFailure; + } + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) +{ + JSON_Value *value = json_value_init_number(number); + if (value == NULL) { + return JSONFailure; + } + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) +{ + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) { + return JSONFailure; + } + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_replace_null(JSON_Array *array, size_t i) +{ + JSON_Value *value = json_value_init_null(); + if (value == NULL) { + return JSONFailure; + } + if (json_array_replace_value(array, i, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_clear(JSON_Array *array) +{ + size_t i = 0; + if (array == NULL) { + return JSONFailure; + } + for (i = 0; i < json_array_get_count(array); i++) { + json_value_free(json_array_get_value(array, i)); + } + array->count = 0; + return JSONSuccess; +} + +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) +{ + if (array == NULL || value == NULL || value->parent != NULL) { + return JSONFailure; + } + return json_array_add(array, value); +} + +JSON_Status json_array_append_string(JSON_Array *array, const char *string) +{ + JSON_Value *value = json_value_init_string(string); + if (value == NULL) { + return JSONFailure; + } + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_number(JSON_Array *array, double number) +{ + JSON_Value *value = json_value_init_number(number); + if (value == NULL) { + return JSONFailure; + } + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) +{ + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) { + return JSONFailure; + } + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_array_append_null(JSON_Array *array) +{ + JSON_Value *value = json_value_init_null(); + if (value == NULL) { + return JSONFailure; + } + if (json_array_append_value(array, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) +{ + size_t i = 0; + JSON_Value *old_value; + if (object == NULL || name == NULL || value == NULL || value->parent != NULL) { + return JSONFailure; + } + old_value = json_object_get_value(object, name); + if (old_value != NULL) { /* free and overwrite old value */ + json_value_free(old_value); + for (i = 0; i < json_object_get_count(object); i++) { + if (strcmp(object->names[i], name) == 0) { + value->parent = json_object_get_wrapping_value(object); + object->values[i] = value; + return JSONSuccess; + } + } + } + /* add new key value pair */ + return json_object_add(object, name, value); +} + +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) +{ + return json_object_set_value(object, name, json_value_init_string(string)); +} + +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) +{ + return json_object_set_value(object, name, json_value_init_number(number)); +} + +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) +{ + return json_object_set_value(object, name, json_value_init_boolean(boolean)); +} + +JSON_Status json_object_set_null(JSON_Object *object, const char *name) +{ + return json_object_set_value(object, name, json_value_init_null()); +} + +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) +{ + const char *dot_pos = NULL; + JSON_Value *temp_value = NULL, *new_value = NULL; + JSON_Object *temp_object = NULL, *new_object = NULL; + JSON_Status status = JSONFailure; + size_t name_len = 0; + if (object == NULL || name == NULL || value == NULL) { + return JSONFailure; + } + dot_pos = strchr(name, '.'); + if (dot_pos == NULL) { + return json_object_set_value(object, name, value); + } + name_len = (size_t)(dot_pos - name); + temp_value = json_object_getn_value(object, name, name_len); + if (temp_value) { + /* Don't overwrite existing non-object (unlike json_object_set_value, but it shouldn't be + * changed at this point) */ + if (json_value_get_type(temp_value) != JSONObject) { + return JSONFailure; + } + temp_object = json_value_get_object(temp_value); + return json_object_dotset_value(temp_object, dot_pos + 1, value); + } + new_value = json_value_init_object(); + if (new_value == NULL) { + return JSONFailure; + } + new_object = json_value_get_object(new_value); + status = json_object_dotset_value(new_object, dot_pos + 1, value); + if (status != JSONSuccess) { + json_value_free(new_value); + return JSONFailure; + } + status = json_object_addn(object, name, name_len, new_value); + if (status != JSONSuccess) { + json_object_dotremove_internal(new_object, dot_pos + 1, 0); + json_value_free(new_value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) +{ + JSON_Value *value = json_value_init_string(string); + if (value == NULL) { + return JSONFailure; + } + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) +{ + JSON_Value *value = json_value_init_number(number); + if (value == NULL) { + return JSONFailure; + } + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) +{ + JSON_Value *value = json_value_init_boolean(boolean); + if (value == NULL) { + return JSONFailure; + } + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) +{ + JSON_Value *value = json_value_init_null(); + if (value == NULL) { + return JSONFailure; + } + if (json_object_dotset_value(object, name, value) == JSONFailure) { + json_value_free(value); + return JSONFailure; + } + return JSONSuccess; +} + +JSON_Status json_object_remove(JSON_Object *object, const char *name) +{ + return json_object_remove_internal(object, name, 1); +} + +JSON_Status json_object_dotremove(JSON_Object *object, const char *name) +{ + return json_object_dotremove_internal(object, name, 1); +} + +JSON_Status json_object_clear(JSON_Object *object) +{ + size_t i = 0; + if (object == NULL) { + return JSONFailure; + } + for (i = 0; i < json_object_get_count(object); i++) { + parson_free(object->names[i]); + json_value_free(object->values[i]); + } + object->count = 0; + return JSONSuccess; +} + +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) +{ + JSON_Value *temp_schema_value = NULL, *temp_value = NULL; + JSON_Array *schema_array = NULL, *value_array = NULL; + JSON_Object *schema_object = NULL, *value_object = NULL; + JSON_Value_Type schema_type = JSONError, value_type = JSONError; + const char *key = NULL; + size_t i = 0, count = 0; + if (schema == NULL || value == NULL) { + return JSONFailure; + } + schema_type = json_value_get_type(schema); + value_type = json_value_get_type(value); + if (schema_type != value_type && schema_type != JSONNull) { /* null represents all values */ + return JSONFailure; + } + switch (schema_type) { + case JSONArray: + schema_array = json_value_get_array(schema); + value_array = json_value_get_array(value); + count = json_array_get_count(schema_array); + if (count == 0) { + return JSONSuccess; /* Empty array allows all types */ + } + /* Get first value from array, rest is ignored */ + temp_schema_value = json_array_get_value(schema_array, 0); + for (i = 0; i < json_array_get_count(value_array); i++) { + temp_value = json_array_get_value(value_array, i); + if (json_validate(temp_schema_value, temp_value) == JSONFailure) { + return JSONFailure; + } + } + return JSONSuccess; + case JSONObject: + schema_object = json_value_get_object(schema); + value_object = json_value_get_object(value); + count = json_object_get_count(schema_object); + if (count == 0) { + return JSONSuccess; /* Empty object allows all objects */ + } else if (json_object_get_count(value_object) < count) { + return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */ + } + for (i = 0; i < count; i++) { + key = json_object_get_name(schema_object, i); + temp_schema_value = json_object_get_value(schema_object, key); + temp_value = json_object_get_value(value_object, key); + if (temp_value == NULL) { + return JSONFailure; + } + if (json_validate(temp_schema_value, temp_value) == JSONFailure) { + return JSONFailure; + } + } + return JSONSuccess; + case JSONString: + case JSONNumber: + case JSONBoolean: + case JSONNull: + return JSONSuccess; /* equality already tested before switch */ + case JSONError: + default: + return JSONFailure; + } +} + +int json_value_equals(const JSON_Value *a, const JSON_Value *b) +{ + JSON_Object *a_object = NULL, *b_object = NULL; + JSON_Array *a_array = NULL, *b_array = NULL; + const char *a_string = NULL, *b_string = NULL; + const char *key = NULL; + size_t a_count = 0, b_count = 0, i = 0; + JSON_Value_Type a_type, b_type; + a_type = json_value_get_type(a); + b_type = json_value_get_type(b); + if (a_type != b_type) { + return 0; + } + switch (a_type) { + case JSONArray: + a_array = json_value_get_array(a); + b_array = json_value_get_array(b); + a_count = json_array_get_count(a_array); + b_count = json_array_get_count(b_array); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + if (!json_value_equals(json_array_get_value(a_array, i), + json_array_get_value(b_array, i))) { + return 0; + } + } + return 1; + case JSONObject: + a_object = json_value_get_object(a); + b_object = json_value_get_object(b); + a_count = json_object_get_count(a_object); + b_count = json_object_get_count(b_object); + if (a_count != b_count) { + return 0; + } + for (i = 0; i < a_count; i++) { + key = json_object_get_name(a_object, i); + if (!json_value_equals(json_object_get_value(a_object, key), + json_object_get_value(b_object, key))) { + return 0; + } + } + return 1; + case JSONString: + a_string = json_value_get_string(a); + b_string = json_value_get_string(b); + if (a_string == NULL || b_string == NULL) { + return 0; /* shouldn't happen */ + } + return strcmp(a_string, b_string) == 0; + case JSONBoolean: + return json_value_get_boolean(a) == json_value_get_boolean(b); + case JSONNumber: + return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */ + case JSONError: + return 1; + case JSONNull: + return 1; + default: + return 1; + } +} + +JSON_Value_Type json_type(const JSON_Value *value) +{ + return json_value_get_type(value); +} + +JSON_Object *json_object(const JSON_Value *value) +{ + return json_value_get_object(value); +} + +JSON_Array *json_array(const JSON_Value *value) +{ + return json_value_get_array(value); +} + +const char *json_string(const JSON_Value *value) +{ + return json_value_get_string(value); +} + +double json_number(const JSON_Value *value) +{ + return json_value_get_number(value); +} + +int json_boolean(const JSON_Value *value) +{ + return json_value_get_boolean(value); +} + +void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) +{ + parson_malloc = malloc_fun; + parson_free = free_fun; +} diff --git a/parson.h b/parson.h new file mode 100644 index 0000000..2033acb --- /dev/null +++ b/parson.h @@ -0,0 +1,234 @@ +/* + This source code comes from Git repository + https://github.com/kgabis/parson at commit id 4f3eaa6 + Patched to avoid any usage of fopen(), and removed implicit + cast warnings by making them explicit. +*/ + +/* + Parson ( http://kgabis.github.com/parson/ ) + Copyright (c) 2012 - 2017 Krzysztof Gabis + + 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. +*/ + +#ifndef parson_parson_h +#define parson_parson_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* size_t */ + +/* Types and enums */ +typedef struct json_object_t JSON_Object; +typedef struct json_array_t JSON_Array; +typedef struct json_value_t JSON_Value; + +enum json_value_type { + JSONError = -1, + JSONNull = 1, + JSONString = 2, + JSONNumber = 3, + JSONObject = 4, + JSONArray = 5, + JSONBoolean = 6 +}; +typedef int JSON_Value_Type; + +enum json_result_t { JSONSuccess = 0, JSONFailure = -1 }; +typedef int JSON_Status; + +typedef void *(*JSON_Malloc_Function)(size_t); +typedef void (*JSON_Free_Function)(void *); + +/* Call only once, before calling any other function from parson API. If not called, malloc and free + from stdlib will be used for all allocations */ +void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun); + +/* Parses first JSON value in a string, returns NULL in case of error */ +JSON_Value *json_parse_string(const char *string); + +/* Parses first JSON value in a string and ignores comments (/ * * / and //), + returns NULL in case of error */ +JSON_Value *json_parse_string_with_comments(const char *string); + +/* Serialization */ +size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */ +JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes); +char *json_serialize_to_string(const JSON_Value *value); + +/* Pretty serialization */ +size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */ +JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, + size_t buf_size_in_bytes); +char *json_serialize_to_string_pretty(const JSON_Value *value); + +void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and + json_serialize_to_string_pretty */ + +/* Comparing */ +int json_value_equals(const JSON_Value *a, const JSON_Value *b); + +/* Validation + This is *NOT* JSON Schema. It validates json by checking if object have identically + named fields with matching types. + For example schema {"name":"", "age":0} will validate + {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"}, + but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}. + In case of arrays, only first value in schema is checked against all values in tested array. + Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays, + null validates values of every type. + */ +JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value); + +/* + * JSON Object + */ +JSON_Value *json_object_get_value(const JSON_Object *object, const char *name); +const char *json_object_get_string(const JSON_Object *object, const char *name); +JSON_Object *json_object_get_object(const JSON_Object *object, const char *name); +JSON_Array *json_object_get_array(const JSON_Object *object, const char *name); +double json_object_get_number(const JSON_Object *object, const char *name); /* returns 0 on fail */ +int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */ + +/* dotget functions enable addressing values with dot notation in nested objects, + just like in structs or c++/java/c# objects (e.g. objectA.objectB.value). + Because valid names in JSON can contain dots, some values may be inaccessible + this way. */ +JSON_Value *json_object_dotget_value(const JSON_Object *object, const char *name); +const char *json_object_dotget_string(const JSON_Object *object, const char *name); +JSON_Object *json_object_dotget_object(const JSON_Object *object, const char *name); +JSON_Array *json_object_dotget_array(const JSON_Object *object, const char *name); +double json_object_dotget_number(const JSON_Object *object, + const char *name); /* returns 0 on fail */ +int json_object_dotget_boolean(const JSON_Object *object, + const char *name); /* returns -1 on fail */ + +/* Functions to get available names */ +size_t json_object_get_count(const JSON_Object *object); +const char *json_object_get_name(const JSON_Object *object, size_t index); +JSON_Value *json_object_get_value_at(const JSON_Object *object, size_t index); +JSON_Value *json_object_get_wrapping_value(const JSON_Object *object); + +/* Functions to check if object has a value with a specific name. Returned value is 1 if object has + * a value and 0 if it doesn't. dothas functions behave exactly like dotget functions. */ +int json_object_has_value(const JSON_Object *object, const char *name); +int json_object_has_value_of_type(const JSON_Object *object, const char *name, + JSON_Value_Type type); + +int json_object_dothas_value(const JSON_Object *object, const char *name); +int json_object_dothas_value_of_type(const JSON_Object *object, const char *name, + JSON_Value_Type type); + +/* Creates new name-value pair or frees and replaces old value with a new one. + * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_set_null(JSON_Object *object, const char *name); + +/* Works like dotget functions, but creates whole hierarchy if necessary. + * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value); +JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string); +JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number); +JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean); +JSON_Status json_object_dotset_null(JSON_Object *object, const char *name); + +/* Frees and removes name-value pair */ +JSON_Status json_object_remove(JSON_Object *object, const char *name); + +/* Works like dotget function, but removes name-value pair only on exact match. */ +JSON_Status json_object_dotremove(JSON_Object *object, const char *key); + +/* Removes all name-value pairs in object */ +JSON_Status json_object_clear(JSON_Object *object); + +/* + *JSON Array + */ +JSON_Value *json_array_get_value(const JSON_Array *array, size_t index); +const char *json_array_get_string(const JSON_Array *array, size_t index); +JSON_Object *json_array_get_object(const JSON_Array *array, size_t index); +JSON_Array *json_array_get_array(const JSON_Array *array, size_t index); +double json_array_get_number(const JSON_Array *array, size_t index); /* returns 0 on fail */ +int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */ +size_t json_array_get_count(const JSON_Array *array); +JSON_Value *json_array_get_wrapping_value(const JSON_Array *array); + +/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't + * exist. Order of values in array may change during execution. */ +JSON_Status json_array_remove(JSON_Array *array, size_t i); + +/* Frees and removes from array value at given index and replaces it with given one. + * Does nothing and returns JSONFailure if index doesn't exist. + * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value); +JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char *string); +JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number); +JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean); +JSON_Status json_array_replace_null(JSON_Array *array, size_t i); + +/* Frees and removes all values from array */ +JSON_Status json_array_clear(JSON_Array *array); + +/* Appends new value at the end of array. + * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */ +JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value); +JSON_Status json_array_append_string(JSON_Array *array, const char *string); +JSON_Status json_array_append_number(JSON_Array *array, double number); +JSON_Status json_array_append_boolean(JSON_Array *array, int boolean); +JSON_Status json_array_append_null(JSON_Array *array); + +/* + *JSON Value + */ +JSON_Value *json_value_init_object(void); +JSON_Value *json_value_init_array(void); +JSON_Value *json_value_init_string(const char *string); /* copies passed string */ +JSON_Value *json_value_init_number(double number); +JSON_Value *json_value_init_boolean(int boolean); +JSON_Value *json_value_init_null(void); +JSON_Value *json_value_deep_copy(const JSON_Value *value); +void json_value_free(JSON_Value *value); + +JSON_Value_Type json_value_get_type(const JSON_Value *value); +JSON_Object *json_value_get_object(const JSON_Value *value); +JSON_Array *json_value_get_array(const JSON_Value *value); +const char *json_value_get_string(const JSON_Value *value); +double json_value_get_number(const JSON_Value *value); +int json_value_get_boolean(const JSON_Value *value); +JSON_Value *json_value_get_parent(const JSON_Value *value); + +/* Same as above, but shorter */ +JSON_Value_Type json_type(const JSON_Value *value); +JSON_Object *json_object(const JSON_Value *value); +JSON_Array *json_array(const JSON_Value *value); +const char *json_string(const JSON_Value *value); +double json_number(const JSON_Value *value); +int json_boolean(const JSON_Value *value); + +#ifdef __cplusplus +} +#endif + +#endif