init rewrite

This commit is contained in:
Tobias Reisinger 2020-05-05 11:42:02 +02:00
parent 9a44bc494e
commit 6d828fcffc
100 changed files with 50541 additions and 2707 deletions

7
.gitignore vendored
View file

@ -1,5 +1,4 @@
build
cmake-build-debug
.idea
build/
docs/
sql/*.h
include/migrations/*.sql.h

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "drogon"]
path = drogon
url = https://github.com/an-tao/drogon.git

View file

@ -1,70 +1,57 @@
cmake_minimum_required (VERSION 3.2)
cmake_minimum_required (VERSION 3.7)
project(core)
include(CheckIncludeFileCXX)
add_executable(core main.c)
check_include_file_cxx(any HAS_ANY)
check_include_file_cxx(string_view HAS_STRING_VIEW)
if(HAS_ANY AND HAS_STRING_VIEW)
set(CMAKE_CXX_STANDARD 17)
else()
set(CMAKE_CXX_STANDARD 14)
endif()
option(WIRING_PI_DEBUG "Use WiringPi Debugging Tool (OFF)" OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
#SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wall -Wextra -Werror -Wpedantic -lwiringPi -lwiringPiDev -luuid -llmdb -g")
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wpedantic -Werror -Wall -Wextra -luuid -lsqlite3 -g")
set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -latomic")
string(LENGTH "${CMAKE_SOURCE_DIR}/" SOURCE_PATH_SIZE)
add_definitions("-DSOURCE_PATH_SIZE=${SOURCE_PATH_SIZE}")
##########
# If you include the drogon source code locally in your project, use this method to add drogon
# add_subdirectory(drogon)
# set(Drogon_DIR ${PROJECT_BINARY_DIR}/drogon)
# find_package(Drogon CONFIG REQUIRED NO_DEFAULT_PATH)
# include_directories(${DROGON_INCLUDE_DIRS})
# link_libraries(${DROGON_LIBRARIES})
##########
if(WIRING_PI_DEBUG)
message("Showing wiringPi calls as debug")
add_definitions("-DWIRING_PI_DEBUG")
endif(WIRING_PI_DEBUG)
# find_package(Drogon CONFIG REQUIRED)
# include_directories(${DROGON_INCLUDE_DIRS})
# link_libraries(${DROGON_LIBRARIES})
add_definitions("-DMG_ENABLE_EXTRA_ERRORS_DESC")
if(CMAKE_CXX_STANDARD LESS 17)
#With C++14, use boost to support any and string_view
message(STATUS "use c++14")
find_package(Boost 1.61.0 REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
else()
message(STATUS "use c++17")
endif()
aux_source_directory(vendor VENDOR_SRC) # vendor first to put their warnings on top
aux_source_directory(. SRC_DIR)
aux_source_directory(models MODELS_SRC)
aux_source_directory(helpers HELPERS_SRC)
aux_source_directory(handlers HANDLERS_SRC)
aux_source_directory(endpoints ENDPOINTSS_SRC)
aux_source_directory(./ SRC_DIR)
aux_source_directory(controllers CTL_SRC)
aux_source_directory(filters FILTER_SRC)
aux_source_directory(plugins PLUGIN_SRC)
aux_source_directory(models MODEL_SRC)
aux_source_directory(helpers HELPER_SRC)
configure_file("core.ini" "core.ini" COPYONLY)
file(GLOB SCP_LIST ${CMAKE_CURRENT_SOURCE_DIR}/views/*.csp)
foreach(cspFile ${SCP_LIST})
message(STATUS "cspFile:" ${cspFile})
EXEC_PROGRAM(basename ARGS "${cspFile} .csp" OUTPUT_VARIABLE classname)
message(STATUS "view classname:" ${classname})
ADD_CUSTOM_COMMAND(OUTPUT ${classname}.h ${classname}.cc
COMMAND drogon_ctl
ARGS create view ${cspFile}
DEPENDS ${cspFile}
VERBATIM )
set(VIEWSRC ${VIEWSRC} ${classname}.cc)
endforeach()
target_sources(core PRIVATE ${VENDOR_SRC} ${SRC_DIR} ${HANDLERS_SRC} ${HELPERS_SRC} ${MODELS_SRC} ${ENDPOINTSS_SRC})
target_include_directories(core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(core PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor)
configure_file("config.json" "config.json" COPYONLY)
add_custom_target(migrations
COMMAND ./compile_migrations.sh
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_executable(core ${SRC_DIR} ${CTL_SRC} ${FILTER_SRC} ${VIEWSRC} ${PLUGIN_SRC} ${MODEL_SRC} ${HELPER_SRC})
add_custom_target(compiled_migrations ${CMAKE_CURRENT_SOURCE_DIR}/compile_migrations.sh)
add_dependencies(core compiled_migrations)
add_subdirectory(drogon)
target_link_libraries(${PROJECT_NAME} PRIVATE drogon)
add_custom_target(run
COMMAND ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
)
add_custom_target(debug
COMMAND valgrind ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
)
add_custom_target(debug-full
COMMAND valgrind --leak-check=full --show-leak-kinds=all ./core start
DEPENDS core
WORKING_DIRECTORY ${CMAKE_PROJECT_DIR}
)
add_custom_target(docs
COMMAND doxygen
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)

2548
Doxyfile Normal file

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ migration_num=0;
while [ -f sql/migration_$migration_num.sql ]
do
xxd -i sql/migration_$migration_num.sql | sed 's/\([0-9a-f]\)$/\0, 0x00/' > sql/migration_$migration_num.h
xxd -i sql/migration_$migration_num.sql | sed 's/\([0-9a-f]\)$/\0, 0x00/' > include/migrations/$migration_num.sql.h
((migration_num++));
done;

74
config.c Normal file
View file

@ -0,0 +1,74 @@
#include <stdlib.h>
#include <string.h>
#include <logger.h>
#include <config.h>
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))
int
config_load(IniDispatch *disp, void *config_void)
{
config_t *config = (config_t*)config_void;
if(disp->type == INI_KEY)
{
if(CONFINI_IS_KEY("core", "database"))
{
config->database = malloc(sizeof(char) * (strlen(disp->value) + 1));
strcpy(config->database, disp->value);
return 0;
}
if(CONFINI_IS_KEY("core", "log-level"))
{
if(strcasecmp(disp->value, "trace") == 0)
{
config->log_level = LOG_LEVEL_TRACE;
return 0;
}
if(strcasecmp(disp->value, "debug") == 0)
{
config->log_level = LOG_LEVEL_DEBUG;
return 0;
}
if(strcasecmp(disp->value, "info") == 0)
{
config->log_level = LOG_LEVEL_INFO;
return 0;
}
if(strcasecmp(disp->value, "warn") == 0)
{
config->log_level = LOG_LEVEL_WARN;
return 0;
}
if(strcasecmp(disp->value, "error") == 0)
{
config->log_level = LOG_LEVEL_ERROR;
return 0;
}
if(strcasecmp(disp->value, "fatal") == 0)
{
config->log_level = LOG_LEVEL_FATAL;
return 0;
}
LOG_WARN("invalid log-level '%s'\n", disp->value);
return 0;
}
if(CONFINI_IS_KEY("core", "discovery-port"))
{
config->discovery_port = atoi(disp->value);
return 0;
}
if(CONFINI_IS_KEY("core", "server-port"))
{
strcpy(config->server_port, disp->value);
return 0;
}
}
return 0;
}

View file

@ -1,22 +0,0 @@
#include "config.h"
namespace config
{
char version[] = "0.0.1";
uint16_t discover_max_client_backlog = 20;
uint16_t discover_port_dev = 4420;
uint16_t discover_port = 4419;
uint16_t discover_timeout_ms = 2000;
uint8_t discover_code_accept = 0;
uint8_t discover_code_reject = 100;
uint8_t command_code_get_time = 1;
uint8_t command_code_get_id = 2;
uint8_t command_code_set_name = 100;
uint8_t command_code_get_name = 101;
uint8_t command_code_set_schedule = 102;
uint8_t command_code_get_schedule = 103;
uint8_t command_code_set_relay_name = 104;
uint8_t command_code_get_relay_name = 105;
}

View file

@ -1,27 +0,0 @@
#ifndef EMGAUWA_CORE_CONFIG_H
#define EMGAUWA_CORE_CONFIG_H
#include <stdint-gcc.h>
namespace config
{
extern char version[];
extern uint16_t discover_max_client_backlog;
extern uint16_t discover_port_dev;
extern uint16_t discover_port;
extern uint16_t discover_timeout_ms;
extern uint8_t discover_code_accept;
extern uint8_t discover_code_reject;
extern uint8_t command_code_get_time;
extern uint8_t command_code_get_id;
extern uint8_t command_code_set_name;
extern uint8_t command_code_get_name;
extern uint8_t command_code_set_schedule;
extern uint8_t command_code_get_schedule;
extern uint8_t command_code_set_relay_name;
extern uint8_t command_code_get_relay_name;
}
#endif //EMGAUWA_CORE_CONFIG_H

View file

@ -1,63 +0,0 @@
{
"listeners": [
{
"address": "0.0.0.0",
"port": 5000,
"https": false
}
],
"app": {
"threads_num": 1,
"enable_session": false,
"session_timeout": 0,
"document_root": "./static/",
"home_page": "index.html",
"upload_path": "uploads",
"file_types": [
"gif",
"png",
"jpg",
"js",
"css",
"html",
"ico",
"swf",
"xap",
"apk",
"cur",
"xml"
],
"max_connections": 100000,
"max_connections_per_ip": 0,
"load_dynamic_views": false,
"dynamic_views_path": [
"./views"
],
"log": {
"logfile_base_name": "",
"log_size_limit": 100000000,
"log_level": "DEBUG"
},
"run_as_daemon": false,
"relaunch_on_error": false,
"use_sendfile": true,
"use_gzip": true,
"static_files_cache_time": 5,
"idle_connection_timeout": 60,
"server_header_field": "",
"keepalive_requests": 0,
"pipelining_requests": 0,
"gzip_static": true,
"client_max_body_size": "1M",
"client_max_memory_body_size": "64K",
"client_max_websocket_message_size": "128K"
},
"plugins": [{
"dependencies": [],
"config": {
"heartbeat_interval": 2
}
}],
"custom_config": {}
}

View file

@ -1,185 +0,0 @@
/* This is a JSON format configuration file
*/
{
/*
//ssl:The global ssl files setting
"ssl": {
"cert": "../../trantor/trantor/tests/server.pem",
"key": "../../trantor/trantor/tests/server.pem"
},
"listeners": [
{
//address: Ip address,0.0.0.0 by default
"address": "0.0.0.0",
//port: Port number
"port": 80,
//https: If true, use https for security,false by default
"https": false
},
{
"address": "0.0.0.0",
"port": 443,
"https": true,
//cert,key: Cert file path and key file path, empty by default,
//if empty, use the global setting
"cert": "",
"key": ""
}
],
"db_clients": [
{
//name: Name of the client,'default' by default
//"name":"",
//rdbms: Server type, postgresql,mysql or sqlite3, "postgresql" by default
"rdbms": "postgresql",
//filename: Sqlite3 db file name
//"filename":"",
//host: Server address,localhost by default
"host": "127.0.0.1",
//port: Server port, 5432 by default
"port": 5432,
//dbname: Database name
"dbname": "test",
//user: 'postgres' by default
"user": "",
//passwd: '' by default
"passwd": "",
//is_fast: false by default, if it is true, the client is faster but user can't call
//any synchronous interface of it.
"is_fast": false,
//connection_number: 1 by default, valid only if is_fast is false.
"connection_number": 1
}
],*/
"app": {
//threads_num: The number of IO threads, 1 by default, if the value is set to 0, the number of threads
//is the number of CPU cores
"threads_num": 1,
//enable_session: False by default
"enable_session": false,
"session_timeout": 0,
//document_root: Root path of HTTP document, defaut path is ./
"document_root": "./",
//home_page: Set the HTML file of the home page, the default value is "index.html"
//If there isn't any handler registered to the path "/", the home page file in the "document_root" is send to clients as a response
//to the request for "/".
"home_page": "index.html",
//upload_path: The path to save the uploaded file. "uploads" by default.
//If the path isn't prefixed with /, ./ or ../,
//it is relative path of document_root path
"upload_path": "uploads",
/* file_types:
* HTTP download file types,The file types supported by drogon
* by default are "html", "js", "css", "xml", "xsl", "txt", "svg",
* "ttf", "otf", "woff2", "woff" , "eot", "png", "jpg", "jpeg",
* "gif", "bmp", "ico", "icns", etc. */
"file_types": [
"gif",
"png",
"jpg",
"js",
"css",
"html",
"ico",
"swf",
"xap",
"apk",
"cur",
"xml"
],
//max_connections: maximum connections number,100000 by default
"max_connections": 100000,
//max_connections_per_ip: maximum connections number per clinet,0 by default which means no limit
"max_connections_per_ip": 0,
//Load_dynamic_views: False by default, when set to true, drogon
//compiles and loads dynamically "CSP View Files" in directories defined
//by "dynamic_views_path"
"load_dynamic_views": false,
//dynamic_views_path: If the path isn't prefixed with /, ./ or ../,
//it is relative path of document_root path
"dynamic_views_path": [
"./views"
],
//log: Set log output, drogon output logs to stdout by default
"log": {
//log_path: Log file path,empty by default,in which case,logs are output to the stdout
//"log_path": "./",
//logfile_base_name: Log file base name,empty by default which means drogon names logfile as
//drogon.log ...
"logfile_base_name": "",
//log_size_limit: 100000000 bytes by default,
//When the log file size reaches "log_size_limit", the log file is switched.
"log_size_limit": 100000000,
//log_level: "DEBUG" by default,options:"TRACE","DEBUG","INFO","WARN"
//The TRACE level is only valid when built in DEBUG mode.
"log_level": "DEBUG"
},
//run_as_daemon: False by default
"run_as_daemon": false,
//relaunch_on_error: False by default, if true, the program will be restart by the parent after exiting;
"relaunch_on_error": false,
//use_sendfile: True by default, if ture, the program
//uses sendfile() system-call to send static files to clients;
"use_sendfile": true,
//use_gzip: True by default, use gzip to compress the response body's content;
"use_gzip": true,
//static_files_cache_time: 5 (seconds) by default, the time in which the static file response is cached,
//0 means cache forever, the negative value means no cache
"static_files_cache_time": 5,
//simple_controllers_map: Used to configure mapping from path to simple controller
"simple_controllers_map": [{
"path": "/path/name",
"controller": "controllerClassName",
"http_methods": [
"get",
"post"
],
"filters": [
"FilterClassName"
]
}],
//idle_connection_timeout: Defaults to 60 seconds, the lifetime
//of the connection without read or write
"idle_connection_timeout": 60,
//server_header_field: Set the 'server' header field in each response sent by drogon,
//empty string by default with which the 'server' header field is set to "Server: drogon/version string\r\n"
"server_header_field": "",
//keepalive_requests: Set the maximum number of requests that can be served through one keep-alive connection.
//After the maximum number of requests are made, the connection is closed.
//The default value of 0 means no limit.
"keepalive_requests": 0,
//pipelining_requests: Set the maximum number of unhandled requests that can be cached in pipelining buffer.
//After the maximum number of requests are made, the connection is closed.
//The default value of 0 means no limit.
"pipelining_requests": 0,
//gzip_static: If it is set to true, when the client requests a static file, drogon first finds the compressed
//file with the extension ".gz" in the same path and send the compressed file to the client.
//The default value of gzip_static is true.
"gzip_static": true,
//client_max_body_size: Set the maximum body size of HTTP requests received by drogon. The default value is "1M".
//One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
"client_max_body_size": "1M",
//max_memory_body_size: Set the maximum body size in memory of HTTP requests received by drogon. The default value is "64K" bytes.
//If the body size of a HTTP request exceeds this limit, the body is stored to a temporary file for processing.
//Setting it to "" means no limit.
"client_max_memory_body_size": "64K",
//client_max_websocket_message_size: Set the maximum size of messages sent by WebSocket client. The default value is "128K".
//One can set it to "1024", "1k", "10M", "1G", etc. Setting it to "" means no limit.
"client_max_websocket_message_size": "128K"
},
//plugins: Define all plugins running in the application
"plugins": [{
//name: The class name of the plugin
//"name": "TestPlugin",
//dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It can be commented out
"config": {
"heartbeat_interval": 2
}
}],
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
"custom_config": {}
}

View file

@ -1,107 +0,0 @@
#include <models/controller_dbo.h>
#include <config.h>
#include "api_v1_controllers.h"
using namespace api::v1;
void
controllers::get_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
controller_dbo **all_controllers = controller_dbo::get_all();
Json::Value all_controllers_json(Json::arrayValue);
for(int i = 0; all_controllers[i] != nullptr; i++)
{
all_controllers_json.append(all_controllers[i]->to_json());
}
auto resp = HttpResponse::newHttpJsonResponse(all_controllers_json);
callback(resp);
controller_dbo::free_list(all_controllers);
}
void
controllers::get_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& controller_id)
{
controller_dbo **controllers = controller_dbo::get_by_simple("id", controller_id.c_str(), (intptr_t) &sqlite3_bind_text);
if(controllers[0])
{
auto resp = HttpResponse::newHttpJsonResponse(controllers[0]->to_json());
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
controller_dbo::free_list(controllers);
}
void
controllers::delete_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& controller_id)
{
controller_dbo **controllers = controller_dbo::get_by_simple("id", controller_id.c_str(), (intptr_t) &sqlite3_bind_text);
if(controllers[0])
{
auto resp = HttpResponse::newHttpResponse();
if(!controllers[0]->remove())
{
resp->setStatusCode(k500InternalServerError);
}
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
controller_dbo::free_list(controllers);
}
void
controllers::put_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &controller_id)
{
Json::Value body = *req->getJsonObject();
controller_dbo **controllers = controller_dbo::get_by_simple("id", controller_id.c_str(), (intptr_t) &sqlite3_bind_text);
if(controllers[0])
{
strncpy(controllers[0]->name, body["name"].asCString(), 127);
strncpy(controllers[0]->ip, body["ip"].asCString(), 16);
controllers[0]->name[127] = '\0';
controllers[0]->ip[16] = '\0';
auto resp = HttpResponse::newHttpResponse();
if(controllers[0]->update())
{
controllers[0]->command(config::command_code_set_name, controllers[0]->name);
resp = HttpResponse::newHttpJsonResponse(controllers[0]->to_json());
}
else
{
resp->setStatusCode(k500InternalServerError);
}
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
controller_dbo::free_list(controllers);
}

View file

@ -1,31 +0,0 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api::v1
{
class controllers:public drogon::HttpController<controllers>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(controllers::post_discover, "/discover", Post, Options);
METHOD_ADD(controllers::get_all, "/", Get, Options);
METHOD_ADD(controllers::get_one_by_id, "/{1}", Get, Options);
METHOD_ADD(controllers::delete_one_by_id, "/{1}", Delete, Options);
METHOD_ADD(controllers::put_one_by_id, "/{1}", Put, Options, "filters::json_required", "filters::controllers::valid_json");
METHOD_ADD(controllers::get_relays_all, "/{1}/relays/", Get, Options);
METHOD_ADD(controllers::get_relays_one_by_id_and_num, "/{1}/relays/{2}", Get, Options);
METHOD_ADD(controllers::put_relays_one_by_id_and_num, "/{1}/relays/{2}", Put, Options, "filters::json_required", "filters::relays::valid_json");
METHOD_LIST_END
static void post_discover(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_all(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id);
static void delete_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id);
static void put_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id);
static void get_relays_all(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id);
static void get_relays_one_by_id_and_num(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id, int relay_num);
static void put_relays_one_by_id_and_num(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& controller_id, int relay_num);
};
}

View file

@ -1,180 +0,0 @@
#include <netdb.h>
#include <unistd.h>
#include <config.h>
#include <helpers.h>
#include <cmath>
#include <models/controller_dbo.h>
#include "api_v1_controllers.h"
using namespace api::v1;
void controllers::post_discover(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
int discover_server_socket = helpers::bind_tcp_server("0.0.0.0", "0", config::discover_max_client_backlog);
int discover_server_port = helpers::get_server_port(discover_server_socket);
if(discover_server_port == -1)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
return;
}
Json::Value payload;
payload["port"] = discover_server_port;
Json::StreamWriterBuilder json_writer;
if(helpers::send_udp_broadcast("255.255.255.255", config::discover_port_dev, Json::writeString(json_writer, payload).c_str()) < 0)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
return;
}
struct sockaddr_storage their_addr{};
socklen_t addr_size;
int client_fd, s_ret;
fd_set accept_fds;
struct timeval timeout{};
uint8_t discover_answer_buf[1];
uint8_t discover_header_buf[1];
controller_dbo **known_controllers = controller_dbo::get_all();
while(true)
{
addr_size = sizeof(their_addr);
FD_ZERO(&accept_fds);
FD_SET(discover_server_socket, &accept_fds); // NOLINT(hicpp-signed-bitwise)
timeout.tv_sec = config::discover_timeout_ms / 1000;
timeout.tv_usec = (config::discover_timeout_ms % 1000) * 1000;
s_ret = select(discover_server_socket + 1, &accept_fds, nullptr, nullptr, &timeout);
if(s_ret == 0)
{
break;
}
else
{
if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0)
{
LOG_ERROR << "Error Accepting client " << strerror(errno);
continue;
}
if(recv(client_fd, discover_header_buf, 1, 0) < 0)
{
LOG_ERROR << "Error Receiving header from client";
continue;
}
size_t payload_length = discover_header_buf[0];
char *answer_payload = (char*)malloc((payload_length + 1) * sizeof(*answer_payload));
if(recv(client_fd, answer_payload, payload_length, 0) < 0)
{
LOG_ERROR << "Error Receiving payload from client";
continue;
}
struct sockaddr_in addr{};
socklen_t client_addr_size = sizeof(struct sockaddr_in);
if(getpeername(client_fd, (struct sockaddr *)&addr, &client_addr_size) != 0)
{
LOG_ERROR << "Error Receiving payload from client";
continue;
}
answer_payload[payload_length] = '\0';
std::istringstream answer_payload_stream(answer_payload);
Json::CharReaderBuilder json_reader;
json_reader["strictRoot"] = true;
std::string errors;
Json::Value client_info;
if (!Json::parseFromStream(json_reader, answer_payload_stream, &client_info, &errors))
{
LOG_ERROR << "Failed to parse response: " << errors;
discover_answer_buf[0] = config::discover_code_reject;
send(client_fd, discover_answer_buf, sizeof(uint8_t), 0);
close(client_fd);
continue;
}
free(answer_payload);
const char *discovered_id = client_info["id"].asCString();
bool found_discovered_in_list = false;
for(int i = 0; known_controllers[i] != nullptr; i++)
{
if(!found_discovered_in_list)
{
if(strcmp(known_controllers[i]->id, discovered_id) == 0)
{
known_controllers[i]->active = true;
known_controllers[i]->update();
delete known_controllers[i];
found_discovered_in_list = true;
known_controllers[i] = known_controllers[i + 1];
}
}
else
{
known_controllers[i] = known_controllers[i + 1];
}
}
if(!found_discovered_in_list)
{
controller_dbo discovered_controller{};
strcpy(discovered_controller.ip, inet_ntoa(addr.sin_addr));
strcpy(discovered_controller.id, discovered_id);
strcpy(discovered_controller.name, client_info["name"].asCString());
discovered_controller.relay_count = client_info["relay_count"].asInt();
discovered_controller.port = client_info["port"].asInt();
discovered_controller.active = true;
discovered_controller.insert();
}
discover_answer_buf[0] = config::discover_code_accept;
send(client_fd, discover_answer_buf, sizeof(uint8_t), 0);
close(client_fd);
}
}
for(int i = 0; known_controllers[i] != nullptr; i++)
{
known_controllers[i]->active = false;
known_controllers[i]->update();
LOG_DEBUG << "Lost: " << known_controllers[i]->name;
}
controller_dbo::free_list(known_controllers);
controller_dbo **all_controllers = controller_dbo::get_all();
Json::Value all_controllers_json(Json::arrayValue);
for(int i = 0; all_controllers[i] != nullptr; i++)
{
all_controllers_json.append(all_controllers[i]->to_json());
}
auto resp = HttpResponse::newHttpJsonResponse(all_controllers_json);
callback(resp);
controller_dbo::free_list(all_controllers);
}

View file

@ -1,119 +0,0 @@
#include <netdb.h>
#include <models/relay_dbo.h>
#include <helpers.h>
#include <models/controller_dbo.h>
#include <models/schedule_dbo.h>
#include <config.h>
#include "api_v1_controllers.h"
using namespace api::v1;
void
controllers::get_relays_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string& controller_id)
{
relay_dbo **all_controller_relays = relay_dbo::get_by_simple("controller_id", (void *) controller_id.c_str(), (intptr_t) sqlite3_bind_text);
Json::Value all_relays_json(Json::arrayValue);
for(int i = 0; all_controller_relays[i] != nullptr; i++)
{
all_relays_json.append(all_controller_relays[i]->to_json());
}
auto resp = HttpResponse::newHttpJsonResponse(all_relays_json);
callback(resp);
relay_dbo::free_list(all_controller_relays);
}
void
controllers::get_relays_one_by_id_and_num(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback, const std::string& controller_id,
int relay_num)
{
relay_dbo *relay = relay_dbo::get_relay_for_controller(controller_id.c_str(), relay_num);
if(relay)
{
auto resp = HttpResponse::newHttpJsonResponse(relay->to_json());
callback(resp);
delete relay;
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
}
void
controllers::put_relays_one_by_id_and_num(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback, const std::string& controller_id,
int relay_num)
{
if(!relay_dbo::valid_num_for_controller(controller_id.c_str(), relay_num))
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k400BadRequest);
callback(resp);
return;
}
relay_dbo *relay = relay_dbo::get_relay_for_controller(controller_id.c_str(), relay_num);
Json::Value body = *req->getJsonObject();
bool db_action_result;
if(relay)
{
strncpy(relay->name, body["name"].asCString(), 127);
strncpy(relay->active_schedule_id, body["active_schedule"].asCString(), 32);
db_action_result = relay->update();
}
else
{
relay = new relay_dbo();
relay->number = relay_num;
strncpy(relay->name, body["name"].asCString(), 127);
strncpy(relay->active_schedule_id, body["active_schedule"].asCString(), 32);
strncpy(relay->controller_id, controller_id.c_str(), 32);
relay->reload_active_schedule();
db_action_result = relay->insert();
}
if(!db_action_result)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
}
else
{
auto schedules = schedule_dbo::get_by_simple("id", body["active_schedule"].asCString(), (intptr_t)&sqlite3_bind_text);
auto controllers = controller_dbo::get_by_simple("id", controller_id.c_str(), (intptr_t)&sqlite3_bind_text);
Json::Value payload;
payload["target"] = relay_num;
payload["schedule"] = schedules[0]->to_json();
Json::StreamWriterBuilder json_writer;
controllers[0]->command(config::command_code_set_schedule, Json::writeString(json_writer, payload).c_str());
auto resp = HttpResponse::newHttpJsonResponse(relay->to_json());
callback(resp);
schedule_dbo::free_list(schedules);
controller_dbo::free_list(controllers);
}
delete relay;
}

View file

@ -1,23 +0,0 @@
#include <netdb.h>
#include <models/relay_dbo.h>
#include <helpers.h>
#include "api_v1_relays.h"
using namespace api::v1;
void
relays::get_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
relay_dbo **all_relays = relay_dbo::get_all();
Json::Value all_relays_json(Json::arrayValue);
for(int i = 0; all_relays[i] != nullptr; i++)
{
all_relays_json.append(all_relays[i]->to_json());
}
auto resp = HttpResponse::newHttpJsonResponse(all_relays_json);
callback(resp);
relay_dbo::free_list(all_relays);
}

View file

@ -1,15 +0,0 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api::v1
{
class relays:public drogon::HttpController<relays>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(relays::get_all, "/", Get, Options);
METHOD_LIST_END
static void get_all(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
};
}

View file

@ -1,152 +0,0 @@
#include <netdb.h>
#include <models/schedule_dbo.h>
#include <helpers.h>
#include "api_v1_schedules.h"
using namespace api::v1;
void
schedules::get_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
schedule_dbo **all_schedules = schedule_dbo::get_all();
Json::Value all_schedules_json(Json::arrayValue);
for(int i = 0; all_schedules[i] != nullptr; i++)
{
all_schedules_json.append(all_schedules[i]->to_json());
}
Json::StreamWriterBuilder jw;
auto resp = HttpResponse::newHttpJsonResponse(all_schedules_json);
callback(resp);
schedule_dbo::free_list(all_schedules);
}
void
schedules::get_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& schedule_id)
{
schedule_dbo **schedules = schedule_dbo::get_by_simple("id", schedule_id.c_str(), (intptr_t) &sqlite3_bind_text);
if(schedules[0])
{
auto resp = HttpResponse::newHttpJsonResponse(schedules[0]->to_json());
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
schedule_dbo::free_list(schedules);
}
void
schedules::delete_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& schedule_id)
{
if(strcmp(schedule_id.c_str(), "off") == 0)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k403Forbidden);
callback(resp);
return;
}
schedule_dbo **schedules = schedule_dbo::get_by_simple("id", schedule_id.c_str(), (intptr_t) &sqlite3_bind_text);
if(schedules[0])
{
auto resp = HttpResponse::newHttpResponse();
if(!schedules[0]->remove())
{
resp->setStatusCode(k500InternalServerError);
}
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
schedule_dbo::free_list(schedules);
}
void
schedules::post_new(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
Json::Value body = *req->jsonObject();
schedule_dbo new_schedule{};
strncpy(new_schedule.name, body["name"].asCString(), 127);
new_schedule.name[127] = '\0';
strncpy(new_schedule.id, drogon::utils::getUuid().c_str(), 32);
new_schedule.id[32] = '\0';
new_schedule.periods = helpers::parse_periods(body["periods"]);
if(!new_schedule.insert())
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpJsonResponse(new_schedule.to_json());
callback(resp);
}
}
void
schedules::put_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
const std::string &schedule_id)
{
if(strcmp(schedule_id.c_str(), "off") == 0)
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k403Forbidden);
callback(resp);
return;
}
Json::Value body = *req->jsonObject();
schedule_dbo **schedules = schedule_dbo::get_by_simple("id", schedule_id.c_str(), (intptr_t) &sqlite3_bind_text);
if(schedules[0])
{
strncpy(schedules[0]->name, body["name"].asCString(), 127);
schedules[0]->name[127] = '\0';
delete schedules[0]->periods;
schedules[0]->periods = helpers::parse_periods(body["periods"]);
if(!schedules[0]->update())
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k500InternalServerError);
callback(resp);
}
else
{
auto resp = HttpResponse::newHttpJsonResponse(schedules[0]->to_json());
callback(resp);
}
}
else
{
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k404NotFound);
callback(resp);
}
schedule_dbo::free_list(schedules);
}

View file

@ -1,27 +0,0 @@
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api::v1
{
class schedules:public drogon::HttpController<schedules>
{
public:
METHOD_LIST_BEGIN
METHOD_ADD(schedules::post_new, "/", Post, Options, "filters::json_required", "filters::schedules::valid_json");
METHOD_ADD(schedules::get_all, "/", Get, Options);
METHOD_ADD(schedules::get_one_by_id, "/{1}", Get, Options);
METHOD_ADD(schedules::delete_one_by_id, "/{1}", Delete, Options);
METHOD_ADD(schedules::put_one_by_id, "/{1}", Put, Options, "filters::json_required", "filters::schedules::valid_json");
//METHOD_ADD(controllers::get_relays_all,"/{1}/relays",Get);
//METHOD_ADD(controllers::get_relays_one,"/{1}/relays/{2}",Get);
METHOD_LIST_END
static void post_new(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_all(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback);
static void get_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& schedule_id);
static void delete_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& schedule_id);
static void put_one_by_id(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback, const std::string& schedule_id);
//void get_relays_all(const HttpRequestPtr& req,std::function<void (const HttpResponsePtr &)> &&callback,std::string schedule_id);
//void get_relays_one(const HttpRequestPtr& req,std::function<void (const HttpResponsePtr &)> &&callback,std::string schedule_id,std::string relay_id);
};
}

5
core.ini Normal file
View file

@ -0,0 +1,5 @@
[core]
server-port = 5000
discovery-port = 4421
database = core.sqlite
log-level = debug

67
database.c Normal file
View file

@ -0,0 +1,67 @@
#include <stdint.h>
#include <stddef.h>
#include <logger.h>
#include <database.h>
#include <migrations/0.sql.h>
sqlite3 *global_database;
int
database_migrate()
{
uint16_t version_num = 0;
int s, rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT version_num FROM meta LIMIT 1;", -1, &stmt, NULL);
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
version_num = sqlite3_column_int(stmt, 0);
}
else
{
version_num = 0;
}
uint16_t new_version_num = version_num;
char* err_msg;
sqlite3_finalize(stmt);
switch(version_num)
{
case 0:
LOG_INFO("migrating LEVEL 0\n");
rc = sqlite3_exec(global_database, (const char *)sql_migration_0_sql, NULL, NULL, &err_msg);
if(rc != 0)
{
LOG_FATAL("couldn't migrate LEVEL 0 (%s)\n", err_msg);
break;
}
new_version_num = 1;
default:
break;
}
if(version_num == 0)
{
sqlite3_prepare_v2(global_database, "INSERT INTO meta (version_num) VALUES (?1);", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "UPDATE meta SET version_num=?1;", -1, &stmt, NULL);
}
sqlite3_bind_int(stmt, 1, new_version_num);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOG_FATAL("couldn't write new schema version");
}
sqlite3_finalize(stmt);
return rc != SQLITE_DONE;
}

14
drivers/gpio.c Normal file
View file

@ -0,0 +1,14 @@
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <drivers.h>
void
driver_gpio_set(int pin, int value)
{
// disable "unused parameter" warning (happens when using wiring_debug)
(void)pin;
(void)value;
digitalWrite(pin, value);
}

14
drivers/piface.c Normal file
View file

@ -0,0 +1,14 @@
#include <wiringPi.h>
#include <piFace.h>
#include <wiring_debug.h>
#include <drivers.h>
void
driver_piface_set(int pin, int value)
{
// disable "unused parameter" warning (happens when using wiring_debug)
(void)pin;
(void)value;
digitalWrite(PIFACE_GPIO_BASE + pin, value);
}

1
drogon

@ -1 +0,0 @@
Subproject commit 543d1a8c8062b3873ef89c64ffd7394c6dd7c7e8

View file

@ -0,0 +1,159 @@
#include <cJSON.h>
#include <endpoints/api_v1_schedules.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <models/schedule.h>
#include <models/tag.h>
void
api_v1_schedules_POST(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm)
{
(void)args;
cJSON *json = cJSON_ParseWithLength(hm->body.p, hm->body.len);
if(json == NULL)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL)
{
LOG_ERROR("error before: %s\n", error_ptr);
}
cJSON_Delete(json);
}
cJSON *json_name = cJSON_GetObjectItemCaseSensitive(json, "name");
if(!cJSON_IsString(json_name) || (json_name->valuestring == NULL))
{
char *error_msg = "ERROR: no name for schedule provided";
mg_send_head(c, 400, strlen(error_msg), "Content-Type: text/plain");
mg_printf(c, "%s", error_msg);
cJSON_Delete(json);
return;
}
cJSON *json_period;
cJSON *json_periods = cJSON_GetObjectItemCaseSensitive(json, "periods");
schedule_t *new_schedule = malloc(sizeof(schedule_t));
new_schedule->id = 0;
uuid_generate(new_schedule->uid);
strncpy(new_schedule->name, json_name->valuestring, MAX_NAME_LENGTH);
new_schedule->name[MAX_NAME_LENGTH] = '\0';
int periods_count = cJSON_GetArraySize(json_periods);
new_schedule->periods = malloc(sizeof(period_t) * periods_count);
int periods_valid = 0;
cJSON_ArrayForEach(json_period, json_periods)
{
cJSON *json_period_start = cJSON_GetObjectItemCaseSensitive(json_period, "start");
cJSON *json_period_end = cJSON_GetObjectItemCaseSensitive(json_period, "end");
if(!cJSON_IsString(json_period_start) || (json_period_start->valuestring == NULL))
{
LOG_DEBUG("period is missing start\n");
continue;
}
if(!cJSON_IsString(json_period_end) || (json_period_end->valuestring == NULL))
{
LOG_DEBUG("period is missing end\n");
continue;
}
uint16_t start;
uint16_t end;
if(period_helper_parse_hhmm(json_period_start->valuestring, &start))
{
LOG_DEBUG("couldn't parse start '%s'\n", json_period_start->valuestring);
continue;
}
if(period_helper_parse_hhmm(json_period_end->valuestring, &end))
{
LOG_DEBUG("couldn't parse end '%s'\n", json_period_end->valuestring);
continue;
}
new_schedule->periods[periods_valid].start = start;
new_schedule->periods[periods_valid].end = end;
++periods_valid;
}
new_schedule->periods_count = periods_valid;
schedule_save(new_schedule);
junction_tag_remove_for_schedule(new_schedule->id);
cJSON *json_tag;
cJSON *json_tags = cJSON_GetObjectItemCaseSensitive(json, "tags");
cJSON_ArrayForEach(json_tag, json_tags)
{
if(!cJSON_IsString(json_tag) || (json_tag->valuestring == NULL))
{
LOG_DEBUG("invalid tag in tags\n");
continue;
}
const char *tag = json_tag->valuestring;
int tag_id = tag_get_id(tag);
if(tag_id == 0)
{
tag_save(tag_id, tag);
tag_id = tag_get_id(tag);
}
junction_tag_insert(tag_id, 0, new_schedule->id);
}
cJSON_Delete(json);
json = schedule_to_json(new_schedule);
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print schedule json\n");
mg_send_head(c, 201, 2, "Content-Type: application/json");
mg_printf(c, "{}");
}
else
{
mg_send_head(c, 201, strlen(json_str), "Content-Type: application/json");
mg_printf(c, "%s", json_str);
free(json_str);
}
cJSON_Delete(json);
schedule_free(new_schedule);
}
void
api_v1_schedules_GET(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm)
{
(void)args;
(void)hm;
schedule_t** all_schedules = schedule_get_all();
cJSON *json = cJSON_CreateArray();
for(int i = 0; all_schedules[i] != NULL; ++i)
{
cJSON *json_schedule = schedule_to_json(all_schedules[i]);
cJSON_AddItemToArray(json, json_schedule);
}
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print schedules json\n");
mg_send_head(c, 500, 2, "Content-Type: application/json");
mg_printf(c, "[]");
}
else
{
mg_send_head(c, 200, strlen(json_str), "Content-Type: application/json");
mg_printf(c, "%s", json_str);
free(json_str);
}
cJSON_Delete(json);
schedule_free_list(all_schedules);
}

View file

@ -0,0 +1,54 @@
#include <cJSON.h>
#include <endpoints/api_v1_schedules.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <models/schedule.h>
#include <models/tag.h>
void
api_v1_schedules_STR_GET(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm)
{
(void)args;
(void)hm;
uuid_t target_uid;
if(schedule_uid_parse(args[0].value.v_str, target_uid))
{
LOG_ERROR("failed to unparse uid\n");
mg_send_head(c, 400, 2, "Content-Type: application/json");
mg_printf(c, "{}");
return;
}
char debug_str[40];
uuid_unparse(target_uid, debug_str);
LOG_DEBUG("uid: %s\n", debug_str);
schedule_t** all_schedules = schedule_get_all();
cJSON *json = cJSON_CreateArray();
for(int i = 0; all_schedules[i] != NULL; ++i)
{
cJSON *json_schedule = schedule_to_json(all_schedules[i]);
cJSON_AddItemToArray(json, json_schedule);
}
char *json_str = cJSON_Print(json);
if (json_str == NULL)
{
LOG_ERROR("failed to print schedules json\n");
mg_send_head(c, 500, 2, "Content-Type: application/json");
mg_printf(c, "[]");
}
else
{
mg_send_head(c, 200, strlen(json_str), "Content-Type: application/json");
mg_printf(c, "%s", json_str);
free(json_str);
}
cJSON_Delete(json);
schedule_free_list(all_schedules);
}

View file

@ -1,34 +0,0 @@
#include <models/relay_dbo.h>
#include "controllers_valid_json.h"
using namespace drogon;
using namespace filters::controllers;
void valid_json::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if(req->getMethod() == Options)
{
fccb();
return;
}
Json::Value body = *req->jsonObject();
bool is_valid = true;
is_valid &= body["name"].type() == Json::ValueType::stringValue;
is_valid &= body["ip"].type() == Json::ValueType::stringValue;
if(is_valid)
{
//Passed
fccb();
return;
}
//Check failed
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
namespace filters::controllers
{
class valid_json : public HttpFilter<valid_json>
{
public:
valid_json() = default;
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
}

View file

@ -1,29 +0,0 @@
#include "json_required.h"
using namespace drogon;
using namespace filters;
void json_required::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if(req->getMethod() == Options)
{
fccb();
return;
}
// TODO remove this workaround
HttpMethod original_method = req->getMethod();
req->setMethod(Post);
if(req->jsonObject())
{
req->setMethod(original_method);
fccb();
return;
}
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
}

View file

@ -1,14 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
namespace filters
{
class json_required : public HttpFilter<json_required>
{
public:
json_required() = default;
void
doFilter(const HttpRequestPtr &req, FilterCallback &&fcb, FilterChainCallback &&fccb) override;
};
}

View file

@ -1,40 +0,0 @@
#include <models/schedule_dbo.h>
#include "relays_valid_json.h"
using namespace drogon;
using namespace filters::relays;
void valid_json::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if(req->getMethod() == Options)
{
fccb();
return;
}
Json::Value body = *req->jsonObject();
bool is_valid = true;
is_valid &= body["name"].type() == Json::ValueType::stringValue;
is_valid &= body["active_schedule"].type() == Json::ValueType::stringValue;
if(is_valid)
{
schedule_dbo **schedules = schedule_dbo::get_by_simple("id", body["active_schedule"].asCString(), (intptr_t)&sqlite3_bind_text);
bool schedule_found = schedules[0] != nullptr;
schedule_dbo::free_list(schedules);
if(schedule_found)
{
//Passed
fccb();
return;
}
}
//Check failed
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
namespace filters::relays
{
class valid_json : public HttpFilter<valid_json>
{
public:
valid_json() = default;
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
}

View file

@ -1,33 +0,0 @@
#include "schedules_valid_json.h"
using namespace drogon;
using namespace filters::schedules;
void valid_json::doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb)
{
if(req->getMethod() == Options)
{
fccb();
return;
}
Json::Value body = *req->jsonObject();
bool is_valid = true;
is_valid &= body["name"].type() == Json::ValueType::stringValue;
is_valid &= body["periods"].type() == Json::ValueType::arrayValue;
if(is_valid)
{
//Passed
fccb();
return;
}
//Check failed
auto res = drogon::HttpResponse::newHttpResponse();
res->setStatusCode(k400BadRequest);
fcb(res);
}

View file

@ -1,17 +0,0 @@
#pragma once
#include <drogon/HttpFilter.h>
using namespace drogon;
namespace filters::schedules
{
class valid_json : public HttpFilter<valid_json>
{
public:
valid_json() = default;
void doFilter(const HttpRequestPtr &req,
FilterCallback &&fcb,
FilterChainCallback &&fccb) override;
};
}

View file

@ -1,7 +0,0 @@
#include <sqlite3.h>
#include "globals.h"
namespace globals
{
sqlite3 *db;
}

View file

@ -1,9 +0,0 @@
#ifndef EMGAUWA_CORE_GLOBALS_H
#define EMGAUWA_CORE_GLOBALS_H
namespace globals
{
extern sqlite3 *db;
}
#endif //EMGAUWA_CORE_GLOBALS_H

28
handlers/connection.c Normal file
View file

@ -0,0 +1,28 @@
#include <mongoose.h>
#include <logger.h>
#include <router.h>
#include <handlers.h>
void
handler_connection(struct mg_connection *c, int ev, void *p)
{
if (ev == MG_EV_HTTP_REQUEST)
{
LOG_DEBUG("new http request\n");
struct http_message *hm = (struct http_message *) p;
endpoint_t *endpoint = router_find_endpoint(hm->uri.p, hm->uri.len, &hm->method);
if(endpoint && endpoint->func)
{
endpoint->func(c, endpoint->args, p);
}
else
{
mg_send_head(c, 501, 0, "Content-Type: text/plain");
}
//mg_printf(c, "%.*s", (int)hm->message.len, hm->message.p);
//mg_printf(c, "%.*s", (int)hm->body.len, hm->body.p);
}
}

View file

@ -1,43 +0,0 @@
#ifndef EMGAUWA_CORE_HELPERS_H
#define EMGAUWA_CORE_HELPERS_H
#include <json/value.h>
#include <models/period.h>
#include <models/period_list.h>
#include <sqlite3.h>
namespace helpers
{
int
bind_tcp_server(const char *addr, const char *port, int max_client_backlog);
int
get_server_port(int fd);
int
send_udp_broadcast(const char *addr, uint16_t port, const char* message);
period_list*
parse_periods(Json::Value periods_json);
typedef struct sql_filter_builder
{
sql_filter_builder(const char *col_name, const void *value, intptr_t bind_func, const char *logic);
const char *col_name;
const void *value;
intptr_t bind_func;
const char *logic;
} sql_filter_builder;
sqlite3_stmt*
create_sql_filtered_query(const char *sql, sql_filter_builder **filters);
int
open_tcp_connection(char* host, char* port);
int
migrate_sql();
}
#endif //EMGAUWA_CORE_HELPERS_H

52
helpers/bind_server.c Normal file
View file

@ -0,0 +1,52 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <logger.h>
#include <helpers.h>
int
helper_bind_tcp_server(char* addr, uint16_t port, int max_client_backlog)
{
char port_str[6];
sprintf(port_str, "%d", port);
struct addrinfo hints, *res;
int fd;
int status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((status = getaddrinfo(addr, port_str, &hints, &res)) != 0)
{
LOG_ERROR("getaddrinfo: %s\n", gai_strerror(status));
return -1;
}
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if ((status = bind(fd, res->ai_addr, res->ai_addrlen)) == -1)
{
LOG_ERROR("error binding socket: %s\n", strerror(errno));
freeaddrinfo(res);
return -1;
}
if ((status = listen(fd, max_client_backlog)) == -1)
{
LOG_ERROR("error setting up listener: %s\n", strerror(errno));
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return fd;
}

View file

@ -1,43 +0,0 @@
#include <netdb.h>
#include <trantor/utils/Logger.h>
#include <helpers.h>
#include "config.h"
int
helpers::bind_tcp_server(const char *addr, const char *port, int max_client_backlog)
{
struct addrinfo hints{}, *res;
int fd;
int status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((status = getaddrinfo(addr, port, &hints, &res)) != 0)
{
LOG_ERROR << "Error getting address info: " << gai_strerror(status);
}
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if ((status = bind(fd, res->ai_addr, res->ai_addrlen)) == -1)
{
LOG_ERROR << "Error binding socket. " << status;
freeaddrinfo(res);
return -1;
}
if ((status = listen(fd, max_client_backlog)) == -1)
{
LOG_ERROR << "Error setting up listener. " << status;
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return fd;
}

39
helpers/connect_server.c Normal file
View file

@ -0,0 +1,39 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <logger.h>
#include <helpers.h>
int
helper_connect_tcp_server(char* host, uint16_t port)
{
char port_str[6];
sprintf(port_str, "%d", port);
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
if ((status = getaddrinfo(host, port_str, &hints, &res)) != 0) { //getaddrinfo() will evaluate the given address, using the hints-flags and port, and return an IP address and other server infos
LOG_ERROR("getaddrinfo: %s\n", gai_strerror(status));
return -1;
}
//res got filled out by getaddrinfo() for us
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0) {
LOG_ERROR("connect() failed\n");
freeaddrinfo(res);
return -1;
}
freeaddrinfo(res);
return s;
}

View file

@ -1,58 +0,0 @@
#include <helpers.h>
#include <drogon/drogon.h>
#include <globals.h>
sqlite3_stmt*
helpers::create_sql_filtered_query(const char *sql, sql_filter_builder **filters)
{
char *old_query = (char*)sql;
char *new_query;
sql_filter_builder *filter;
int filter_count = 0;
do
{
filter = filters[filter_count];
filter_count++;
asprintf(&new_query, " %s %s=?%d %s", old_query, filter->col_name, filter_count, filter->logic);
if(old_query != sql)
{
free(old_query);
}
old_query = new_query;
} while(filter->logic[0] != ';');
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, new_query, -1, &stmt, nullptr);
free(new_query);
for(int i = 0; i < filter_count; i++)
{
filter = filters[i];
if(filter->bind_func == (intptr_t)&sqlite3_bind_int)
{
sqlite3_bind_int(stmt, i + 1, (int)(intptr_t)filter->value);
}
if(filter->bind_func == (intptr_t)&sqlite3_bind_text)
{
sqlite3_bind_text(stmt, i + 1, (char*)filter->value, -1, SQLITE_STATIC);
}
}
return stmt;
}
helpers::sql_filter_builder::sql_filter_builder(const char *col_name, const void *value, intptr_t bind_func,
const char *logic)
{
this->col_name = col_name;
this->value = value;
this->bind_func = bind_func;
this->logic = logic;
}

12
helpers/get_day_of_week.c Normal file
View file

@ -0,0 +1,12 @@
#include <time.h>
#include <helpers.h>
int
helper_get_weekday(const time_t timestamp_now)
{
struct tm *now = localtime(&timestamp_now);
int wday_sun_sat = now->tm_wday;
int wday_mon_sun = (wday_sun_sat + 6) % 7;
return wday_mon_sun;
}

24
helpers/get_port.c Normal file
View file

@ -0,0 +1,24 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <helpers.h>
#include <logger.h>
uint16_t
helper_get_port(int sock)
{
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
if (getsockname(sock, (struct sockaddr *)&sin, &len) == -1)
{
LOG_ERROR("could not get socket name for port: %s\n", strerror(errno));
return 0;
}
else
{
return ntohs(sin.sin_port);
}
}

View file

@ -1,18 +0,0 @@
#include <netdb.h>
#include <helpers.h>
int
helpers::get_server_port(int fd)
{
if(fd == -1)
{
return -1;
}
struct sockaddr_in sin{};
socklen_t addr_len = sizeof(sin);
if(getsockname(fd, (struct sockaddr *)&sin, &addr_len) == 0)
{
return ntohs(sin.sin_port);
}
return -1;
}

View file

@ -1,63 +0,0 @@
#include <helpers.h>
#include <globals.h>
#include <drogon/trantor/trantor/utils/Logger.h>
#include <config.h>
#include <sql/migration_0.h>
int
helpers::migrate_sql()
{
uint16_t version_num = 0;
int s, rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT version_num FROM meta LIMIT 1;", -1, &stmt, nullptr);
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
version_num = sqlite3_column_int(stmt, 0);
}
else
{
version_num = 0;
}
uint16_t new_version_num = version_num;
char* err_msg;
char* sql;
sqlite3_finalize(stmt);
switch(version_num)
{
case 0:
rc = sqlite3_exec(globals::db, (const char *)sql_migration_0_sql, nullptr, nullptr, &err_msg);
if(rc != 0)
{
LOG_FATAL << "Couldn't migrate LEVEL 0 (" << err_msg << ")";
break;
}
new_version_num = 1;
default:
break;
}
if(version_num == 0)
{
sqlite3_prepare_v2(globals::db, "INSERT INTO meta (version_num) VALUES (?1);", -1, &stmt, nullptr);
}
else
{
sqlite3_prepare_v2(globals::db, "UPDATE meta SET version_num=?1;", -1, &stmt, nullptr);
}
sqlite3_bind_int(stmt, 1, new_version_num);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOG_FATAL << "Couldn't write new Schema Version";
}
return rc == SQLITE_DONE;
}

View file

@ -0,0 +1,57 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <logger.h>
#include <helpers.h>
int
helper_open_discovery_socket(uint16_t discovery_port)
{
struct addrinfo hints, *res;
int fd, status;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; // use ipv4
hints.ai_socktype = SOCK_DGRAM; //set socket flag
hints.ai_flags = AI_PASSIVE; // get my IP
char discovery_port_str[6];
sprintf(discovery_port_str, "%u", discovery_port);
//get connection info for our computer
if ((status = getaddrinfo(NULL, discovery_port_str, &hints, &res)) != 0)
{
LOG_FATAL("getaddrinfo: %s\n", gai_strerror(status));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
//creating socket
fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
int yes = 1;
// lose the pesky "Address already in use" error message
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
{
LOG_FATAL("setsockopt: %s\n", strerror(errno));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
if (bind(fd, res->ai_addr, res->ai_addrlen) == -1)
{
LOG_FATAL("bind: %s\n", strerror(errno));
freeaddrinfo(res);
exit(EXIT_FAILURE);
}
freeaddrinfo(res);
LOG_INFO("opened discovery socket on port %u\n", discovery_port);
return fd;
}

View file

@ -1,33 +0,0 @@
#include <helpers.h>
#include <netdb.h>
#include <trantor/utils/Logger.h>
int
helpers::open_tcp_connection(char *host, char *port)
{
int s, status;
struct addrinfo hints{}, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if ((status = getaddrinfo(host, port, &hints, &res)) != 0)
{
LOG_ERROR << "Error getting address info: " << gai_strerror(status);
freeaddrinfo(res);
return 0;
}
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); //creating Socket
if ((status = connect(s, res->ai_addr, res->ai_addrlen)) != 0)
{
LOG_ERROR << "Error opening connection " << status;
freeaddrinfo(res);
return 0;
}
freeaddrinfo(res);
return s;
}

62
helpers/parse_cli.c Normal file
View file

@ -0,0 +1,62 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <argparse.h>
#include <config.h>
#include <logger.h>
#include <helpers.h>
static const char *const usage[] = {
"controller [options] [[--] args]",
"controller [options]",
NULL,
};
#define PERM_READ (1<<0)
#define PERM_WRITE (1<<1)
#define PERM_EXEC (1<<2)
void
helper_parse_cli(int argc, const char **argv, config_t *config)
{
struct argparse_option options[] =
{
OPT_HELP(),
OPT_GROUP("Basic options"),
OPT_STRING('c', "config", &config->file, "path to config file", NULL, 0, OPT_NONEG),
OPT_END(),
};
struct argparse argparse;
argparse_init(&argparse, options, usage, 0);
argparse_describe(
&argparse,
"\nA brief description of what the program does and how it works.",
"\nAdditional description of the program after the description of the arguments."
);
argc = argparse_parse(&argparse, argc, argv);
if(argc == 1)
{
if(strcmp(argv[0], "start") == 0)
{
config->run_type = RUN_TYPE_START;
return;
}
if(strcmp(argv[0], "test") == 0)
{
config->run_type = RUN_TYPE_TEST;
return;
}
LOG_FATAL("bad action '%s' given ('start', 'test')\n", argv[0]);
exit(1);
}
else
{
LOG_FATAL("no action given ('start', 'test')\n");
exit(1);
}
return;
}

View file

@ -1,68 +0,0 @@
#include <helpers.h>
#include <drogon/drogon.h>
#include <models/period_list.h>
static int
parse_HHMM(const char *begin, uint16_t *h, uint16_t *m)
{
uint16_t tmp_h, tmp_m;
char *check = nullptr;
tmp_h = (uint16_t)strtol(begin, &check, 10);
if(begin == check)
{
return 1;
}
begin = check + 1;
tmp_m = (uint16_t)strtol(begin, &check, 10);
if(begin == check)
{
return 1;
}
*h = tmp_h;
*m = tmp_m;
return 0;
}
period_list*
helpers::parse_periods(Json::Value periods_json)
{
auto result = new period_list();
for (Json::Value::ArrayIndex i = 0; i != periods_json.size(); i++)
{
Json::Value p = periods_json[i];
if(!(p.isMember("start") && p.isMember("end")))
{
continue;
}
const char *start_str = p["start"].asCString();
const char *end_str = p["end"].asCString();
uint16_t h, m, start, end;
if(parse_HHMM(start_str, &h, &m))
{
continue;
}
start = (uint16_t)((h * 60) + m);
if(parse_HHMM(end_str, &h, &m))
{
continue;
}
end = (uint16_t)((h * 60) + m);
if(start < 0 || start > 24 * 60 || end < 0 || end > 24 * 60)
{
continue;
}
result->add_period(start, end);
}
return result;
}

View file

@ -1,39 +0,0 @@
#include <arpa/inet.h>
#include <trantor/utils/Logger.h>
#include "config.h"
#include <unistd.h>
#include <helpers.h>
int
helpers::send_udp_broadcast(const char *addr, uint16_t port, const char* message)
{
struct sockaddr_in their_addr{};
int fd;
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
LOG_ERROR << "Error creating socket";
return -1;
}
int broadcast = 1;
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof broadcast) < 0)
{
LOG_ERROR << "Error setting broadcast";
return -1;
}
memset(&their_addr, 0, sizeof(their_addr));
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(port);
their_addr.sin_addr.s_addr = inet_addr(addr);
if(sendto(fd, message, strlen(message), 0, (struct sockaddr *)&their_addr, sizeof(their_addr)) < 0)
{
LOG_ERROR << "Error sending broadcast " << errno << " " << strerror(errno);
return -1;
}
close(fd);
return 0;
}

18
include/colors.h Normal file
View file

@ -0,0 +1,18 @@
#ifndef CORE_COLORS_H
#define CORE_COLORS_H
#define COLOR_RED "\033[0;31m"
#define COLORB_RED "\033[1;31m"
#define COLOR_GREEN "\033[0;32m"
#define COLORB_GREEN "\033[1;32m"
#define COLOR_YELLOW "\033[0;33m"
#define COLORB_YELLOW "\033[1;33m"
#define COLOR_BLUE "\033[0;34m"
#define COLORB_BLUE "\033[1;34m"
#define COLOR_MAGENTA "\033[0;35m"
#define COLORB_MAGENTA "\033[1;35m"
#define COLOR_CYAN "\033[0;36m"
#define COLORB_CYAN "\033[1;36m"
#define COLOR_NONE "\033[0m"
#endif //CORE_COLORS_H

24
include/config.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef CORE_CONFIG_H
#define CORE_CONFIG_H
#include <stdint.h>
#include <confini.h>
#include <enums.h>
typedef struct
{
char *file;
char *database;
log_level_t log_level;
run_type_t run_type;
char server_port[6];
uint16_t discovery_port;
} config_t;
extern config_t global_config;
int
config_load(IniDispatch *disp, void *config_void);
#endif /* CORE_CONFIG_H */

31
include/constants.h Normal file
View file

@ -0,0 +1,31 @@
#ifndef CORE_CONTANTS_H
#define CORE_CONTANTS_H
#define SECONDS_PER_DAY 86400 // 60 * 60 * 24
#define SECONDS_PER_MINUTE 60
#define POLL_FDS_COUNT 2
/**
* @brief Limit the maximum length of a controller/relay/etc name
*
* The NULL terminator is not included. Arrays of length #MAX_NAME_LENGTH + 1 are required.
*/
#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
*/
#define ACCEPT_TIMEOUT_MSECONDS 1000
#define PIFACE_GPIO_BASE 200
#endif /* CORE_CONTANTS_H */

11
include/database.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef CORE_DATABASE_H
#define CORE_DATABASE_H
#include <sqlite3.h>
extern sqlite3 *global_database;
int
database_migrate();
#endif /* CORE_DATABASE_H */

13
include/drivers.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef CORE_DRIVERS_H
#define CORE_DRIVERS_H
#include <models/relay.h>
#include <enums.h>
void
driver_piface_set(int pin, int value);
void
driver_gpio_set(int pin, int value);
#endif /* CORE_DRIVERS_H */

View file

@ -0,0 +1,15 @@
#ifndef CORE_ENDPOINTS_API_V1_SCHEDULES_H
#define CORE_ENDPOINTS_API_V1_SCHEDULES_H
#include <router.h>
void
api_v1_schedules_POST(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm);
void
api_v1_schedules_GET(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm);
void
api_v1_schedules_STR_GET(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm);
#endif /* CORE_ENDPOINTS_API_V1_SCHEDULES_H */

64
include/enums.h Normal file
View file

@ -0,0 +1,64 @@
#ifndef CORE_ENUMS_H
#define CORE_ENUMS_H
typedef enum
{
POLL_FDS_DISCOVERY,
POLL_FDS_COMMAND
} poll_fds_t;
typedef enum
{
DISCOVERY_MAPPING_ID = 0,
DISCOVERY_MAPPING_NAME = 1,
DISCOVERY_MAPPING_COMMAND_PORT = 2,
DISCOVERY_MAPPING_RELAY_COUNT = 3,
} discovery_mapping_t;
typedef enum
{
COMMAND_MAPPING_CODE = 0,
COMMAND_MAPPING_NAME = 1,
COMMAND_MAPPING_RELAY_NUM = 2,
COMMAND_MAPPING_SCHEDULES_ARRAY = 3,
COMMAND_MAPPING_SCHEDULE_ID = 4,
COMMAND_MAPPING_PERIODS_COUNT = 5,
COMMAND_MAPPING_PERIODS_BLOB = 6,
} control_mapping_t;
typedef enum
{
COMMAND_CODE_GET_TIME = 1,
COMMAND_CODE_GET_ID = 2,
COMMAND_CODE_SET_NAME = 100,
COMMAND_CODE_GET_NAME = 101,
COMMAND_CODE_SET_SCHEDULE = 102,
COMMAND_CODE_GET_SCHEDULE = 103,
COMMAND_CODE_SET_RELAY_NAME = 104,
COMMAND_CODE_GET_RELAY_NAME = 105,
} command_code_t;
typedef enum
{
RELAY_DRIVER_NONE,
RELAY_DRIVER_GPIO,
RELAY_DRIVER_PIFACE,
} relay_driver_t;
typedef enum
{
RUN_TYPE_START,
RUN_TYPE_TEST,
} run_type_t;
typedef enum
{
LOG_LEVEL_TRACE = 5,
LOG_LEVEL_DEBUG = 4,
LOG_LEVEL_INFO = 3,
LOG_LEVEL_WARN = 2,
LOG_LEVEL_ERROR = 1,
LOG_LEVEL_FATAL = 0,
} log_level_t;
#endif /* CORE_ENUMS_H */

7
include/handlers.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef CORE_HANDLERS_H
#define CORE_HANDLERS_H
void
handler_connection(struct mg_connection *c, int ev, void *p);
#endif /* CORE_HANDLERS_H */

34
include/helpers.h Normal file
View file

@ -0,0 +1,34 @@
#ifndef CORE_HELPERS_H
#define CORE_HELPERS_H
#include <config.h>
#include <confini.h>
int
helper_connect_tcp_server(char* host, uint16_t port);
int
helper_bind_tcp_server(char* addr, uint16_t port, int max_client_backlog);
uint16_t
helper_get_port(int sock);
/**
* @brief Open socket for discovery
*
* Will exit program when unable to open socket.
*
* @param discovery_port Port number to listen on for discovery broadcasts
*
* @return Open socket to accept discovery broadcasts on
*/
int
helper_open_discovery_socket(uint16_t discovery_port);
void
helper_parse_cli(int argc, const char **argv, config_t *config);
int
helper_get_weekday(const time_t timestamp_now);
#endif /* CORE_HELPERS_H */

21
include/logger.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef CORE_LOGGER_H
#define CORE_LOGGER_H
#include <stdio.h>
#include <time.h>
#include <colors.h>
#include <config.h>
#include <macros.h>
void
logger_log(FILE *stream, log_level_t level, const char *filename, int line, const char *func, const char *msg, ...);
#define LOG_TRACE(...) logger_log(stdout, LOG_LEVEL_TRACE, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_DEBUG(...) logger_log(stdout, LOG_LEVEL_DEBUG, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_INFO(...) logger_log(stdout, LOG_LEVEL_INFO , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_WARN(...) logger_log(stdout, LOG_LEVEL_WARN , __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_ERROR(...) logger_log(stderr, LOG_LEVEL_ERROR, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#define LOG_FATAL(...) logger_log(stderr, LOG_LEVEL_FATAL, __FILENAME__, __LINE__, __func__, ##__VA_ARGS__)
#endif //CORE_LOGGER_H

13
include/macros.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef CORE_MACROS_H
#define CORE_MACROS_H
#include <colors.h>
#include <logger.h>
#ifndef SOURCE_PATH_SIZE
#define SOURCE_PATH_SIZE 0
#endif
#define __FILENAME__ (__FILE__ + SOURCE_PATH_SIZE)
#endif //CORE_MACROS_H

View file

@ -0,0 +1,45 @@
#ifndef CORE_MODELS_CONTROLLER_H
#define CORE_MODELS_CONTROLLER_H
#include <uuid/uuid.h>
#include <sqlite3.h>
#include <helpers.h>
#include <models/relay.h>
typedef struct
{
uuid_t id;
char name[128];
char ip[17];
int active;
int port;
int relay_count;
relay_t **relays;
} controller_t;
void
controller_free(controller_t* contoller);
int
controller_save(controller_t* contoller);
int
controller_remove(controller_t* contoller);
char*
controller_to_json(controller_t* contoller);
controller_t**
controller_get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param);
controller_t**
controller_get_all();
int
controller_command(int command_code, char *payload, uint32_t payload_size);
void
controller_free_list(controller_t **controllers_list);
#endif /* CORE_MODELS_CONTROLLER_H */

View file

@ -0,0 +1,19 @@
#ifndef CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H
#define CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H
int
junction_relay_schedule_get_schedule_id(uint8_t weekday, int relay_id);
int
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id);
int
junction_relay_schedule_remove(uint8_t weekday, int relay_id, int schedule_id);
int
junction_relay_schedule_remove_for_relay(int relay_id);
int
junction_relay_schedule_remove_for_schedule(int schedule_id);
#endif /* CORE_MODELS_JUNCTION_RELAY_SCHEDULE_H */

View file

@ -0,0 +1,32 @@
#ifndef CORE_MODELS_JUNCTION_TAG_H
#define CORE_MODELS_JUNCTION_TAG_H
int*
junction_tag_get_relays_for_tag_id(int tag_id);
int*
junction_tag_get_schedules_for_tag_id(int tag_id);
int*
junction_tag_get_tags_for_relay_id(int relay_id);
int*
junction_tag_get_tags_for_schedule_id(int schedule_id);
int
junction_tag_insert(int tag_id, int relay_id, int schedule_id);
int
junction_tag_remove(int tag_id, int relay_id, int schedule_id);
int
junction_tag_remove_for_tag(int tag_id);
int
junction_tag_remove_for_relay(int relay_id);
int
junction_tag_remove_for_schedule(int schedule_id);
#endif /* CORE_MODELS_JUNCTION_TAG_H */

21
include/models/period.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef CORE_PERIOD_H
#define CORE_PERIOD_H
#include <stdint.h>
typedef struct
{
uint16_t start;
uint16_t end;
} period_t;
period_t*
period_create(uint16_t start, uint16_t end);
int
period_includes_time(period_t *period, uint16_t timestamp);
int
period_helper_parse_hhmm(const char *hhmm_str, uint16_t *hhmm);
#endif /* CORE_PERIOD_H */

49
include/models/relay.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef CORE_RELAY_H
#define CORE_RELAY_H
#include <string.h>
#include <uuid/uuid.h>
#include <helpers.h>
#include <database.h>
#include <models/schedule.h>
typedef struct
{
int id;
char name[128];
int number;
uuid_t controller_id;
int active_schedule_id;
schedule_t *active_schedule;
schedule_t *schedules[7];
} relay_t;
bool
relay_save();
bool
relay_remove();
char*
relay_to_json();
void
relay_free_list(relay_t **relays_list);
relay_t**
relay_get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param);
relay_t*
relay_get_by_id(int id);
relay_t*
relay_get_relay_for_controller(uuid_t controller_id, int relay_num);
bool
relay_valid_num_is_for_controller(uuid_t controller_id, int relay_num);
relay_t**
relay_get_all();
#endif /* CORE_RELAY_H */

58
include/models/schedule.h Normal file
View file

@ -0,0 +1,58 @@
#ifndef CORE_SCHEDULE_H
#define CORE_SCHEDULE_H
#include <uuid/uuid.h>
#include <cJSON.h>
#include <constants.h>
#include <models/period.h>
typedef struct
{
int id;
uuid_t uid;
char name[MAX_NAME_LENGTH + 1];
uint16_t periods_count;
period_t *periods;
} schedule_t;
int
schedule_save(schedule_t *schedule);
int
schedule_remove(schedule_t *schedule);
void
schedule_free(schedule_t *schedule);
void
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);
schedule_t**
schedule_get_by_simple(const char *key, const void *value, intptr_t bind_func, int bind_func_param);
schedule_t*
schedule_get_by_id_or_off(int id);
schedule_t*
schedule_get_by_id(int id);
schedule_t**
schedule_get_all();
int
schedule_uid_parse(const char *uid_str, uuid_t result);
void
schedule_uid_unparse(const uuid_t uid, char *result);
#endif /* CORE_SCHEDULE_H */

17
include/models/tag.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef CORE_MODELS_TAG_H
#define CORE_MODELS_TAG_H
int
tag_save(int id, const char *tag);
int
tag_remove(int id);
char*
tag_get_tag(int id);
int
tag_get_id(const char* tag);
#endif /* CORE_MODELS_TAG_H */

52
include/router.h Normal file
View file

@ -0,0 +1,52 @@
#ifndef CORE_ROUTER_H
#define CORE_ROUTER_H
#include <mongoose.h>
#define ENDPOINTS_MAX_COUNT 16
typedef enum
{
ENDPOINT_ARG_TYPE_INT,
ENDPOINT_ARG_TYPE_STR
} endpoint_arg_type_e;
typedef struct
{
endpoint_arg_type_e type;
union
{
int v_int;
char *v_str;
} value;
} endpoint_args_t;
typedef void (*endpoint_func_f)(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm);
typedef struct
{
char **route;
char *route_keeper;
int methods;
endpoint_func_f func;
int args_count;
endpoint_args_t *args;
int possible_route;
int args_found;
} endpoint_t;
void
router_init();
void
router_register_endpoint(const char *route, int methods, endpoint_func_f func);
endpoint_t*
router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method);
void
router_free();
#endif /* CORE_ROUTER_H */

59
logger.c Normal file
View file

@ -0,0 +1,59 @@
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <config.h>
#include <logger.h>
#define COLOR_TRACE COLOR_GREEN
#define COLOR_DEBUG COLOR_BLUE
#define COLOR_INFO COLOR_CYAN
#define COLOR_WARN COLOR_YELLOW
#define COLOR_ERROR COLOR_RED
#define COLOR_FATAL COLOR_MAGENTA
void
logger_log(FILE *stream, log_level_t level, const char *filename, int line, const char *func, const char *msg, ...)
{
if(global_config.log_level < level)
{
return;
}
switch(level)
{
case LOG_LEVEL_TRACE:
fprintf(stream, COLOR_TRACE "[TRACE] ");
break;
case LOG_LEVEL_DEBUG:
fprintf(stream, COLOR_DEBUG "[DEBUG] ");
break;
case LOG_LEVEL_INFO:
fprintf(stream, COLOR_INFO "[INFO ] ");
break;
case LOG_LEVEL_WARN:
fprintf(stream, COLOR_WARN "[WARN ] ");
break;
case LOG_LEVEL_ERROR:
fprintf(stream, COLOR_ERROR "[ERROR] ");
break;
case LOG_LEVEL_FATAL:
fprintf(stream, COLOR_FATAL "[FATAL] ");
break;
default:
fprintf(stream, COLOR_NONE "[LOG ] ");
break;
}
char timestamp_str[32];
time_t rawtime;
time(&rawtime);
strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime));
fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func);
va_list args;
va_start(args, msg);
vfprintf(stream, msg, args);
va_end(args);
}

107
main.c Normal file
View file

@ -0,0 +1,107 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <mongoose.h>
#include <router.h>
#include <logger.h>
#include <config.h>
#include <database.h>
#include <handlers.h>
#include <enums.h>
#include <helpers.h>
#include <confini.h>
#include <models/controller.h>
static struct mg_mgr mgr;
static void
terminate(int signum)
{
LOG_INFO("terminating controller (%d)\n", signum);
mg_mgr_free(&mgr);
sqlite3_close(global_database);
free(global_config.database);
router_free();
exit(signum);
}
/**
* @brief The main function
*
* @param argc UNUSED
* @param argv UNUSED
*
* @return Statuscode to indicate success (0) or failure (!0)
*/
int
main(int argc, const char** argv)
{
signal(SIGINT, terminate);
signal(SIGABRT, terminate);
signal(SIGTERM, terminate);
/******************** LOAD CONFIG ********************/
global_config.file = "core.ini";
global_config.log_level = LOG_LEVEL_INFO;
helper_parse_cli(argc, argv, &global_config);
FILE * const ini_file = fopen(global_config.file, "rb");
if(ini_file == NULL)
{
LOG_FATAL("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))
{
LOG_FATAL("unable to parse ini file\n");
exit(1);
}
fclose(ini_file);
/******************** SETUP DATABASE ********************/
int rc = sqlite3_open(global_config.database, &global_database);
if(rc) {
LOG_FATAL("can't open database: %s\n", sqlite3_errmsg(global_database));
return 1;
}
if(database_migrate())
{
terminate(1);
}
/******************** INIT ROUTER ********************/
router_init();
/******************** START MAIN LOOP ********************/
struct mg_connection *c;
mg_mgr_init(&mgr, NULL);
c = mg_bind(&mgr, global_config.server_port, handler_connection);
mg_set_protocol_http_websocket(c);
for (;;)
{
mg_mgr_poll(&mgr, 1000);
}
terminate(0);
}

68
main.cc
View file

@ -1,68 +0,0 @@
#include <drogon/drogon.h>
#include <sqlite3.h>
#include <models/controller_dbo.h>
#include <csignal>
#include "globals.h"
#include "config.h"
static void
add_cors_headers(const drogon::HttpRequestPtr &requestPtr, const drogon::HttpResponsePtr &responsePtr)
{
responsePtr->addHeader("Access-Control-Allow-Origin", "*");
}
static void
terminate(int signum)
{
LOG_INFO << "Terminating Server (" << signum << ")";
sqlite3_close(globals::db);
quick_exit(signum);
}
/*static void test()
{
LOG_DEBUG << "LOOP";
}*/
int
main()
{
signal(SIGINT, terminate);
signal(SIGABRT, terminate);
signal(SIGTERM, terminate);
signal(SIGKILL, terminate);
int rc;
/* Open database */
rc = sqlite3_open("core.sqlite", &globals::db);
if( rc ) {
LOG_FATAL << "Can't open database: " << sqlite3_errmsg(globals::db);
return 1;
}
if(!helpers::migrate_sql())
{
terminate(1);
}
//Load config file
drogon::app().loadConfigFile("config.json");
drogon::app().registerPostHandlingAdvice(add_cors_headers);
drogon::app().instance().setCustom404Page(drogon::HttpResponse::newFileResponse("./static/index.html", "", drogon::CT_TEXT_HTML));
//drogon::app().getLoop()->runEvery(1, &test);
//Run HTTP framework,the method will block in the internal event loop
LOG_INFO << "Starting Emgauwa Core (" << config::version << ")";
drogon::app().run();
return 0;
}

View file

@ -1,246 +0,0 @@
#include <cstdio>
#include <cstring>
#include <trantor/utils/Logger.h>
#include <sys/socket.h>
#include <unistd.h>
#include <helpers.h>
#include "controller_dbo.h"
#include "globals.h"
controller_dbo::~controller_dbo()
{
if(this->relays)
{
relay_dbo::free_list(this->relays);
}
}
static bool controller_db_update_insert(controller_dbo *controller, sqlite3_stmt *stmt)
{
int rc;
sqlite3_bind_text(stmt, 1, controller->id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, controller->name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, controller->ip, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, controller->active);
sqlite3_bind_int(stmt, 5, controller->port);
sqlite3_bind_int(stmt, 6, controller->relay_count);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
printf("ERROR inserting data: %s\n", sqlite3_errmsg(globals::db));
return false;
}
sqlite3_finalize(stmt);
return true;
}
static controller_dbo*
controller_db_select_mapper(sqlite3_stmt *stmt)
{
auto *new_controller = new controller_dbo();
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'a': // active
new_controller->active = (bool)sqlite3_column_int(stmt, i);
break;
case 'i':
switch(name[1])
{
case 'd': // id
strncpy(new_controller->id, (const char*)sqlite3_column_text(stmt, i), 32);
break;
case 'p': // ip
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), 127);
break;
case 'p': // port
new_controller->port = sqlite3_column_int(stmt, i);
break;
case 'r': // relay_count
new_controller->relay_count = sqlite3_column_int(stmt, i);
break;
default: // ignore columns not implemented
break;
}
}
return new_controller;
}
static controller_dbo**
controller_db_select(sqlite3_stmt *stmt)
{
auto **all_controllers = (controller_dbo**)malloc(sizeof(controller_dbo*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
controller_dbo *new_controller = controller_db_select_mapper(stmt);
new_controller->relays = relay_dbo::get_by_simple("controller_id", new_controller->id, (intptr_t)&sqlite3_bind_text);
row++;
all_controllers = (controller_dbo**)realloc(all_controllers, sizeof(controller_dbo*) * (row + 1));
all_controllers[row - 1] = new_controller;
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR << "Error Selecting controllers from database";
sqlite3_finalize(stmt);
return nullptr;
}
}
}
sqlite3_finalize(stmt);
all_controllers[row] = nullptr;
return all_controllers;
}
bool
controller_dbo::update()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "UPDATE controllers set name = ?2, ip = ?3, active = ?4, port = ?5, relay_count = ?6 WHERE id = ?1;", -1, &stmt, nullptr);
return controller_db_update_insert(this, stmt);
}
bool
controller_dbo::insert()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "INSERT INTO controllers(id, name, ip, active, port, relay_count) values (?1, ?2, ?3, ?4, ?5, ?6);", -1, &stmt, nullptr);
return controller_db_update_insert(this, stmt);
}
bool
controller_dbo::remove()
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM controllers WHERE id=?1;", -1, &stmt, nullptr);
sqlite3_bind_text(stmt, 1, this->id, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
Json::Value
controller_dbo::to_json()
{
Json::Value controller_json;
controller_json["name"] = this->name;
controller_json["id"] = this->id;
controller_json["ip"] = this->ip;
//controller_json["port"] = this->port;
controller_json["relay_count"] = this->relay_count;
controller_json["active"] = this->active;
controller_json["relays"] = Json::arrayValue;
for(int i = 0; this->relays[i] != nullptr; i++)
{
controller_json["relays"].append(this->relays[i]->to_json());
}
return controller_json;
}
controller_dbo**
controller_dbo::get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT * FROM controllers;", -1, &stmt, nullptr);
return controller_db_select(stmt);
}
controller_dbo**
controller_dbo::get_by_simple(const char *key, const void *value, intptr_t bind_func)
{
helpers::sql_filter_builder *filters[1];
helpers::sql_filter_builder filter
{
key,
value,
bind_func,
";"
};
filters[0] = &filter;
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM controllers WHERE", filters);
return controller_db_select(stmt);
}
controller_dbo**
controller_dbo::get_by(helpers::sql_filter_builder **filters)
{
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM controllers WHERE", filters);
return controller_db_select(stmt);
}
bool
controller_dbo::command(int command_code, const char *payload)
{
char port_str[6];
sprintf(port_str, "%d", this->port);
int controller_socket = helpers::open_tcp_connection(this->ip, port_str);
if(!controller_socket)
{
LOG_ERROR << "Can't open command socket " << this->ip << ":" << port_str;
return false;
}
LOG_DEBUG << "Commanding (" << command_code << ") " << payload;
send(controller_socket, &command_code, 1, 0);
send(controller_socket, payload, strlen(payload), 0);
close(controller_socket);
return true;
}
void
controller_dbo::free_list(controller_dbo **controllers_list)
{
for(int i = 0; controllers_list[i] != nullptr; i++)
{
delete controllers_list[i];
}
free(controllers_list);
}

View file

@ -1,53 +0,0 @@
#ifndef EMGAUWA_CORE_controller_DBO_H
#define EMGAUWA_CORE_controller_DBO_H
#include <string>
#include <sqlite3.h>
#include <json/value.h>
#include <helpers.h>
#include "relay_dbo.h"
class controller_dbo
{
public:
char id[33];
char name[128];
char ip[17];
bool active;
int port;
int relay_count;
relay_dbo **relays;
~controller_dbo();
bool
update();
bool
insert();
bool
remove();
Json::Value
to_json();
static controller_dbo**
get_by_simple(const char *key, const void *value, intptr_t bind_func);
static controller_dbo**
get_by(helpers::sql_filter_builder **filters);
static controller_dbo**
get_all();
bool
command(int command_code, const char *payload);
static void
free_list(controller_dbo **controllers_list);
};
#endif //EMGAUWA_CORE_controller_DBO_H

View file

@ -0,0 +1,114 @@
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <models/junction_relay_schedule.h>
#include <logger.h>
#include <database.h>
int
junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id)
{
int rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "INSERT INTO junction_relay_schedule(weekday, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOG_ERROR("error inserting data: %s", sqlite3_errmsg(global_database));
return false;
}
sqlite3_finalize(stmt);
return true;
}
int
junction_relay_schedule_get_schedule_id(uint8_t weekday, int relay_id)
{
int result = 0;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT schedule_id FROM junction_relay_schedule WHERE weekday=?1 AND relay_id=?2 LIMIT 1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, relay_id);
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
result = sqlite3_column_int(stmt, 0);
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error reading from database: %s", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
return result;
}
int
junction_relay_schedule_remove(uint8_t weekday, int relay_id, int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_relay_schedule WHERE weekday=?1 AND schedule_id=?2 AND relay_id=?3;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, weekday);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_relay_schedule_remove_for_relay(int relay_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_relay_schedule WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_relay_schedule_remove_for_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_relay_schedule WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}

175
models/junction_tag.c Normal file
View file

@ -0,0 +1,175 @@
#include <stdlib.h>
#include <stddef.h>
#include <sqlite3.h>
#include <logger.h>
#include <models/junction_tag.h>
#include <database.h>
int
junction_tag_insert(int tag_id, int relay_id, int schedule_id)
{
int rc;
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "INSERT INTO junction_tag(tag_id, schedule_id, relay_id) values (?1, ?2, ?3);", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
printf("ERROR inserting data: %s\n", sqlite3_errmsg(global_database));
return false;
}
sqlite3_finalize(stmt);
return true;
}
static int*
get_ids(sqlite3_stmt *stmt)
{
int *ids = malloc(sizeof(int));
int new_id;
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
new_id = sqlite3_column_int(stmt, 0);
row++;
ids = (int*)realloc(ids, sizeof(int) * (row + 1));
ids[row - 1] = new_id;
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error selecting relays from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
ids[row] = 0;
return ids;
}
int*
junction_tag_get_relays_for_tag_id(int tag_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT relay_id FROM junction_tag WHERE tag_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
return get_ids(stmt);
}
int*
junction_tag_get_schedules_for_tag_id(int tag_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT schedule_id FROM junction_tag WHERE tag_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
return get_ids(stmt);
}
int*
junction_tag_get_tags_for_relay_id(int relay_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT tag_id FROM junction_tag WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
return get_ids(stmt);
}
int*
junction_tag_get_tags_for_schedule_id(int schedule_id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT tag_id FROM junction_tag WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
return get_ids(stmt);
}
int
junction_tag_remove(int tag_id, int relay_id, int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_tag WHERE tag_id=?1 AND schedule_id=?2 AND relay_id=?3;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
sqlite3_bind_int(stmt, 2, schedule_id);
sqlite3_bind_int(stmt, 3, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_tag_remove_for_tag(int tag_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_tag WHERE tag_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, tag_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_tag_remove_for_relay(int relay_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_tag WHERE relay_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, relay_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
int
junction_tag_remove_for_schedule(int schedule_id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM junction_tag WHERE schedule_id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, schedule_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}

37
models/period.c Normal file
View file

@ -0,0 +1,37 @@
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <models/period.h>
int
period_helper_parse_hhmm(const char *hhmm_str, uint16_t *hhmm)
{
if(strlen(hhmm_str) != 5)
{
return 1;
}
if(hhmm_str[2] != ':')
{
return 1;
}
for(int i = 0; i < 5; ++i)
{
if(i != 2)
{
if(hhmm_str[i] < '0' || hhmm_str[i] > '9')
{
return 1;
}
}
}
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);
*hhmm = (tmp_h * 60) + tmp_m;
return 0;
}

View file

@ -1,39 +0,0 @@
#include <cstdio>
#include <cstdint>
#include "period.h"
period::period(uint16_t start, uint16_t end)
{
this->start = start;
this->end = end;
}
Json::Value
period::to_json()
{
Json::Value result;
char start_str[8], end_str[8];
sprintf(start_str, "%02d:%02d", this->start / 60, this->start % 60);
sprintf(end_str, "%02d:%02d", this->end / 60, this->end % 60);
result["start"] = std::string(start_str);
result["end"] = std::string(end_str);
return result;
}
bool
period::is_in_period(uint16_t timestamp)
{
if(this->start < this->end)
{
return this->start <= timestamp and timestamp <= this->end;
}
if(this->start > this->end)
{
return this->end <= timestamp and timestamp <= this->start;
}
return this->start == timestamp;
}

View file

@ -1,23 +0,0 @@
#ifndef EMGAUWA_CORE_PERIOD_H
#define EMGAUWA_CORE_PERIOD_H
#include <json/json.h>
class period
{
public:
uint16_t start;
uint16_t end;
period(uint16_t start, uint16_t end);
Json::Value
to_json();
bool
is_in_period(uint16_t timestamp);
};
#endif //EMGAUWA_CORE_PERIOD_H

View file

@ -1,70 +0,0 @@
#include <cstdint>
#include <cstdlib>
#include "period_list.h"
period_list::period_list()
{
this->periods = (period**)malloc(0);
this->length = 0;
}
period_list::period_list(const uint16_t* periods_blob)
{
this->length = periods_blob[0];
this->periods = (period**)malloc(sizeof(period*) * this->length);
for(int i = 0; i < length; i++)
{
auto new_period = new period(periods_blob[(i * 2) + 1], periods_blob[(i * 2) + 2]);
periods[i] = new_period;
}
}
period_list::period_list(period **periods, uint16_t length)
{
this->periods = periods;
this->length = length;
}
period_list::~period_list()
{
for(int i = 0; i < length; i++)
{
delete this->periods[i];
}
free(this->periods);
}
void
period_list::add_period(uint16_t start, uint16_t end)
{
this->length++;
this->periods = (period**)realloc(this->periods, sizeof(period*) * this->length);
this->periods[this->length - 1] = new period(start, end);
}
Json::Value
period_list::to_json()
{
Json::Value result(Json::arrayValue);
for(int i = 0; i < this->length; i++)
{
result.append(this->periods[i]->to_json());
}
return result;
}
uint16_t*
period_list::to_db_blob()
{
auto result = (uint16_t*)malloc(sizeof(uint16_t) * ((this->length * 2) + 1));
result[0] = this->length;
for(int i = 0; i < this->length; i++)
{
result[(i * 2) + 1] = this->periods[i]->start;
result[(i * 2) + 2] = this->periods[i]->end;
}
return result;
}

View file

@ -1,28 +0,0 @@
#ifndef EMGAUWA_CORE_PERIOD_LIST_H
#define EMGAUWA_CORE_PERIOD_LIST_H
#include <json/json.h>
#include "period.h"
class period_list
{
public:
period **periods;
uint16_t length;
period_list();
explicit period_list(const uint16_t *periods_blob);
period_list(period **periods, uint16_t length);
~period_list();
void
add_period(uint16_t start, uint16_t end);
Json::Value
to_json();
uint16_t*
to_db_blob();
};
#endif //EMGAUWA_CORE_PERIOD_LIST_H

View file

@ -1,257 +0,0 @@
#include <cstdio>
#include <cstring>
#include <trantor/utils/Logger.h>
#include <helpers.h>
#include "relay_dbo.h"
#include "globals.h"
#include "controller_dbo.h"
#include "schedule_dbo.h"
static bool relay_db_update_insert(relay_dbo *relay, sqlite3_stmt *stmt)
{
int rc;
sqlite3_bind_int(stmt, 1, relay->id);
sqlite3_bind_int(stmt, 2, relay->number);
sqlite3_bind_text(stmt, 3, relay->name, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 4, relay->active_schedule_id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 5, relay->controller_id, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
printf("ERROR inserting data: %s\n", sqlite3_errmsg(globals::db));
return false;
}
sqlite3_finalize(stmt);
return true;
}
static relay_dbo*
relay_db_select_mapper(sqlite3_stmt *stmt)
{
auto *new_relay = new relay_dbo();
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'a': // active_schedule_id
strncpy(new_relay->active_schedule_id, (const char*)sqlite3_column_text(stmt, i), 32);
break;
case 'c': // controller_id
strncpy(new_relay->controller_id, (const char*)sqlite3_column_text(stmt, i), 32);
break;
case 'i':
new_relay->id = sqlite3_column_int(stmt, i);
break;
case 'n':
switch(name[1])
{
case 'a': // name
strncpy(new_relay->name, (const char*)sqlite3_column_text(stmt, i), 127);
break;
case 'u': // number
new_relay->number = sqlite3_column_int(stmt, i);
break;
default:
break;
}
default: // ignore columns not implemented
break;
}
}
return new_relay;
}
static relay_dbo**
relay_db_select(sqlite3_stmt *stmt)
{
auto **all_relays = (relay_dbo**)malloc(sizeof(relay_dbo*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
relay_dbo *new_relay = relay_db_select_mapper(stmt);
new_relay->reload_active_schedule();
row++;
all_relays = (relay_dbo**)realloc(all_relays, sizeof(relay_dbo*) * (row + 1));
all_relays[row - 1] = new_relay;
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR << "Error Selecting relays from database: " << sqlite3_errstr(s);
break;
}
}
}
sqlite3_finalize(stmt);
all_relays[row] = nullptr;
return all_relays;
}
void
relay_dbo::reload_active_schedule()
{
schedule_dbo **schedules = schedule_dbo::get_by_simple("id", this->active_schedule_id, (intptr_t)&sqlite3_bind_text);
if(!schedules[0])
{
free(schedules);
schedules = schedule_dbo::get_by_simple("id", "off", (intptr_t)&sqlite3_bind_text);
strcpy(this->active_schedule_id, "off");
}
this->active_schedule = schedules[0];
free(schedules);
}
bool
relay_dbo::update()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "UPDATE relays set number = ?2, name = ?3, active_schedule_id = ?4, controller_id = ?5 WHERE id = ?1;", -1, &stmt, nullptr);
return relay_db_update_insert(this, stmt);
}
bool
relay_dbo::insert()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "INSERT INTO relays(number, name, active_schedule_id, controller_id) values (?2, ?3, ?4, ?5);", -1, &stmt, nullptr);
return relay_db_update_insert(this, stmt);
}
bool
relay_dbo::remove()
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM relays WHERE id=?1;", -1, &stmt, nullptr);
sqlite3_bind_int(stmt, 1, this->id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
Json::Value
relay_dbo::to_json()
{
Json::Value relay_json;
// relay_json["id"] = this->id;
relay_json["name"] = this->name;
relay_json["number"] = this->number;
relay_json["active_schedule_id"] = this->active_schedule_id;
relay_json["controller_id"] = this->controller_id;
relay_json["active_schedule"] = this->active_schedule->to_json();
return relay_json;
}
relay_dbo**
relay_dbo::get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT * FROM relays;", -1, &stmt, nullptr);
return relay_db_select(stmt);
}
relay_dbo**
relay_dbo::get_by_simple(const char *key, const void *value, intptr_t bind_func)
{
helpers::sql_filter_builder *filters[1];
helpers::sql_filter_builder filter
{
key,
value,
bind_func,
";"
};
filters[0] = &filter;
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM relays WHERE", filters);
return relay_db_select(stmt);
}
relay_dbo**
relay_dbo::get_by(helpers::sql_filter_builder **filters)
{
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM relays WHERE", filters);
return relay_db_select(stmt);
}
relay_dbo*
relay_dbo::get_relay_for_controller(const char *controller_id, int relay_num)
{
helpers::sql_filter_builder *filters[2];
helpers::sql_filter_builder filter(
"number",
(void*)(intptr_t)relay_num,
(intptr_t)&sqlite3_bind_int,
"AND"
);
helpers::sql_filter_builder filter2(
"controller_id",
(void*)controller_id,
(intptr_t)sqlite3_bind_text,
";"
);
filters[0] = &filter;
filters[1] = &filter2;
auto relays = relay_dbo::get_by(filters);
relay_dbo *relay = relays[0];
free(relays);
return relay;
}
bool
relay_dbo::valid_num_for_controller(const char *search_controller_id, int relay_num)
{
controller_dbo **controllers = controller_dbo::get_by_simple("id", search_controller_id, (intptr_t)&sqlite3_bind_text);
bool valid_id_and_num = controllers[0] && controllers[0]->relay_count > relay_num;
controller_dbo::free_list(controllers);
return valid_id_and_num;
}
void
relay_dbo::free_list(relay_dbo **relays_list)
{
for(int i = 0; relays_list[i] != nullptr; i++)
{
delete relays_list[i];
}
free(relays_list);
}

View file

@ -1,56 +0,0 @@
#ifndef EMGAUWA_CORE_RELAY_DBO_H
#define EMGAUWA_CORE_RELAY_DBO_H
#include <string>
#include <sqlite3.h>
#include <json/value.h>
#include <helpers.h>
#include "schedule_dbo.h"
class relay_dbo
{
public:
int id;
char name[128];
int number;
char controller_id[33];
char active_schedule_id[33];
schedule_dbo *active_schedule;
void
reload_active_schedule();
bool
update();
bool
insert();
bool
remove();
Json::Value
to_json();
static void
free_list(relay_dbo **relays_list);
static relay_dbo**
get_by_simple(const char *key, const void *value, intptr_t bind_func);
static relay_dbo**
get_by(helpers::sql_filter_builder **filters);
static relay_dbo*
get_relay_for_controller(const char *controller_id, int relay_num);
static bool
valid_num_for_controller(const char *search_controller_id, int relay_num);
static relay_dbo**
get_all();
};
#endif //EMGAUWA_CORE_RELAY_DBO_H

325
models/schedule.c Normal file
View file

@ -0,0 +1,325 @@
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <cJSON.h>
#include <logger.h>
#include <database.h>
#include <models/schedule.h>
#include <models/junction_tag.h>
#include <models/tag.h>
static int
db_update_insert(schedule_t *schedule, sqlite3_stmt *stmt)
{
int rc;
uint16_t *periods_blob = schedule_periods_to_blob(schedule);
int blob_size = (int)sizeof(uint16_t) * ((periods_blob[0] * 2) + 1);
sqlite3_bind_int(stmt, 1, schedule->id);
sqlite3_bind_blob(stmt, 2, schedule->uid, sizeof(uuid_t), SQLITE_STATIC);
sqlite3_bind_text(stmt, 3, schedule->name, -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 4, periods_blob, blob_size, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
free(periods_blob);
return rc != SQLITE_DONE;
}
static schedule_t*
schedule_db_select_mapper(sqlite3_stmt *stmt)
{
const uint16_t *periods_blob;
schedule_t *new_schedule = malloc(sizeof(schedule_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_schedule->id = sqlite3_column_int(stmt, i);
break;
case 'n': // name
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);
new_schedule->periods_count = periods_blob[0];
new_schedule->periods = malloc(sizeof(period_t) * periods_blob[0]);
for(int i = 0; i < periods_blob[0]; ++i)
{
new_schedule->periods[i].start = periods_blob[(i * 2) + 1];
new_schedule->periods[i].end = periods_blob[(i * 2) + 2];
}
break;
case 'u': // uid
uuid_copy(new_schedule->uid, (const unsigned char*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
return new_schedule;
}
static schedule_t**
schedule_db_select(sqlite3_stmt *stmt)
{
schedule_t **all_schedules = malloc(sizeof(schedule_t*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
schedule_t *new_schedule = schedule_db_select_mapper(stmt);
row++;
all_schedules = (schedule_t**)realloc(all_schedules, sizeof(schedule_t*) * (row + 1));
all_schedules[row - 1] = new_schedule;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("srror selecting schedules from database: %s\n", sqlite3_errstr(s));
break;
}
}
}
sqlite3_finalize(stmt);
all_schedules[row] = NULL;
return all_schedules;
}
int
schedule_save(schedule_t *schedule)
{
sqlite3_stmt *stmt;
if(schedule->id)
{
sqlite3_prepare_v2(global_database, "UPDATE schedules SET uid = ?2, name = ?3, periods = ?4 WHERE id=?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO schedules(uid, name, periods) values (?2, ?3, ?4);", -1, &stmt, NULL);
}
int result = db_update_insert(schedule, stmt);
if(result)
{
if(schedule->id)
{
LOG_ERROR("error inserting data: %s\n", sqlite3_errmsg(global_database));
}
else
{
LOG_ERROR("error updating data: %s\n", sqlite3_errmsg(global_database));
}
}
else
{
schedule->id = sqlite3_last_insert_rowid(global_database);
}
return result;
}
void
schedule_free(schedule_t *schedule)
{
free(schedule->periods);
free(schedule);
}
void
schedule_free_list(schedule_t **schedules)
{
for(int i = 0; schedules[i] != NULL; ++i)
{
schedule_free(schedules[i]);
}
free(schedules);
}
uint16_t*
schedule_periods_to_blob(schedule_t *schedule)
{
uint16_t *blob = malloc(sizeof(uint16_t) * ((schedule->periods_count * 2) + 1));
blob[0] = schedule->periods_count;
for(int i = 0; i < schedule->periods_count; i++)
{
blob[(i * 2) + 1] = schedule->periods[i].start;
blob[(i * 2) + 2] = schedule->periods[i].end;
}
return blob;
}
cJSON*
schedule_to_json(schedule_t *schedule)
{
cJSON *json = cJSON_CreateObject();
cJSON *json_name = cJSON_CreateString(schedule->name);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "name", json_name);
char uuid_str[UUID_STR_LEN];
schedule_uid_unparse(schedule->uid, uuid_str);
cJSON *json_id = cJSON_CreateString(uuid_str);
if(json_name == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "id", json_id);
cJSON *json_periods = cJSON_CreateArray();
if(json_periods == NULL)
{
cJSON_Delete(json);
return NULL;
}
cJSON_AddItemToObject(json, "periods", json_periods);
for(int i = 0; i < schedule->periods_count; ++i)
{
cJSON *json_period = cJSON_CreateObject();
if (json_period == NULL)
{
continue;
}
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(end_str, "%02d:%02d", period->end / 60, period->end % 60);
cJSON *json_period_start = cJSON_CreateString(start_str);
if (json_period_start == NULL)
{
LOG_DEBUG("failed to add start period from string '%s'\n", start_str);
cJSON_Delete(json_period);
continue;
}
cJSON_AddItemToObject(json_period, "start", json_period_start);
cJSON *json_period_end = cJSON_CreateString(end_str);
if (json_period_end == NULL)
{
LOG_DEBUG("failed to add end period from string '%s'\n", end_str);
cJSON_Delete(json_period);
continue;
}
cJSON_AddItemToObject(json_period, "end", json_period_end);
cJSON_AddItemToArray(json_periods, json_period);
}
cJSON *json_tags = cJSON_CreateArray();
int *tags_ids = junction_tag_get_tags_for_schedule_id(schedule->id);
if(tags_ids != NULL)
{
for(int i = 0; tags_ids[i] != 0; ++i)
{
char *tag = tag_get_tag(tags_ids[i]);
if(tag == NULL)
{
continue;
}
cJSON *json_tag = cJSON_CreateString(tag);
if (json_tag == NULL)
{
LOG_DEBUG("failed to add tag from string '%s'\n", tag);
free(tag);
continue;
}
cJSON_AddItemToArray(json_tags, json_tag);
free(tag);
}
free(tags_ids);
}
cJSON_AddItemToObject(json, "tags", json_tags);
return json;
}
schedule_t**
schedule_get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT * FROM schedules;", -1, &stmt, NULL);
return schedule_db_select(stmt);
}
int
schedule_uid_parse(const char *uid_str, uuid_t result)
{
if(strcmp("off", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "off", 3);
return 0;
}
if(strcmp("on", uid_str) == 0)
{
memset(result, 0, sizeof(uuid_t));
memcpy(result, "on", 2);
return 0;
}
if(uuid_parse(uid_str, result))
{
return 1;
}
return 0;
}
void
schedule_uid_unparse(const uuid_t uid, char *result)
{
uuid_t tmp_uuid;
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "off", 3);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strcpy(result, "off");
return;
}
memset(tmp_uuid, 0, sizeof(uuid_t));
memcpy(tmp_uuid, "on", 2);
if(uuid_compare(uid, tmp_uuid) == 0)
{
strcpy(result, "on");
return;
}
uuid_unparse(uid, result);
}

View file

@ -1,197 +0,0 @@
#include <cstring>
#include <trantor/utils/Logger.h>
#include <helpers.h>
#include "schedule_dbo.h"
#include "globals.h"
#include "period.h"
static bool schedule_db_update_insert(schedule_dbo *schedule, sqlite3_stmt *stmt)
{
int rc;
uint16_t *periods_blob = schedule->periods->to_db_blob();
int blob_size = (int)sizeof(uint16_t) * ((periods_blob[0] * 2) + 1);
sqlite3_bind_text(stmt, 1, schedule->id, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, schedule->name, -1, SQLITE_STATIC);
sqlite3_bind_blob(stmt, 3, periods_blob, blob_size, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
free(periods_blob);
if (rc != SQLITE_DONE)
{
LOG_ERROR << "ERROR inserting/updating data: " << sqlite3_errmsg(globals::db);
return false;
}
return true;
}
static schedule_dbo*
schedule_db_select_mapper(sqlite3_stmt *stmt)
{
auto new_schedule = new schedule_dbo();
for(int i = 0; i < sqlite3_column_count(stmt); i++)
{
const char *name = sqlite3_column_name(stmt, i);
switch(name[0])
{
case 'i': // id
strncpy(new_schedule->id, (const char*)sqlite3_column_text(stmt, i), 32);
new_schedule->id[32] = '\0';
break;
case 'n': // name
strncpy(new_schedule->name, (const char*)sqlite3_column_text(stmt, i), 127);
new_schedule->name[127] = '\0';
break;
case 'p': // periods
new_schedule->periods = new period_list((const uint16_t*)sqlite3_column_blob(stmt, i));
break;
default: // ignore columns not implemented
break;
}
}
return new_schedule;
}
static schedule_dbo**
schedule_db_select(sqlite3_stmt *stmt)
{
auto **all_schedules = (schedule_dbo**)malloc(sizeof(schedule_dbo*));
int row = 0;
while(true)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
schedule_dbo *new_schedule = schedule_db_select_mapper(stmt);
row++;
all_schedules = (schedule_dbo**)realloc(all_schedules, sizeof(schedule_dbo*) * (row + 1));
all_schedules[row - 1] = new_schedule;
}
else
{
if(s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR << "Error Selecting schedules from database: " << sqlite3_errstr(s);
break;
}
}
}
sqlite3_finalize(stmt);
all_schedules[row] = nullptr;
return all_schedules;
}
bool
schedule_dbo::update()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "UPDATE schedules SET name = ?2, periods = ?3 WHERE id=?1;", -1, &stmt, nullptr);
return schedule_db_update_insert(this, stmt);
}
bool
schedule_dbo::insert()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "INSERT INTO schedules(id, name, periods) values (?1, ?2, ?3);", -1, &stmt, nullptr);
return schedule_db_update_insert(this, stmt);
}
bool
schedule_dbo::remove()
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(globals::db, "DELETE FROM schedules WHERE id=?1;", -1, &stmt, nullptr);
sqlite3_bind_text(stmt, 1, this->id, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
schedule_dbo::~schedule_dbo()
{
delete this->periods;
}
Json::Value
schedule_dbo::to_json()
{
Json::Value schedule_json;
schedule_json["name"] = this->name;
schedule_json["id"] = this->id;
schedule_json["periods"] = this->periods->to_json();
return schedule_json;
}
schedule_dbo**
schedule_dbo::get_all()
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(globals::db, "SELECT * FROM schedules;", -1, &stmt, nullptr);
return schedule_db_select(stmt);
}
schedule_dbo**
schedule_dbo::get_by_simple(const char *key, const void *value, intptr_t bind_func)
{
helpers::sql_filter_builder *filters[1];
helpers::sql_filter_builder filter
{
key,
value,
bind_func,
";"
};
filters[0] = &filter;
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM schedules WHERE", filters);
return schedule_db_select(stmt);
}
schedule_dbo**
schedule_dbo::get_by(helpers::sql_filter_builder **filters)
{
sqlite3_stmt *stmt = helpers::create_sql_filtered_query("SELECT * FROM schedules WHERE", filters);
return schedule_db_select(stmt);
}
void
schedule_dbo::free_list(schedule_dbo **schedules_list)
{
for(int i = 0; schedules_list[i] != nullptr; i++)
{
delete schedules_list[i];
}
free(schedules_list);
}

View file

@ -1,47 +0,0 @@
#ifndef EMGAUWA_CORE_SCHEDULE_DBO_H
#define EMGAUWA_CORE_SCHEDULE_DBO_H
#include <string>
#include <sqlite3.h>
#include <json/value.h>
#include <helpers.h>
#include "period.h"
#include "period_list.h"
class schedule_dbo
{
public:
char id[33];
char name[128];
period_list *periods;
bool
update();
bool
insert();
bool
remove();
~schedule_dbo();
Json::Value
to_json();
static void
free_list(schedule_dbo **schedules_list);
static schedule_dbo**
get_by_simple(const char *key, const void *value, intptr_t bind_func);
static schedule_dbo**
get_by(helpers::sql_filter_builder **filters);
static schedule_dbo**
get_all();
};
#endif //EMGAUWA_CORE_SCHEDULE_DBO_H

133
models/tag.c Normal file
View file

@ -0,0 +1,133 @@
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <logger.h>
#include <database.h>
#include <models/tag.h>
int
tag_save(int id, const char *tag)
{
int rc;
sqlite3_stmt *stmt;
if(id)
{
sqlite3_prepare_v2(global_database, "UPDATE tags SET tag = ?2 WHERE id = ?1;", -1, &stmt, NULL);
}
else
{
sqlite3_prepare_v2(global_database, "INSERT INTO tags(tag) values (?2);", -1, &stmt, NULL);
}
sqlite3_bind_int(stmt, 1, id);
sqlite3_bind_text(stmt, 2, tag, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
LOG_ERROR("error saving tag: %s\n", sqlite3_errmsg(global_database));
return false;
}
sqlite3_finalize(stmt);
return 1;
}
char*
tag_get_tag(int id)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT tag FROM tags WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, id);
char *result = NULL;
while(1)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
const char *found_tag = (const char *)sqlite3_column_text(stmt, 0);
result = (char*)malloc(sizeof(char) * (strlen(found_tag) + 1));
strcpy(result, found_tag);
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error selecting tags from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return NULL;
}
}
}
sqlite3_finalize(stmt);
return result;
}
int
tag_get_id(const char *tag)
{
sqlite3_stmt *stmt;
sqlite3_prepare_v2(global_database, "SELECT id FROM tags WHERE tag=?1;", -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, tag, -1, SQLITE_STATIC);
int result = 0;
while(1)
{
int s;
s = sqlite3_step(stmt);
if (s == SQLITE_ROW)
{
result = sqlite3_column_int(stmt, 0);
}
else
{
if (s == SQLITE_DONE)
{
break;
}
else
{
LOG_ERROR("error selecting tags from database: %s\n", sqlite3_errstr(s));
sqlite3_finalize(stmt);
return 0;
}
}
}
sqlite3_finalize(stmt);
return result;
}
int
tag_remove(int id)
{
sqlite3_stmt *stmt;
int rc;
sqlite3_prepare_v2(global_database, "DELETE FROM tags WHERE id=?1;", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}

275
router.c Normal file
View file

@ -0,0 +1,275 @@
#include <string.h>
#include <logger.h>
#include <router.h>
#include <endpoints/api_v1_schedules.h>
static const int HTTP_METHOD_GET = (1 << 0);
static const int HTTP_METHOD_POST = (1 << 1);
static const int HTTP_METHOD_PUT = (1 << 2);
static const int HTTP_METHOD_DELETE = (1 << 3);
static const int HTTP_METHOD_OPTIONS = (1 << 4);
static endpoint_t endpoints[ENDPOINTS_MAX_COUNT];
static endpoint_t endpoint_index;
static endpoint_t endpoint_not_found;
static int endpoints_registered = 0;
static const char delimiter[2] = "/";
static void
endpoint_index_func(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm)
{
(void)args;
mg_send_head(c, 200, hm->body.len, "Content-Type: text/plain");
//mg_printf(c, "%.*s", (int)hm->message.len, hm->message.p);
mg_printf(c, "%.*s", (int)hm->body.len, hm->body.p);
}
static void
endpoint_not_found_func(struct mg_connection *c, endpoint_args_t *args, struct http_message *hm)
{
(void)args;
mg_send_head(c, 404, hm->body.len, "Content-Type: text/plain");
//mg_printf(c, "%.*s", (int)hm->message.len, hm->message.p);
mg_printf(c, "%.*s", (int)hm->body.len, hm->body.p);
}
void
router_init()
{
// add index endpoint
endpoint_index.route = NULL;
endpoint_index.func = endpoint_index_func;
endpoint_index.methods = 0;
endpoint_index.args_count = 0;
endpoint_index.args = NULL;
// add 404 endpoint
endpoint_not_found.route = NULL;
endpoint_not_found.func = endpoint_not_found_func;
endpoint_not_found.methods = 0;
endpoint_not_found.args_count = 0;
endpoint_not_found.args = NULL;
router_register_endpoint("/api/v1/schedules/", HTTP_METHOD_GET, api_v1_schedules_GET);
router_register_endpoint("/api/v1/schedules/", HTTP_METHOD_POST, api_v1_schedules_POST);
router_register_endpoint("/api/v1/schedules/{str}/", HTTP_METHOD_GET, api_v1_schedules_STR_GET);
}
void
router_register_endpoint(const char *route, int methods, endpoint_func_f func)
{
if(endpoints_registered >= ENDPOINTS_MAX_COUNT)
{
LOG_ERROR("can't register more than %d endpoints\n", ENDPOINTS_MAX_COUNT);
return;
}
endpoint_t *endpoint = &endpoints[endpoints_registered];
int route_parts_count = 1;
size_t route_len = strlen(route);
for(size_t i = 0; i < route_len; ++i)
{
if(route[i] == delimiter[0] || route[i] == '\0')
{
++route_parts_count;
}
}
// +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);
endpoint->route_keeper[route_len] = '\0';
int route_part = 0;
int route_args_count = 0;
char *route_token = strtok(endpoint->route_keeper, delimiter);
while(route_token)
{
if(strcmp(route_token, "{int}") == 0)
{
++route_args_count;
}
if(strcmp(route_token, "{str}") == 0)
{
++route_args_count;
}
endpoint->route[route_part] = route_token;
++route_part;
route_token = strtok(NULL, delimiter);
}
endpoint->route[route_part] = NULL;
endpoint->args_count = route_args_count;
endpoint->args = NULL;
if(route_args_count)
{
endpoint->args = malloc(sizeof(endpoint_args_t) * route_args_count);
}
endpoint->func = func;
endpoint->methods = methods;
++endpoints_registered;
}
static int
get_method_int_for_str(struct mg_str *method_str)
{
if(strncmp(method_str->p, "GET", method_str->len) == 0)
{
return HTTP_METHOD_GET;
}
if(strncmp(method_str->p, "POST", method_str->len) == 0)
{
return HTTP_METHOD_POST;
}
if(strncmp(method_str->p, "PUT", method_str->len) == 0)
{
return HTTP_METHOD_PUT;
}
if(strncmp(method_str->p, "DELETE", method_str->len) == 0)
{
return HTTP_METHOD_DELETE;
}
if(strncmp(method_str->p, "OPTIONS", method_str->len) == 0)
{
return HTTP_METHOD_OPTIONS;
}
return HTTP_METHOD_GET;
}
endpoint_t*
router_find_endpoint(const char *uri_str, size_t uri_len, struct mg_str *method_str)
{
(void)uri_str;
(void)uri_len;
char *uri = malloc(sizeof(char) * (uri_len + 1));
strncpy(uri, uri_str, uri_len);
uri[uri_len] = '\0';
int method = get_method_int_for_str(method_str);
for(int i = 0; i < endpoints_registered; ++i)
{
if(endpoints[i].methods & method)
{
endpoints[i].possible_route = 1;
}
else
{
endpoints[i].possible_route = 0;
}
endpoints[i].args_found = 0;
}
char *uri_token;
char *uri_token_save;
uri_token = strtok_r(uri, delimiter, &uri_token_save);
int route_part = 0;
if(!uri_token)
{
free(uri);
return &endpoint_index;
}
while(uri_token)
{
for(int i = 0; i < endpoints_registered; ++i)
{
if(!endpoints[i].possible_route)
{
continue;
}
if(!endpoints[i].route[route_part])
{
endpoints[i].possible_route = 0;
continue;
}
if(uri_token_save[0] == '\0' && endpoints[i].route[route_part + 1])
{
endpoints[i].possible_route = 0;
continue;
}
if(strcmp(endpoints[i].route[route_part], uri_token) == 0)
{
endpoints[i].possible_route += 3;
continue;
}
if(strcmp(endpoints[i].route[route_part], "{int}") == 0)
{
char *endptr;
errno = 0;
int found_arg_int = strtol(uri_token, &endptr, 10);
if(errno || (endptr && *endptr != '\0'))
{
endpoints[i].possible_route = 0;
continue;
}
endpoints[i].possible_route += 2;
endpoints[i].args[endpoints[i].args_found].type = ENDPOINT_ARG_TYPE_INT;
endpoints[i].args[endpoints[i].args_found].value.v_int = found_arg_int;
++endpoints[i].args_found;
continue;
}
if(strcmp(endpoints[i].route[route_part], "{str}") == 0)
{
endpoints[i].possible_route += 1;
endpoints[i].args[endpoints[i].args_found].type = ENDPOINT_ARG_TYPE_STR;
endpoints[i].args[endpoints[i].args_found].value.v_str = uri_token;
++endpoints[i].args_found;
continue;
}
endpoints[i].possible_route = 0;
continue;
}
uri_token = strtok_r(NULL, delimiter, &uri_token_save);
++route_part;
}
endpoint_t *best_endpoint = &endpoint_not_found;
for(int i = 0; i < endpoints_registered; ++i)
{
int rating = endpoints[i].possible_route;
if(rating > best_endpoint->possible_route)
{
best_endpoint = &endpoints[i];
}
}
for(int i = 0; i < best_endpoint->args_count; ++i)
{
if(best_endpoint->args[i].type == ENDPOINT_ARG_TYPE_STR)
{
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);
best_endpoint->args[i].value.v_str = arg_value_str;
}
}
free(uri);
return best_endpoint;
}
void
router_free()
{
for(int i = 0; i < endpoints_registered; ++i)
{
free(endpoints[i].route_keeper);
free(endpoints[i].route);
if(endpoints[i].args)
{
free(endpoints[i].args);
}
}
}

View file

@ -1,40 +1,83 @@
create table meta
(
version_num int not null
version_num INTEGER
NOT NULL
);
create table controllers
(
id VARCHAR(33) not null
primary key
unique,
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
uid BLOB
NOT NULL
UNIQUE,
name VARCHAR(128),
ip VARCHAR(16),
port INTEGER,
relay_count INTEGER,
active BOOLEAN not null
active BOOLEAN
NOT NULL
);
create table relays
(
id INTEGER not null
primary key
unique,
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
name VARCHAR(128),
number INTEGER not null,
controller_id VARCHAR(33) not null
references controllers (id),
active_schedule_id VARCHAR(33)
references schedules
number INTEGER
NOT NULL,
controller_id INTEGER
NOT NULL
REFERENCES controllers (id)
);
create table schedules
(
id VARCHAR(33) not null
primary key
unique,
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
uid BLOB
NOT NULL
UNIQUE,
name VARCHAR(128),
periods BLOB
);
INSERT INTO schedules (id, name, periods) VALUES ('off', 'off', x'00');
create table tags
(
id INTEGER
PRIMARY KEY
AUTOINCREMENT,
tag VARCHAR(128)
NOT NULL
UNIQUE
);
create table junction_tag
(
tag_id INTEGER
NOT NULL
REFERENCES tags (id),
relay_id INTEGER
REFERENCES relays (id),
schedule_id INTEGER
REFERENCES schedules (id)
);
create table junction_relay_schedule
(
weekday SMALLINT
NOT NULL,
relay_id INTEGER
NOT NULL
REFERENCES relays (id),
schedule_id INTEGER
NOT NULL
REFERENCES schedules (id)
);
INSERT INTO schedules (uid, name, periods) VALUES (x'6f666600000000000000000000000000', 'off', x'00');
INSERT INTO schedules (uid, name, periods) VALUES (x'6f6e0000000000000000000000000000', 'on', x'010000009F05');

View file

@ -1,5 +1,18 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -marm")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfpu=vfp")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv6zk+fp")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=arm1176jzf-s")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=arm1176jzf-s")
set(CMAKE_C_COMPILER /usr/bin/arm-none-eabi-gcc)
set(CMAKE_C_COMPILER_WORKS 1)
set(ARM-SYSROOT /usr/arm-none-eabi)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -marm")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mfpu=vfp")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv6zk+fp")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=arm1176jzf-s")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mtune=arm1176jzf-s")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --sysroot=${ARM-SYSROOT}" CACHE INTERNAL "" FORCE)
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} --sysroot=${ARM-SYSROOT}" CACHE INTERNAL "" FORCE)
set(CMAKE_FIND_ROOT_PATH ${ARM-SYSROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

384
vendor/argparse.c vendored Normal file
View file

@ -0,0 +1,384 @@
/**
* Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
* All rights reserved.
*
* Use of this source code is governed by a MIT-style license that can be found
* in the LICENSE file.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "argparse.h"
#define OPT_UNSET 1
#define OPT_LONG (1 << 1)
static const char *
prefix_skip(const char *str, const char *prefix)
{
size_t len = strlen(prefix);
return strncmp(str, prefix, len) ? NULL : str + len;
}
static int
prefix_cmp(const char *str, const char *prefix)
{
for (;; str++, prefix++)
if (!*prefix) {
return 0;
} else if (*str != *prefix) {
return (unsigned char)*prefix - (unsigned char)*str;
}
}
static void
argparse_error(struct argparse *self, const struct argparse_option *opt,
const char *reason, int flags)
{
(void)self;
if (flags & OPT_LONG) {
fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason);
} else {
fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason);
}
exit(1);
}
static int
argparse_getvalue(struct argparse *self, const struct argparse_option *opt,
int flags)
{
const char *s = NULL;
if (!opt->value)
goto skipped;
switch (opt->type) {
case ARGPARSE_OPT_BOOLEAN:
if (flags & OPT_UNSET) {
*(int *)opt->value = *(int *)opt->value - 1;
} else {
*(int *)opt->value = *(int *)opt->value + 1;
}
if (*(int *)opt->value < 0) {
*(int *)opt->value = 0;
}
break;
case ARGPARSE_OPT_BIT:
if (flags & OPT_UNSET) {
*(int *)opt->value &= ~opt->data;
} else {
*(int *)opt->value |= opt->data;
}
break;
case ARGPARSE_OPT_STRING:
if (self->optvalue) {
*(const char **)opt->value = self->optvalue;
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(const char **)opt->value = *++self->argv;
} else {
argparse_error(self, opt, "requires a value", flags);
}
break;
case ARGPARSE_OPT_INTEGER:
errno = 0;
if (self->optvalue) {
*(int *)opt->value = strtol(self->optvalue, (char **)&s, 0);
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(int *)opt->value = strtol(*++self->argv, (char **)&s, 0);
} else {
argparse_error(self, opt, "requires a value", flags);
}
if (errno)
argparse_error(self, opt, strerror(errno), flags);
if (s[0] != '\0')
argparse_error(self, opt, "expects an integer value", flags);
break;
case ARGPARSE_OPT_FLOAT:
errno = 0;
if (self->optvalue) {
*(float *)opt->value = strtof(self->optvalue, (char **)&s);
self->optvalue = NULL;
} else if (self->argc > 1) {
self->argc--;
*(float *)opt->value = strtof(*++self->argv, (char **)&s);
} else {
argparse_error(self, opt, "requires a value", flags);
}
if (errno)
argparse_error(self, opt, strerror(errno), flags);
if (s[0] != '\0')
argparse_error(self, opt, "expects a numerical value", flags);
break;
default:
assert(0);
}
skipped:
if (opt->callback) {
return opt->callback(self, opt);
}
return 0;
}
static void
argparse_options_check(const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
switch (options->type) {
case ARGPARSE_OPT_END:
case ARGPARSE_OPT_BOOLEAN:
case ARGPARSE_OPT_BIT:
case ARGPARSE_OPT_INTEGER:
case ARGPARSE_OPT_FLOAT:
case ARGPARSE_OPT_STRING:
case ARGPARSE_OPT_GROUP:
continue;
default:
fprintf(stderr, "wrong option type: %d", options->type);
break;
}
}
}
static int
argparse_short_opt(struct argparse *self, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
if (options->short_name == *self->optvalue) {
self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL;
return argparse_getvalue(self, options, 0);
}
}
return -2;
}
static int
argparse_long_opt(struct argparse *self, const struct argparse_option *options)
{
for (; options->type != ARGPARSE_OPT_END; options++) {
const char *rest;
int opt_flags = 0;
if (!options->long_name)
continue;
rest = prefix_skip(self->argv[0] + 2, options->long_name);
if (!rest) {
// negation disabled?
if (options->flags & OPT_NONEG) {
continue;
}
// only OPT_BOOLEAN/OPT_BIT supports negation
if (options->type != ARGPARSE_OPT_BOOLEAN && options->type !=
ARGPARSE_OPT_BIT) {
continue;
}
if (prefix_cmp(self->argv[0] + 2, "no-")) {
continue;
}
rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name);
if (!rest)
continue;
opt_flags |= OPT_UNSET;
}
if (*rest) {
if (*rest != '=')
continue;
self->optvalue = rest + 1;
}
return argparse_getvalue(self, options, opt_flags | OPT_LONG);
}
return -2;
}
int
argparse_init(struct argparse *self, struct argparse_option *options,
const char *const *usages, int flags)
{
memset(self, 0, sizeof(*self));
self->options = options;
self->usages = usages;
self->flags = flags;
self->description = NULL;
self->epilog = NULL;
return 0;
}
void
argparse_describe(struct argparse *self, const char *description,
const char *epilog)
{
self->description = description;
self->epilog = epilog;
}
int
argparse_parse(struct argparse *self, int argc, const char **argv)
{
self->argc = argc - 1;
self->argv = argv + 1;
self->out = argv;
argparse_options_check(self->options);
for (; self->argc; self->argc--, self->argv++) {
const char *arg = self->argv[0];
if (arg[0] != '-' || !arg[1]) {
if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) {
goto end;
}
// if it's not option or is a single char '-', copy verbatim
self->out[self->cpidx++] = self->argv[0];
continue;
}
// short option
if (arg[1] != '-') {
self->optvalue = arg + 1;
switch (argparse_short_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
while (self->optvalue) {
switch (argparse_short_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
}
continue;
}
// if '--' presents
if (!arg[2]) {
self->argc--;
self->argv++;
break;
}
// long option
switch (argparse_long_opt(self, self->options)) {
case -1:
break;
case -2:
goto unknown;
}
continue;
unknown:
fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]);
argparse_usage(self);
exit(1);
}
end:
memmove(self->out + self->cpidx, self->argv,
self->argc * sizeof(*self->out));
self->out[self->cpidx + self->argc] = NULL;
return self->cpidx + self->argc;
}
void
argparse_usage(struct argparse *self)
{
if (self->usages) {
fprintf(stdout, "Usage: %s\n", *self->usages++);
while (*self->usages && **self->usages)
fprintf(stdout, " or: %s\n", *self->usages++);
} else {
fprintf(stdout, "Usage:\n");
}
// print description
if (self->description)
fprintf(stdout, "%s\n", self->description);
fputc('\n', stdout);
const struct argparse_option *options;
// figure out best width
size_t usage_opts_width = 0;
size_t len;
options = self->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
len = 0;
if ((options)->short_name) {
len += 2;
}
if ((options)->short_name && (options)->long_name) {
len += 2; // separator ", "
}
if ((options)->long_name) {
len += strlen((options)->long_name) + 2;
}
if (options->type == ARGPARSE_OPT_INTEGER) {
len += strlen("=<int>");
}
if (options->type == ARGPARSE_OPT_FLOAT) {
len += strlen("=<flt>");
} else if (options->type == ARGPARSE_OPT_STRING) {
len += strlen("=<str>");
}
len = (len + 3) - ((len + 3) & 3);
if (usage_opts_width < len) {
usage_opts_width = len;
}
}
usage_opts_width += 4; // 4 spaces prefix
options = self->options;
for (; options->type != ARGPARSE_OPT_END; options++) {
size_t pos = 0;
int pad = 0;
if (options->type == ARGPARSE_OPT_GROUP) {
fputc('\n', stdout);
fprintf(stdout, "%s", options->help);
fputc('\n', stdout);
continue;
}
pos = fprintf(stdout, " ");
if (options->short_name) {
pos += fprintf(stdout, "-%c", options->short_name);
}
if (options->long_name && options->short_name) {
pos += fprintf(stdout, ", ");
}
if (options->long_name) {
pos += fprintf(stdout, "--%s", options->long_name);
}
if (options->type == ARGPARSE_OPT_INTEGER) {
pos += fprintf(stdout, "=<int>");
} else if (options->type == ARGPARSE_OPT_FLOAT) {
pos += fprintf(stdout, "=<flt>");
} else if (options->type == ARGPARSE_OPT_STRING) {
pos += fprintf(stdout, "=<str>");
}
if (pos <= usage_opts_width) {
pad = usage_opts_width - pos;
} else {
fputc('\n', stdout);
pad = usage_opts_width;
}
fprintf(stdout, "%*s%s\n", pad + 2, "", options->help);
}
// print epilog
if (self->epilog)
fprintf(stdout, "%s\n", self->epilog);
}
int
argparse_help_cb(struct argparse *self, const struct argparse_option *option)
{
(void)option;
argparse_usage(self);
exit(0);
}

130
vendor/argparse.h vendored Normal file
View file

@ -0,0 +1,130 @@
/**
* Copyright (C) 2012-2015 Yecheng Fu <cofyc.jackson at gmail dot com>
* All rights reserved.
*
* Use of this source code is governed by a MIT-style license that can be found
* in the LICENSE file.
*/
#ifndef ARGPARSE_H
#define ARGPARSE_H
/* For c++ compatibility */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct argparse;
struct argparse_option;
typedef int argparse_callback (struct argparse *self,
const struct argparse_option *option);
enum argparse_flag {
ARGPARSE_STOP_AT_NON_OPTION = 1,
};
enum argparse_option_type {
/* special */
ARGPARSE_OPT_END,
ARGPARSE_OPT_GROUP,
/* options with no arguments */
ARGPARSE_OPT_BOOLEAN,
ARGPARSE_OPT_BIT,
/* options with arguments (optional or required) */
ARGPARSE_OPT_INTEGER,
ARGPARSE_OPT_FLOAT,
ARGPARSE_OPT_STRING,
};
enum argparse_option_flags {
OPT_NONEG = 1, /* disable negation */
};
/**
* argparse option
*
* `type`:
* holds the type of the option, you must have an ARGPARSE_OPT_END last in your
* array.
*
* `short_name`:
* the character to use as a short option name, '\0' if none.
*
* `long_name`:
* the long option name, without the leading dash, NULL if none.
*
* `value`:
* stores pointer to the value to be filled.
*
* `help`:
* the short help message associated to what the option does.
* Must never be NULL (except for ARGPARSE_OPT_END).
*
* `callback`:
* function is called when corresponding argument is parsed.
*
* `data`:
* associated data. Callbacks can use it like they want.
*
* `flags`:
* option flags.
*/
struct argparse_option {
enum argparse_option_type type;
const char short_name;
const char *long_name;
void *value;
const char *help;
argparse_callback *callback;
intptr_t data;
int flags;
};
/**
* argpparse
*/
struct argparse {
// user supplied
const struct argparse_option *options;
const char *const *usages;
int flags;
const char *description; // a description after usage
const char *epilog; // a description at the end
// internal context
int argc;
const char **argv;
const char **out;
int cpidx;
const char *optvalue; // current option value
};
// built-in callbacks
int argparse_help_cb(struct argparse *self,
const struct argparse_option *option);
// built-in option macros
#define OPT_END() { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 }
#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ }
#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ }
#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ }
#define OPT_FLOAT(...) { ARGPARSE_OPT_FLOAT, __VA_ARGS__ }
#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ }
#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 }
#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \
"show this help message and exit", \
argparse_help_cb, 0, OPT_NONEG)
int argparse_init(struct argparse *self, struct argparse_option *options,
const char *const *usages, int flags);
void argparse_describe(struct argparse *self, const char *description,
const char *epilog);
int argparse_parse(struct argparse *self, int argc, const char **argv);
void argparse_usage(struct argparse *self);
#ifdef __cplusplus
}
#endif
#endif

3074
vendor/cJSON.c vendored Normal file

File diff suppressed because it is too large Load diff

293
vendor/cJSON.h vendored Normal file
View file

@ -0,0 +1,293 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 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 cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 13
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(cJSON *item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(cJSON *item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable adress area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif

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 */

16123
vendor/mongoose.c vendored Normal file

File diff suppressed because it is too large Load diff

6277
vendor/mongoose.h vendored Normal file

File diff suppressed because it is too large Load diff

6440
vendor/mpack.c vendored Normal file

File diff suppressed because it is too large Load diff

7151
vendor/mpack.h vendored Normal file

File diff suppressed because it is too large Load diff