Compare commits

..

No commits in common. "testing" and "main" have entirely different histories.

83 changed files with 6677 additions and 5361 deletions

View file

@ -1,2 +0,0 @@
---
Checks: 'clang-diagnostics-*,clang-analyzer-*,linuxkernel-*,modernize-*,readability-*,-readability-magic-numbers,portability-*'

View file

@ -53,8 +53,9 @@ steps:
api_key:
from_secret: gitea_token
base_url: https://git.serguzim.me
title: ${DRONE_TAG}
files:
- build/core
- build/core.ini
when:
event: tag

View file

@ -10,7 +10,3 @@ end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
[*.yml]
indent_style = space
indent_size = 2

1
.envrc
View file

@ -1 +0,0 @@
use nix

8
.gitignore vendored
View file

@ -1,11 +1,7 @@
build/
docs/
tests/testing/
tests/testing_latest/
tests/testing_bak/
include/sql/*.h
emgauwa-core.conf.d
emgauwa-core.sqlite
vgcore.*

View file

@ -1,6 +1,6 @@
cmake_minimum_required (VERSION 3.7)
project(core
VERSION 0.4.5
VERSION 0.3.4
LANGUAGES C)
add_executable(core src/main.c)
@ -8,18 +8,11 @@ add_executable(core src/main.c)
target_link_libraries(core -lsqlite3)
target_link_libraries(core -luuid)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D'__FILENAME__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR}/src/)/,,$(abspath $<))\"'")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra")
set(CMAKE_C_FLAGS "$ENV{CFLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra -ffile-prefix-map=${CMAKE_SOURCE_DIR}/src/=")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
file(GLOB_RECURSE ALL_SOURCE_FILES src/*.c)
add_definitions("-DMG_ENABLE_EXTRA_ERRORS_DESC -DMG_ENABLE_MQTT_BROKER")
aux_source_directory(src/ SRC_DIR)
@ -31,6 +24,7 @@ aux_source_directory(vendor VENDOR_SRC)
add_dependencies(core sql)
configure_file("core.ini" "core.ini" COPYONLY)
configure_file("version.h.in" "version.h" @ONLY)
@ -45,8 +39,27 @@ add_custom_target(sql
)
add_custom_target(run
COMMAND ${CMAKE_BINARY_DIR}/core
COMMAND ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(debug
COMMAND valgrind -s ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(debug-leak
COMMAND valgrind --leak-check=full --show-leak-kinds=all ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(debug-callgrind
COMMAND valgrind --tool=callgrind ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
add_custom_target(docs
COMMAND doxygen
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
@ -55,57 +68,13 @@ add_custom_target(test
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
)
add_custom_target(docs
COMMAND doxygen
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
IF(CMAKE_BUILD_TYPE MATCHES Debug)
message(STATUS "loading debug targets")
add_custom_target(debug
COMMAND gdb ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(valgrind
COMMAND valgrind -s ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(valgrind-leak
COMMAND valgrind --leak-check=full --show-leak-kinds=all ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(valgrind-callgrind
COMMAND valgrind --tool=callgrind ${CMAKE_BINARY_DIR}/core
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(coverage
COMMAND gcovr -s --root ${CMAKE_SOURCE_DIR} -e ${CMAKE_SOURCE_DIR}/vendor --html-details ${CMAKE_BINARY_DIR}/coverage.html --html-title "Emgauwa Core Coverage" ${CMAKE_BINARY_DIR}/CMakeFiles/core.dir
DEPENDS test
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
add_custom_target(test-callgrind
add_custom_target(test-callgrind
COMMAND ./run_tests.sh ${CMAKE_BINARY_DIR}/core "--tool=callgrind"
DEPENDS core
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests
)
add_custom_target(
clang-tidy
COMMAND /usr/bin/clang-tidy
${ALL_SOURCE_FILES}
--header-filter=${CMAKE_SOURCE_DIR}/include/*
--
-std=gnu99
-I${CMAKE_SOURCE_DIR}/include
-I${CMAKE_SOURCE_DIR}/vendor
-I${CMAKE_BINARY_DIR}
-DMG_ENABLE_EXTRA_ERRORS_DESC
-DMG_ENABLE_MQTT_BROKER
)
ENDIF(CMAKE_BUILD_TYPE MATCHES Debug)
)
add_custom_target(coverage
COMMAND gcovr -s --root ${CMAKE_SOURCE_DIR} -e ${CMAKE_SOURCE_DIR}/vendor --html-details ${CMAKE_BINARY_DIR}/coverage.html --html-title "Emgauwa Core Coverage" ${CMAKE_BINARY_DIR}/CMakeFiles/core.dir
DEPENDS test
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)

View file

@ -1,4 +1,4 @@
# Doxyfile 1.8.20
# Doxyfile 1.8.17
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -227,14 +227,6 @@ QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
# By default Python docstrings are displayed as preformatted text and doxygen's
# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
# doxygen's special commands can be used and the contents of the docstring
# documentation blocks is shown as doxygen documentation.
# The default value is: YES.
PYTHON_DOCSTRING = YES
# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
# documentation from any documented member that it re-implements.
# The default value is: YES.
@ -271,6 +263,12 @@ TAB_SIZE = 4
ALIASES =
# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all
@ -312,13 +310,13 @@ OPTIMIZE_OUTPUT_SLICE = NO
# extension. Doxygen has a built-in mapping, but you can override or extend it
# using this tag. The format is ext=language, where ext is a file extension, and
# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL,
# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,
# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
# tries to guess whether the code is fixed or free formatted code, this is the
# default for Fortran type files). For instance to make doxygen treat .inc files
# as Fortran files (default is PHP), and .f files as C (default is Fortran),
# use: inc=Fortran f=C.
# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat
# .inc files as Fortran files (default is PHP), and .f files as C (default is
# Fortran), use: inc=Fortran f=C.
#
# Note: For files without extension you can use no_extension as a placeholder.
#
@ -457,19 +455,6 @@ TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
# during processing. When set to 0 doxygen will based this on the number of
# cores available in the system. You can set it explicitly to a value larger
# than 0 to get more control over the balance between CPU load and processing
# speed. At this moment only the input processing can be done using multiple
# threads. Since this is still an experimental feature the default is set to 1,
# which efficively disables parallel processing. Please report any issues you
# encounter. Generating dot graphs in parallel is controlled by the
# DOT_NUM_THREADS setting.
# Minimum value: 0, maximum value: 32, default value: 1.
NUM_PROC_THREADS = 1
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
@ -574,7 +559,7 @@ INTERNAL_DOCS = NO
# names in lower-case letters. If set to YES, upper-case letters are also
# allowed. This is useful if you have classes or files whose names only differ
# in case and if your file system supports case sensitive file names. Windows
# (including Cygwin) and Mac users are advised to set this option to NO.
# (including Cygwin) ands Mac users are advised to set this option to NO.
# The default value is: system dependent.
CASE_SENSE_NAMES = NO
@ -844,8 +829,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = src/ \
include/
INPUT = .
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@ -869,7 +853,7 @@ INPUT_ENCODING = UTF-8
# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment),
# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS = *.c \
@ -1394,7 +1378,7 @@ CHM_FILE =
HHC_LOCATION =
# The GENERATE_CHI flag controls if a separate .chi index file is generated
# (YES) or that it should be included in the main .chm file (NO).
# (YES) or that it should be included in the master .chm file (NO).
# The default value is: NO.
# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
@ -1530,7 +1514,7 @@ DISABLE_INDEX = NO
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
GENERATE_TREEVIEW = YES
GENERATE_TREEVIEW = NO
# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
# doxygen will group on one line in the generated HTML documentation.
@ -1556,17 +1540,6 @@ TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
# the HTML output. These images will generally look nicer at scaled resolutions.
# Possible values are: png (the default) and svg (looks nicer but requires the
# pdf2svg or inkscape tool).
# The default value is: png.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FORMULA_FORMAT = png
# Use this tag to change the font size of LaTeX formulas included as images in
# the HTML documentation. When you change the font size after a successful
# doxygen run you need to manually remove any form_*.png images from the HTML
@ -1622,7 +1595,7 @@ MATHJAX_FORMAT = HTML-CSS
# Content Delivery Network so you can quickly see the result without installing
# MathJax. However, it is strongly recommended to install a local copy of
# MathJax from https://www.mathjax.org before deployment.
# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2.
# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.
# This tag requires that the tag USE_MATHJAX is set to YES.
MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/
@ -1861,11 +1834,9 @@ LATEX_EXTRA_FILES =
PDF_HYPERLINKS = YES
# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
# files. Set this option to YES, to get a higher quality PDF documentation.
#
# See also section LATEX_CMD_NAME for selecting the engine.
# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
# the PDF file directly from the LaTeX files. Set this option to YES, to get a
# higher quality PDF documentation.
# The default value is: YES.
# This tag requires that the tag GENERATE_LATEX is set to YES.
@ -2104,10 +2075,6 @@ DOCBOOK_PROGRAMLISTING = NO
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# Configuration options related to Sqlite3 output
#---------------------------------------------------------------------------
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
@ -2281,7 +2248,7 @@ EXTERNAL_PAGES = YES
# powerful graphs.
# The default value is: YES.
CLASS_DIAGRAMS = NO
CLASS_DIAGRAMS = YES
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
@ -2303,7 +2270,7 @@ HIDE_UNDOC_RELATIONS = YES
# set to NO
# The default value is: NO.
HAVE_DOT = YES
HAVE_DOT = NO
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
# to run in parallel. When set to 0 doxygen will base this on the number of
@ -2420,7 +2387,7 @@ INCLUDED_BY_GRAPH = YES
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
CALL_GRAPH = YES
CALL_GRAPH = NO
# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
# dependency graph for every global function or class method.
@ -2432,7 +2399,7 @@ CALL_GRAPH = YES
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
CALLER_GRAPH = YES
CALLER_GRAPH = NO
# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
# hierarchy of all classes instead of a textual one.

16
core.ini Normal file
View file

@ -0,0 +1,16 @@
[core]
server-port = 5000
database = core.sqlite
content-dir = /usr/share/webapps/emgauwa
not-found-file = 404.html
not-found-file-mime = text/html
not-found-content = 404 - NOT FOUND
not-found-content-type = text/plain
: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery-port = 4421
: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt-port = 1885
log-level = debug
log-file = stdout

View file

@ -1,19 +0,0 @@
[core]
database = "emgauwa-core.sqlite"
content-dir = "/usr/share/webapps/emgauwa"
#include = "./emgauwa-core.conf.d/"
not-found-file = "404.html"
not-found-file-mime = "text/html"
not-found-content = "404 - NOT FOUND"
not-found-content-type = "text/plain"
[ports]
server = 4419
# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery = 4421
# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt = 1885
[logging]
level = "debug"
file = "stdout"

View file

@ -36,6 +36,7 @@ void
cache_invalidate_relay(int relay_id, int status_relay);
void
cache_put_json_controller(int controller_id, char *controller_json);
@ -46,16 +47,6 @@ void
cache_invalidate_controller(int controller_id);
void
cache_put_json_macro(int macro_id, char *macro_json);
char*
cache_get_json_macro(int macro_id);
void
cache_invalidate_macro(int macro_id);
void
cache_invalidate_tagged(int tag_id);

View file

@ -1,12 +0,0 @@
#ifndef CORE_CLI_H
#define CORE_CLI_H
typedef struct cli
{
const char *config_file;
} cli_t;
void
cli_parse(int argc, const char **argv, cli_t *cli);
#endif /* CORE_CLI_H */

View file

@ -4,54 +4,37 @@
#include <stdint.h>
#include <mongoose.h>
#include <toml.h>
#include <confini.h>
typedef enum
{
RUN_TYPE_START,
RUN_TYPE_INVALID,
} run_type_t;
typedef struct
{
char *include;
char *database;
char *user;
char *group;
char *content_dir;
char *not_found_file;
char *not_found_file_type;
char *not_found_content;
char *not_found_content_type;
struct
{
int level;
FILE *file;
} logging;
struct
{
uint16_t server;
uint16_t discovery;
uint16_t mqtt;
} ports;
char *file;
char database[256];
char user[256];
char group[256];
int log_level;
FILE *log_file;
run_type_t run_type;
uint16_t server_port;
uint16_t discovery_port;
uint16_t mqtt_port;
char content_dir[1024];
char not_found_file[256];
char not_found_file_type[256];
char not_found_content[256];
char not_found_content_type[256];
struct mg_serve_http_opts http_server_opts;
} config_t;
extern config_t *global_config;
extern config_t global_config;
void
config_init();
void
config_free();
void
config_load_string(char **holder, const char *value);
void
config_load(config_t *config, const char *cli_config_file);
void
config_load_file(config_t *config, const char *file_name);
void
config_load_directory(config_t *config, const char *directory_name);
int
config_load(IniDispatch *disp, void *config_void);
#endif /* CORE_CONFIG_H */

View file

@ -2,7 +2,6 @@
#define CORE_CONTANTS_H
#define SECONDS_PER_DAY 86400 // 60 * 60 * 24
#define DAYS_PER_WEEK 7
#define SECONDS_PER_MINUTE 60
@ -15,6 +14,13 @@
*/
#define MAX_NAME_LENGTH 128
/**
* @brief Maximum number of dbs for the databases for the MDB_env
*
* Used when calling mdb_env_set_maxdbs() in database_setup()
*/
#define MDB_MAXDBS 8
/**
* @brief How many milli seconds to wait until poll timeout in main loop
*/
@ -22,11 +28,4 @@
#define PIFACE_GPIO_BASE 200
#define DEFAULT_CONFIG_PATH "emgauwa-core.conf"
#define DEFAULT_GLOBAL_CONFIG_PATH "/etc/emgauwa/core.conf"
#define DEFAULT_DATABASE_PATH "emgauwa-core.sqlite"
#define DEFAULT_DISCOVERY_PORT 4421
#define DEFAULT_MQTT_PORT 1885
#define DEFAULT_SERVER_PORT 5000
#endif /* CORE_CONTANTS_H */

View file

@ -3,8 +3,6 @@
#include <sqlite3.h>
typedef int database_transaction_lock;
extern sqlite3 *global_database;
void
@ -17,14 +15,14 @@ void
database_migrate();
void
database_transaction_begin(database_transaction_lock *lock);
int
database_transaction_begin();
void
database_transaction_commit(const database_transaction_lock *lock);
database_transaction_commit();
void
database_transaction_rollback(const database_transaction_lock *lock);
database_transaction_rollback();
int

View file

@ -24,7 +24,7 @@ typedef struct
int status_code;
const char *content_type;
size_t content_length;
char *content;
const char *content;
int alloced_content;
} endpoint_response_t;
@ -53,16 +53,10 @@ endpoint_func_index(struct mg_connection *nc, struct http_message *hm, endpoint_
void
endpoint_func_not_found(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
endpoint_response_msg(endpoint_response_t *response, int status_code, const char *content, int content_length);
void
endpoint_response_text(endpoint_response_t *response, int status_code, const char *content, int content_length);
void
endpoint_response_json(endpoint_response_t *response, int status_code, const cJSON *json_root);
void
endpoint_response_free_content(endpoint_response_t *response);
#endif /* CORE_ENDPOINT_H */

View file

@ -4,7 +4,7 @@
#include <router.h>
void
api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_controllers_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);

View file

@ -1,24 +0,0 @@
#ifndef CORE_ENDPOINTS_API_V1_MACROS_H
#define CORE_ENDPOINTS_API_V1_MACROS_H
#include <router.h>
void
api_v1_macros_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_STR_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
void
api_v1_macros_STR_execute_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response);
#endif /* CORE_ENDPOINTS_API_V1_MACROS_H */

View file

@ -2,19 +2,19 @@
#define CORE_HELPERS_H
#include <time.h>
#include <mongoose.h>
#include <config.h>
#include <confini.h>
int
helper_connect_tcp_server(char* host, uint16_t port);
void
helper_parse_cli(int argc, const char **argv, config_t *config);
int
helper_get_weekday(const struct tm *time_struct);
int
helper_drop_privileges();
char*
find_query_param(struct mg_str query_mg_str, char* search_key);
#endif /* CORE_HELPERS_H */

View file

@ -8,22 +8,15 @@
#include <colors.h>
#include <config.h>
#define LOG_NONE INT_MAX
#ifndef __FILENAME__
#define __FILENAME__ __FILE__
#endif
void
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...);
#define LOGGER_NONE(...) logger_log(LOG_NONE , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_DEBUG(...) logger_log(LOG_DEBUG , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_INFO(...) logger_log(LOG_INFO , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_NOTICE(...) logger_log(LOG_NOTICE , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_ERR(...) logger_log(LOG_ERR , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_CRIT(...) logger_log(LOG_CRIT , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_EMERG(...) logger_log(LOG_EMERG , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_DEBUG(...) logger_log(LOG_DEBUG , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_INFO(...) logger_log(LOG_INFO , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_NOTICE(...) logger_log(LOG_NOTICE , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_WARNING(...) logger_log(LOG_WARNING, __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_ERR(...) logger_log(LOG_ERR , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_CRIT(...) logger_log(LOG_CRIT , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#define LOGGER_EMERG(...) logger_log(LOG_EMERG , __FILE__, __LINE__, __func__, ##__VA_ARGS__)
#endif //EMGAUWA_LOGGER_H

View file

@ -1,30 +1,6 @@
#ifndef CORE_MACROS_H
#define CORE_MACROS_H
#include <logger.h>
#define M_STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
#define M_RESPONSE_MSG(logger_func, response, code, content) do { logger_func("%s\n", content); endpoint_response_msg(response, code, content, M_STRLEN(content)); } while(0)
#define M_RESPONSE_TEXT(logger_func, response, code, content) do { logger_func("%s\n", content); endpoint_response_text(response, code, content, M_STRLEN(content)); } while(0)
#define M_RESPONSE_400_NO_VALID_JSON(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request is not valid json")
#define M_RESPONSE_400_NO_VALID_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid id (uuid)")
#define M_RESPONSE_400_NO_NAME(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a name")
#define M_RESPONSE_400_NO_VALID_NAME(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid name")
#define M_RESPONSE_400_NO_IP(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain an ip address")
#define M_RESPONSE_400_NO_VALID_IP(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a valid ip address")
#define M_RESPONSE_403_PROTECTED_SCHEDULE(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 403, "the target schedule is protected")
#define M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no controller was found for the requested id")
#define M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no relay was found for the requested controller id and relay number")
#define M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no schedule was found for the requested id")
#define M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "no schedule was found for the requested id")
#define M_RESPONSE_404_NO_TAG_FOUND(response) M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the requested tag was not found")
#define M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to save to the database")
#define M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to read from the database")
#define M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response) M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to delete from the database")
#define STRLEN(s) ((sizeof(s)/sizeof(s[0])) - sizeof(s[0]))
#endif /* CORE_MACROS_H */

View file

@ -25,16 +25,16 @@ typedef struct
} controller_t;
void
controller_free(controller_t *controller);
controller_free(controller_t* contoller);
int
controller_save(controller_t *controller);
controller_save(controller_t* contoller);
int
controller_remove(controller_t *controller);
controller_remove(controller_t* contoller);
cJSON*
controller_to_json(controller_t *controller);
controller_to_json(controller_t* contoller);
controller_t*
controller_get_by_id(int id);

View file

@ -10,7 +10,4 @@ junction_relay_schedule_remove_for_relay(int relay_id);
int
junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids);
int*
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id);
#endif /* CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H */

View file

@ -1,45 +0,0 @@
#ifndef CORE_MACRO_H
#define CORE_MACRO_H
#include <uuid/uuid.h>
#include <cJSON.h>
#include <constants.h>
#include <models/macro_action.h>
typedef struct
{
int id;
uuid_t uid;
char name[MAX_NAME_LENGTH + 1];
} macro_t;
int
macro_save(macro_t *macro);
int
macro_remove(macro_t *macro);
void
macro_free(macro_t *macro);
void
macro_free_list(macro_t **macro);
cJSON*
macro_to_json(macro_t *macro);
macro_t*
macro_get_by_id(int id);
macro_t*
macro_get_by_uid(uuid_t uid);
macro_t**
macro_get_all();
int*
macro_get_target_relay_ids(int macro_id);
#endif /* CORE_MACRO_H */

View file

@ -1,36 +0,0 @@
#ifndef CORE_MODELS_MACRO_ACTION_H
#define CORE_MODELS_MACRO_ACTION_H
typedef struct
{
int macro_id;
int relay_id;
int schedule_id;
uint8_t weekday;
} macro_action_t;
int
macro_action_insert(macro_action_t *macro_action);
int
macro_action_delete_for_macro(int macro_id);
macro_action_t**
macro_action_get_for_macro(int macro_id);
macro_action_t**
macro_action_get_for_macro_and_weekday(int macro_id, int weekday);
int
macro_action_execute(macro_action_t *macro_action);
void
macro_action_free_list(macro_action_t **macro_actions);
int*
macro_action_get_macro_ids_with_schedule(int schedule_id);
int*
macro_action_get_macro_ids_with_relay(int relay_id);
#endif /* CORE_MODELS_MACRO_ACTION_H */

View file

@ -35,6 +35,9 @@ schedule_free_list(schedule_t **schedule);
cJSON*
schedule_to_json(schedule_t *schedule);
void
schedule_free_list(schedule_t **schedules_list);
uint16_t*
schedule_periods_to_blob(schedule_t *schedule);
@ -62,10 +65,4 @@ schedule_uid_parse(const char *uid_str, uuid_t result);
void
schedule_uid_unparse(const uuid_t uid, char *result);
void
schedule_get_uid_off(uuid_t target);
void
schedule_get_uid_on(uuid_t target);
#endif /* CORE_SCHEDULE_H */

View file

@ -1,8 +0,0 @@
with import <nixpkgs> {};
mkShell {
nativeBuildInputs = [
cmake
sqlite
util-linux
];
}

View file

@ -1,5 +1,3 @@
-- a key-value table used for the json-cache
CREATE TABLE cache (
key STRING
PRIMARY KEY,

View file

@ -1,6 +1,4 @@
-- base migration
CREATE TABLE controllers
create table controllers
(
id INTEGER
PRIMARY KEY
@ -16,7 +14,7 @@ CREATE TABLE controllers
NOT NULL
);
CREATE TABLE relays
create table relays
(
id INTEGER
PRIMARY KEY
@ -30,7 +28,7 @@ CREATE TABLE relays
ON DELETE CASCADE
);
CREATE TABLE schedules
create table schedules
(
id INTEGER
PRIMARY KEY
@ -42,7 +40,7 @@ CREATE TABLE schedules
periods BLOB
);
CREATE TABLE tags
create table tags
(
id INTEGER
PRIMARY KEY
@ -52,7 +50,7 @@ CREATE TABLE tags
UNIQUE
);
CREATE TABLE junction_tag
create table junction_tag
(
tag_id INTEGER
NOT NULL
@ -66,7 +64,7 @@ CREATE TABLE junction_tag
ON DELETE CASCADE
);
CREATE TABLE junction_relay_schedule
create table junction_relay_schedule
(
weekday SMALLINT
NOT NULL,
@ -80,4 +78,4 @@ CREATE TABLE junction_relay_schedule
);
INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00');
INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000000000');
INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000009F05');

View file

@ -1,28 +0,0 @@
-- migration to add macros
CREATE TABLE macros
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
uid BLOB
NOT NULL
UNIQUE,
name VARCHAR(128)
);
CREATE TABLE macro_actions
(
macro_id INTEGER
NOT NULL
REFERENCES macros (id)
ON DELETE CASCADE,
relay_id INTEGER
REFERENCES relays (id)
ON DELETE CASCADE,
schedule_id INTEGER
REFERENCES schedules (id)
ON DELETE CASCADE,
weekday SMALLINT
NOT NULL
);

View file

@ -1,9 +1,7 @@
#include <cache.h>
#include <logger.h>
#include <models/junction_relay_schedule.h>
#include <models/junction_tag.h>
#include <models/macro_action.h>
#include <sql/cache.h>
#include <models/junction_tag.h>
sqlite3 *cache_database;
@ -25,15 +23,8 @@ cache_get_value(char *key)
if (s == SQLITE_ROW)
{
const char *found_value = (const char *)sqlite3_column_text(stmt, 0);
size_t found_value_len = sqlite3_column_bytes(stmt, 0);
if(result)
{
free(result);
}
result = (char*)malloc(sizeof(char) * (found_value_len + 1));
strncpy(result, found_value, found_value_len);
result[found_value_len] = '\0';
result = (char*)malloc(sizeof(char) * (strlen(found_value) + 1));
strcpy(result, found_value);
}
else
{
@ -41,11 +32,13 @@ cache_get_value(char *key)
{
break;
}
else
{
LOGGER_WARNING("failed selecting %s from cache: %s\n", key, sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
@ -141,19 +134,14 @@ cache_invalidate_schedule(int schedule_id)
sprintf(key, "schedule_json:%d", schedule_id);
cache_invalidate(key);
int *relay_ids = junction_relay_schedule_get_relay_ids_with_schedule(schedule_id);
for(int i = 0; relay_ids[i] != 0; ++i)
{
cache_invalidate_relay(relay_ids[i], -1);
}
free(relay_ids);
relay_t **relays = relay_get_with_schedule(schedule_id);
int *macro_ids = macro_action_get_macro_ids_with_schedule(schedule_id);
for(int i = 0; macro_ids[i] != 0; ++i)
for(int i = 0; relays[i] != NULL; ++i)
{
cache_invalidate_macro(macro_ids[i]);
cache_invalidate_relay(relays[i]->id, -1);
}
free(macro_ids);
relay_free_list(relays);
}
@ -192,13 +180,6 @@ cache_invalidate_relay(int relay_id, int status_relay)
{
cache_invalidate_controller(controller_id);
}
int *macro_ids = macro_action_get_macro_ids_with_relay(relay_id);
for(int i = 0; macro_ids[i] != 0; ++i)
{
cache_invalidate_macro(macro_ids[i]);
}
free(macro_ids);
}
@ -227,34 +208,6 @@ cache_invalidate_controller(int controller_id)
cache_invalidate(key);
}
void
cache_put_json_macro(int macro_id, char *macro_json)
{
char key[32];
sprintf(key, "macro_json:%d", macro_id);
cache_insert_value(key, macro_json);
}
char*
cache_get_json_macro(int macro_id)
{
char key[32];
sprintf(key, "macro_json:%d", macro_id);
return cache_get_value(key);
}
void
cache_invalidate_macro(int macro_id)
{
char key[32];
sprintf(key, "macro_json:%d", macro_id);
cache_invalidate(key);
}
void
cache_invalidate_tagged(int tag_id)
{

View file

@ -90,17 +90,13 @@ command_schedule_update(schedule_t *schedule)
LOGGER_ERR("couldn't find controller for relay %d\n", relays[i]->id);
continue;
}
controller_free(controller);
LOGGER_DEBUG("sending command to controller %s\n", controller->name);
result |= command_send(controller, payload, payload_size);
controller_free(controller);
}
relay_free_list(relays);
free(payload);
return result;
}
@ -211,6 +207,8 @@ command_controller_name_set(controller_t *controller)
int
command_send(controller_t *controller, char *payload, uint32_t payload_size)
{
int bytes_transferred;
int fd_controller = helper_connect_tcp_server(controller->ip, controller->port);
if(fd_controller == -1)
@ -219,12 +217,12 @@ command_send(controller_t *controller, char *payload, uint32_t payload_size)
return 1;
}
if(send(fd_controller, &payload_size, sizeof(payload_size), 0) <= 0)
if((bytes_transferred = send(fd_controller, &payload_size, sizeof(payload_size), 0)) <= 0)
{
LOGGER_ERR("error during sending size\n");
return 1;
}
if(send(fd_controller, payload, payload_size, 0) <= 0)
if((bytes_transferred = send(fd_controller, payload, payload_size, 0)) <= 0)
{
LOGGER_ERR("error during sending\n");
return 1;

View file

@ -1,380 +1,151 @@
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <config.h>
#include <constants.h>
#include <logger.h>
#include <config.h>
config_t *global_config;
config_t global_config;
#define CONFINI_IS_KEY(SECTION, KEY) \
(ini_array_match(SECTION, disp->append_to, '.', disp->format) && \
ini_string_match_ii(KEY, disp->data, disp->format))
static int
config_load_log_level(config_t *config, char *value)
config_load_log_level(IniDispatch *disp, config_t *config)
{
if(strcmp(value, "debug") == 0)
if(strcasecmp(disp->value, "debug") == 0)
{
setlogmask(LOG_UPTO(LOG_DEBUG));
config->logging.level = LOG_DEBUG;
config->log_level = LOG_DEBUG;
return 0;
}
if(strcmp(value, "info") == 0)
if(strcasecmp(disp->value, "info") == 0)
{
setlogmask(LOG_UPTO(LOG_INFO));
config->logging.level = LOG_INFO;
config->log_level = LOG_INFO;
return 0;
}
if(strcmp(value, "notice") == 0)
if(strcasecmp(disp->value, "notice") == 0)
{
setlogmask(LOG_UPTO(LOG_NOTICE));
config->logging.level = LOG_NOTICE;
config->log_level = LOG_NOTICE;
return 0;
}
if(strcmp(value, "warning") == 0)
if(strcasecmp(disp->value, "warning") == 0)
{
setlogmask(LOG_UPTO(LOG_WARNING));
config->logging.level = LOG_WARNING;
config->log_level = LOG_WARNING;
return 0;
}
if(strcmp(value, "err") == 0)
if(strcasecmp(disp->value, "err") == 0)
{
setlogmask(LOG_UPTO(LOG_ERR));
config->logging.level = LOG_ERR;
config->log_level = LOG_ERR;
return 0;
}
if(strcmp(value, "crit") == 0)
if(strcasecmp(disp->value, "crit") == 0)
{
setlogmask(LOG_UPTO(LOG_CRIT));
config->logging.level = LOG_CRIT;
config->log_level = LOG_CRIT;
return 0;
}
if(strcmp(value, "emerg") == 0)
if(strcasecmp(disp->value, "emerg") == 0)
{
setlogmask(LOG_UPTO(LOG_EMERG));
config->logging.level = LOG_EMERG;
config->log_level = LOG_EMERG;
return 0;
}
LOGGER_WARNING("invalid log-level '%s'\n", value);
LOGGER_WARNING("invalid log-level '%s'\n", disp->value);
return 0;
}
static int
config_load_log_file(config_t *config, char *value)
config_load_log_file(IniDispatch *disp, config_t *config)
{
if(strcmp(value, "stdout") == 0)
if(strcasecmp(disp->value, "stdout") == 0)
{
config->logging.file = stdout;
config->log_file = stdout;
return 0;
}
if(strcmp(value, "stderr") == 0)
if(strcasecmp(disp->value, "stderr") == 0)
{
config->logging.file = stderr;
config->log_file = stderr;
return 0;
}
config->logging.file = fopen(value, "a+");
config->log_file = fopen(disp->value, "a+");
return 0;
}
static void
config_load_section_core(config_t *config, toml_table_t* core)
int
config_load(IniDispatch *disp, void *config_void)
{
toml_datum_t config_entry;
config_t *config = (config_t*)config_void;
config_entry = toml_string_in(core, "database");
if(config_entry.ok)
if(disp->type == INI_KEY)
{
config_load_string(&config->database, config_entry.u.s);
free(config_entry.u.s);
if(CONFINI_IS_KEY("core", "database"))
{
strcpy(config->database, disp->value);
return 0;
}
config_entry = toml_string_in(core, "user");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "user"))
{
config_load_string(&config->user, config_entry.u.s);
free(config_entry.u.s);
strcpy(config->user, disp->value);
return 0;
}
config_entry = toml_string_in(core, "group");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "group"))
{
config_load_string(&config->group, config_entry.u.s);
free(config_entry.u.s);
strcpy(config->group, disp->value);
return 0;
}
config_entry = toml_string_in(core, "content-dir");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "content-dir"))
{
config_load_string(&config->content_dir, config_entry.u.s);
free(config_entry.u.s);
strcpy(config->content_dir, disp->value);
return 0;
}
config_entry = toml_string_in(core, "not-found-file");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "not-found-file"))
{
config_load_string(&config->not_found_file, config_entry.u.s);
free(config_entry.u.s);
strcpy(config->not_found_file, disp->value);
return 0;
}
config_entry = toml_string_in(core, "not-found-file-type");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "not-found-file-type"))
{
config_load_string(&config->not_found_file_type, config_entry.u.s);
free(config_entry.u.s);
strcpy(config->not_found_file_type, disp->value);
return 0;
}
config_entry = toml_string_in(core, "not-found-content");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "not-found-content"))
{
config_load_string(&config->not_found_content, config_entry.u.s);
free(config_entry.u.s);
strcpy(config->not_found_content, disp->value);
return 0;
}
config_entry = toml_string_in(core, "not-found-content-type");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "not-found-content-type"))
{
config_load_string(&config->not_found_content_type, config_entry.u.s);
free(config_entry.u.s);
strcpy(config->not_found_content_type, disp->value);
return 0;
}
config_entry = toml_string_in(core, "include");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "log-level"))
{
config_load_string(&config->include, config_entry.u.s);
free(config_entry.u.s);
return config_load_log_level(disp, config);
}
}
static void
config_load_section_logging(config_t *config, toml_table_t* logging)
{
toml_datum_t config_entry;
config_entry = toml_string_in(logging, "level");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "log-file"))
{
config_load_log_level(config, config_entry.u.s);
free(config_entry.u.s);
return config_load_log_file(disp, config);
}
config_entry = toml_string_in(logging, "file");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "server-port"))
{
config_load_log_file(config, config_entry.u.s);
free(config_entry.u.s);
config->server_port = atoi(disp->value);
return 0;
}
}
static void
config_load_section_ports(config_t *config, toml_table_t* ports)
{
toml_datum_t config_entry;
config_entry = toml_int_in(ports, "server");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "discovery-port"))
{
config->ports.server = config_entry.u.i;
config->discovery_port = atoi(disp->value);
return 0;
}
config_entry = toml_int_in(ports, "discovery");
if(config_entry.ok)
if(CONFINI_IS_KEY("core", "mqtt-port"))
{
config->ports.discovery = config_entry.u.i;
config->mqtt_port = atoi(disp->value);
return 0;
}
config_entry = toml_int_in(ports, "mqtt");
if(config_entry.ok)
{
config->ports.mqtt = config_entry.u.i;
}
}
void
config_init()
{
global_config = calloc(1, sizeof(config_t));
config_load_string(&global_config->database, DEFAULT_DATABASE_PATH);
global_config->ports.discovery = DEFAULT_DISCOVERY_PORT;
global_config->ports.mqtt = DEFAULT_MQTT_PORT;
global_config->ports.server = DEFAULT_SERVER_PORT;
global_config->logging.level = LOG_DEBUG;
global_config->logging.file = stdout;
global_config->user = NULL;
global_config->group = NULL;
global_config->include = NULL;
config_load_string(&global_config->content_dir, ".");
config_load_string(&global_config->not_found_file, "404.html");
config_load_string(&global_config->not_found_file_type, "text/html");
config_load_string(&global_config->not_found_content, "404 - NOT FOUND");
config_load_string(&global_config->not_found_content_type, "text/plain");
}
void
config_free()
{
free(global_config->include);
free(global_config->database);
free(global_config->user);
free(global_config->group);
free(global_config->content_dir);
free(global_config->not_found_file);
free(global_config->not_found_file_type);
free(global_config->not_found_content);
free(global_config->not_found_content_type);
free(global_config);
}
void
config_load_string(char **holder, const char *value)
{
if(*holder)
{
free(*holder);
}
size_t value_len = strlen(value);
char *new_holder = malloc(sizeof(char) * (value_len + 1));
strcpy(new_holder, value);
new_holder[value_len] = '\0';
*holder = new_holder;
}
static int
config_try_file(const char *path)
{
if(access(path, F_OK) != 0)
{
return 1;
}
if(access(path, R_OK) != 0)
{
return 1;
}
return 0;
}
void
config_load(config_t *config, const char *cli_config_file)
{
if(cli_config_file)
{
if(config_try_file(cli_config_file) == 0)
{
config_load_file(config, cli_config_file);
return;
}
LOGGER_CRIT("unable to open the passed config file '%s'\n", cli_config_file);
exit(1);
}
if(config_try_file(DEFAULT_CONFIG_PATH) == 0)
{
config_load_file(config, DEFAULT_CONFIG_PATH);
return;
}
if(config_try_file(DEFAULT_GLOBAL_CONFIG_PATH) == 0)
{
config_load_file(config, DEFAULT_GLOBAL_CONFIG_PATH);
return;
}
}
void
config_load_file(config_t *config, const char *file_name)
{
FILE *fp;
toml_table_t* config_toml;
char errbuf[256];
/* Open the file and parse content */
fp = fopen(file_name, "r");
if(fp == NULL) {
LOGGER_CRIT("unable to open config file '%s'\n", file_name);
exit(1);
}
config_toml = toml_parse_file(fp, errbuf, sizeof(errbuf));
fclose(fp);
if(config_toml == NULL) {
LOGGER_CRIT("unable to parse config file '%s': %s\n", file_name, errbuf);
exit(1);
}
toml_table_t* core = toml_table_in(config_toml, "core");
if(core)
{
config_load_section_core(config, core);
}
toml_table_t* logging = toml_table_in(config_toml, "logging");
if(logging)
{
config_load_section_logging(config, logging);
}
toml_table_t* ports = toml_table_in(config_toml, "ports");
if(ports)
{
config_load_section_ports(config, ports);
}
toml_free(config_toml);
LOGGER_DEBUG("Loaded config from %s\n", file_name);
}
void
config_load_directory(config_t *config, const char *directory_name)
{
struct dirent *directory_entry;
DIR *directory;
(void)config;
directory = opendir(directory_name);
if(directory == NULL)
{
LOGGER_CRIT("cannot open directory '%s': %s\n", directory_name, strerror(errno));
exit(1);
}
while((directory_entry = readdir(directory)) != NULL)
{
struct stat sb;
const char *entry_name = directory_entry->d_name;
size_t copied = 0;
// Add 2 for '/' and '\0'.
size_t path_len = strlen(directory_name) + strlen(entry_name) + 1;
char *path = malloc(sizeof(char) * (path_len + 1));
path[0] = '\0';
strncat(path + copied, directory_name, path_len - copied);
copied = strlen(path);
if(path[copied - 1] != '/')
{
strncat(path + copied, "/", path_len - copied);
copied = strlen(path);
}
strncat(path + copied, entry_name, path_len - copied);
if(stat(path, &sb))
{
LOGGER_WARNING("failed to get info for '%s': %s\n", path, strerror(errno));
}
if(S_ISREG(sb.st_mode))
{
config_load_file(config, path);
}
free(path);
}
closedir(directory);
}

View file

@ -5,15 +5,14 @@
#include <database.h>
#include <sql/migration_0.h>
#include <sql/migration_1.h>
sqlite3 *global_database;
static database_transaction_lock *transaction_lock;
static int in_transaction;
void
database_init()
{
int rc = sqlite3_open(global_config->database, &global_database);
int rc = sqlite3_open(global_config.database, &global_database);
if(rc)
{
@ -21,12 +20,10 @@ database_init()
exit(1);
}
LOGGER_DEBUG("Opened database %s\n", global_config->database);
database_migrate();
sqlite3_exec(global_database, "PRAGMA foreign_keys = ON", 0, 0, 0);
transaction_lock = NULL;
in_transaction = 0;
}
void
@ -35,35 +32,11 @@ database_free()
sqlite3_close(global_database);
}
static void
database_migrate_step_simple(int level, const char* sql_migration)
{
LOGGER_INFO("migrating LEVEL %d\n", level);
char* err_msg;
sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
int rc = sqlite3_exec(global_database, sql_migration, NULL, NULL, &err_msg);
if(rc)
{
LOGGER_CRIT("couldn't migrate LEVEL %d (%s)\n", level, err_msg);
sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
LOGGER_DEBUG("storing new user_version %d\n", level + 1);
char pragma_query[32];
sprintf(pragma_query, "PRAGMA user_version=%d;", level + 1);
sqlite3_exec(global_database, pragma_query, 0, 0, 0);
sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
}
void
database_migrate()
{
uint16_t version_num = 0;
int s;
int s, rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "PRAGMA user_version;", -1, &stmt, NULL);
s = sqlite3_step(stmt);
@ -76,52 +49,61 @@ database_migrate()
version_num = 0;
}
uint16_t new_version_num = version_num;
char* err_msg;
sqlite3_finalize(stmt);
switch(version_num)
{
case 0:
database_migrate_step_simple(0, (const char*)sql_migration_0_sql);
__attribute__ ((fallthrough));
case 1:
database_migrate_step_simple(1, (const char*)sql_migration_1_sql);
__attribute__ ((fallthrough));
LOGGER_INFO("migrating LEVEL 0\n");
rc = sqlite3_exec(global_database, (const char *)sql_migration_0_sql, NULL, NULL, &err_msg);
if(rc)
{
LOGGER_CRIT("couldn't migrate LEVEL 0 (%s)\n", err_msg);
exit(1);
}
new_version_num = 1;
default:
break;
}
char pragma_query[32];
sprintf(pragma_query, "PRAGMA user_version=%d;", new_version_num);
sqlite3_exec(global_database, pragma_query, 0, 0, 0);
LOGGER_DEBUG("storing new user_version %d\n", new_version_num);
return;
}
void
database_transaction_begin(database_transaction_lock *lock)
int
database_transaction_begin()
{
if(transaction_lock == NULL)
if(!in_transaction)
{
LOGGER_DEBUG("beginning transaction\n");
sqlite3_exec(global_database, "BEGIN TRANSACTION;", NULL, NULL, NULL);
transaction_lock = lock;
in_transaction = 1;
return 1;
}
return 0;
}
void
database_transaction_commit(const database_transaction_lock *lock)
database_transaction_commit()
{
if(transaction_lock == lock)
{
LOGGER_DEBUG("commiting transaction\n");
sqlite3_exec(global_database, "COMMIT TRANSACTION;", NULL, NULL, NULL);
transaction_lock = NULL;
}
in_transaction = 0;
}
void
database_transaction_rollback(const database_transaction_lock *lock)
database_transaction_rollback()
{
if(transaction_lock == lock)
{
LOGGER_DEBUG("rolling back transaction\n");
sqlite3_exec(global_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
transaction_lock = NULL;
}
in_transaction = 0;
}
int
@ -144,12 +126,14 @@ database_helper_get_id(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting id from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return 0;
}
}
}
sqlite3_finalize(stmt);
@ -186,16 +170,14 @@ database_helper_get_ids(sqlite3_stmt *stmt)
{
break;
}
if(result)
else
{
free(result);
}
LOGGER_ERR("error selecting ids from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
result[row] = 0;
@ -216,15 +198,8 @@ database_helper_get_string(sqlite3_stmt *stmt)
if (s == SQLITE_ROW)
{
const char *found_string = (const char *)sqlite3_column_text(stmt, 0);
size_t found_string_len = sqlite3_column_bytes(stmt, 0);
if(result)
{
free(result);
}
result = (char*)malloc(sizeof(char) * (found_string_len + 1));
strncpy(result, found_string, found_string_len);
result[found_string_len] = '\0';
result = (char*)malloc(sizeof(char) * (strlen(found_string) + 1));
strcpy(result, found_string);
}
else
{
@ -232,16 +207,14 @@ database_helper_get_string(sqlite3_stmt *stmt)
{
break;
}
if(result)
else
{
free(result);
}
LOGGER_ERR("error selecting string from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
@ -263,13 +236,12 @@ database_helper_get_strings(sqlite3_stmt *stmt)
if (s == SQLITE_ROW)
{
const char *new_string = (const char *)sqlite3_column_text(stmt, 0);
size_t new_string_len = sqlite3_column_bytes(stmt, 0);
int new_string_len = strlen(new_string);
row++;
result = (char**)realloc(result, sizeof(char*) * (row + 1));
result[row - 1] = malloc(sizeof(char) * (new_string_len + 1));
strncpy(result[row - 1], new_string, new_string_len);
result[row - 1][new_string_len] = '\0';
strcpy(result[row - 1], new_string);
}
else
{
@ -277,11 +249,13 @@ database_helper_get_strings(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting strings from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
result[row] = NULL;
return result;

View file

@ -14,7 +14,7 @@ endpoint_func_index(struct mg_connection *nc, struct http_message *hm, endpoint_
response->status_code = 0;
mg_serve_http(nc, hm, global_config->http_server_opts);
mg_serve_http(nc, hm, global_config.http_server_opts);
}
void
@ -24,61 +24,27 @@ endpoint_func_not_found(struct mg_connection *nc, struct http_message *hm, endpo
(void)hm;
(void)nc;
if(access(global_config->not_found_file, R_OK) != -1)
if(access(global_config.not_found_file, R_OK) != -1)
{
struct mg_str mime_type = mg_mk_str(global_config->not_found_file_type);
struct mg_str mime_type = mg_mk_str(global_config.not_found_file_type);
response->status_code = 0;
mg_http_serve_file(nc, hm, global_config->not_found_file, mime_type, mg_mk_str(""));
mg_http_serve_file(nc, hm, global_config.not_found_file, mime_type, mg_mk_str(""));
}
else
{
LOGGER_DEBUG("404 file not found\n");
endpoint_response_free_content(response);
response->status_code = 404;
response->content_type = global_config->not_found_content_type;
response->content_length = strlen(global_config->not_found_content);
response->content = global_config->not_found_content;
response->content_type = global_config.not_found_content_type;
response->content_length = strlen(global_config.not_found_content);
response->content = global_config.not_found_content;
response->alloced_content = false;
}
}
void
endpoint_response_msg(endpoint_response_t *response, int status_code, const char *content, int content_length)
{
endpoint_response_free_content(response);
cJSON *json;
json = cJSON_CreateObject();
cJSON *json_msg;
if(content_length)
{
json_msg = cJSON_CreateStringReference(content);
}
else
{
json_msg = cJSON_CreateString(content);
}
if(json_msg == NULL)
{
endpoint_response_text(response, status_code, content, content_length);
return;
}
cJSON_AddItemToObject(json, "msg", json_msg);
endpoint_response_json(response, status_code, json);
cJSON_Delete(json);
}
void
endpoint_response_text(endpoint_response_t *response, int status_code, const char *content, int content_length)
{
endpoint_response_free_content(response);
if(content == NULL)
{
content = "";
@ -87,27 +53,22 @@ endpoint_response_text(endpoint_response_t *response, int status_code, const cha
response->status_code = status_code;
response->content_type = "text/plain";
if(content_length)
if(content_length >= 0)
{
response->content_length = content_length;
response->alloced_content = false;
response->content = (char*)content;
}
else
{
response->content_length = strlen(content);
response->alloced_content = true;
response->content = malloc(sizeof(char) * (response->content_length + 1));
strcpy(response->content, content);
response->content[response->content_length] = '\0';
}
response->content = content;
}
void
endpoint_response_json(endpoint_response_t *response, int status_code, const cJSON *json_root)
{
endpoint_response_free_content(response);
if(json_root != NULL)
{
char *json_str = cJSON_Print(json_root);
@ -123,16 +84,8 @@ endpoint_response_json(endpoint_response_t *response, int status_code, const cJS
}
}
M_RESPONSE_MSG(LOGGER_ERR, response, 500, "failed to print json");
}
LOGGER_ERR("failed to print schedule json\n");
void
endpoint_response_free_content(endpoint_response_t *response)
{
if(response->alloced_content)
{
free(response->content);
response->content = NULL;
response->alloced_content = false;
}
static const char content[] = "failed to print json";
endpoint_response_text(response, status_code, content, STRLEN(content));
}

View file

@ -18,7 +18,10 @@ api_v1_controllers_STR_GET(struct mg_connection *nc, struct http_message *hm, en
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -26,7 +29,10 @@ api_v1_controllers_STR_GET(struct mg_connection *nc, struct http_message *hm, en
if(!controller)
{
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
LOGGER_DEBUG("returning controller for uid '%s'\n", args[0].value.v_str);
@ -47,7 +53,10 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -55,7 +64,10 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
if(!controller)
{
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
LOGGER_DEBUG("starting overwrite for controller %s\n", args[0].value.v_str);
@ -64,41 +76,36 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
if(json == NULL)
{
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
controller_free(controller);
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(json_name)
{
if(!cJSON_IsString(json_name) || json_name->valuestring == NULL)
if(cJSON_IsString(json_name) && json_name->valuestring)
{
cJSON_Delete(json);
controller_free(controller);
M_RESPONSE_400_NO_VALID_NAME(response);
return;
}
strncpy(controller->name, json_name->valuestring, MAX_NAME_LENGTH);
controller->name[MAX_NAME_LENGTH] = '\0';
LOGGER_DEBUG("new controller name: %s\n", controller->name);
}
else
{
static const char content[] = "the given name is no valid string";
endpoint_response_text(response, 400, content, STRLEN(content));
cJSON_Delete(json);
controller_free(controller);
return;
}
}
cJSON *json_ip = cJSON_GetObjectItemCaseSensitive(json, "ip");
if(json_ip)
{
if(!cJSON_IsString(json_ip) || json_ip->valuestring == NULL)
if(cJSON_IsString(json_ip) && json_ip->valuestring)
{
cJSON_Delete(json);
controller_free(controller);
M_RESPONSE_400_NO_IP(response);
return;
}
unsigned char buf[sizeof(struct in_addr)];
if(inet_pton(AF_INET, json_ip->valuestring, buf))
{
@ -108,20 +115,31 @@ api_v1_controllers_STR_PUT(struct mg_connection *nc, struct http_message *hm, en
}
else
{
static const char content[] = "the given ip address is no valid IPv4 address";
endpoint_response_text(response, 400, content, STRLEN(content));
cJSON_Delete(json);
controller_free(controller);
return;
}
}
else
{
static const char content[] = "the given ip address is no valid string";
endpoint_response_text(response, 400, content, STRLEN(content));
cJSON_Delete(json);
controller_free(controller);
M_RESPONSE_400_NO_VALID_IP(response);
return;
}
}
if(controller_save(controller))
{
LOGGER_ERR("failed to save controller\n");
controller_free(controller);
cJSON_Delete(json);
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
static const char content[] = "failed to save controller to database";
endpoint_response_text(response, 500, content, STRLEN(content));
return;
}
LOGGER_DEBUG("saved controller %s\n", args[0].value.v_str);
@ -147,7 +165,10 @@ api_v1_controllers_STR_DELETE(struct mg_connection *nc, struct http_message *hm,
uuid_t target_uid;
if(uuid_parse(target_uid_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -155,17 +176,25 @@ api_v1_controllers_STR_DELETE(struct mg_connection *nc, struct http_message *hm,
if(!controller)
{
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
if(controller_remove(controller))
{
M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
LOGGER_ERR("failed to remove controller from database\n");
static const char content[] = "failed to remove controller from database";
endpoint_response_text(response, 500, content, STRLEN(content));
}
else
{
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "deleted controller");
LOGGER_DEBUG("deleted controller %s\n", args[0].value.v_str);
endpoint_response_text(response, 200, "", 0);
}
controller_free(controller);
return;
}

View file

@ -15,7 +15,10 @@ api_v1_controllers_STR_relays_GET(struct mg_connection *nc, struct http_message
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -23,7 +26,10 @@ api_v1_controllers_STR_relays_GET(struct mg_connection *nc, struct http_message
if(!controller)
{
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}

View file

@ -18,7 +18,10 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -26,18 +29,21 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess
if(!controller)
{
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
int controller_id = controller->id;
controller_free(controller);
relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int);
relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
if(!relay)
{
M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response);
LOGGER_DEBUG("could not find a relay with num %d for controller '%s'\n", args[1].value.v_int, args[0].value.v_str);
static const char content[] = "no relay for this controller found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
@ -47,6 +53,7 @@ api_v1_controllers_STR_relays_INT_GET(struct mg_connection *nc, struct http_mess
endpoint_response_json(response, 200, json);
cJSON_Delete(json);
relay_free(relay);
controller_free(controller);
}
void
@ -58,7 +65,10 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -66,21 +76,23 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
if(!controller)
{
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
int controller_id = controller->id;
int controller_relay_count = controller->relay_count;
controller_free(controller);
if(args[1].value.v_int > controller_relay_count)
if(args[1].value.v_int > controller->relay_count)
{
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "relay number is too high for this controller");
LOGGER_DEBUG("relay num %d is too high for %s\n", args[1].value.v_int, args[0].value.v_str);
static const char content[] = "relay number is too high for this controller";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int);
relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
if(!relay)
{
@ -90,7 +102,7 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
relay->number = args[1].value.v_int;
snprintf(relay->name, MAX_NAME_LENGTH, "Relay %d", relay->number);
relay->name[MAX_NAME_LENGTH] = '\0';
relay->controller_id = controller_id;
relay->controller_id = controller->id;
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
@ -105,13 +117,15 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
relay->active_schedule = relay->schedules[helper_get_weekday(time_struct)];
}
controller_free(controller);
LOGGER_DEBUG("overwriting relay %d for controller %s\n", args[1].value.v_int, args[0].value.v_str);
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -135,17 +149,21 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
cJSON *json_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_schedule, "id");
if(!cJSON_IsString(json_schedule_uid) || (json_schedule_uid->valuestring == NULL))
{
LOGGER_DEBUG("schedules[%d] is missing uid\n", schedule_position);
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one schedule is missing an id");
static const char content[] = "at least one schedule is missing an id";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
uuid_t target_uid;
if(schedule_uid_parse(json_schedule_uid->valuestring, target_uid))
{
LOGGER_DEBUG("schedules[%d] has bad uid\n", schedule_position);
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "at least one schedule has a bad id");
static const char content[] = "at least one schedule has a bad id";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -172,9 +190,11 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
uuid_t target_uid;
if(schedule_uid_parse(json_active_schedule_uid->valuestring, target_uid))
{
LOGGER_DEBUG("active_schedule has bad uid\n");
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "active schedule has a bad uid");
static const char content[] = "active_schedule has a bad id";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
relay->schedules[day_of_week] = schedule_get_by_uid_or_off(target_uid);
@ -182,15 +202,21 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
}
}
database_transaction_lock lock;
database_transaction_begin(&lock);
int opened_transaction = database_transaction_begin();
if(relay_save(relay))
{
database_transaction_rollback(&lock);
LOGGER_ERR("failed to save relay\n");
if(opened_transaction)
{
database_transaction_rollback();
}
cJSON_Delete(json);
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
static const char content[] = "failed to save relay to database";
endpoint_response_text(response, 500, content, STRLEN(content));
return;
}
@ -209,12 +235,19 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
database_transaction_rollback(&lock);
LOGGER_DEBUG("invalid tag in tags\n");
if(opened_transaction)
{
database_transaction_rollback();
}
relay_free(relay);
cJSON_Delete(json);
free(tag_ids);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "invalid tag in tags");
static const char content[] = "invalid tag in tags";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
const char *tag = json_tag->valuestring;
@ -226,12 +259,13 @@ api_v1_controllers_STR_relays_INT_PUT(struct mg_connection *nc, struct http_mess
}
tag_ids[i++] = tag_id;
}
junction_tag_insert_list(tag_ids, relay->id, 0, json_tags_count);
free(tag_ids);
}
database_transaction_commit(&lock);
if(opened_transaction)
{
database_transaction_commit();
}
cJSON_Delete(json);
json = relay_to_json(relay, 0);

View file

@ -18,7 +18,10 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -26,18 +29,21 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht
if(!controller)
{
M_RESPONSE_404_NO_CONTROLLER_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a controller for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no controller for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
int controller_id = controller->id;
controller_free(controller);
relay_t* relay = relay_get_for_controller(controller_id, args[1].value.v_int);
relay_t* relay = relay_get_for_controller(controller->id, args[1].value.v_int);
if(!relay)
{
M_RESPONSE_404_NO_RELAY_FOUND_FOR_ID_AND_NUMBER(response);
LOGGER_DEBUG("could not find a relay with num %d for controller '%s'\n", args[1].value.v_int, args[0].value.v_str);
static const char content[] = "no relay for this controller found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
@ -58,6 +64,7 @@ api_v1_controllers_STR_relays_INT_pulse_POST(struct mg_connection *nc, struct ht
LOGGER_DEBUG("commanding pulse to relay %d for controller %s\n", args[1].value.v_int, args[0].value.v_str);
command_relay_pulse(relay, duration);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "sent pulse");
endpoint_response_text(response, 200, "", 0);
relay_free(relay);
controller_free(controller);
}

View file

@ -19,8 +19,7 @@ typedef enum
static int
bind_tcp_server(const char *addr, const char *port, int max_client_backlog)
{
struct addrinfo hints;
struct addrinfo *res;
struct addrinfo hints, *res;
int fd;
int status;
@ -106,7 +105,7 @@ send_udp_broadcast(const char *addr, uint16_t port, void *message, size_t length
}
void
api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
api_v1_controllers_discover_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)nc;
(void)hm;
@ -115,7 +114,13 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h
if(discover_server_port == -1)
{
M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to prepare discovery");
LOGGER_ERR("failed to get server port for discovery\n");
static const char content[] = "";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
@ -123,16 +128,21 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h
payload[0] = discover_server_port;
LOGGER_DEBUG("sending udp broadcast\n");
if(send_udp_broadcast("255.255.255.255", global_config->ports.discovery, payload, sizeof(payload)) < 0)
if(send_udp_broadcast("255.255.255.255", global_config.discovery_port, payload, sizeof(payload)) < 0)
{
M_RESPONSE_MSG(LOGGER_ERR, response, 500, "the server failed to send discovery broadcast");
LOGGER_ERR("failed to send UDP broadcast\n");
static const char content[] = "";
response->status_code = 500;
response->content_type = "text/plain";
response->content_length = STRLEN(content);;
response->content = content;
response->alloced_content = false;
return;
}
struct sockaddr_storage their_addr;
socklen_t addr_size;
int s_ret;
int client_fd;
int client_fd, s_ret;
fd_set accept_fds;
struct timeval timeout;
@ -155,7 +165,8 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h
{
break;
}
else
{
if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0)
{
LOGGER_ERR("error accepting client %s\n", strerror(errno));
@ -170,8 +181,10 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h
continue;
}
char *answer_payload = (char*)malloc((payload_length + 1));
if(recv(client_fd, answer_payload, payload_length, 0) <= 0)
char *answer_payload = (char*)malloc((payload_length));
ssize_t bytes_transferred;
if((bytes_transferred = recv(client_fd, answer_payload, payload_length, 0)) <= 0)
{
LOGGER_ERR("error receiving payload from client\n");
continue;
@ -219,11 +232,8 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h
known_controllers[i]->active = 1;
strncpy(known_controllers[i]->name, discovered_name, discovered_name_len);
strcpy(known_controllers[i]->ip, inet_ntoa(addr.sin_addr));
known_controllers[i]->name[discovered_name_len] = '\0';
strncpy(known_controllers[i]->ip, inet_ntoa(addr.sin_addr), IP_LENGTH);
known_controllers[i]->ip[IP_LENGTH] = '\0';
known_controllers[i]->port = discovered_command_port;
known_controllers[i]->relay_count = discovered_relay_count;
@ -246,11 +256,8 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h
controller_t *discovered_controller = malloc(sizeof(controller_t));
discovered_controller->id = 0;
strncpy(discovered_controller->ip, inet_ntoa(addr.sin_addr), IP_LENGTH + 1);
discovered_controller->ip[IP_LENGTH] = '\0';
uuid_copy(discovered_controller->uid, discovered_id);
strcpy(discovered_controller->ip, inet_ntoa(addr.sin_addr));
memcpy(discovered_controller->uid, discovered_id, sizeof(uuid_t));
strncpy(discovered_controller->name, discovered_name, discovered_name_len);
discovered_controller->name[discovered_name_len] = '\0';
discovered_controller->relay_count = discovered_relay_count;
@ -297,6 +304,7 @@ api_v1_controllers_discover_PUT(struct mg_connection *nc, struct http_message *h
send(client_fd, discover_answer_buf, sizeof(uint8_t), 0);
close(client_fd);
}
}
for(int i = 0; known_controllers[i] != NULL; i++)
{
LOGGER_DEBUG("lost controller %s at %s\n", known_controllers[i]->name, known_controllers[i]->ip);

View file

@ -1,240 +0,0 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <database.h>
#include <endpoints/api_v1_macros.h>
#include <logger.h>
#include <models/macro.h>
#include <models/macro_action.h>
#include <models/schedule.h>
#include <models/relay.h>
#include <models/controller.h>
void
api_v1_macros_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)args;
(void)hm;
(void)nc;
macro_t** all_macros = macro_get_all();
cJSON *json = cJSON_CreateArray();
for(int i = 0; all_macros[i] != NULL; ++i)
{
cJSON_AddItemToArray(json, macro_to_json(all_macros[i]));
free(all_macros[i]);
}
endpoint_response_json(response, 200, json);
cJSON_Delete(json);
free(all_macros);
}
void
api_v1_macros_POST(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)args;
(void)nc;
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL))
{
M_RESPONSE_400_NO_NAME(response);
return;
}
cJSON *json_actions = cJSON_GetObjectItemCaseSensitive(json, "actions");
if(!cJSON_IsArray(json_actions))
{
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contains actions for the macro");
return;
}
database_transaction_lock lock;
database_transaction_begin(&lock);
macro_t *new_macro = malloc(sizeof(macro_t));
new_macro->id = 0;
uuid_generate(new_macro->uid);
strncpy(new_macro->name, json_name->valuestring, MAX_NAME_LENGTH);
new_macro->name[MAX_NAME_LENGTH] = '\0';
if(macro_save(new_macro))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
return;
}
cJSON *json_action;
cJSON_ArrayForEach(json_action, json_actions)
{
cJSON *json_action_weekday = cJSON_GetObjectItemCaseSensitive(json_action, "weekday");
if(!cJSON_IsNumber(json_action_weekday) || (json_action_weekday->valueint < 0) || (json_action_weekday->valueint > 6))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without weekday");
return;
}
cJSON *json_action_schedule = cJSON_GetObjectItemCaseSensitive(json_action, "schedule");
if(!cJSON_IsObject(json_action_schedule))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without schedule");
return;
}
cJSON *json_action_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_action_schedule, "id");
if(!cJSON_IsString(json_action_schedule_uid) || (json_action_schedule_uid->valuestring == NULL))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without schedule id");
return;
}
uuid_t action_schedule_uid;
if(schedule_uid_parse(json_action_schedule_uid->valuestring, action_schedule_uid))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action with an invalid schedule id");
return;
}
schedule_t *action_schedule = schedule_get_by_uid(action_schedule_uid);
if(action_schedule == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the schedule for at least one action was not found");
return;
}
int action_schedule_id = action_schedule->id;
schedule_free(action_schedule);
cJSON *json_action_relay = cJSON_GetObjectItemCaseSensitive(json_action, "relay");
if(!cJSON_IsObject(json_action_relay))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay");
return;
}
cJSON *json_action_relay_number = cJSON_GetObjectItemCaseSensitive(json_action_relay, "number");
if(!cJSON_IsNumber(json_action_relay_number))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay number");
return;
}
cJSON *json_action_relay_controller_uid = cJSON_GetObjectItemCaseSensitive(json_action_relay, "controller_id");
if(!cJSON_IsString(json_action_relay_controller_uid) || (json_action_relay_controller_uid->valuestring == NULL))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action without relay controller id");
return;
}
uuid_t action_controller_uid;
if(uuid_parse(json_action_relay_controller_uid->valuestring, action_controller_uid))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an action with an invalid relay controller id");
return;
}
controller_t *action_controller = controller_get_by_uid(action_controller_uid);
if(action_controller == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the controller for at least one action relay was not found");
return;
}
int controller_id = action_controller->id;
int relay_num = json_action_relay_number->valueint;
controller_free(action_controller);
relay_t *action_relay = relay_get_for_controller(controller_id, relay_num);
if(action_relay == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(new_macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 404, "the relay for at least one action was not found");
return;
}
int action_relay_id = action_relay->id;
relay_free(action_relay);
macro_action_t *new_action = malloc(sizeof(macro_action_t));
new_action->macro_id = new_macro->id;
new_action->relay_id = action_relay_id;
new_action->schedule_id = action_schedule_id;
new_action->weekday = json_action_weekday->valueint;
macro_action_insert(new_action);
free(new_action);
}
database_transaction_commit(&lock);
cJSON_Delete(json);
json = macro_to_json(new_macro);
endpoint_response_json(response, 201, json);
cJSON_Delete(json);
macro_free(new_macro);
}

View file

@ -1,280 +0,0 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_macros.h>
#include <logger.h>
#include <command.h>
#include <models/macro_action.h>
#include <models/macro.h>
#include <models/relay.h>
#include <models/tag.h>
void
api_v1_macros_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
(void)nc;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
macro_t* macro = macro_get_by_uid(target_uid);
if(!macro)
{
M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response);
return;
}
cJSON *json = macro_to_json(macro);
endpoint_response_json(response, 200, json);
cJSON_Delete(json);
macro_free(macro);
}
void
api_v1_macros_STR_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
(void)nc;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
macro_t* macro = macro_get_by_uid(target_uid);
if(!macro)
{
M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response);
return;
}
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
return;
}
database_transaction_lock lock;
database_transaction_begin(&lock);
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(cJSON_IsString(json_name) && (json_name->valuestring != NULL))
{
strncpy(macro->name, json_name->valuestring, MAX_NAME_LENGTH);
macro->name[MAX_NAME_LENGTH] = '\0';
if(macro_save(macro))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
return;
}
}
macro_action_delete_for_macro(macro->id);
cJSON *json_actions = cJSON_GetObjectItemCaseSensitive(json, "actions");
if(cJSON_IsArray(json_actions))
{
cJSON *json_action;
cJSON_ArrayForEach(json_action, json_actions)
{
cJSON *json_action_weekday = cJSON_GetObjectItemCaseSensitive(json_action, "weekday");
if(!cJSON_IsNumber(json_action_weekday) || (json_action_weekday->valueint < 0) || (json_action_weekday->valueint > 6))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a weekday");
return;
}
cJSON *json_action_schedule = cJSON_GetObjectItemCaseSensitive(json_action, "schedule");
if(!cJSON_IsObject(json_action_schedule))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a schedule");
return;
}
cJSON *json_action_schedule_uid = cJSON_GetObjectItemCaseSensitive(json_action_schedule, "id");
if(!cJSON_IsString(json_action_schedule_uid) || (json_action_schedule_uid->valuestring == NULL))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a schedule id");
return;
}
uuid_t action_schedule_uid;
if(schedule_uid_parse(json_action_schedule_uid->valuestring, action_schedule_uid))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action has a bad schedule id");
return;
}
schedule_t *action_schedule = schedule_get_by_uid(action_schedule_uid);
if(action_schedule == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action schedule was not found");
return;
}
int action_schedule_id = action_schedule->id;
schedule_free(action_schedule);
cJSON *json_action_relay = cJSON_GetObjectItemCaseSensitive(json_action, "relay");
if(!cJSON_IsObject(json_action_relay))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay");
return;
}
cJSON *json_action_relay_number = cJSON_GetObjectItemCaseSensitive(json_action_relay, "number");
if(!cJSON_IsNumber(json_action_relay_number))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay number");
return;
}
cJSON *json_action_relay_controller_uid = cJSON_GetObjectItemCaseSensitive(json_action_relay, "controller_id");
if(!cJSON_IsString(json_action_relay_controller_uid) || (json_action_relay_controller_uid->valuestring == NULL))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action is missing a relay controller id");
return;
}
uuid_t action_controller_uid;
if(uuid_parse(json_action_relay_controller_uid->valuestring, action_controller_uid))
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action has a bad relay controller id");
return;
}
controller_t *action_controller = controller_get_by_uid(action_controller_uid);
if(action_controller == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "at least one action relay controller was not found");
return;
}
int controller_id = action_controller->id;
int relay_num = json_action_relay_number->valueint;
controller_free(action_controller);
relay_t *action_relay = relay_get_for_controller(controller_id, relay_num);
if(action_relay == NULL)
{
database_transaction_rollback(&lock);
cJSON_Delete(json);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "action relay was not found");
return;
}
int action_relay_id = action_relay->id;
relay_free(action_relay);
macro_action_t *action = malloc(sizeof(macro_action_t));
action->macro_id = macro->id;
action->relay_id = action_relay_id;
action->schedule_id = action_schedule_id;
action->weekday = json_action_weekday->valueint;
macro_action_insert(action);
free(action);
}
}
database_transaction_commit(&lock);
cJSON_Delete(json);
json = macro_to_json(macro);
endpoint_response_json(response, 201, json);
cJSON_Delete(json);
macro_free(macro);
}
void
api_v1_macros_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)hm;
(void)nc;
const char *target_uid_str = args[0].value.v_str;
uuid_t target_uid;
if(uuid_parse(target_uid_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
macro_t* macro = macro_get_by_uid(target_uid);
if(!macro)
{
M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response);
return;
}
if(macro_remove(macro))
{
M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
}
else
{
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "deleted macro");
}
macro_free(macro);
}

View file

@ -1,85 +0,0 @@
#include <cJSON.h>
#include <macros.h>
#include <constants.h>
#include <endpoints/api_v1_macros.h>
#include <logger.h>
#include <command.h>
#include <models/macro_action.h>
#include <models/macro.h>
#include <models/relay.h>
#include <models/tag.h>
void
api_v1_macros_STR_execute_PUT(struct mg_connection *nc, struct http_message *hm, endpoint_args_t *args, endpoint_response_t *response)
{
(void)nc;
uuid_t target_uid;
if(uuid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
return;
}
macro_t* macro = macro_get_by_uid(target_uid);
if(!macro)
{
M_RESPONSE_404_NO_MACRO_FOUND_FOR_ID(response);
return;
}
char *weekday_str = find_query_param(hm->query_string, "weekday");
macro_action_t** macro_actions;
if (weekday_str != NULL) {
errno = 0;
char *end;
long weekday = strtol(weekday_str, &end, 10);
bool weekday_str_is_end = weekday_str == end;
free(weekday_str);
if (errno != 0 || weekday_str_is_end || weekday < 0 || weekday >= 7) {
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the query contains an invalid weekday");
return;
}
macro_actions = macro_action_get_for_macro_and_weekday(macro->id, weekday);
}
else {
macro_actions = macro_action_get_for_macro(macro->id);
}
database_transaction_lock lock;
database_transaction_begin(&lock);
for(int i = 0; macro_actions[i] != NULL; ++i)
{
macro_action_execute(macro_actions[i]);
}
database_transaction_commit(&lock);
int *target_relay_ids = macro_get_target_relay_ids(macro->id);
for(int i = 0; target_relay_ids[i] != 0; ++i)
{
relay_t *target_relay = relay_get_by_id(target_relay_ids[i]);
if(!target_relay)
{
LOGGER_ERR("failed to load target relay from database\n");
continue;
}
command_relay_schedules_set(target_relay);
relay_free(target_relay);
}
free(target_relay_ids);
macro_action_free_list(macro_actions);
macro_free(macro);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "macro got executed");
}

View file

@ -16,13 +16,17 @@ api_v1_relays_tag_STR_GET(struct mg_connection *nc, struct http_message *hm, end
int tag_id = tag_get_id(args[0].value.v_str);
if(tag_id == 0)
{
M_RESPONSE_404_NO_TAG_FOUND(response);
static const char content[] = "tag was not found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id);
if(relays_ids == NULL)
{
M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
LOGGER_ERR("failed to load relays for tag from database\n");
static const char content[] = "failed to load relays for tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
return;
}

View file

@ -16,25 +16,29 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL))
{
LOGGER_DEBUG("no name for schedule provided\n");
cJSON_Delete(json);
M_RESPONSE_400_NO_NAME(response);
static const char content[] = "no name for schedule provided";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods");
if(!cJSON_IsArray(json_periods))
{
LOGGER_DEBUG("no periods for schedule provided\n");
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain periods");
static const char content[] = "no periods for schedule provided";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -44,9 +48,11 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOGGER_DEBUG("invalid tag in tags\n");
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one invalid tag");
static const char content[] = "invalid tag in tags";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
}
@ -72,18 +78,22 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL))
{
LOGGER_DEBUG("period is missing start\n");
cJSON_Delete(json);
schedule_free(new_schedule);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without a start");
static const char content[] = "one period is missing a start";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL))
{
LOGGER_DEBUG("period is missing end\n");
cJSON_Delete(json);
schedule_free(new_schedule);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without an end");
static const char content[] = "one period is missing an end";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -91,18 +101,22 @@ api_v1_schedules_POST(struct mg_connection *nc, struct http_message *hm, endpoin
uint16_t end;
if(period_helper_parse_hhmm(json_period_start->valuestring, &start))
{
LOGGER_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring);
cJSON_Delete(json);
schedule_free(new_schedule);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid start");
static const char content[] = "the start for one period is invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
if(period_helper_parse_hhmm(json_period_end->valuestring, &end))
{
LOGGER_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring);
cJSON_Delete(json);
schedule_free(new_schedule);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid end");
static const char content[] = "the end for one period is invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}

View file

@ -19,7 +19,10 @@ api_v1_schedules_STR_GET(struct mg_connection *nc, struct http_message *hm, endp
uuid_t target_uid;
if(schedule_uid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -27,7 +30,10 @@ api_v1_schedules_STR_GET(struct mg_connection *nc, struct http_message *hm, endp
if(!schedule)
{
M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
@ -47,7 +53,10 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
uuid_t target_uid;
if(schedule_uid_parse(args[0].value.v_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -55,7 +64,10 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
if(!schedule)
{
M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
@ -63,7 +75,8 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -124,10 +137,12 @@ api_v1_schedules_STR_PUT(struct mg_connection *nc, struct http_message *hm, endp
if(schedule_save(schedule))
{
LOGGER_ERR("failed to save schedule\n");
free(schedule);
cJSON_Delete(json);
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
static const char content[] = "failed to save schedule to database";
endpoint_response_text(response, 500, content, STRLEN(content));
return;
}
@ -175,7 +190,10 @@ api_v1_schedules_STR_DELETE(struct mg_connection *nc, struct http_message *hm, e
uuid_t target_uid;
if(schedule_uid_parse(target_uid_str, target_uid))
{
M_RESPONSE_400_NO_VALID_ID(response);
LOGGER_DEBUG("failed to unparse uid\n");
static const char content[] = "given id was invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -183,25 +201,33 @@ api_v1_schedules_STR_DELETE(struct mg_connection *nc, struct http_message *hm, e
if(!schedule)
{
M_RESPONSE_404_NO_SCHEDULE_FOUND_FOR_ID(response);
LOGGER_DEBUG("could not find a schedule for uid '%s'\n", args[0].value.v_str);
static const char content[] = "no schedule for id found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
if(schedule_is_protected(schedule))
{
schedule_free(schedule);
static const char content[] = "target schedule is protected";
endpoint_response_text(response, 403, content, STRLEN(content));
M_RESPONSE_403_PROTECTED_SCHEDULE(response);
schedule_free(schedule);
return;
}
if(schedule_remove(schedule))
{
M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
LOGGER_ERR("failed to remove schedule from database\n");
static const char content[] = "failed to remove schedule from database";
endpoint_response_text(response, 500, content, STRLEN(content));
}
else
{
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "the target schedule got deleted");
endpoint_response_text(response, 200, "", 0);
}
schedule_free(schedule);
return;
}

View file

@ -20,23 +20,29 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en
{
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL))
{
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a name for at least one schedule");
LOGGER_DEBUG("no name for schedule provided\n");
cJSON_Delete(json_list);
static const char content[] = "no name for schedule provided";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods");
if(!cJSON_IsArray(json_periods))
{
LOGGER_DEBUG("no periods for schedule provided\n");
cJSON_Delete(json_list);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain periods for at least one schedule");
static const char content[] = "no periods for schedule provided";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -46,9 +52,11 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOGGER_DEBUG("invalid tag in tags\n");
cJSON_Delete(json_list);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one invalid tag");
static const char content[] = "invalid tag in tags";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
}
@ -74,18 +82,22 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en
if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL))
{
LOGGER_DEBUG("period is missing start\n");
cJSON_Delete(json_list);
schedule_free(new_schedule);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without a start");
static const char content[] = "one period is missing a start";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL))
{
LOGGER_DEBUG("period is missing end\n");
cJSON_Delete(json_list);
schedule_free(new_schedule);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period without an end");
static const char content[] = "one period is missing an end";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
@ -93,18 +105,22 @@ api_v1_schedules_list_POST(struct mg_connection *nc, struct http_message *hm, en
uint16_t end;
if(period_helper_parse_hhmm(json_period_start->valuestring, &start))
{
LOGGER_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring);
cJSON_Delete(json_list);
schedule_free(new_schedule);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid start");
static const char content[] = "the start for one period is invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
if(period_helper_parse_hhmm(json_period_end->valuestring, &end))
{
LOGGER_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring);
cJSON_Delete(json_list);
schedule_free(new_schedule);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains at least one period with an invalid end");
static const char content[] = "the end for one period is invalid";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}

View file

@ -16,13 +16,17 @@ api_v1_schedules_tag_STR_GET(struct mg_connection *nc, struct http_message *hm,
int tag_id = tag_get_id(args[0].value.v_str);
if(tag_id == 0)
{
M_RESPONSE_404_NO_TAG_FOUND(response);
static const char content[] = "tag was not found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id);
if(schedules_ids == NULL)
{
M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
LOGGER_ERR("failed to load schedules for tag from database\n");
static const char content[] = "failed to load schedules for tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
return;
}

View file

@ -44,54 +44,56 @@ api_v1_tags_POST(struct mg_connection *nc, struct http_message *hm, endpoint_arg
if(json == NULL)
{
M_RESPONSE_400_NO_VALID_JSON(response);
static const char content[] = "no valid json was supplied";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
cJSON *json_tag = cJSON_GetObjectItemCaseSensitive(json, "tag");
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOGGER_DEBUG("no tag provided\n");
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request does not contain a tag");
return;
}
if(strlen(json_tag->valuestring) == 0)
{
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the request contains an empty tag");
static const char content[] = "no tag provided";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
if(tag_get_id(json_tag->valuestring))
{
LOGGER_DEBUG("tag existed already\n");
cJSON_Delete(json);
M_RESPONSE_MSG(LOGGER_DEBUG, response, 400, "the tag does already exist");
static const char content[] = "tag existed already";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
if(strlen(json_tag->valuestring) == 0)
{
LOGGER_DEBUG("tag is empty\n");
cJSON_Delete(json);
static const char content[] = "tag is empty";
endpoint_response_text(response, 400, content, STRLEN(content));
return;
}
if(tag_save(0, json_tag->valuestring))
{
M_RESPONSE_500_FAILED_TO_SAVE_TO_DATABASE(response);
LOGGER_DEBUG("tag could not be saved\n");
static const char content[] = "tag could not be saved";
endpoint_response_text(response, 500, content, STRLEN(content));
}
else
{
LOGGER_DEBUG("new tag saved\n");
size_t tag_len = strlen(json_tag->valuestring);
// NOLINT(clang-analyzer-unix.Malloc): The endpoint response will be freed later.
char *tag = malloc(sizeof(char) * (tag_len + 1));
strcpy(tag, json_tag->valuestring);
tag[tag_len] = '\0';
endpoint_response_text(response, 201, tag, 0);
free(tag);
endpoint_response_text(response, 201, json_tag->valuestring, 0);
}
cJSON_Delete(json);
return;
}

View file

@ -16,19 +16,26 @@ api_v1_tags_STR_GET(struct mg_connection *nc, struct http_message *hm, endpoint_
int tag_id = tag_get_id(args[0].value.v_str);
if(tag_id == 0)
{
M_RESPONSE_404_NO_TAG_FOUND(response);
static const char content[] = "tag was not found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
int *relays_ids = junction_tag_get_relays_for_tag_id(tag_id);
if(relays_ids == NULL)
{
M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
LOGGER_ERR("failed to load relays for tag from database\n");
static const char content[] = "failed to load relays for tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
return;
}
int *schedules_ids = junction_tag_get_schedules_for_tag_id(tag_id);
if(schedules_ids == NULL)
{
M_RESPONSE_500_FAILED_TO_READ_FROM_DATABASE(response);
LOGGER_ERR("failed to load schedules for tag from database\n");
static const char content[] = "failed to load schedules for tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
return;
}
@ -84,16 +91,20 @@ api_v1_tags_STR_DELETE(struct mg_connection *nc, struct http_message *hm, endpoi
int tag_id = tag_get_id(args[0].value.v_str);
if(tag_id == 0)
{
M_RESPONSE_404_NO_TAG_FOUND(response);
static const char content[] = "tag was not found";
endpoint_response_text(response, 404, content, STRLEN(content));
return;
}
if(tag_remove(tag_id))
{
M_RESPONSE_500_FAILED_TO_DELETE_FROM_DATABASE(response);
LOGGER_ERR("failed to remove tag from database\n");
static const char content[] = "failed to remove tag from database";
endpoint_response_text(response, 500, content, STRLEN(content));
}
else
{
M_RESPONSE_MSG(LOGGER_DEBUG, response, 200, "the tag got deleted");
endpoint_response_text(response, 200, "", 0);
}
}

View file

@ -17,17 +17,16 @@ static char*
add_extra_headers(char *extra_headers)
{
char *result;
size_t std_headers_len = strlen(global_config->http_server_opts.extra_headers);
size_t std_headers_len = strlen(global_config.http_server_opts.extra_headers);
if(extra_headers == NULL)
{
result = malloc(sizeof(char) * (std_headers_len + 1));
strcpy(result, global_config->http_server_opts.extra_headers);
result[std_headers_len] = '\0';
strcpy(result, global_config.http_server_opts.extra_headers);
return result;
}
result = malloc(sizeof(char) * (std_headers_len + strlen(extra_headers) + 3));
sprintf(result, "%s\r\n%s", global_config->http_server_opts.extra_headers, extra_headers);
sprintf(result, "%s\r\n%s", global_config.http_server_opts.extra_headers, extra_headers);
return result;
}
@ -45,6 +44,11 @@ send_response(struct mg_connection *nc, endpoint_response_t *response)
free(response_headers);
free(extra_headers);
if(response->alloced_content)
{
free((char*)response->content);
}
}
}
@ -78,18 +82,15 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm)
endpoint_t *endpoint = router_find_endpoint(hm->uri.p, hm->uri.len, &hm->method);
endpoint_response_t response;
response.content = NULL;
response.alloced_content = false;
M_RESPONSE_MSG(LOGGER_NONE, &response, 500, "server did not create a response");
static const char content[] = "the server did not create a response";
endpoint_response_text(&response, 500, content, STRLEN(content));
if(!endpoint)
{
/* Normalize path - resolve "." and ".." (in-place). */
if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) {
mg_http_send_error(nc, 400, global_config->http_server_opts.extra_headers);
mg_http_send_error(nc, 400, global_config.http_server_opts.extra_headers);
LOGGER_DEBUG("failed to normalize uri %.*s\n", hm->uri.len, hm->uri.p);
endpoint_response_free_content(&response);
return;
}
LOGGER_DEBUG("no endpoint found - serving file\n");
@ -104,23 +105,24 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm)
++request_file;
}
char *request_file_path = malloc(sizeof(char) * (strlen(request_file) + strlen(global_config->content_dir) + 2));
sprintf(request_file_path, "%s/%s", global_config->content_dir, request_file);
char *request_file_path = malloc(sizeof(char) * (strlen(request_file) + strlen(global_config.content_dir) + 2));
sprintf(request_file_path, "%s/%s", global_config.content_dir, request_file);
int access_result = access(request_file_path, R_OK);
free(request_file_path);
free(request_file_org);
if(access_result != -1)
{
response.status_code = 0;
mg_serve_http(nc, hm, global_config->http_server_opts);
mg_serve_http(nc, hm, global_config.http_server_opts);
LOGGER_DEBUG("serving %.*s\n", hm->uri.len, hm->uri.p);
endpoint_response_free_content(&response);
return;
}
else
{
LOGGER_DEBUG("serving 'not found'\n");
endpoint = router_get_not_found_endpoint();
}
}
if(endpoint->method == HTTP_METHOD_OPTIONS)
{
@ -146,7 +148,6 @@ handle_http_request(struct mg_connection *nc, struct http_message *hm)
send_response(nc, &response);
}
endpoint_response_free_content(&response);
LOGGER_DEBUG("freeing endpoint args\n");
for(int i = 0; i < endpoint->args_count; ++i)
{

View file

@ -9,7 +9,7 @@
#include <models/relay.h>
static void
handle_mqtt_publish_controller(char **topic_save, int controller_id, const char *payload)
handle_mqtt_publish_controller(char **topic_save, int controller_id, char *payload)
{
(void)controller_id;
(void)payload;

View file

@ -13,11 +13,8 @@ helper_connect_tcp_server(char* host, uint16_t port)
char port_str[6];
sprintf(port_str, "%d", port);
int s;
int status;
struct addrinfo *res;
struct addrinfo hints;
int s, status;
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; //set IP Protocol flag (IPv4 or IPv6 - we don't care)
hints.ai_socktype = SOCK_STREAM; //set socket flag
@ -30,7 +27,7 @@ helper_connect_tcp_server(char* host, uint16_t port)
//res got filled out by getaddrinfo() for us
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
if (connect(s, res->ai_addr, res->ai_addrlen) != 0) {
if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
LOGGER_ERR("connect() failed\n");
freeaddrinfo(res);
return -1;

View file

@ -15,20 +15,21 @@ get_uid_for_user(char *user)
{
return getuid();
}
struct passwd pwd;
struct passwd *result = NULL;
struct passwd *pwd = calloc(1, sizeof(struct passwd));
size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
char *buffer = malloc(buffer_len);
getpwnam_r(user, &pwd, buffer, buffer_len, &result);
getpwnam_r(user, pwd, buffer, buffer_len, &pwd);
if(result == NULL)
if(pwd == NULL)
{
LOGGER_CRIT("couldn't find user to drop privileges\n");
exit(1);
}
uid_t result = pwd->pw_uid;
free(buffer);
return result->pw_uid;
free(pwd);
return result;
}
static gid_t
@ -38,27 +39,28 @@ get_gid_for_group(char *group)
{
return getgid();
}
struct group grp;
struct group *result = NULL;
struct group *grp = calloc(1, sizeof(struct group));
size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
char *buffer = malloc(buffer_len);
getgrnam_r(group, &grp, buffer, buffer_len, &result);
getgrnam_r(group, grp, buffer, buffer_len, &grp);
if(result == NULL)
if(grp == NULL)
{
LOGGER_CRIT("couldn't find group to drop privileges\n");
exit(1);
}
gid_t result = grp->gr_gid;
free(buffer);
return result->gr_gid;
free(grp);
return result;
}
int
helper_drop_privileges()
{
uid_t uid = get_uid_for_user(global_config->user);
gid_t gid = get_gid_for_group(global_config->group);
uid_t uid = get_uid_for_user(global_config.user);
gid_t gid = get_gid_for_group(global_config.group);
LOGGER_DEBUG("drop privileges to %lu:%lu\n", uid, gid);

View file

@ -1,37 +0,0 @@
#include <string.h>
#include <mongoose.h>
char*
find_query_param(struct mg_str query_mg_str, char* search_key)
{
if (query_mg_str.len == 0) {
return NULL;
}
// Convert query string to null-terminated string
char* query = malloc(sizeof(char) * (query_mg_str.len + 1));
strncpy(query, query_mg_str.p, query_mg_str.len);
query[query_mg_str.len] = '\0';
char* result = NULL;
// Tokenize query string
char *token = strtok(query, "&");
while (token != NULL) {
char *key = strtok(token, "=");
char *val = strtok(NULL, "=");
if (key != NULL && val != NULL) {
if (strcmp(key, search_key) == 0) {
size_t val_len = strlen(val);
result = malloc(sizeof(char) * (val_len + 1));
strncpy(result, val, val_len + 1);
result[val_len] = '\0'; // Ensure null termination
break;
}
}
token = strtok(NULL, "&");
}
free(query);
return result;
}

View file

@ -1,12 +1,11 @@
#include <time.h>
#include <constants.h>
#include <helpers.h>
int
helper_get_weekday(const struct tm *time_struct)
{
int wday_sun_sat = time_struct->tm_wday;
int wday_mon_sun = (wday_sun_sat + (DAYS_PER_WEEK - 1)) % DAYS_PER_WEEK;
int wday_mon_sun = (wday_sun_sat + 6) % 7;
return wday_mon_sun;
}

View file

@ -5,29 +5,25 @@
#include <argparse.h>
#include <config.h>
#include <cli.h>
#include <logger.h>
#include <helpers.h>
#include <version.h>
cli_t *global_cli;
static const char *const usage[] = {
"core [options] [[--] args]",
"core [options]",
NULL,
};
void
cli_parse(int argc, const char **argv, cli_t *cli)
helper_parse_cli(int argc, const char **argv, config_t *config)
{
cli->config_file = NULL;
int version = 0;
struct argparse_option options[] =
{
OPT_HELP(),
OPT_GROUP("Basic options"),
OPT_STRING('c', "config", &cli->config_file, "path to config file", NULL, 0, OPT_NONEG),
OPT_STRING('c', "config", &config->file, "path to config file", NULL, 0, OPT_NONEG),
OPT_BOOLEAN('v', "version", &version, "print version", NULL, 0, OPT_NONEG),
OPT_END(),
};
@ -39,11 +35,29 @@ cli_parse(int argc, const char **argv, cli_t *cli)
"\nA brief description of what the program does and how it works.",
"\nAdditional description of the program after the description of the arguments."
);
argparse_parse(&argparse, argc, argv);
argc = argparse_parse(&argparse, argc, argv);
if(version)
{
printf("%s\n", EMGAUWA_CORE_VERSION);
exit(0);
}
if(argc == 1)
{
config->run_type = RUN_TYPE_INVALID;
if(strcmp(argv[0], "start") == 0)
{
config->run_type = RUN_TYPE_START;
return;
}
LOGGER_CRIT("bad action '%s' given ('start')\n", argv[0]);
exit(1);
}
else
{
LOGGER_CRIT("no action given ('start')\n");
exit(1);
}
return;
}

View file

@ -17,11 +17,10 @@ const char *COLOR_EMERG = COLOR_MAGENTA;
void
logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...)
{
if(global_config->logging.level < level || level == LOG_NONE )
if(global_config.log_level < level)
{
return;
}
va_list args;
const char *level_str;
const char *color;
@ -63,34 +62,24 @@ logger_log(int level, const char *filename, int line, const char *func, const ch
time_t rawtime;
time(&rawtime);
strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime));
size_t timestamp_len = strlen(timestamp_str);
size_t buffer_size = 128;
buffer_size += timestamp_len;
buffer_size += strlen(filename);
buffer_size += strlen(func);
buffer_size += strlen(msg);
char *buffer = malloc(sizeof(char) * (128 + strlen(msg)));
sprintf(buffer, "%s[%5s] %s:%d:%s " COLOR_NONE "%s", color, level_str, filename, line, func, msg);
char *buffer = malloc(sizeof(char) * (buffer_size));
sprintf(buffer, "%s %s[%5s] %s:%d:%s " COLOR_NONE "%s", timestamp_str, color, level_str, filename, line, func, msg);
//fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func);
// start arg va_list and find log_len
va_list args;
va_start(args, msg);
size_t log_len = vsnprintf(NULL, 0, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
vsyslog(level, buffer, args);
va_end(args);
char *log_line = malloc(sizeof(char) * (log_len + 1));
// start arg va_list again and write log_line
char *buffer_timed = malloc(sizeof(char) * (strlen(timestamp_str) + strlen(buffer) + 2));
sprintf(buffer_timed, "%s %s", timestamp_str, buffer);
va_start(args, msg);
vsprintf(log_line, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug
vfprintf(global_config.log_file, buffer_timed, args);
fflush(global_config.log_file);
va_end(args);
syslog(level, "%s", log_line + timestamp_len + 1);
fprintf(global_config->logging.file, "%s", log_line);
fflush(global_config->logging.file);
free(buffer);
free(log_line);
free(buffer_timed);
}

View file

@ -1,28 +1,28 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <syslog.h>
#include <mongoose.h>
#include <confini.h>
#include <cache.h>
#include <cli.h>
#include <router.h>
#include <logger.h>
#include <config.h>
#include <database.h>
#include <enums.h>
#include <handlers.h>
#include <enums.h>
#include <helpers.h>
#include <logger.h>
#include <models/controller.h>
#include <router.h>
#include <status.h>
#include <models/controller.h>
static struct mg_mgr mgr;
static void
terminate(int signum)
{
LOGGER_INFO("terminating core (%d)\n", signum);
LOGGER_INFO("terminating controller (%d)\n", signum);
mg_mgr_free(&mgr);
@ -30,7 +30,6 @@ terminate(int signum)
router_free();
status_free();
cache_free();
config_free();
closelog();
@ -52,27 +51,54 @@ main(int argc, const char** argv)
signal(SIGABRT, terminate);
signal(SIGTERM, terminate);
openlog("emgauwa-core", 0, LOG_USER);
setlogmask(LOG_UPTO(LOG_DEBUG));
setlogmask(LOG_UPTO(LOG_INFO));
/******************** LOAD CONFIG ********************/
config_init();
global_config.file = "core.ini";
global_config.discovery_port = 4421;
global_config.mqtt_port = 1885;
global_config.server_port = 5000;
cli_t cli;
cli_parse(argc, argv, &cli);
global_config.log_level = LOG_INFO;
global_config.log_file = stdout;
config_load(global_config, cli.config_file);
strcpy(global_config.user, "");
strcpy(global_config.group, "");
if(global_config->logging.file == NULL)
strcpy(global_config.content_dir, ".");
strcpy(global_config.not_found_file, "404.html");
strcpy(global_config.not_found_file_type, "text/html");
strcpy(global_config.not_found_content, "404 - NOT FOUND");
strcpy(global_config.not_found_content_type, "text/plain");
helper_parse_cli(argc, argv, &global_config);
FILE * const ini_file = fopen(global_config.file, "rb");
if(ini_file == NULL)
{
global_config->logging.file = stdout;
LOGGER_CRIT("config file '%s' was not found\n", global_config.file);
exit(1);
}
if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, config_load, &global_config))
{
LOGGER_CRIT("unable to parse ini file\n");
exit(1);
}
if(global_config->include)
fclose(ini_file);
memset(&global_config.http_server_opts, 0, sizeof(global_config.http_server_opts));
global_config.http_server_opts.document_root = global_config.content_dir;
global_config.http_server_opts.enable_directory_listing = "no";
global_config.http_server_opts.extra_headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *";
if(global_config.log_file == NULL)
{
config_load_directory(global_config, global_config->include);
global_config.log_file = stdout;
}
openlog("emgauwa-core", 0, LOG_USER);
/******************** SETUP CONNECTION ********************/
@ -82,20 +108,20 @@ main(int argc, const char** argv)
mg_mgr_init(&mgr, NULL);
char address[100];
sprintf(address, "tcp://0.0.0.0:%u", global_config->ports.server);
sprintf(address, "tcp://0.0.0.0:%u", global_config.server_port);
struct mg_connection *c_http = mg_bind(&mgr, address, handler_http);
if(c_http == NULL)
{
LOGGER_CRIT("failed to bind http server to port %u\n", global_config->ports.server);
LOGGER_CRIT("failed to bind http server to port %u\n", global_config.server_port);
exit(1);
}
mg_set_protocol_http_websocket(c_http);
sprintf(address, "tcp://0.0.0.0:%u", global_config->ports.mqtt);
sprintf(address, "tcp://0.0.0.0:%u", global_config.mqtt_port);
struct mg_connection *c_mqtt = mg_bind(&mgr, address, handler_mqtt);
if(c_mqtt == NULL)
{
LOGGER_CRIT("failed to bind mqtt server to port %u\n", global_config->ports.mqtt);
LOGGER_CRIT("failed to bind mqtt server to port %u\n", global_config.mqtt_port);
exit(1);
}
mg_mqtt_broker_init(&brk, NULL);
@ -104,11 +130,6 @@ main(int argc, const char** argv)
helper_drop_privileges();
memset(&global_config->http_server_opts, 0, sizeof(global_config->http_server_opts));
global_config->http_server_opts.document_root = global_config->content_dir;
global_config->http_server_opts.enable_directory_listing = "no";
global_config->http_server_opts.extra_headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Allow-Methods: *";
/******************** INIT COMPONENTS ********************/

View file

@ -35,8 +35,6 @@ static controller_t*
controller_db_select_mapper(sqlite3_stmt *stmt)
{
controller_t *new_controller = malloc(sizeof(controller_t));
new_controller->id = 0;
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
@ -52,16 +50,14 @@ controller_db_select_mapper(sqlite3_stmt *stmt)
new_controller->id = sqlite3_column_int(stmt, i);
break;
case 'p': // ip
strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), IP_LENGTH);
new_controller->ip[IP_LENGTH] = '\0';
strncpy(new_controller->ip, (const char*)sqlite3_column_text(stmt, i), 16);
break;
default: // ignore columns not implemented
break;
}
break;
case 'n': // name
strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
new_controller->name[MAX_NAME_LENGTH] = '\0';
strncpy(new_controller->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'p': // port
new_controller->port = sqlite3_column_int(stmt, i);
@ -106,11 +102,13 @@ controller_db_select(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting controllers from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_controllers[row] = NULL;
return all_controllers;
@ -119,8 +117,7 @@ controller_db_select(sqlite3_stmt *stmt)
int
controller_save(controller_t *controller)
{
database_transaction_lock lock;
database_transaction_begin(&lock);
int opened_transaction = database_transaction_begin();
sqlite3_stmt *stmt;
if(controller->id)
@ -145,7 +142,10 @@ controller_save(controller_t *controller)
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
database_transaction_rollback(&lock);
if(opened_transaction)
{
database_transaction_rollback();
}
}
else
{
@ -154,7 +154,10 @@ controller_save(controller_t *controller)
controller->id = sqlite3_last_insert_rowid(global_database);
}
database_transaction_commit(&lock);
if(opened_transaction)
{
database_transaction_commit();
}
}
cache_invalidate_controller(controller->id);

View file

@ -40,16 +40,14 @@ junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids)
static const char query_base[] = "INSERT INTO junction_relay_schedule (weekday, schedule_id, relay_id) VALUES";
static const char query_extender[] = " (?, ?, ?)";
size_t query_len = M_STRLEN(query_base) + (7 * (M_STRLEN(query_extender) + 1)) + 1;
size_t query_len = STRLEN(query_base) + (7 * (STRLEN(query_extender) + 1)) + 1;
char *query = malloc(sizeof(char) * query_len + 1);
strncpy(query, query_base, query_len);
query[query_len] = '\0';
query_len -= M_STRLEN(query_base);
query_len -= STRLEN(query_base);
for(int i = 0; i < 7; ++i)
{
strncat(query, query_extender, query_len);
query_len -= M_STRLEN(query_extender);
query_len -= STRLEN(query_extender);
char *query_divider = (i < 7 - 1) ? "," : ";";
strncat(query, query_divider, query_len);
query_len -= 1;
@ -64,18 +62,16 @@ junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids)
sqlite3_bind_int(stmt, i * 3 + 3, relay_id);
}
free(query);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOGGER_ERR("error inserting data: %s", sqlite3_errmsg(global_database));
return 1;
return false;
}
sqlite3_finalize(stmt);
return 0;
return true;
}
int
@ -91,14 +87,3 @@ junction_relay_schedule_remove_for_relay(int relay_id)
return rc == SQLITE_DONE;
}
int*
junction_relay_schedule_get_relay_ids_with_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT DISTINCT relay_id FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return database_helper_get_ids(stmt);
}

View file

@ -67,16 +67,14 @@ junction_tag_insert_list(int *tag_ids, int relay_id, int schedule_id, int count)
static const char query_base[] = "INSERT INTO junction_tag(tag_id, schedule_id, relay_id) VALUES";
static const char query_extender[] = " (?, ?, ?)";
size_t query_len = M_STRLEN(query_base) + (count * (M_STRLEN(query_extender) + 1)) + 1;
char *query = malloc(sizeof(char) * (query_len + 1));
size_t query_len = STRLEN(query_base) + (count * (STRLEN(query_extender) + 1)) + 1;
char *query = malloc(sizeof(char) * query_len + 1);
strncpy(query, query_base, query_len);
query[query_len] = '\0';
query_len -= M_STRLEN(query_base);
query_len -= STRLEN(query_base);
for(int i = 0; i < count; ++i)
{
strncat(query, query_extender, query_len);
query_len -= M_STRLEN(query_extender);
query_len -= STRLEN(query_extender);
char *query_divider = (i < count - 1) ? "," : ";";
strncat(query, query_divider, query_len);
query_len -= 1;
@ -107,8 +105,6 @@ junction_tag_insert_list(int *tag_ids, int relay_id, int schedule_id, int count)
}
}
free(query);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{

View file

@ -1,328 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cache.h>
#include <cJSON.h>
#include <logger.h>
#include <database.h>
#include <models/macro.h>
#include <models/macro_action.h>
#include <models/schedule.h>
#include <models/tag.h>
static int
db_update_insert(macro_t *macro, sqlite3_stmt *stmt)
{
LOGGER_DEBUG("saving macro '%s' into database (id: %d)\n", macro->name, macro->id);
int rc;
sqlite3_bind_int(stmt, 1, macro->id);
sqlite3_bind_blob(stmt, 2, macro->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, macro->name, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}
static macro_t*
macro_db_select_mapper(sqlite3_stmt *stmt)
{
macro_t *new_macro = malloc(sizeof(macro_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i': // id
new_macro->id = sqlite3_column_int(stmt, i);
break;
case 'n': // name
strncpy(new_macro->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
new_macro->name[MAX_NAME_LENGTH] = '\0';
break;
case 'u': // uid
uuid_copy(new_macro->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
return new_macro;
}
static macro_t**
macro_db_select(sqlite3_stmt *stmt)
{
macro_t **all_macros = malloc(sizeof(macro_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
macro_t *new_macro = macro_db_select_mapper(stmt);
row++;
all_macros = (macro_t**)realloc(all_macros, sizeof(macro_t*) * (row + 1));
all_macros[row - 1] = new_macro;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
LOGGER_ERR("error selecting macros from database: %s\n", sqlite3_errstr(s));
break;
}
}
sqlite3_finalize(stmt);
all_macros[row] = NULL;
return all_macros;
}
int
macro_save(macro_t *macro)
{
database_transaction_lock lock;
database_transaction_begin(&lock);
sqlite3_stmt *stmt;
if(macro->id)
{
sqlite3_prepare_v2(global_database, "UPDATE macros SET uid = ?2, name = ?3 WHERE id=?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO macros(uid, name) values (?2, ?3);", -1, &stmt, NULL);
}
int result = db_update_insert(macro, stmt);
if(result)
{
if(macro->id)
{
LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
database_transaction_rollback(&lock);
}
else
{
if(!macro->id)
{
macro->id = sqlite3_last_insert_rowid(global_database);
}
database_transaction_commit(&lock);
}
cache_invalidate_macro(macro->id);
return result;
}
int
macro_remove(macro_t *macro)
{
sqlite3_stmt *stmt;
if(macro->id == 0)
{
return 0;
}
sqlite3_prepare_v2(global_database, "DELETE FROM macros WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro->id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
cache_invalidate_macro(macro->id);
return rc != SQLITE_DONE;
}
void
macro_free(macro_t *macro)
{
free(macro);
}
void
macro_free_list(macro_t **macros)
{
for(int i = 0; macros[i] != NULL; ++i)
{
macro_free(macros[i]);
}
free(macros);
}
cJSON*
macro_to_json(macro_t *macro)
{
cJSON *json;
char *cached = cache_get_json_macro(macro->id);
if(cached)
{
json = cJSON_CreateRaw(cached);
free(cached);
return json;
}
char uuid_str[UUID_STR_LEN];
uuid_unparse(macro->uid, uuid_str);
LOGGER_DEBUG("JSONifying macro %s\n", uuid_str);
json = cJSON_CreateObject();
cJSON *json_name = cJSON_CreateString(macro->name);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "name", json_name);
cJSON *json_id = cJSON_CreateString(uuid_str);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "id", json_id);
cJSON *json_actions = cJSON_CreateArray();
macro_action_t **macro_actions = macro_action_get_for_macro(macro->id);
for(int i = 0; macro_actions[i] != NULL; ++i)
{
cJSON *json_action = cJSON_CreateObject();
cJSON *json_action_weekday = cJSON_CreateNumber(macro_actions[i]->weekday);
if(json_action_weekday == NULL)
{
LOGGER_DEBUG("failed to create weekday from number %d\n", macro_actions[i]->weekday);
continue;
}
relay_t *relay = relay_get_by_id(macro_actions[i]->relay_id);
if(!relay)
{
LOGGER_DEBUG("failed to get relay\n");
continue;
}
schedule_t *schedule = schedule_get_by_id(macro_actions[i]->schedule_id);
if(!schedule)
{
LOGGER_DEBUG("failed to get schedule\n");
relay_free(relay);
continue;
}
cJSON_AddItemToObject(json_action, "weekday", json_action_weekday);
cJSON_AddItemToObject(json_action, "relay", relay_to_json(relay, 0));
cJSON_AddItemToObject(json_action, "schedule", schedule_to_json(schedule));
cJSON_AddItemToArray(json_actions, json_action);
}
cJSON_AddItemToObject(json, "actions", json_actions);
macro_action_free_list(macro_actions);
char *json_str = cJSON_Print(json);
cache_put_json_macro(macro->id, json_str);
cJSON_Delete(json);
json = cJSON_CreateRaw(json_str);
free(json_str);
return json;
}
macro_t*
macro_get_by_id(int id)
{
LOGGER_DEBUG("getting macro [id=%d] from database\n", id);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macros WHERE id = ?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, id);
macro_t **sql_result = macro_db_select(stmt);
macro_t *result = sql_result[0];
free(sql_result);
return result;
}
macro_t*
macro_get_by_uid(uuid_t uid)
{
char uuid_str[UUID_STR_LEN];
uuid_unparse(uid, uuid_str);
LOGGER_DEBUG("getting macro [uid=%s] from database\n", uuid_str);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macros WHERE uid = ?1;", -1, &stmt, NULL);
sqlite3_bind_blob(stmt, 1, uid, sizeof(uuid_t), SQLITE_STATIC);
macro_t **sql_result = macro_db_select(stmt);
macro_t *result = sql_result[0];
free(sql_result);
return result;
}
macro_t**
macro_get_relay_weekdays(int relay_id)
{
LOGGER_DEBUG("getting macros [relay_id=%d] from database\n", relay_id);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT macros.* FROM macros INNER JOIN junction_relay_macro ON macros.id == junction_relay_macro.macro_id WHERE junction_relay_macro.relay_id = ?1 ORDER BY junction_relay_macro.weekday ASC", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return macro_db_select(stmt);
}
macro_t**
macro_get_all()
{
LOGGER_DEBUG("getting all macros from database\n");
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macros;", -1, &stmt, NULL);
return macro_db_select(stmt);
}
int*
macro_get_target_relay_ids(int macro_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT DISTINCT relay_id FROM macro_actions WHERE macro_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_id);
return database_helper_get_ids(stmt);
}

View file

@ -1,189 +0,0 @@
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <models/macro_action.h>
#include <logger.h>
#include <cache.h>
#include <database.h>
static macro_action_t*
macro_action_db_select_mapper(sqlite3_stmt *stmt)
{
macro_action_t *new_macro_action = malloc(sizeof(macro_action_t));
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'm': // macro_id
new_macro_action->macro_id = sqlite3_column_int(stmt, i);
break;
case 'r': // relay_id
new_macro_action->relay_id = sqlite3_column_int(stmt, i);
break;
case 's': // schedule_id
new_macro_action->schedule_id = sqlite3_column_int(stmt, i);
break;
case 'w': // weekday
new_macro_action->weekday = (uint8_t)sqlite3_column_int(stmt, i);
break;
default: // ignore columns not implemented
break;
}
}
return new_macro_action;
}
static macro_action_t**
macro_action_db_select(sqlite3_stmt *stmt)
{
macro_action_t **all_macro_actions = malloc(sizeof(macro_action_t*));
int row = 0;
for(;;)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
macro_action_t *new_macro_action = macro_action_db_select_mapper(stmt);
row++;
all_macro_actions = (macro_action_t**)realloc(all_macro_actions, sizeof(macro_action_t*) * (row + 1));
all_macro_actions[row - 1] = new_macro_action;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
LOGGER_ERR("error selecting macro_actions from database: %s\n", sqlite3_errstr(s));
break;
}
}
sqlite3_finalize(stmt);
all_macro_actions[row] = NULL;
return all_macro_actions;
}
int
macro_action_insert(macro_action_t *macro_action)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "INSERT INTO macro_actions(macro_id, relay_id, schedule_id, weekday) values (?1, ?2, ?3, ?4);", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_action->macro_id);
sqlite3_bind_int(stmt, 2, macro_action->relay_id);
sqlite3_bind_int(stmt, 3, macro_action->schedule_id);
sqlite3_bind_int(stmt, 4, macro_action->weekday);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
cache_invalidate_macro(macro_action->macro_id);
return rc != SQLITE_DONE;
}
int
macro_action_delete_for_macro(int macro_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "DELETE FROM macro_actions WHERE macro_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_id);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
cache_invalidate_macro(macro_id);
return rc != SQLITE_DONE;
}
macro_action_t**
macro_action_get_for_macro(int macro_id)
{
LOGGER_DEBUG("getting macro_actions [macro_id=%d] from database\n", macro_id);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macro_actions WHERE macro_id=?", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_id);
return macro_action_db_select(stmt);
}
macro_action_t**
macro_action_get_for_macro_and_weekday(int macro_id, int weekday)
{
LOGGER_DEBUG("getting macro_actions [macro_id=%d weekday=%d] from database\n", macro_id, weekday);
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM macro_actions WHERE macro_id=? AND weekday=?", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, macro_id);
sqlite3_bind_int(stmt, 2, weekday);
return macro_action_db_select(stmt);
}
int
macro_action_execute(macro_action_t *macro_action)
{
schedule_t *schedule = schedule_get_by_id(macro_action->schedule_id);
if(!schedule)
{
return 1;
}
relay_t *relay = relay_get_by_id(macro_action->relay_id);
if(!relay)
{
free(schedule);
return 1;
}
schedule_free(relay->schedules[macro_action->weekday]);
relay->schedules[macro_action->weekday] = schedule;
return relay_save(relay);
}
void
macro_action_free_list(macro_action_t **macro_actions)
{
for(int i = 0; macro_actions[i] != NULL; ++i)
{
free(macro_actions[i]);
}
free(macro_actions);
}
int*
macro_action_get_macro_ids_with_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT macro_id FROM macro_actions WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return database_helper_get_ids(stmt);
}
int*
macro_action_get_macro_ids_with_relay(int relay_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT macro_id FROM macro_actions WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return database_helper_get_ids(stmt);
}

View file

@ -27,8 +27,9 @@ period_helper_parse_hhmm(const char *hhmm_str, uint16_t *hhmm)
}
}
uint16_t tmp_h = (uint16_t)strtol(&hhmm_str[0], NULL, 10);
uint16_t tmp_m = (uint16_t)strtol(&hhmm_str[3], NULL, 10);
uint16_t tmp_h, tmp_m;
tmp_h = (uint16_t)strtol(&hhmm_str[0], NULL, 10);
tmp_m = (uint16_t)strtol(&hhmm_str[3], NULL, 10);
if(tmp_h > 24 || tmp_m >= 60)
{

View file

@ -1,19 +1,18 @@
#include <sqlite3.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cJSON.h>
#include <cache.h>
#include <constants.h>
#include <database.h>
#include <cJSON.h>
#include <logger.h>
#include <models/controller.h>
#include <models/junction_relay_schedule.h>
#include <models/junction_tag.h>
#include <models/relay.h>
#include <models/schedule.h>
#include <models/tag.h>
#include <database.h>
#include <status.h>
#include <models/relay.h>
#include <models/controller.h>
#include <models/schedule.h>
#include <models/junction_tag.h>
#include <models/junction_relay_schedule.h>
#include <models/tag.h>
static int
db_update_insert(relay_t *relay, sqlite3_stmt *stmt)
@ -36,7 +35,6 @@ static relay_t*
relay_db_select_mapper(sqlite3_stmt *stmt)
{
relay_t *new_relay = malloc(sizeof(relay_t));
new_relay->id = 0;
new_relay->is_on = 0;
for(int i = 0; i < sqlite3_column_count(stmt); i++)
@ -54,8 +52,7 @@ relay_db_select_mapper(sqlite3_stmt *stmt)
switch(name[1])
{
case 'a': // name
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
new_relay->name[MAX_NAME_LENGTH] = '\0';
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'u': // number
new_relay->number = sqlite3_column_int(stmt, i);
@ -69,7 +66,7 @@ relay_db_select_mapper(sqlite3_stmt *stmt)
}
}
memset(new_relay->schedules, 0, sizeof(schedule_t*) * DAYS_PER_WEEK);
memset(new_relay->schedules, 0, sizeof(schedule_t*) * 7);
relay_reload_schedules(new_relay);
relay_reload_active_schedule(new_relay);
@ -103,11 +100,13 @@ relay_db_select(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting relays from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_relays[row] = NULL;
return all_relays;
@ -116,8 +115,7 @@ relay_db_select(sqlite3_stmt *stmt)
int
relay_save(relay_t *relay)
{
database_transaction_lock lock;
database_transaction_begin(&lock);
int opened_transaction = database_transaction_begin();
sqlite3_stmt *stmt;
if(relay->id)
@ -142,7 +140,10 @@ relay_save(relay_t *relay)
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
database_transaction_rollback(&lock);
if(opened_transaction)
{
database_transaction_rollback();
}
}
else
{
@ -155,14 +156,17 @@ relay_save(relay_t *relay)
junction_relay_schedule_remove_for_relay(relay->id);
LOGGER_DEBUG("rebuilding relay_schedule junction\n");
int schedule_ids[DAYS_PER_WEEK];
for(int i = 0; i < DAYS_PER_WEEK; ++i)
int schedule_ids[7];
for(int i = 0; i < 7; ++i)
{
schedule_ids[i] = relay->schedules[i]->id;
}
junction_relay_schedule_insert_weekdays(relay->id, schedule_ids);
database_transaction_commit(&lock);
if(opened_transaction)
{
database_transaction_commit();
}
}
cache_invalidate_relay(relay->id, -1);
@ -184,16 +188,17 @@ relay_reload_schedules(relay_t *relay)
{
schedule_t **schedules = schedule_get_relay_weekdays(relay->id);
uuid_t off_uid;
schedule_get_uid_off(off_uid);
uuid_t off_id;
memset(off_id, 0, sizeof(uuid_t));
memcpy(off_id, "off", 3);
int fill_with_off = 0;
for(int i = 0; i < DAYS_PER_WEEK; ++i)
for(int i = 0; i < 7; ++i)
{
if(schedules[i] == NULL || fill_with_off)
{
LOGGER_WARNING("got only %d/7 schedules for relay_id %d\n", i, relay->id);
relay->schedules[i] = schedule_get_by_uid(off_uid);
relay->schedules[i] = schedule_get_by_uid(off_id);
fill_with_off = 1;
}
@ -213,7 +218,7 @@ relay_reload_schedules(relay_t *relay)
void
relay_free(relay_t *relay)
{
for(int i = 0; i < DAYS_PER_WEEK; ++i)
for(int i = 0; i < 7; ++i)
{
schedule_free(relay->schedules[i]);
}
@ -306,7 +311,7 @@ relay_to_json(relay_t *relay, int status_relay)
cJSON_AddItemToObject(json, "active_schedule", json_active_schedule);
cJSON *json_schedules = cJSON_CreateArray();
for(int i = 0; i < DAYS_PER_WEEK; ++i)
for(int i = 0; i < 7; ++i)
{
cJSON *json_schedule = schedule_to_json(relay->schedules[i]);
cJSON_AddItemToArray(json_schedules, json_schedule);

View file

@ -37,8 +37,6 @@ schedule_db_select_mapper(sqlite3_stmt *stmt)
{
const uint16_t *periods_blob;
schedule_t *new_schedule = malloc(sizeof(schedule_t));
new_schedule->id = 0;
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
@ -48,8 +46,8 @@ schedule_db_select_mapper(sqlite3_stmt *stmt)
new_schedule->id = sqlite3_column_int(stmt, i);
break;
case 'n': // name
strncpy(new_schedule->name, (const char*)sqlite3_column_text(stmt, i), MAX_NAME_LENGTH);
new_schedule->name[MAX_NAME_LENGTH] = '\0';
strncpy(new_schedule->name, (const char*)sqlite3_column_text(stmt, i), 127);
new_schedule->name[127] = '\0';
break;
case 'p': // periods
periods_blob = sqlite3_column_blob(stmt, i);
@ -99,11 +97,13 @@ schedule_db_select(sqlite3_stmt *stmt)
{
break;
}
else
{
LOGGER_ERR("error selecting schedules from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_schedules[row] = NULL;
return all_schedules;
@ -112,8 +112,7 @@ schedule_db_select(sqlite3_stmt *stmt)
int
schedule_save(schedule_t *schedule)
{
database_transaction_lock lock;
database_transaction_begin(&lock);
int opened_transaction = database_transaction_begin();
sqlite3_stmt *stmt;
if(schedule->id)
@ -138,7 +137,10 @@ schedule_save(schedule_t *schedule)
LOGGER_ERR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
database_transaction_rollback(&lock);
if(opened_transaction)
{
database_transaction_rollback();
}
}
else
{
@ -147,7 +149,10 @@ schedule_save(schedule_t *schedule)
schedule->id = sqlite3_last_insert_rowid(global_database);
}
database_transaction_commit(&lock);
if(opened_transaction)
{
database_transaction_commit();
}
}
cache_invalidate_schedule(schedule->id);
@ -177,16 +182,18 @@ schedule_remove(schedule_t *schedule)
int
schedule_is_protected(schedule_t *schedule)
{
uuid_t tmp_uid;
uuid_t tmp_uuid;
schedule_get_uid_off(tmp_uid);
if(uuid_compare(schedule->uid, tmp_uid) == 0)
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(schedule->uid, tmp_uuid) == 0)
{
return 1;
}
schedule_get_uid_on(tmp_uid);
if(uuid_compare(schedule->uid, tmp_uid) == 0)
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(schedule->uid, tmp_uuid) == 0)
{
return 1;
}
@ -278,10 +285,9 @@ schedule_to_json(schedule_t *schedule)
continue;
}
char start_str[8];
char end_str[8];
char start_str[8], end_str[8];
period_t *period = &schedule->periods[i];
sprintf(start_str, "%02d:%02d", period->start / 60 , period->start % 60);
sprintf(start_str, "%02d:%02d", period->start / 60, period->start % 60);
sprintf(end_str, "%02d:%02d", period->end / 60, period->end % 60);
cJSON *json_period_start = cJSON_CreateString(start_str);
@ -358,10 +364,11 @@ schedule_get_by_id_or_off(int id)
return result;
}
uuid_t tmp_uid;
schedule_get_uid_off(tmp_uid);
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
return schedule_get_by_uid(tmp_uid);
return schedule_get_by_uid(tmp_uuid);
}
schedule_t*
@ -402,10 +409,11 @@ schedule_get_by_uid_or_off(uuid_t uid)
return result;
}
uuid_t tmp_uid;
schedule_get_uid_off(tmp_uid);
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
return schedule_get_by_uid(tmp_uid);
return schedule_get_by_uid(tmp_uuid);
}
schedule_t*
@ -456,12 +464,14 @@ schedule_uid_parse(const char *uid_str, uuid_t result)
{
if(strcmp("off", uid_str) == 0)
{
schedule_get_uid_off(result);
memset(result, 0, sizeof(uuid_t));
memcpy(result, "off", 3);
return 0;
}
if(strcmp("on", uid_str) == 0)
{
schedule_get_uid_on(result);
memset(result, 0, sizeof(uuid_t));
memcpy(result, "on", 2);
return 0;
}
@ -475,37 +485,23 @@ schedule_uid_parse(const char *uid_str, uuid_t result)
void
schedule_uid_unparse(const uuid_t uid, char *result)
{
uuid_t tmp_uid;
uuid_t tmp_uuid;
schedule_get_uid_off(tmp_uid);
if(uuid_compare(uid, tmp_uid) == 0)
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strncpy(result, "off", 4);
result[3] = '\0';
strcpy(result, "off");
return;
}
schedule_get_uid_on(tmp_uid);
if(uuid_compare(uid, tmp_uid) == 0)
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strncpy(result, "on", 3);
result[2] = '\0';
strcpy(result, "on");
return;
}
uuid_unparse(uid, result);
}
void
schedule_get_uid_off(uuid_t target)
{
uuid_clear(target);
strncpy((char*)target, "off", 4);
}
void
schedule_get_uid_on(uuid_t target)
{
uuid_clear(target);
strncpy((char*)target, "on", 3);
}

View file

@ -10,7 +10,6 @@
#include <endpoints/api_v1_controllers.h>
#include <endpoints/api_v1_relays.h>
#include <endpoints/api_v1_tags.h>
#include <endpoints/api_v1_macros.h>
#include <endpoints/api_v1_ws.h>
static endpoint_t endpoints[ROUTER_ENDPOINTS_MAX_COUNT];
@ -72,7 +71,7 @@ router_init()
router_register_endpoint("/api/v1/schedules/{str}", HTTP_METHOD_DELETE, api_v1_schedules_STR_DELETE);
router_register_endpoint("/api/v1/schedules/tag/{str}", HTTP_METHOD_GET, api_v1_schedules_tag_STR_GET);
router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_PUT, api_v1_controllers_discover_PUT);
router_register_endpoint("/api/v1/controllers/discover/", HTTP_METHOD_POST, api_v1_controllers_discover_POST);
router_register_endpoint("/api/v1/controllers/", HTTP_METHOD_GET, api_v1_controllers_GET);
router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_GET, api_v1_controllers_STR_GET);
router_register_endpoint("/api/v1/controllers/{str}", HTTP_METHOD_PUT, api_v1_controllers_STR_PUT);
@ -91,13 +90,6 @@ router_init()
router_register_endpoint("/api/v1/tags/{str}", HTTP_METHOD_GET, api_v1_tags_STR_GET);
router_register_endpoint("/api/v1/tags/{str}", HTTP_METHOD_DELETE, api_v1_tags_STR_DELETE);
router_register_endpoint("/api/v1/macros/", HTTP_METHOD_GET, api_v1_macros_GET);
router_register_endpoint("/api/v1/macros/", HTTP_METHOD_POST, api_v1_macros_POST);
router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_GET, api_v1_macros_STR_GET);
router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_PUT, api_v1_macros_STR_PUT);
router_register_endpoint("/api/v1/macros/{str}", HTTP_METHOD_DELETE, api_v1_macros_STR_DELETE);
router_register_endpoint("/api/v1/macros/{str}/execute", HTTP_METHOD_PUT, api_v1_macros_STR_execute_PUT);
router_register_endpoint("/api/v1/ws/relays", HTTP_METHOD_WEBSOCKET, api_v1_ws_relays);
}
@ -154,7 +146,7 @@ router_register_endpoint(const char *route, int method, endpoint_func_f func)
// +1 for NULL terminator
endpoint->route = malloc(sizeof(char*) * (route_parts_count + 1));
endpoint->route_keeper = malloc(sizeof(char) * (route_len + 1));
strncpy(endpoint->route_keeper, route, route_len + 1);
strncpy(endpoint->route_keeper, route, route_len);
endpoint->route_keeper[route_len] = '\0';
int route_part = 0;
@ -313,6 +305,7 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_
continue;
}
endpoints[i].possible_route = 0;
continue;
}
uri_token = strtok_r(NULL, delimiter, &uri_token_save);
++route_part;
@ -340,10 +333,8 @@ router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_
{
if(best_endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR)
{
size_t arg_value_str_len = strlen(best_endpoint->args[i].value.v_str);
char *arg_value_str = malloc(sizeof(char) * (arg_value_str_len + 1));
char *arg_value_str = malloc(sizeof(char) * (strlen(best_endpoint->args[i].value.v_str) + 1));
strcpy(arg_value_str, best_endpoint->args[i].value.v_str);
arg_value_str[arg_value_str_len] = '\0';
best_endpoint->args[i].value.v_str = arg_value_str;
}
}

View file

@ -0,0 +1,65 @@
[controller]
name = new emgauwa device
: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery-port = 4422
: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt-port = 1886
mqtt-host = localhost
relay-count = 10
database = controller.sqlite
log-level = debug
log-file = stdout
[relay-0]
driver = piface
pin = 0
inverted = 0
[relay-1]
driver = piface
pin = 1
inverted = 0
[relay-2]
driver = gpio
pin = 5
inverted = 1
[relay-3]
driver = gpio
pin = 4
inverted = 1
[relay-4]
driver = gpio
pin = 3
inverted = 1
[relay-5]
driver = gpio
pin = 2
inverted = 1
[relay-6]
driver = gpio
pin = 1
inverted = 1
pulse-duration = 3
[relay-7]
driver = gpio
pin = 0
inverted = 1
pulse-duration = 3
[relay-8]
driver = gpio
pin = 16
inverted = 1
[relay-9]
driver = gpio
pin = 15
inverted = 1

16
tests/core.testing.ini Normal file
View file

@ -0,0 +1,16 @@
[core]
server-port = 5000
database = core.sqlite
content-dir = /usr/share/webapps/emgauwa
not-found-file = 404.html
not-found-file-mime = text/html
not-found-content = 404 - NOT FOUND
not-found-content-type = text/plain
: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery-port = 4422
: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt-port = 1886
log-level = debug
log-file = stdout

View file

@ -1,66 +0,0 @@
[controller]
database = "emgauwa-controller.sqlite"
mqtt-host = "127.0.0.1"
[ports]
# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery = 4422
# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt = 1886
[logging]
level = "debug"
file = "stdout"
[[relays]]
driver = "piface"
pin = 0
inverted = 0
[[relays]]
driver = "piface"
pin = 1
inverted = 0
[[relays]]
driver = "gpio"
pin = 5
inverted = 1
[[relays]]
driver = "gpio"
pin = 4
inverted = 1
[[relays]]
driver = "gpio"
pin = 3
inverted = 1
[[relays]]
driver = "gpio"
pin = 2
inverted = 1
[[relays]]
driver = "gpio"
pin = 1
inverted = 1
pulse-duration = 3
[[relays]]
driver = "gpio"
pin = 0
inverted = 1
pulse-duration = 3
[[relays]]
driver = "gpio"
pin = 16
inverted = 1
[[relays]]
driver = "gpio"
pin = 15
inverted = 1

View file

@ -1,17 +0,0 @@
[core]
database = "emgauwa-core.sqlite"
include = "../emgauwa-core-testing.conf.d"
content-dir = "."
not-found-file = "404.html"
not-found-file-mime = "text/html"
[ports]
server = 5000
# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env
discovery = 4422
# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env
mqtt = 1886
[logging]
level = "debug"
file = "stdout"

View file

@ -1,3 +0,0 @@
[core]
not-found-content = '{"msg": "conf.d working"}'
not-found-content-type = "application/json"

View file

@ -1,9 +1,14 @@
#!/usr/bin/env sh
source_dir=$PWD
working_dir=$PWD/testing
working_dir=$PWD/testing_latest
working_bak=$PWD/testing_bak
alias valgrind_emgauwa="valgrind -s $2 --log-file=$working_dir/valgrind.log"
alias valgrind_emgauwa="valgrind $2 --log-file=$working_dir/valgrind.log"
rm -rf $working_bak
[ -d $working_dir ] && mv $working_dir $working_bak
mkdir -p $working_dir
cd $working_dir
@ -12,18 +17,15 @@ target_branch=$(git rev-parse --abbrev-ref HEAD)
if [ -z "$EMGAUWA_CONTROLLER_EXE" ]
then
mkdir -p controller
cd controller
echo "Trying to pull or clone controller"
git clone --quiet ssh://git@git.serguzim.me:3022/emgauwa/controller.git . >/dev/null 2>&1 || git pull >/dev/null || exit
git clone --quiet ssh://git@git.serguzim.me:3022/emgauwa/controller.git controller || exit
cd ./controller
git checkout dev >/dev/null 2>&1
git checkout $target_branch >/dev/null 2>&1
git checkout $2 >/dev/null 2>&1
echo "Building controller on branch $(git rev-parse --abbrev-ref HEAD)"
mkdir -p build
mkdir build
cd build
cmake -DWIRING_PI_DEBUG=on .. >/dev/null
@ -33,16 +35,20 @@ fi
echo "Emgauwa controller: $($EMGAUWA_CONTROLLER_EXE --version)"
$EMGAUWA_CONTROLLER_EXE -c $source_dir/emgauwa-controller-testing.conf >$working_dir/controller.log 2>&1 &
$EMGAUWA_CONTROLLER_EXE start -c $source_dir/controller.testing.ini >$working_dir/controller.log 2>&1 &
controller_id=$!
cd $working_dir
touch $working_dir/index.html
cp $1 $working_dir/core
cp $source_dir/core.testing.ini $working_dir/core.ini
valgrind_emgauwa $working_dir/core -c $source_dir/emgauwa-core-testing.conf >>$working_dir/core.log 2>&1 &
echo "=== invalids start (must exit) ===" >$working_dir/core.log
$working_dir/core >>$working_dir/core.log 2>&1
$working_dir/core INVALID_ACTION >>$working_dir/core.log 2>&1
echo "=== valid start ===" >>$working_dir/core.log
valgrind_emgauwa $working_dir/core start >>$working_dir/core.log 2>&1 &
core_id=$!
@ -62,12 +68,4 @@ test_result=$?
kill $core_id
kill $controller_id
timestamp=$(date -Iseconds)
for backup_file in core.log controller.log valgrind.log emgauwa-core.sqlite; do
mv $backup_file $timestamp.$backup_file
ln -sf $timestamp.$backup_file latest.$backup_file
done
cat latest.valgrind.log
exit $test_result

View file

@ -1,25 +0,0 @@
test_name: "[test_basics] Test basic calls"
stages:
- name: "[test_basics] get index"
request:
url: "http://localhost:5000/"
method: GET
response:
status_code: 200
- name: "[test_basics] get 404"
request:
url: "http://localhost:5000/invalid_url_for_testing_do_not_use"
method: GET
response:
status_code: 404
- name: "[test_basics] verify conf.d by 404"
request:
url: "http://localhost:5000/invalid_url_for_testing_do_not_use"
method: GET
response:
status_code: 404
json:
msg: "conf.d working"

View file

@ -3,7 +3,7 @@ test_name: Test basic controller functions
stages:
- name: "[controllers_basic] discover controllers"
request:
method: PUT
method: POST
url: "http://localhost:5000/api/v1/controllers/discover/"
response:
status_code: 200
@ -86,7 +86,7 @@ stages:
- name: "[controllers_basic] discover controllers again"
request:
method: PUT
method: POST
url: "http://localhost:5000/api/v1/controllers/discover/"
response:
status_code: 200

View file

@ -26,7 +26,7 @@ stages:
extra_kwargs:
relay_count: !int "{returned_relay_count:d}"
- name: "[controller_relays_basic] get controller relay"
- name: "[controller_relays_basic] get controller relays, check length"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/5"
@ -40,69 +40,3 @@ stages:
function: validate_relay:check_number
extra_kwargs:
number: 5
- name: "[controller_relays_basic] get controller relay with invalid uid"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/INVALID-UUID/relays/5"
response:
status_code: 400
- name: "[controller_relays_basic] get controller relay with unavailable uid"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/00000000-0000-0000-0000-000000000000/relays/5"
response:
status_code: 404
- name: "[controller_relays_basic] get controller relay with invalid number"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/not_a_number"
response:
status_code: 404
- name: "[controller_relays_basic] get controller relay with unavailable number"
request:
method: GET
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/9001"
response:
status_code: 404
- name: "[controller_relays_basic] pulse relay"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/6/pulse"
response:
status_code: 200
- name: "[controller_relays_basic] pulse relay with invalid uid"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/INVALID-UUID/relays/6/pulse"
response:
status_code: 400
- name: "[controller_relays_basic] pulse relay with unavailable uid"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/00000000-0000-0000-0000-000000000000/relays/6/pulse"
response:
status_code: 404
- name: "[controller_relays_basic] pulse relay with invalid number"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/not_a_number/pulse"
response:
status_code: 404
- name: "[controller_relays_basic] pulse relay with unavailable number"
request:
method: POST
url: "http://localhost:5000/api/v1/controllers/{returned_id}/relays/9001/pulse"
response:
status_code: 404

View file

@ -98,13 +98,6 @@ stages:
controller_id: "{returned_id}"
tag: "{returned_tag}"
- name: "[tags] get returned tag with relays and schedules"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/{returned_tag}"
response:
status_code: 200
- name: "[tags] get tags"
request:
method: GET
@ -113,56 +106,3 @@ stages:
status_code: 200
verify_response_with:
function: validate_tag:multiple
- name: "[tags] get unavailable tag"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/invalid_unavailable_tag"
response:
status_code: 404
- name: "[tags] post tag"
request:
method: POST
url: "http://localhost:5000/api/v1/tags/"
json:
tag: "unused_tag_1"
response:
status_code: 201
- name: "[tags] get posted tag"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/unused_tag_1"
response:
status_code: 200
- name: "[tags] delete posted tag"
request:
method: DELETE
url: "http://localhost:5000/api/v1/tags/unused_tag_1"
response:
status_code: 200
- name: "[tags] get deleted tag"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/unused_tag_1"
response:
status_code: 404
- name: "[tags] delete deleted tag again"
request:
method: DELETE
url: "http://localhost:5000/api/v1/tags/unused_tag_1"
response:
status_code: 404
- name: "[tags] get tags again"
request:
method: GET
url: "http://localhost:5000/api/v1/tags/"
response:
status_code: 200
verify_response_with:
function: validate_tag:multiple

View file

@ -11,9 +11,24 @@ def multiple(response):
for tag in response.json():
_verify_single(tag)
def find(response, tag):
print(response.json())
for response_tag in response.json():
if response_tag == tag:
return
assert False, "tag not found in list"
#def find(response, name=None, number=None, controller_id=None, tag=None):
# print(response.json())
# for tag in response.json():
# if number != None and number != tag.get("number"):
# continue
#
# if name != None and name != tag.get("name"):
# continue
#
# if controller_id != None and controller_id != tag.get("controller_id"):
# continue
#
# if tag != None:
# found_in_response = False
# for response_tag in tag.get("tags"):
# if response_tag == tag:
# found_in_response = True
# if not found_in_response:
# continue
# return
# assert False, "tag not found in list"

5016
vendor/confini.c vendored Normal file

File diff suppressed because it is too large Load diff

547
vendor/confini.h vendored Normal file
View file

@ -0,0 +1,547 @@
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
/**
@file confini.h
@brief libconfini header
@author Stefano Gioffr&eacute;
@copyright GNU General Public License, version 3 or any later version
@version 1.14.0
@date 2016-2020
@see https://madmurphy.github.io/libconfini
**/
#ifndef _LIBCONFINI_HEADER_
#define _LIBCONFINI_HEADER_
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* PRIVATE (HEADER-SCOPED) MACROS */
#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \
unsigned char NAME:SIZE;
#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL,
#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0,
#define _LIBCONFINI_INIFORMAT_TYPE_ \
struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) }
#define _LIBCONFINI_DEFAULT_FORMAT_ \
{ INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) }
#define _LIBCONFINI_UNIXLIKE_FORMAT_ \
{ INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) }
/* PUBLIC MACROS */
/**
@brief Calls a user-given macro (that accepts four arguments) for each row
of the table
**/
/*
NOTE: The following table and the order of its rows **define** (and link
together) both the #IniFormat and #IniFormatNum data types declared in this
header
*/
#define INIFORMAT_TABLE_AS(_____) /* IniFormat table *\
NAME BIT SIZE DEFAULT
*/\
_____( delimiter_symbol, 0, 7, INI_EQUALS ) \
_____( case_sensitive, 7, 1, false )/*
*/\
_____( semicolon_marker, 8, 2, INI_DISABLED_OR_COMMENT ) \
_____( hash_marker, 10, 2, INI_DISABLED_OR_COMMENT ) \
_____( section_paths, 12, 2, INI_ABSOLUTE_AND_RELATIVE ) \
_____( multiline_nodes, 14, 2, INI_MULTILINE_EVERYWHERE )/*
*/\
_____( no_single_quotes, 16, 1, false ) \
_____( no_double_quotes, 17, 1, false ) \
_____( no_spaces_in_names, 18, 1, false ) \
_____( implicit_is_not_empty, 19, 1, false ) \
_____( do_not_collapse_values, 20, 1, false ) \
_____( preserve_empty_quotes, 21, 1, false ) \
_____( disabled_after_space, 22, 1, false ) \
_____( disabled_can_be_implicit, 23, 1, false )
/**
@brief Checks whether a format does **not** support escape sequences
**/
#define INIFORMAT_HAS_NO_ESC(FORMAT) \
(FORMAT.multiline_nodes == INI_NO_MULTILINE && \
FORMAT.no_double_quotes && FORMAT.no_single_quotes)
/* PUBLIC TYPEDEFS */
/**
@brief 24-bit bitfield representing the format of an INI file (INI
dialect)
**/
typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat;
/**
@brief Global statistics about an INI file
**/
typedef struct IniStatistics {
const IniFormat format;
const size_t bytes;
const size_t members;
} IniStatistics;
/**
@brief Dispatch of a single INI node
**/
typedef struct IniDispatch {
const IniFormat format;
uint8_t type;
char * data;
char * value;
const char * append_to;
size_t d_len;
size_t v_len;
size_t at_len;
size_t dispatch_id;
} IniDispatch;
/**
@brief The unique ID of an INI format (24-bit maximum)
**/
typedef uint32_t IniFormatNum;
/**
@brief Callback function for handling an #IniStatistics structure
**/
typedef int (* IniStatsHandler) (
IniStatistics * statistics,
void * user_data
);
/**
@brief Callback function for handling an #IniDispatch structure
**/
typedef int (* IniDispHandler) (
IniDispatch * dispatch,
void * user_data
);
/**
@brief Callback function for handling an INI string belonging to a
sequence of INI strings
**/
typedef int (* IniStrHandler) (
char * ini_string,
size_t string_length,
size_t string_num,
IniFormat format,
void * user_data
);
/**
@brief Callback function for handling a selected fragment of an INI string
**/
typedef int (* IniSubstrHandler) (
const char * ini_string,
size_t fragm_offset,
size_t fragm_length,
size_t fragm_num,
IniFormat format,
void * user_data
);
/* PUBLIC FUNCTIONS */
extern int strip_ini_cache (
register char * const ini_source,
const size_t ini_length,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
);
extern int load_ini_file (
FILE * const ini_file,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
);
extern int load_ini_path (
const char * const path,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
);
extern bool ini_string_match_ss (
const char * const simple_string_a,
const char * const simple_string_b,
const IniFormat format
);
extern bool ini_string_match_si (
const char * const simple_string,
const char * const ini_string,
const IniFormat format
);
extern bool ini_string_match_ii (
const char * const ini_string_a,
const char * const ini_string_b,
const IniFormat format
);
extern bool ini_array_match (
const char * const ini_string_a,
const char * const ini_string_b,
const char delimiter,
const IniFormat format
);
extern size_t ini_unquote (
char * const ini_string,
const IniFormat format
);
extern size_t ini_string_parse (
char * const ini_string,
const IniFormat format
);
extern size_t ini_array_get_length (
const char * const ini_string,
const char delimiter,
const IniFormat format
);
extern int ini_array_foreach (
const char * const ini_string,
const char delimiter,
const IniFormat format,
const IniSubstrHandler f_foreach,
void * const user_data
);
extern size_t ini_array_shift (
const char ** const ini_strptr,
const char delimiter,
const IniFormat format
);
extern size_t ini_array_collapse (
char * const ini_string,
const char delimiter,
const IniFormat format
);
extern char * ini_array_break (
char * const ini_string,
const char delimiter,
const IniFormat format
);
extern char * ini_array_release (
char ** const ini_strptr,
const char delimiter,
const IniFormat format
);
extern int ini_array_split (
char * const ini_string,
const char delimiter,
const IniFormat format,
const IniStrHandler f_foreach,
void * const user_data
);
extern void ini_global_set_lowercase_mode (
const bool lowercase
);
extern void ini_global_set_implicit_value (
char * const implicit_value,
const size_t implicit_v_len
);
extern IniFormatNum ini_fton (
const IniFormat format
);
extern IniFormat ini_ntof (
const IniFormatNum format_id
);
extern int ini_get_bool (
const char * const ini_string,
const int when_fail
);
/* PUBLIC LINKS */
extern int (* const ini_get_int) (
const char * ini_string
);
extern long int (* const ini_get_lint) (
const char * ini_string
);
extern long long int (* const ini_get_llint) (
const char * ini_string
);
extern double (* const ini_get_double) (
const char * ini_string
);
/**
@brief Legacy support, soon to be replaced with a `float` data type --
please **do not use `ini_get_float()`!**
**/
#define ini_get_float \
_Pragma("GCC warning \"function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead\"") \
ini_get_double
/* PUBLIC CONSTANTS AND VARIABLES */
/**
@brief Error mask (flags not present in user-generated interruptions)
**/
#define CONFINI_ERROR 252
/**
@brief Error codes
**/
enum ConfiniInterruptNo {
CONFINI_SUCCESS = 0, /**< There have been no interruptions, everything
went well [value=0] **/
CONFINI_IINTR = 1, /**< Interrupted by the user during `f_init()`
[value=1] **/
CONFINI_FEINTR = 2, /**< Interrupted by the user during `f_foreach()`
[value=2] **/
CONFINI_ENOENT = 4, /**< File inaccessible [value=4] **/
CONFINI_ENOMEM = 5, /**< Error allocating virtual memory [value=5] **/
CONFINI_EIO = 6, /**< Error reading the file [value=6] **/
CONFINI_EOOR = 7, /**< Out-of-range error: callbacks are more than
expected [value=7] **/
CONFINI_EBADF = 8, /**< The stream specified is not a seekable stream
[value=8] **/
CONFINI_EFBIG = 9, /**< File too large [value=9] **/
CONFINI_EROADDR = 10 /**< Address is read-only [value=10] **/
};
/**
@brief INI node types
**/
enum IniNodeType {
INI_UNKNOWN = 0, /**< This is a node impossible to categorize
[value=0] **/
INI_VALUE = 1, /**< Not used by **libconfini** (values are
dispatched together with keys) -- but
available for user's implementations
[value=1] **/
INI_KEY = 2, /**< This is a key [value=2] **/
INI_SECTION = 3, /**< This is a section or a section path
[value=3] **/
INI_COMMENT = 4, /**< This is a comment [value=4] **/
INI_INLINE_COMMENT = 5, /**< This is an inline comment [value=5] **/
INI_DISABLED_KEY = 6, /**< This is a disabled key [value=6] **/
INI_DISABLED_SECTION = 7 /**< This is a disabled section path
[value=7] **/
};
/**
@brief Common array and key-value delimiters (but a delimiter may also be
any other ASCII character not present in this list)
**/
enum IniDelimiters {
INI_ANY_SPACE = 0, /**< In multi-line INIs:
`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in
non-multi-line INIs: `/[\t \v\f])+/` **/
INI_EQUALS = '=', /**< Equals character (`=`) **/
INI_COLON = ':', /**< Colon character (`:`) **/
INI_DOT = '.', /**< Dot character (`.`) **/
INI_COMMA = ',' /**< Comma character (`,`) **/
};
/**
@brief Possible values of #IniFormat::semicolon_marker and
#IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in
respect to a format)
**/
enum IniCommentMarker {
INI_DISABLED_OR_COMMENT = 0, /**< This marker opens a comment or a
disabled entry **/
INI_ONLY_COMMENT = 1, /**< This marker opens a comment **/
INI_IGNORE = 2, /**< This marker opens a comment that has
been marked for deletion and must not
be dispatched or counted **/
INI_IS_NOT_A_MARKER = 3 /**< This is not a marker at all, but a
normal character instead **/
};
/**
@brief Possible values of #IniFormat::section_paths
**/
enum IniSectionPaths {
INI_ABSOLUTE_AND_RELATIVE = 0, /**< Section paths starting with a dot
express nesting to the current parent,
to root otherwise **/
INI_ABSOLUTE_ONLY = 1, /**< Section paths starting with a dot will
be cleaned of their leading dot and
appended to root **/
INI_ONE_LEVEL_ONLY = 2, /**< Format supports sections, but the dot
does not express nesting and is not a
meta-character **/
INI_NO_SECTIONS = 3 /**< Format does *not* support sections --
`/\[[^\]]*\]/g`, if any, will be
treated as keys! **/
};
/**
@brief Possible values of #IniFormat::multiline_nodes
**/
enum IniMultiline {
INI_MULTILINE_EVERYWHERE = 0, /**< Comments, section paths and keys
-- disabled or not -- are allowed
to be multi-line **/
INI_BUT_COMMENTS = 1, /**< Only section paths and keys --
disabled or not -- are allowed to
be multi-line **/
INI_BUT_DISABLED_AND_COMMENTS = 2, /**< Only active section paths and
active keys are allowed to be
multi-line **/
INI_NO_MULTILINE = 3 /**< Multi-line escape sequences are
disabled **/
};
/**
@brief A model format for standard INI files
**/
static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_;
/**
@brief A model format for Unix-like .conf files (where space characters
are delimiters between keys and values)
**/
/* All fields are set to `0` here. */
static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_;
/**
@brief If set to `true`, key and section names in case-insensitive INI
formats will be dispatched lowercase, verbatim otherwise (default
value: `false`)
**/
extern bool INI_GLOBAL_LOWERCASE_MODE;
/**
@brief Value to be assigned to implicit keys (default value: `NULL`)
**/
extern char * INI_GLOBAL_IMPLICIT_VALUE;
/**
@brief Length of the value assigned to implicit keys (default value: `0`)
**/
extern size_t INI_GLOBAL_IMPLICIT_V_LEN;
/* CLEAN THE PRIVATE ENVIRONMENT */
#undef _LIBCONFINI_UNIXLIKE_FORMAT_
#undef _LIBCONFINI_DEFAULT_FORMAT_
#undef _LIBCONFINI_INIFORMAT_TYPE_
#undef __INIFORMAT_TABLE_CB_ZERO__
#undef __INIFORMAT_TABLE_CB_DEFAULT__
#undef __INIFORMAT_TABLE_CB_FIELDS__
/* END OF `_LIBCONFINI_HEADER_` */
#ifdef __cplusplus
}
#endif
#endif
/* EOF */

2
vendor/mongoose.h vendored
View file

@ -1867,7 +1867,7 @@ typedef struct {
void cs_md5_init(cs_md5_ctx *c);
void cs_md5_update(cs_md5_ctx *c, const unsigned char *data, size_t len);
void cs_md5_final(unsigned char digest[16], cs_md5_ctx *c);
void cs_md5_final(unsigned char *md, cs_md5_ctx *c);
#ifdef __cplusplus
}

2274
vendor/toml.c vendored

File diff suppressed because it is too large Load diff

175
vendor/toml.h vendored
View file

@ -1,175 +0,0 @@
/*
MIT License
Copyright (c) 2017 - 2019 CK Tan
https://github.com/cktan/tomlc99
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 TOML_H
#define TOML_H
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
#define TOML_EXTERN extern "C"
#else
#define TOML_EXTERN extern
#endif
typedef struct toml_timestamp_t toml_timestamp_t;
typedef struct toml_table_t toml_table_t;
typedef struct toml_array_t toml_array_t;
typedef struct toml_datum_t toml_datum_t;
/* Parse a file. Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
char* errbuf,
int errbufsz);
/* Parse a string containing the full config.
* Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
char* errbuf,
int errbufsz);
/* Free the table returned by toml_parse() or toml_parse_file(). Once
* this function is called, any handles accessed through this tab
* directly or indirectly are no longer valid.
*/
TOML_EXTERN void toml_free(toml_table_t* tab);
/* Timestamp types. The year, month, day, hour, minute, second, z
* fields may be NULL if they are not relevant. e.g. In a DATE
* type, the hour, minute, second and z fields will be NULLs.
*/
struct toml_timestamp_t {
struct { /* internal. do not use. */
int year, month, day;
int hour, minute, second, millisec;
char z[10];
} __buffer;
int *year, *month, *day;
int *hour, *minute, *second, *millisec;
char* z;
};
/*-----------------------------------------------------------------
* Enhanced access methods
*/
struct toml_datum_t {
int ok;
union {
toml_timestamp_t* ts; /* ts must be freed after use */
char* s; /* string value. s must be freed after use */
int b; /* bool value */
int64_t i; /* int value */
double d; /* double value */
} u;
};
/* on arrays: */
/* ... retrieve size of array. */
TOML_EXTERN int toml_array_nelem(const toml_array_t* arr);
/* ... retrieve values using index. */
TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx);
/* ... retrieve array or table using index. */
TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx);
/* on tables: */
/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx);
/* ... retrieve values using key. */
TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key);
/* .. retrieve array or table using key. */
TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab,
const char* key);
TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
const char* key);
/*-----------------------------------------------------------------
* lesser used
*/
/* Return the array kind: 't'able, 'a'rray, 'v'alue */
TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
/* For array kind 'v'alue, return the type of values
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
0 if unknown
*/
TOML_EXTERN char toml_array_type(const toml_array_t* arr);
/* Return the key of an array */
TOML_EXTERN const char* toml_array_key(const toml_array_t* arr);
/* Return the number of key-values in a table */
TOML_EXTERN int toml_table_nkval(const toml_table_t* tab);
/* Return the number of arrays in a table */
TOML_EXTERN int toml_table_narr(const toml_table_t* tab);
/* Return the number of sub-tables in a table */
TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
/* Return the key of a table*/
TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
/*--------------------------------------------------------------
* misc
*/
TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
void (*xxfree)(void*));
/*--------------------------------------------------------------
* deprecated
*/
/* A raw value, must be processed by toml_rto* before using. */
typedef const char* toml_raw_t;
TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key);
TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx);
TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret);
TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret);
TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret);
TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret);
TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen);
TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret);
#endif /* TOML_H */