From 3def7aaad282d3efdf0cf3e1eea5b0466fdd4f9d Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 24 Jan 2015 18:47:03 +0100 Subject: [PATCH 001/181] Rename the project lightsd And re-organize the sources accordingly. There is no reason to make this LIFX specific. --HG-- rename : core/lifxd.c => core/lightsd.c rename : core/lifxd.h => core/lightsd.h rename : core/broadcast.c => lifx/broadcast.c rename : core/broadcast.h => lifx/broadcast.h rename : core/bulb.c => lifx/bulb.c rename : core/bulb.h => lifx/bulb.h rename : core/gateway.c => lifx/gateway.c rename : core/gateway.h => lifx/gateway.h rename : core/timer.c => lifx/timer.c rename : core/timer.h => lifx/timer.h rename : core/wire_proto.c => lifx/wire_proto.c rename : core/wire_proto.h => lifx/wire_proto.h --- CMakeLists.txt | 15 +- CMakeScripts/CompatTimeMonotonic.cmake | 16 +- CMakeScripts/FindEndian.cmake | 6 +- README.rst | 44 +++-- compat/Darwin/time_monotonic.c | 2 +- compat/Darwin/time_monotonic.h | 4 +- compat/generic/time_monotonic.c | 2 +- compat/generic/time_monotonic.h | 4 +- core/CMakeLists.txt | 22 +-- core/client.c | 0 core/client.h | 6 - core/{lifxd.c => lightsd.c} | 104 +++++------- core/{lifxd.h => lightsd.h} | 53 +++--- core/log.c | 64 +++---- core/timer.c | 209 ----------------------- core/version.h.in | 2 +- core/wire_proto.h | 223 ------------------------ lifx/CMakeLists.txt | 15 ++ {core => lifx}/broadcast.c | 181 ++++++++++---------- {core => lifx}/broadcast.h | 6 +- {core => lifx}/bulb.c | 45 ++--- {core => lifx}/bulb.h | 48 +++--- {core => lifx}/gateway.c | 201 +++++++++++----------- {core => lifx}/gateway.h | 74 ++++---- lifx/timer.c | 218 ++++++++++++++++++++++++ {core => lifx}/timer.h | 16 +- {core => lifx}/wire_proto.c | 131 +++++++------- lifx/wire_proto.h | 225 +++++++++++++++++++++++++ 28 files changed, 978 insertions(+), 958 deletions(-) delete mode 100644 core/client.c delete mode 100644 core/client.h rename core/{lifxd.c => lightsd.c} (65%) rename core/{lifxd.h => lightsd.h} (60%) delete mode 100644 core/timer.c delete mode 100644 core/wire_proto.h create mode 100644 lifx/CMakeLists.txt rename {core => lifx}/broadcast.c (57%) rename {core => lifx}/broadcast.h (93%) rename {core => lifx}/bulb.c (68%) rename {core => lifx}/bulb.h (61%) rename {core => lifx}/gateway.c (54%) rename {core => lifx}/gateway.h (53%) create mode 100644 lifx/timer.c rename {core => lifx}/timer.h (80%) rename {core => lifx}/wire_proto.c (56%) create mode 100644 lifx/wire_proto.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cbaaa3e..be8599e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,20 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8) -PROJECT(LIFXD C) +PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "0") SET(CPACK_PACKAGE_VERSION_MINOR "0") SET(CPACK_PACKAGE_VERSION_PATCH "1") -SET(LIFXD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") -MESSAGE(STATUS "lifxd version: ${LIFXD_VERSION}") +MESSAGE(STATUS "lgtd version: ${LIGHTSD_VERSION}") MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") MESSAGE(STATUS "System: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}") MESSAGE(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") -MESSAGE(STATUS "Source directory: ${LIFXD_SOURCE_DIR}") +MESSAGE(STATUS "Source directory: ${LIGHTSD_SOURCE_DIR}") -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${LIFXD_SOURCE_DIR}/CMakeScripts) +SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${LIGHTSD_SOURCE_DIR}/CMakeScripts) ### Platform checks ############################################################ @@ -25,7 +25,7 @@ INCLUDE(CompatTimeMonotonic) INCLUDE(CheckTypeSize) CHECK_TYPE_SIZE(suseconds_t SUSECONDS_T_SIZE) -ADD_DEFINITIONS("-DLIFXD_SUSECONDS_T_SIZE=${SUSECONDS_T_SIZE}") +ADD_DEFINITIONS("-DLGTD_SUSECONDS_T_SIZE=${SUSECONDS_T_SIZE}") ### Global definitions ######################################################### @@ -45,6 +45,7 @@ IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") ENDIF () ENDIF () -INCLUDE_DIRECTORIES(${LIFXD_SOURCE_DIR}/compat/generic ${LIFXD_BINARY_DIR}/compat) +INCLUDE_DIRECTORIES(${LIGHTSD_SOURCE_DIR}/compat/generic ${LIGHTSD_BINARY_DIR}/compat) ADD_SUBDIRECTORY(core) +ADD_SUBDIRECTORY(lifx) diff --git a/CMakeScripts/CompatTimeMonotonic.cmake b/CMakeScripts/CompatTimeMonotonic.cmake index ad1e80c..de794e8 100644 --- a/CMakeScripts/CompatTimeMonotonic.cmake +++ b/CMakeScripts/CompatTimeMonotonic.cmake @@ -1,10 +1,10 @@ INCLUDE(CheckFunctionExists) IF (NOT TIME_MONOTONIC_IMPL) - SET(COMPAT_TIME_MONOTONIC_IMPL "${LIFXD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.c") - SET(COMPAT_TIME_MONOTONIC_H "${LIFXD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.h") - SET(GENERIC_TIME_MONOTONIC_IMPL "${LIFXD_SOURCE_DIR}/compat/generic/time_monotonic.c") - SET(GENERIC_TIME_MONOTONIC_H "${LIFXD_SOURCE_DIR}/compat/generic/time_monotonic.h") + SET(COMPAT_TIME_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.c") + SET(COMPAT_TIME_MONOTONIC_H "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.h") + SET(GENERIC_TIME_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/generic/time_monotonic.c") + SET(GENERIC_TIME_MONOTONIC_H "${LIGHTSD_SOURCE_DIR}/compat/generic/time_monotonic.h") SET(CMAKE_REQUIRED_QUIET TRUE) MESSAGE(STATUS "Looking for clock_gettime") @@ -13,17 +13,17 @@ IF (NOT TIME_MONOTONIC_IMPL) IF (HAVE_CLOCK_GETTIME) MESSAGE(STATUS "Looking for clock_gettime - found") - FILE(COPY "${GENERIC_TIME_MONOTONIC_H}" DESTINATION "${LIFXD_BINARY_DIR}/compat/") + FILE(COPY "${GENERIC_TIME_MONOTONIC_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/core/") SET( TIME_MONOTONIC_IMPL "${GENERIC_TIME_MONOTONIC_IMPL}" - CACHE INTERNAL "lifxd_time_monotonic (POSIX generic implementation)" + CACHE INTERNAL "lgtd_time_monotonic (POSIX generic implementation)" ) ELSEIF (EXISTS "${COMPAT_TIME_MONOTONIC_IMPL}") MESSAGE(STATUS "Looking for clock_gettime - not found, using built-in compatibilty file") - FILE(COPY "${COMPAT_TIME_MONOTONIC_H}" DESTINATION "${LIFXD_BINARY_DIR}/compat/") + FILE(COPY "${COMPAT_TIME_MONOTONIC_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/core/") SET( TIME_MONOTONIC_IMPL "${COMPAT_TIME_MONOTONIC_IMPL}" - CACHE INTERNAL "lifxd_time_monotonic (${CMAKE_SYSTEM_NAME} specific implementation)" + CACHE INTERNAL "lgtd_time_monotonic (${CMAKE_SYSTEM_NAME} specific implementation)" ) ELSE () MESSAGE(SEND_ERROR "Looking for clock_gettime - not found") diff --git a/CMakeScripts/FindEndian.cmake b/CMakeScripts/FindEndian.cmake index 45ade06..b964c65 100644 --- a/CMakeScripts/FindEndian.cmake +++ b/CMakeScripts/FindEndian.cmake @@ -1,8 +1,8 @@ INCLUDE(CheckIncludeFile) IF (NOT ENDIAN_H_PATH) - SET(COMPAT_ENDIAN_H "${LIFXD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/endian.h") - SET(GENERIC_ENDIAN_H "${LIFXD_SOURCE_DIR}/compat/generic/endian.h") + SET(COMPAT_ENDIAN_H "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/endian.h") + SET(GENERIC_ENDIAN_H "${LIGHTSD_SOURCE_DIR}/compat/generic/endian.h") SET(CMAKE_REQUIRED_QUIET TRUE) MESSAGE(STATUS "Looking for endian.h") @@ -14,7 +14,7 @@ IF (NOT ENDIAN_H_PATH) SET(ENDIAN_H_PATH "using native headers" CACHE INTERNAL "endian.h path") ELSEIF (EXISTS "${COMPAT_ENDIAN_H}") MESSAGE(STATUS "Looking for endian.h - not found, using built-in compatibility file") - FILE(COPY "${COMPAT_ENDIAN_H}" DESTINATION "${LIFXD_BINARY_DIR}/compat/") + FILE(COPY "${COMPAT_ENDIAN_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/compat/") SET(ENDIAN_H_PATH "${COMPAT_ENDIAN_H}" CACHE INTERNAL "endian.h path") ELSE () MESSAGE(STATUS "Looking for endian.h - not found") diff --git a/README.rst b/README.rst index 3a57bf6..fb8223b 100644 --- a/README.rst +++ b/README.rst @@ -1,18 +1,18 @@ -lifxd, a LIFX broker -==================== +lightsd, a LIFX broker +====================== -lifxd acts a central point of control for your LIFX_ WiFi bulbs. lifxd should be -a small, simple and fast daemon exposing an easy to use protocol inspired by how -musicpd_ works. +lightsd acts a central point of control for your LIFX_ WiFi bulbs. lightsd +should be a small, simple and fast daemon exposing an easy to use protocol +inspired by how musicpd_ works. -Having to run a daemon to control your LIFX bulbs may seem a little bit -backward but has some advantages: +Having to run a daemon to control your LIFX bulbs may seem a little bit backward +but has some advantages: - no discovery delay ever, you get all the bulbs and their state right away; -- lifxd is always in sync with the bulbs and always know their state; -- lifxd act as an abstraction layer and can expose new discovery mechanism (e.g: - zeroconf) and totally new APIs; -- For those of you with a high paranoia factor, lifxd let you place your bulbs +- lightsd is always in sync with the bulbs and always know their state; +- lightsd act as an abstraction layer and can expose new discovery mechanism and + totally new APIs; +- For those of you with a high paranoia factor, lightsd let you place your bulbs in a totally separate and closed network. .. _LIFX: http://lifx.co/ @@ -21,24 +21,22 @@ backward but has some advantages: Current features ---------------- -lifxd doesn't do much yet, it just discovers your bulbs and stay in sync with -them. +lightsd doesn't do much yet, it just discovers your LIFX bulbs and stay in sync +with them. -Developers ----------- +Developpers +----------- -The project is far from being usable right now, but I'll be happy to hear your -feedback and share ideas. - -Be aware that some parts of the code aren't really clean yet: I'm more focused -on getting things working and good abstractions. Testing is definitely missing. +Feel free to reach out via email or irc (kalessin on freenode). As the project +name implies, I'm fairly interested in other smart bulbs. Requirements ------------ -lifxd aims to be highly portable on any POSIX system (win32 support should be -quite easy, but isn't really the goal) and on any kind of hardware including -embedded devices. Hence why lifxd is written in C with reasonable dependencies: +lightsd aims to be highly portable on any slightly POSIX system (win32 support +should be quite easy, but isn't really the goal) and on any kind of hardware +including embedded devices. Hence why lightsd is written in C with reasonable +dependencies: - CMake ≥ 2.8; - libevent ≥ 2.0.19. diff --git a/compat/Darwin/time_monotonic.c b/compat/Darwin/time_monotonic.c index 80457b9..9405893 100644 --- a/compat/Darwin/time_monotonic.c +++ b/compat/Darwin/time_monotonic.c @@ -40,7 +40,7 @@ enum { MSECS_IN_NSEC = 1000000 }; time_t -lifxd_time_monotonic_msecs(void) +lgtd_time_monotonic_msecs(void) { static mach_timebase_info_data_t timebase = { 0, 0 }; if (timebase.denom == 0) { diff --git a/compat/Darwin/time_monotonic.h b/compat/Darwin/time_monotonic.h index f6e5aef..170c600 100644 --- a/compat/Darwin/time_monotonic.h +++ b/compat/Darwin/time_monotonic.h @@ -29,6 +29,6 @@ #pragma once -typedef time_t lifxd_time_mono_t; +typedef time_t lgtd_time_mono_t; -lifxd_time_mono_t lifxd_time_monotonic_msecs(void); +lgtd_time_mono_t lgtd_time_monotonic_msecs(void); diff --git a/compat/generic/time_monotonic.c b/compat/generic/time_monotonic.c index d4e0483..54d7e2d 100644 --- a/compat/generic/time_monotonic.c +++ b/compat/generic/time_monotonic.c @@ -32,7 +32,7 @@ #include "time_monotonic.h" time_t -lifxd_time_monotonic_msecs(void) +lgtd_time_monotonic_msecs(void) { struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); diff --git a/compat/generic/time_monotonic.h b/compat/generic/time_monotonic.h index f6e5aef..170c600 100644 --- a/compat/generic/time_monotonic.h +++ b/compat/generic/time_monotonic.h @@ -29,6 +29,6 @@ #pragma once -typedef time_t lifxd_time_mono_t; +typedef time_t lgtd_time_mono_t; -lifxd_time_mono_t lifxd_time_monotonic_msecs(void); +lgtd_time_mono_t lgtd_time_monotonic_msecs(void); diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index fde6857..a72fc26 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,18 +1,20 @@ -INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) +# As you'll find out the code in this directory isn't really generic at all. +# I only have lifx bulbs right now and I don't want to do any premature work. + +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/../ + ${CMAKE_CURRENT_BINARY_DIR} +) CONFIGURE_FILE(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) ADD_EXECUTABLE( - lifxd - broadcast.c - bulb.c - client.c - gateway.c - lifxd.c + lightsd + lightsd.c log.c ${TIME_MONOTONIC_IMPL} - timer.c - wire_proto.c ) -TARGET_LINK_LIBRARIES(lifxd ${EVENT2_CORE_LIBRARY}) +TARGET_LINK_LIBRARIES(lightsd lifx ${EVENT2_CORE_LIBRARY}) diff --git a/core/client.c b/core/client.c deleted file mode 100644 index e69de29..0000000 diff --git a/core/client.h b/core/client.h deleted file mode 100644 index 845bb17..0000000 --- a/core/client.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -struct lifxd_client { - LIST_ENTRY(lifxd_client) link; -}; -LIST_HEAD(lifxd_client_list, lifxd_client); diff --git a/core/lifxd.c b/core/lightsd.c similarity index 65% rename from core/lifxd.c rename to core/lightsd.c index 9fe1677..8a8024f 100644 --- a/core/lifxd.c +++ b/core/lightsd.c @@ -46,38 +46,37 @@ #include #include -#include "wire_proto.h" +#include "lifx/wire_proto.h" #include "time_monotonic.h" -#include "bulb.h" -#include "gateway.h" -#include "broadcast.h" +#include "lifx/bulb.h" +#include "lifx/gateway.h" +#include "lifx/broadcast.h" #include "version.h" -#include "timer.h" -#include "lifxd.h" +#include "lifx/timer.h" +#include "lightsd.h" -struct lifxd_opts lifxd_opts = { +struct lgtd_opts lgtd_opts = { .foreground = false, .log_timestamps = true, - .master_port = 56700, - .verbosity = LIFXD_DEBUG + .verbosity = LGTD_DEBUG }; -struct event_base *lifxd_ev_base = NULL; +struct event_base *lgtd_ev_base = NULL; void -lifxd_cleanup(void) +lgtd_cleanup(void) { - lifxd_timer_close(); - lifxd_broadcast_close(); - lifxd_gateway_close_all(); - event_base_free(lifxd_ev_base); + lgtd_lifx_timer_close(); + lgtd_lifx_broadcast_close(); + lgtd_lifx_gateway_close_all(); + event_base_free(lgtd_ev_base); #if LIBEVENT_VERSION_NUMBER >= 0x02010100 libevent_global_shutdown(); #endif } short -lifxd_sockaddrport(const struct sockaddr_storage *peer) +lgtd_sockaddrport(const struct sockaddr_storage *peer) { assert(peer); @@ -91,38 +90,37 @@ lifxd_sockaddrport(const struct sockaddr_storage *peer) } static void -lifxd_signal_event_callback(int signum, short events, void *ctx) +lgtd_signal_event_callback(int signum, short events, void *ctx) { assert(ctx); - lifxd_info( + lgtd_info( "received signal %d (%s), exiting...", signum, strsignal(signum) ); event_del((struct event *)ctx); // restore default behavior - event_base_loopbreak(lifxd_ev_base); + event_base_loopbreak(lgtd_ev_base); (void)events; } static void -lifxd_configure_libevent(void) +lgtd_configure_libevent(void) { - lifxd_gateway_close_all(); - event_set_log_callback(lifxd_libevent_log); - lifxd_ev_base = event_base_new(); + event_set_log_callback(lgtd_libevent_log); + lgtd_ev_base = event_base_new(); } static void -lifxd_configure_signal_handling(void) +lgtd_configure_signal_handling(void) { const int signals[] = {SIGINT, SIGTERM, SIGQUIT}; - static struct event sigevs[LIFXD_ARRAY_SIZE(signals)]; + static struct event sigevs[LGTD_ARRAY_SIZE(signals)]; - for (int i = 0; i != LIFXD_ARRAY_SIZE(signals); i++) { + for (int i = 0; i != LGTD_ARRAY_SIZE(signals); i++) { evsignal_assign( &sigevs[i], - lifxd_ev_base, + lgtd_ev_base, signals[i], - lifxd_signal_event_callback, + lgtd_signal_event_callback, &sigevs[i] ); evsignal_add(&sigevs[i], NULL); @@ -130,11 +128,10 @@ lifxd_configure_signal_handling(void) } static void -lifxd_usage(const char *progname) +lgtd_usage(const char *progname) { printf( - "Usage: %s [-p master_bulb_port] " - "[-v debug|info|warning|error] [-f] [-t] [-h] [-V]\n", + "Usage: %s [-v debug|info|warning|error] [-f] [-t] [-h] [-V]\n", progname ); exit(0); @@ -147,73 +144,62 @@ main(int argc, char *argv[]) {"foreground", no_argument, NULL, 'f'}, {"no-timestamps", no_argument, NULL, 't'}, {"help", no_argument, NULL, 'h'}, - {"master-port", required_argument, NULL, 'p'}, {"verbosity", required_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} }; - const char short_opts[] = "fthp:v:V"; + const char short_opts[] = "fthv:V"; for (int rv = getopt_long(argc, argv, short_opts, long_opts, NULL); rv != -1; rv = getopt_long(argc, argv, short_opts, long_opts, NULL)) { switch (rv) { case 'f': - lifxd_opts.foreground = true; + lgtd_opts.foreground = true; break; case 't': - lifxd_opts.log_timestamps = false; + lgtd_opts.log_timestamps = false; break; case 'h': - lifxd_usage(argv[0]); - case 'p': - errno = 0; - long port = strtol(optarg, NULL, 10); - if (!errno && port <= UINT16_MAX && port > 0) { - lifxd_opts.master_port = port; - break; - } - lifxd_errx( - 1, "The master port must be between 1 and %d", UINT16_MAX - ); + lgtd_usage(argv[0]); case 'v': for (int i = 0;;) { const char *verbose_levels[] = { "debug", "info", "warning", "error" }; if (!strcasecmp(optarg, verbose_levels[i])) { - lifxd_opts.verbosity = i; + lgtd_opts.verbosity = i; break; } - if (++i == LIFXD_ARRAY_SIZE(verbose_levels)) { - lifxd_errx(1, "Unknown verbosity level: %s", optarg); + if (++i == LGTD_ARRAY_SIZE(verbose_levels)) { + lgtd_errx(1, "Unknown verbosity level: %s", optarg); } } break; case 'V': - printf("%s v%s\n", argv[0], LIFXD_VERSION); + printf("%s v%s\n", argv[0], LGTD_VERSION); return 0; default: - lifxd_usage(argv[0]); + lgtd_usage(argv[0]); } } argc -= optind; argv += optind; - lifxd_configure_libevent(); - lifxd_configure_signal_handling(); + lgtd_configure_libevent(); + lgtd_configure_signal_handling(); - lifxd_wire_load_packet_infos_map(); - if (!lifxd_timer_setup() || !lifxd_broadcast_setup()) { - lifxd_err(1, "can't setup lifxd"); + lgtd_lifx_wire_load_packet_infos_map(); + if (!lgtd_lifx_timer_setup() || !lgtd_lifx_broadcast_setup()) { + lgtd_err(1, "can't setup lgtd_lifx"); } - lifxd_timer_start_discovery(); + lgtd_lifx_timer_start_discovery(); - event_base_dispatch(lifxd_ev_base); + event_base_dispatch(lgtd_ev_base); - lifxd_cleanup(); + lgtd_cleanup(); return 0; } diff --git a/core/lifxd.h b/core/lightsd.h similarity index 60% rename from core/lifxd.h rename to core/lightsd.h index 7c26ef6..d5bdae0 100644 --- a/core/lifxd.h +++ b/core/lightsd.h @@ -33,46 +33,45 @@ # define __atttribute__(e) #endif -#define LIFXD_ABS(v) ((v) >= 0 ? (v) : (v) * -1) -#define LIFXD_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) -#define LIFXD_MSECS_TO_TIMEVAL(v) { \ +#define LGTD_ABS(v) ((v) >= 0 ? (v) : (v) * -1) +#define LGTD_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#define LGTD_MSECS_TO_TIMEVAL(v) { \ .tv_sec = (v) / 1000, \ .tv_usec = ((v) % 1000) * 1000 \ } -enum lifxd_verbosity { - LIFXD_DEBUG = 0, - LIFXD_INFO, - LIFXD_WARN, - LIFXD_ERR +enum lgtd_verbosity { + LGTD_DEBUG = 0, + LGTD_INFO, + LGTD_WARN, + LGTD_ERR }; -enum { LIFXD_ERROR_MSG_BUFSIZE = 2048 }; +enum { LGTD_ERROR_MSG_BUFSIZE = 2048 }; -struct lifxd_opts { +struct lgtd_opts { bool foreground; bool log_timestamps; - uint16_t master_port; - enum lifxd_verbosity verbosity; + enum lgtd_verbosity verbosity; }; -extern struct lifxd_opts lifxd_opts; -extern struct event_base *lifxd_ev_base; +extern struct lgtd_opts lgtd_opts; +extern struct event_base *lgtd_ev_base; -const char *lifxd_addrtoa(const uint8_t *); -void lifxd_sockaddrtoa(const struct sockaddr_storage *, char *buf, int buflen); -short lifxd_sockaddrport(const struct sockaddr_storage *); +const char *lgtd_addrtoa(const uint8_t *); +void lgtd_sockaddrtoa(const struct sockaddr_storage *, char *buf, int buflen); +short lgtd_sockaddrport(const struct sockaddr_storage *); -void _lifxd_err(void (*)(int, const char *, ...), int, const char *, ...) +void _lgtd_err(void (*)(int, const char *, ...), int, const char *, ...) __attribute__((format(printf, 3, 4))); -#define lifxd_err(eval, fmt, ...) _lifxd_err(err, (eval), (fmt), ##__VA_ARGS__); -#define lifxd_errx(eval, fmt, ...) _lifxd_err(errx, (eval), (fmt), ##__VA_ARGS__); -void _lifxd_warn(void (*)(const char *, va_list), const char *, ...) +#define lgtd_err(eval, fmt, ...) _lgtd_err(err, (eval), (fmt), ##__VA_ARGS__); +#define lgtd_errx(eval, fmt, ...) _lgtd_err(errx, (eval), (fmt), ##__VA_ARGS__); +void _lgtd_warn(void (*)(const char *, va_list), const char *, ...) __attribute__((format(printf, 2, 3))); -#define lifxd_warn(fmt, ...) _lifxd_warn(vwarn, (fmt), ##__VA_ARGS__); -#define lifxd_warnx(fmt, ...) _lifxd_warn(vwarnx, (fmt), ##__VA_ARGS__); -void lifxd_info(const char *, ...) __attribute__((format(printf, 1, 2))); -void lifxd_debug(const char *, ...) __attribute__((format(printf, 1, 2))); -void lifxd_libevent_log(int, const char *); +#define lgtd_warn(fmt, ...) _lgtd_warn(vwarn, (fmt), ##__VA_ARGS__); +#define lgtd_warnx(fmt, ...) _lgtd_warn(vwarnx, (fmt), ##__VA_ARGS__); +void lgtd_info(const char *, ...) __attribute__((format(printf, 1, 2))); +void lgtd_debug(const char *, ...) __attribute__((format(printf, 1, 2))); +void lgtd_libevent_log(int, const char *); -void lifxd_cleanup(void); +void lgtd_cleanup(void); diff --git a/core/log.c b/core/log.c index 32db101..6966b2a 100644 --- a/core/log.c +++ b/core/log.c @@ -41,11 +41,11 @@ #include -#include "wire_proto.h" -#include "lifxd.h" +#include "lifx/wire_proto.h" +#include "lightsd.h" static void -lifxd_isotime_now(char *strbuf, int bufsz) +lgtd_isotime_now(char *strbuf, int bufsz) { assert(strbuf); assert(bufsz > 0); @@ -60,7 +60,7 @@ lifxd_isotime_now(char *strbuf, int bufsz) } // '2015-01-02T10:13:16.132222+00:00' snprintf( -#if LIFXD_SUSECONDS_T_SIZE == 4 +#if LGTD_SUSECONDS_T_SIZE == 4 strbuf, bufsz, "%d-%02d-%02dT%02d:%02d:%02d.%d%c%02ld:%02ld", #else strbuf, bufsz, "%d-%02d-%02dT%02d:%02d:%02d.%ld%c%02ld:%02ld", @@ -68,7 +68,7 @@ lifxd_isotime_now(char *strbuf, int bufsz) 1900 + tm_now.tm_year, 1 + tm_now.tm_mon, tm_now.tm_mday, tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec, now.tv_usec, tm_now.tm_gmtoff >= 0 ? '+' : '-', // %+02ld doesn't work - LIFXD_ABS(tm_now.tm_gmtoff / 60 / 60), tm_now.tm_gmtoff % (60 * 60) + LGTD_ABS(tm_now.tm_gmtoff / 60 / 60), tm_now.tm_gmtoff % (60 * 60) ); return; error: @@ -76,26 +76,26 @@ lifxd_isotime_now(char *strbuf, int bufsz) } static void -lifxd_log_header(const char *loglvl, bool showprogname) +lgtd_log_header(const char *loglvl, bool showprogname) { - if (lifxd_opts.log_timestamps) { + if (lgtd_opts.log_timestamps) { char timestr[64]; - lifxd_isotime_now(timestr, sizeof(timestr)); + lgtd_isotime_now(timestr, sizeof(timestr)); fprintf( stderr, "[%s] [%s] %s", - timestr, loglvl, showprogname ? "lifxd: " : "" + timestr, loglvl, showprogname ? "lightsd " : "" ); return; } - fprintf(stderr, "[%s] %s", loglvl, showprogname ? "lifxd: " : ""); + fprintf(stderr, "[%s] %s", loglvl, showprogname ? "lightsd " : ""); } const char * -lifxd_addrtoa(const uint8_t *addr) +lgtd_addrtoa(const uint8_t *addr) { assert(addr); - static char str[LIFXD_ADDR_LENGTH * 2 + LIFXD_ADDR_LENGTH - 1 + 1]; + static char str[LGTD_LIFX_ADDR_LENGTH * 2 + LGTD_LIFX_ADDR_LENGTH - 1 + 1]; snprintf( str, sizeof(str), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] @@ -104,7 +104,7 @@ lifxd_addrtoa(const uint8_t *addr) } void -lifxd_sockaddrtoa(const struct sockaddr_storage *peer, char *buf, int buflen) +lgtd_sockaddrtoa(const struct sockaddr_storage *peer, char *buf, int buflen) { assert(peer); assert(buf); @@ -120,7 +120,7 @@ lifxd_sockaddrtoa(const struct sockaddr_storage *peer, char *buf, int buflen) } void -_lifxd_err(void (*errfn)(int, const char *, ...), +_lgtd_err(void (*errfn)(int, const char *, ...), int eval, const char *fmt, ...) @@ -128,36 +128,36 @@ _lifxd_err(void (*errfn)(int, const char *, ...), int errsave = errno; va_list ap; va_start(ap, fmt); - // lifxd_cleanup is probably going to free some of the arguments we got, so + // lgtd_cleanup is probably going to free some of the arguments we got, so // let's print to a buffer before we call err. - char errmsg[LIFXD_ERROR_MSG_BUFSIZE]; + char errmsg[LGTD_ERROR_MSG_BUFSIZE]; vsnprintf(errmsg, sizeof(errmsg), fmt, ap); va_end(ap); - lifxd_cleanup(); - lifxd_log_header("ERR", false); + lgtd_cleanup(); + lgtd_log_header("ERR", false); errno = errsave; errfn(eval, errmsg); } void -_lifxd_warn(void (*warnfn)(const char *, va_list), const char *fmt, ...) +_lgtd_warn(void (*warnfn)(const char *, va_list), const char *fmt, ...) { - if (lifxd_opts.verbosity <= LIFXD_WARN) { + if (lgtd_opts.verbosity <= LGTD_WARN) { va_list ap; va_start(ap, fmt); - lifxd_log_header("WARN", false); + lgtd_log_header("WARN", false); warnfn(fmt, ap); va_end(ap); } } void -lifxd_info(const char *fmt, ...) +lgtd_info(const char *fmt, ...) { - if (lifxd_opts.verbosity <= LIFXD_INFO) { + if (lgtd_opts.verbosity <= LGTD_INFO) { va_list ap; va_start(ap, fmt); - lifxd_log_header("INFO", true); + lgtd_log_header("INFO", true); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); @@ -165,12 +165,12 @@ lifxd_info(const char *fmt, ...) } void -lifxd_debug(const char *fmt, ...) +lgtd_debug(const char *fmt, ...) { - if (lifxd_opts.verbosity <= LIFXD_DEBUG) { + if (lgtd_opts.verbosity <= LGTD_DEBUG) { va_list ap; va_start(ap, fmt); - lifxd_log_header("DEBUG", true); + lgtd_log_header("DEBUG", true); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); @@ -178,13 +178,13 @@ lifxd_debug(const char *fmt, ...) } void -lifxd_libevent_log(int severity, const char *msg) +lgtd_libevent_log(int severity, const char *msg) { switch (severity) { - case EVENT_LOG_DEBUG: lifxd_debug("%s", msg); break; - case EVENT_LOG_MSG: lifxd_info("%s", msg); break; - case EVENT_LOG_WARN: lifxd_warnx("%s", msg) break; - case EVENT_LOG_ERR: lifxd_warnx("%s", msg); break; + case EVENT_LOG_DEBUG: lgtd_debug("%s", msg); break; + case EVENT_LOG_MSG: lgtd_info("%s", msg); break; + case EVENT_LOG_WARN: lgtd_warnx("%s", msg) break; + case EVENT_LOG_ERR: lgtd_warnx("%s", msg); break; default: break; } } diff --git a/core/timer.c b/core/timer.c deleted file mode 100644 index 4d247cc..0000000 --- a/core/timer.c +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2015, Louis Opter -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "wire_proto.h" -#include "time_monotonic.h" -#include "broadcast.h" -#include "bulb.h" -#include "gateway.h" -#include "timer.h" -#include "lifxd.h" - -static struct { - struct event *watchdog_interval_ev; - struct event *discovery_timeout_ev; -} lifxd_timer_context = { - .watchdog_interval_ev = NULL, - .discovery_timeout_ev = NULL -}; - -static void -lifxd_timer_discovery_timeout_event_callback(evutil_socket_t socket, - short events, - void *ctx) -{ - (void)socket; - (void)events; - (void)ctx; - - int timeout = LIFXD_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS; - if (LIST_EMPTY(&lifxd_gateways)) { - lifxd_debug( - "discovery didn't returned anything in %dms, restarting it", - LIFXD_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS - ); - timeout = LIFXD_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS; - } else { - lifxd_debug("sending periodic discovery packet"); - } - - struct timeval tv = LIFXD_MSECS_TO_TIMEVAL(timeout); - if (event_add(lifxd_timer_context.discovery_timeout_ev, &tv) - || !lifxd_broadcast_discovery()) { - lifxd_err(1, "can't start discovery"); - } -} - -static void -lifxd_timer_watchdog_timeout_event_callback(evutil_socket_t socket, - short events, - void *ctx) -{ - (void)socket; - (void)events; - (void)ctx; - - bool start_discovery = false; - lifxd_time_mono_t now = lifxd_time_monotonic_msecs(); - - struct lifxd_bulb *bulb, *next_bulb; - RB_FOREACH_SAFE(bulb, lifxd_bulb_map, &lifxd_bulbs_table, next_bulb) { - int light_state_lag = now - bulb->last_light_state_at; - if (light_state_lag >= LIFXD_TIMER_DEVICE_TIMEOUT_MSECS) { - lifxd_info( - "closing bulb \"%.*s\" that hasn't been updated for %dms", - LIFXD_LABEL_SIZE, bulb->state.label, light_state_lag - ); - lifxd_bulb_close(bulb); - start_discovery = true; - } - } - - // Repeat for the gateways, we could also look if we are removing the last - // bulb on the gateway but this will also support architectures where - // gateways aren't bulbs themselves: - struct lifxd_gateway *gw, *next_gw; - LIST_FOREACH_SAFE(gw, &lifxd_gateways, link, next_gw) { - int gw_lag = now - gw->last_pkt_at; - if (gw_lag >= LIFXD_TIMER_DEVICE_TIMEOUT_MSECS) { - lifxd_info( - "closing bulb gateway [%s]:%hu that " - "hasn't received traffic for %dms", - gw->ip_addr, gw->port, - gw_lag - ); - lifxd_gateway_close(gw); - start_discovery = true; - } - } - - // If anything happens restart a discovery right away, maybe something just - // moved on the network: - if (start_discovery) { - lifxd_broadcast_discovery(); - } -} - -bool -lifxd_timer_setup(void) -{ - assert(!lifxd_timer_context.watchdog_interval_ev); - assert(!lifxd_timer_context.discovery_timeout_ev); - - lifxd_timer_context.discovery_timeout_ev = event_new( - lifxd_ev_base, - -1, - 0, - lifxd_timer_discovery_timeout_event_callback, - NULL - ); - lifxd_timer_context.watchdog_interval_ev = event_new( - lifxd_ev_base, - -1, - EV_PERSIST, - lifxd_timer_watchdog_timeout_event_callback, - NULL - ); - - if (lifxd_timer_context.discovery_timeout_ev - && lifxd_timer_context.watchdog_interval_ev) { - return true; - } - - int errsave = errno; - lifxd_timer_close(); - errno = errsave; - return false; -} - -void -lifxd_timer_close(void) -{ - if (lifxd_timer_context.discovery_timeout_ev) { - event_del(lifxd_timer_context.discovery_timeout_ev); - event_free(lifxd_timer_context.discovery_timeout_ev); - lifxd_timer_context.discovery_timeout_ev = NULL; - } - if (lifxd_timer_context.watchdog_interval_ev) { - event_del(lifxd_timer_context.watchdog_interval_ev); - event_free(lifxd_timer_context.watchdog_interval_ev); - lifxd_timer_context.watchdog_interval_ev = NULL; - } -} - -void -lifxd_timer_start_watchdog(void) -{ - assert(!RB_EMPTY(&lifxd_bulbs_table) || !LIST_EMPTY(&lifxd_gateways)); - - if (!evtimer_pending(lifxd_timer_context.watchdog_interval_ev, NULL)) { - struct timeval tv = LIFXD_MSECS_TO_TIMEVAL( - LIFXD_TIMER_WATCHDOG_INTERVAL_MSECS - ); - if (event_add(lifxd_timer_context.watchdog_interval_ev, &tv)) { - lifxd_err(1, "can't start watchdog"); - } - lifxd_debug("starting watchdog timer"); - } -} - -void -lifxd_timer_start_discovery(void) -{ - assert(!evtimer_pending(lifxd_timer_context.discovery_timeout_ev, NULL)); - - struct timeval tv = LIFXD_MSECS_TO_TIMEVAL( - LIFXD_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS - ); - if (event_add(lifxd_timer_context.discovery_timeout_ev, &tv)) { - lifxd_err(1, "can't start discovery timer"); - } - lifxd_debug("starting discovery timer"); -} diff --git a/core/version.h.in b/core/version.h.in index 3f8a435..85843e5 100644 --- a/core/version.h.in +++ b/core/version.h.in @@ -29,4 +29,4 @@ #pragma once -const char LIFXD_VERSION[] = "@LIFXD_VERSION@"; +const char LGTD_VERSION[] = "@LGTD_VERSION@"; diff --git a/core/wire_proto.h b/core/wire_proto.h deleted file mode 100644 index 7e2eaa5..0000000 --- a/core/wire_proto.h +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) 2014, Louis Opter -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -#pragma once - -typedef uint16_t uint16le_t; -typedef uint16_t uint16be_t; -typedef uint32_t uint32le_t; -typedef uint32_t uint32be_t; -typedef uint64_t uint64le_t; -typedef uint64_t uint64be_t; - -enum { LIFXD_ADDR_LENGTH = 6 }; - -#pragma pack(push, 1) - -struct lifxd_packet_header { - //! Packet size including the headers (i.e: this structure). - uint16le_t size; - struct { - //! Protocol version should be LIFXD_LIFX_PROTOCOL_V1. - uint16le_t version:12; - //! True when the target field holds a device address. - uint16le_t addressable:1; - //! True when the target field holds tags. - uint16le_t tagged:1; - //! LIFX internal use should be 0. - uint16le_t origin:2; - } protocol; - //! This seems to be for LIFX internal use only. - uint32le_t source; - union { - //! All targeted tags ORed together. - uint64le_t tags; - //! Address of the targeted device. - uint8_t device_addr[LIFXD_ADDR_LENGTH]; - } target; - uint8_t site[LIFXD_ADDR_LENGTH]; - struct { - //! True when a response is required, called acknowledge in lifx-gem... - uint8_t response_required:1; - //! True when an acknowledgement is required, no idea what it means. - uint8_t ack_required:1; - uint8_t reserved:6; - } flags; - //! Wrap-around sequence number, LIFX internal use. - uint8_t seqn; - uint64le_t timestamp; - uint16le_t packet_type; - uint8_t reserved[2]; -}; - -enum { LIFXD_PACKET_HEADER_SIZE = sizeof(struct lifxd_packet_header) }; - -enum { LIFXD_LIFX_PROTOCOL_V1 = 1024 }; - -// Let's define a maximum packet size just in case somebody sends us weird -// headers: -enum { LIFXD_MAX_PACKET_SIZE = 4096 }; - -enum lifxd_packet_type { - LIFXD_GET_PAN_GATEWAY = 0x02, - LIFXD_PAN_GATEWAY = 0x03, - LIFXD_GET_TIME = 0x04, - LIFXD_SET_TIME = 0x05, - LIFXD_TIME_STATE = 0x06, - LIFXD_GET_RESET_SWITCH_STATE = 0x07, - LIFXD_RESET_SWITCH_STATE = 0x08, - LIFXD_GET_MESH_INFO = 0x0c, - LIFXD_MESH_INFO = 0x0d, - LIFXD_GET_MESH_FIRMWARE = 0x0e, - LIFXD_MESH_FIRMWARE = 0x0f, - LIFXD_GET_WIFI_INFO = 0x10, - LIFXD_WIFI_INFO = 0x11, - LIFXD_GET_WIFI_FIRMWARE_STATE = 0x12, - LIFXD_WIFI_FIRMWARE_STATE = 0x13, - LIFXD_GET_POWER_STATE = 0x14, - LIFXD_SET_POWER_STATE = 0x15, - LIFXD_POWER_STATE = 0x16, - LIFXD_GET_BULB_LABEL = 0x17, - LIFXD_SET_BULB_LABEL = 0x18, - LIFXD_BULB_LABEL = 0x19, - LIFXD_GET_TAGS = 0x1a, - LIFXD_SET_TAGS = 0x1b, - LIFXD_TAGS = 0x1c, - LIFXD_GET_TAG_LABELS = 0x1d, - LIFXD_SET_TAG_LABELS = 0x1e, - LIFXD_TAG_LABELS = 0x1f, - LIFXD_GET_VERSION = 0x20, - LIFXD_VERSION_STATE = 0x21, - LIFXD_GET_INFO = 0x22, - LIFXD_INFO_STATE = 0x23, - LIFXD_GET_MCU_RAIL_VOLTAGE = 0x24, - LIFXD_MCU_RAIL_VOLTAGE = 0x25, - LIFXD_REBOOT = 0x26, - LIFXD_SET_FACTORY_TEST_MODE = 0x27, - LIFXD_DISABLE_FACTORY_TEST_MODE = 0x28, - LIFXD_GET_LIGHT_STATE = 0x65, - LIFXD_SET_LIGHT_COLOUR = 0x66, - LIFXD_SET_WAVEFORM = 0x67, - LIFXD_SET_DIM_ABSOLUTE = 0x68, - LIFXD_SET_DIM_RELATIVE = 0x69, - LIFXD_LIGHT_STATUS = 0x6b, - LIFXD_GET_WIFI_STATE = 0x12d, - LIFXD_SET_WIFI_STATE = 0x12e, - LIFXD_WIFI_STATE = 0x12f, - LIFXD_GET_ACCESS_POINTS = 0x130, - LIFXD_SET_ACCESS_POINTS = 0x131, - LIFXD_ACCESS_POINT = 0x132, -}; - -enum { LIFXD_LABEL_SIZE = 32 }; - -struct lifxd_packet_light_status { - uint16le_t hue; - uint16le_t saturation; - uint16le_t brightness; - uint16le_t kelvin; - uint16le_t dim; - uint16le_t power; - uint8_t label[LIFXD_LABEL_SIZE]; - uint64be_t tags; -}; - -enum lifxd_power_state { - LIFXD_POWER_OFF = 0, - LIFXD_POWER_ON = 0xffff -}; - -struct lifxd_packet_power_state { - uint16_t power; // see enum lifxd_power_state -}; - -enum lifxd_service_type { - LIFXD_SERVICE_TCP = 1, - LIFXD_SERVICE_UDP = 2 -}; - -struct lifxd_packet_pan_gateway { - uint8_t service_type; // see enum lifxd_service_type - uint32le_t port; -}; - -enum lifxd_target_type { - LIFXD_TARGET_SITE, - LIFXD_TARGET_TAGS, - LIFXD_TARGET_DEVICE, - LIFXD_TARGET_ALL_DEVICES -}; - -#pragma pack(pop) - -struct lifxd_gateway; - -struct lifxd_packet_infos { - RB_ENTRY(lifxd_packet_infos) link; - const char *name; - enum lifxd_packet_type type; - unsigned size; - void (*decode)(void *); - void (*encode)(void *); - void (*handle)(struct lifxd_gateway *, - const struct lifxd_packet_header *, - const void *); -}; -RB_HEAD(lifxd_packet_infos_map, lifxd_packet_infos); - -static inline int -lifxd_packet_infos_cmp(struct lifxd_packet_infos *a, - struct lifxd_packet_infos *b) -{ - return a->type - b->type; -} - -union lifxd_target { - uint64_t tags; - const uint8_t *addr; //! site or device address -}; - -extern union lifxd_target LIFXD_UNSPEC_TARGET; - -const struct lifxd_packet_infos *lifxd_wire_get_packet_infos(enum lifxd_packet_type); -void lifxd_wire_load_packet_infos_map(void); - -const struct lifxd_packet_infos *lifxd_wire_setup_header(struct lifxd_packet_header *, - enum lifxd_target_type, - union lifxd_target, - const uint8_t *, - enum lifxd_packet_type); -void lifxd_wire_decode_header(struct lifxd_packet_header *); -void lifxd_wire_encode_header(struct lifxd_packet_header *); - -void lifxd_wire_decode_pan_gateway(struct lifxd_packet_pan_gateway *); -void lifxd_wire_encode_pan_gateway(struct lifxd_packet_pan_gateway *); -void lifxd_wire_decode_light_status(struct lifxd_packet_light_status *); -void lifxd_wire_encode_light_status(struct lifxd_packet_light_status *); -void lifxd_wire_decode_power_state(struct lifxd_packet_power_state *); diff --git a/lifx/CMakeLists.txt b/lifx/CMakeLists.txt new file mode 100644 index 0000000..46962e3 --- /dev/null +++ b/lifx/CMakeLists.txt @@ -0,0 +1,15 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR}/../ + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/../ + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_LIBRARY( + lifx + broadcast.c + bulb.c + gateway.c + timer.c + wire_proto.c +) diff --git a/core/broadcast.c b/lifx/broadcast.c similarity index 57% rename from core/broadcast.c rename to lifx/broadcast.c index b1736d3..b21a238 100644 --- a/core/broadcast.c +++ b/lifx/broadcast.c @@ -45,40 +45,40 @@ #include #include "wire_proto.h" -#include "time_monotonic.h" +#include "core/time_monotonic.h" #include "bulb.h" #include "gateway.h" #include "broadcast.h" -#include "lifxd.h" +#include "core/lightsd.h" static struct { evutil_socket_t socket; struct event *read_ev; struct event *write_ev; -} lifxd_broadcast_endpoint = { +} lgtd_lifx_broadcast_endpoint = { .socket = -1, .read_ev = NULL, .write_ev = NULL, }; static bool -lifxd_broadcast_handle_read(void) +lgtd_lifx_broadcast_handle_read(void) { - assert(lifxd_broadcast_endpoint.socket != -1); + assert(lgtd_lifx_broadcast_endpoint.socket != -1); while (true) { struct sockaddr_storage peer; // if we get back from recvfrom with a sockaddr_in the end of the struct // will not be initialized and we will be comparing unintialized stuff - // in lifxd_gateway_get: + // in lgtd_lifx_gateway_get: memset(&peer, 0, sizeof(peer)); ev_socklen_t addrlen = sizeof(peer); union { - char buf[LIFXD_MAX_PACKET_SIZE]; - struct lifxd_packet_header hdr; + char buf[LGTD_LIFX_MAX_PACKET_SIZE]; + struct lgtd_lifx_packet_header hdr; } read; int nbytes = recvfrom( - lifxd_broadcast_endpoint.socket, + lgtd_lifx_broadcast_endpoint.socket, read.buf, sizeof(read.buf), 0, @@ -93,70 +93,72 @@ lifxd_broadcast_handle_read(void) if (error == EAGAIN) { return true; } - lifxd_warn("can't receive broadcast packet"); + lgtd_warn("can't receive broadcast packet"); return false; } - lifxd_time_mono_t received_at = lifxd_time_monotonic_msecs(); + lgtd_time_mono_t received_at = lgtd_time_monotonic_msecs(); char peer_addr[INET6_ADDRSTRLEN]; - lifxd_sockaddrtoa(&peer, peer_addr, sizeof(peer_addr)); - short peer_port = lifxd_sockaddrport(&peer); + lgtd_sockaddrtoa(&peer, peer_addr, sizeof(peer_addr)); + short peer_port = lgtd_sockaddrport(&peer); - if (nbytes < LIFXD_PACKET_HEADER_SIZE) { - lifxd_warnx( + if (nbytes < LGTD_LIFX_PACKET_HEADER_SIZE) { + lgtd_warnx( "broadcast packet too short from [%s]:%hu", peer_addr, peer_port ); return false; } - lifxd_wire_decode_header(&read.hdr); + lgtd_lifx_wire_decode_header(&read.hdr); if (read.hdr.size != nbytes) { - lifxd_warnx( + lgtd_warnx( "incomplete broadcast packet from [%s]:%hu", peer_addr, peer_port ); return false; } - if (read.hdr.protocol.version != LIFXD_LIFX_PROTOCOL_V1) { - lifxd_warnx( + if (read.hdr.protocol.version != LGTD_LIFX_PROTOCOL_V1) { + lgtd_warnx( "unsupported protocol %d from [%s]:%hu", read.hdr.protocol.version, peer_addr, peer_port ); } - if (read.hdr.packet_type == LIFXD_GET_PAN_GATEWAY) { + if (read.hdr.packet_type == LGTD_LIFX_GET_PAN_GATEWAY) { continue; } - const struct lifxd_packet_infos *pkt_infos = - lifxd_wire_get_packet_infos(read.hdr.packet_type); + const struct lgtd_lifx_packet_infos *pkt_infos = + lgtd_lifx_wire_get_packet_infos(read.hdr.packet_type); if (!pkt_infos) { - lifxd_warnx( + lgtd_warnx( "received unknown packet %#x from [%s]:%hu", read.hdr.packet_type, peer_addr, peer_port ) continue; } if (read.hdr.protocol.tagged || !read.hdr.protocol.addressable) { - lifxd_warnx( + lgtd_warnx( "received non-addressable packet %s from [%s]:%hu", pkt_infos->name, peer_addr, peer_port ); continue; } - struct lifxd_gateway *gw = lifxd_gateway_get(&peer); - if (!gw && read.hdr.packet_type == LIFXD_PAN_GATEWAY) { - gw = lifxd_gateway_open(&peer, addrlen, read.hdr.site, received_at); + struct lgtd_lifx_gateway *gw = lgtd_lifx_gateway_get(&peer); + if (!gw && read.hdr.packet_type == LGTD_LIFX_PAN_GATEWAY) { + gw = lgtd_lifx_gateway_open( + &peer, addrlen, read.hdr.site, received_at + ); if (!gw) { - lifxd_err(1, "can't allocate gateway"); + lgtd_err(1, "can't allocate gateway"); } } if (gw) { - void *pkt = &read.buf[LIFXD_PACKET_HEADER_SIZE]; + void *pkt = &read.buf[LGTD_LIFX_PACKET_HEADER_SIZE]; gw->last_pkt_at = received_at; pkt_infos->decode(pkt); pkt_infos->handle(gw, &read.hdr, pkt); } else { - lifxd_warnx( + lgtd_warnx( "got packet from unknown gateway [%s]:%hu", peer_addr, peer_port ); } @@ -164,28 +166,28 @@ lifxd_broadcast_handle_read(void) } static bool -lifxd_broadcast_handle_write(void) +lgtd_lifx_broadcast_handle_write(void) { - assert(lifxd_broadcast_endpoint.socket != -1); + assert(lgtd_lifx_broadcast_endpoint.socket != -1); struct sockaddr_in lifx_addr = { .sin_family = AF_INET, .sin_addr = { INADDR_BROADCAST }, - .sin_port = htons(lifxd_opts.master_port) + .sin_port = htons(LGTD_LIFX_PROTOCOL_PORT) }; - struct lifxd_packet_header get_pan_gateway; - lifxd_wire_setup_header( + struct lgtd_lifx_packet_header get_pan_gateway; + lgtd_lifx_wire_setup_header( &get_pan_gateway, - LIFXD_TARGET_ALL_DEVICES, - LIFXD_UNSPEC_TARGET, + LGTD_LIFX_TARGET_ALL_DEVICES, + LGTD_LIFX_UNSPEC_TARGET, NULL, - LIFXD_GET_PAN_GATEWAY + LGTD_LIFX_GET_PAN_GATEWAY ); int nbytes; retry: nbytes = sendto( - lifxd_broadcast_endpoint.socket, + lgtd_lifx_broadcast_endpoint.socket, (void *)&get_pan_gateway, sizeof(get_pan_gateway), 0, @@ -193,8 +195,8 @@ lifxd_broadcast_handle_write(void) sizeof(lifx_addr) ); if (nbytes == sizeof(get_pan_gateway)) { - if (event_del(lifxd_broadcast_endpoint.write_ev)) { - lifxd_err(1, "can't setup events"); + if (event_del(lgtd_lifx_broadcast_endpoint.write_ev)) { + lgtd_err(1, "can't setup events"); } return true; } @@ -202,31 +204,33 @@ lifxd_broadcast_handle_write(void) if (EVUTIL_SOCKET_ERROR() == EINTR) { goto retry; } - lifxd_warn("can't broadcast discovery packet"); + lgtd_warn("can't broadcast discovery packet"); } else { - lifxd_warnx("can't broadcast discovery packet"); + lgtd_warnx("can't broadcast discovery packet"); } return false; } static void -lifxd_broadcast_event_callback(evutil_socket_t socket, short events, void *ctx) +lgtd_lifx_broadcast_event_callback(evutil_socket_t socket, + short events, + void *ctx) { (void)socket; (void)ctx; if (events & EV_TIMEOUT) { // not sure how that could happen but eh. - lifxd_warnx("timeout on the udp broadcast socket"); + lgtd_warnx("timeout on the udp broadcast socket"); goto error_reset; } if (events & EV_READ) { - if (!lifxd_broadcast_handle_read()) { + if (!lgtd_lifx_broadcast_handle_read()) { goto error_reset; } } if (events & EV_WRITE) { - if (!lifxd_broadcast_handle_write()) { + if (!lgtd_lifx_broadcast_handle_write()) { goto error_reset; } } @@ -234,45 +238,47 @@ lifxd_broadcast_event_callback(evutil_socket_t socket, short events, void *ctx) return; error_reset: - lifxd_broadcast_close(); - lifxd_broadcast_setup(); - lifxd_broadcast_discovery(); + lgtd_lifx_broadcast_close(); + lgtd_lifx_broadcast_setup(); + lgtd_lifx_broadcast_discovery(); } void -lifxd_broadcast_close(void) +lgtd_lifx_broadcast_close(void) { - if (lifxd_broadcast_endpoint.read_ev) { - event_del(lifxd_broadcast_endpoint.read_ev); - event_free(lifxd_broadcast_endpoint.read_ev); - lifxd_broadcast_endpoint.read_ev = NULL; + if (lgtd_lifx_broadcast_endpoint.read_ev) { + event_del(lgtd_lifx_broadcast_endpoint.read_ev); + event_free(lgtd_lifx_broadcast_endpoint.read_ev); + lgtd_lifx_broadcast_endpoint.read_ev = NULL; } - if (lifxd_broadcast_endpoint.write_ev) { - event_del(lifxd_broadcast_endpoint.write_ev); - event_free(lifxd_broadcast_endpoint.write_ev); - lifxd_broadcast_endpoint.write_ev = NULL; + if (lgtd_lifx_broadcast_endpoint.write_ev) { + event_del(lgtd_lifx_broadcast_endpoint.write_ev); + event_free(lgtd_lifx_broadcast_endpoint.write_ev); + lgtd_lifx_broadcast_endpoint.write_ev = NULL; } - if (lifxd_broadcast_endpoint.socket != -1) { - evutil_closesocket(lifxd_broadcast_endpoint.socket); - lifxd_broadcast_endpoint.socket = -1; + if (lgtd_lifx_broadcast_endpoint.socket != -1) { + evutil_closesocket(lgtd_lifx_broadcast_endpoint.socket); + lgtd_lifx_broadcast_endpoint.socket = -1; } } bool -lifxd_broadcast_setup(void) +lgtd_lifx_broadcast_setup(void) { - assert(lifxd_broadcast_endpoint.socket == -1); - assert(lifxd_broadcast_endpoint.read_ev == NULL); - assert(lifxd_broadcast_endpoint.write_ev == NULL); + assert(lgtd_lifx_broadcast_endpoint.socket == -1); + assert(lgtd_lifx_broadcast_endpoint.read_ev == NULL); + assert(lgtd_lifx_broadcast_endpoint.write_ev == NULL); - lifxd_broadcast_endpoint.socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (lifxd_broadcast_endpoint.socket == -1) { + lgtd_lifx_broadcast_endpoint.socket = socket( + AF_INET, SOCK_DGRAM, IPPROTO_UDP + ); + if (lgtd_lifx_broadcast_endpoint.socket == -1) { return false; } int val = 1; int err = setsockopt( - lifxd_broadcast_endpoint.socket, + lgtd_lifx_broadcast_endpoint.socket, SOL_SOCKET, SO_BROADCAST, &val, @@ -282,18 +288,19 @@ lifxd_broadcast_setup(void) goto error; } - if (evutil_make_socket_nonblocking(lifxd_broadcast_endpoint.socket) == -1) { + err = evutil_make_socket_nonblocking(lgtd_lifx_broadcast_endpoint.socket); + if (err == -1) { goto error; } struct sockaddr_in lifx_addr = { .sin_family = AF_INET, .sin_addr = { INADDR_ANY }, - .sin_port = htons(lifxd_opts.master_port) + .sin_port = htons(LGTD_LIFX_PROTOCOL_PORT) }; err = bind( - lifxd_broadcast_endpoint.socket, + lgtd_lifx_broadcast_endpoint.socket, (const struct sockaddr *)&lifx_addr, sizeof(lifx_addr) ); @@ -301,40 +308,40 @@ lifxd_broadcast_setup(void) goto error; } - lifxd_broadcast_endpoint.read_ev = event_new( - lifxd_ev_base, - lifxd_broadcast_endpoint.socket, + lgtd_lifx_broadcast_endpoint.read_ev = event_new( + lgtd_ev_base, + lgtd_lifx_broadcast_endpoint.socket, EV_READ|EV_PERSIST, - lifxd_broadcast_event_callback, + lgtd_lifx_broadcast_event_callback, NULL ); - lifxd_broadcast_endpoint.write_ev = event_new( - lifxd_ev_base, - lifxd_broadcast_endpoint.socket, + lgtd_lifx_broadcast_endpoint.write_ev = event_new( + lgtd_ev_base, + lgtd_lifx_broadcast_endpoint.socket, EV_WRITE|EV_PERSIST, - lifxd_broadcast_event_callback, + lgtd_lifx_broadcast_event_callback, NULL ); - if (!lifxd_broadcast_endpoint.read_ev - || !lifxd_broadcast_endpoint.write_ev) { + if (!lgtd_lifx_broadcast_endpoint.read_ev + || !lgtd_lifx_broadcast_endpoint.write_ev) { goto error; } - if (!event_add(lifxd_broadcast_endpoint.read_ev, NULL)) { + if (!event_add(lgtd_lifx_broadcast_endpoint.read_ev, NULL)) { return true; } int errsave; error: errsave = errno; - lifxd_broadcast_close(); + lgtd_lifx_broadcast_close(); errno = errsave; return false; } bool -lifxd_broadcast_discovery(void) +lgtd_lifx_broadcast_discovery(void) { - assert(lifxd_broadcast_endpoint.write_ev); - return event_add(lifxd_broadcast_endpoint.write_ev, NULL) == 0; + assert(lgtd_lifx_broadcast_endpoint.write_ev); + return event_add(lgtd_lifx_broadcast_endpoint.write_ev, NULL) == 0; } diff --git a/core/broadcast.h b/lifx/broadcast.h similarity index 93% rename from core/broadcast.h rename to lifx/broadcast.h index 47c3667..3c1ed8d 100644 --- a/core/broadcast.h +++ b/lifx/broadcast.h @@ -29,6 +29,6 @@ #pragma once -bool lifxd_broadcast_setup(void); -void lifxd_broadcast_close(void); -bool lifxd_broadcast_discovery(void); +bool lgtd_lifx_broadcast_setup(void); +void lgtd_lifx_broadcast_close(void); +bool lgtd_lifx_broadcast_discovery(void); diff --git a/core/bulb.c b/lifx/bulb.c similarity index 68% rename from core/bulb.c rename to lifx/bulb.c index 68f4ead..f9b425c 100644 --- a/core/bulb.c +++ b/lifx/bulb.c @@ -40,56 +40,57 @@ #include #include "wire_proto.h" -#include "time_monotonic.h" +#include "core/time_monotonic.h" #include "bulb.h" #include "gateway.h" -#include "lifxd.h" +#include "core/lightsd.h" -struct lifxd_bulb_map lifxd_bulbs_table = RB_INITIALIZER(&lifxd_bulbs_table); +struct lgtd_lifx_bulb_map lgtd_lifx_bulbs_table = + RB_INITIALIZER(&lgtd_lifx_bulbs_table); -struct lifxd_bulb * -lifxd_bulb_get(struct lifxd_gateway *gw, const uint8_t *addr) +struct lgtd_lifx_bulb * +lgtd_lifx_bulb_get(struct lgtd_lifx_gateway *gw, const uint8_t *addr) { assert(gw); assert(addr); - struct lifxd_bulb bulb; + struct lgtd_lifx_bulb bulb; memcpy(bulb.addr, addr, sizeof(bulb.addr)); - return RB_FIND(lifxd_bulb_map, &lifxd_bulbs_table, &bulb); + return RB_FIND(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, &bulb); } -struct lifxd_bulb * -lifxd_bulb_open(struct lifxd_gateway *gw, const uint8_t *addr) +struct lgtd_lifx_bulb * +lgtd_lifx_bulb_open(struct lgtd_lifx_gateway *gw, const uint8_t *addr) { assert(gw); assert(addr); - struct lifxd_bulb *bulb = calloc(1, sizeof(*bulb)); + struct lgtd_lifx_bulb *bulb = calloc(1, sizeof(*bulb)); if (!bulb) { - lifxd_warn("can't allocate a new bulb"); + lgtd_warn("can't allocate a new bulb"); return NULL; } bulb->gw = gw; memcpy(bulb->addr, addr, sizeof(bulb->addr)); - RB_INSERT(lifxd_bulb_map, &lifxd_bulbs_table, bulb); + RB_INSERT(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, bulb); - bulb->last_light_state_at = lifxd_time_monotonic_msecs(); + bulb->last_light_state_at = lgtd_time_monotonic_msecs(); return bulb; } void -lifxd_bulb_close(struct lifxd_bulb *bulb) +lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *bulb) { assert(bulb); assert(bulb->gw); - RB_REMOVE(lifxd_bulb_map, &lifxd_bulbs_table, bulb); - SLIST_REMOVE(&bulb->gw->bulbs, bulb, lifxd_bulb, link_by_gw); - lifxd_info( + RB_REMOVE(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, bulb); + SLIST_REMOVE(&bulb->gw->bulbs, bulb, lgtd_lifx_bulb, link_by_gw); + lgtd_info( "closed bulb \"%.*s\" on [%s]:%hu", - LIFXD_LABEL_SIZE, + LGTD_LIFX_LABEL_SIZE, bulb->state.label, bulb->gw->ip_addr, bulb->gw->port @@ -98,9 +99,9 @@ lifxd_bulb_close(struct lifxd_bulb *bulb) } void -lifxd_bulb_set_light_state(struct lifxd_bulb *bulb, - const struct lifxd_light_state *state, - lifxd_time_mono_t received_at) +lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *bulb, + const struct lgtd_lifx_light_state *state, + lgtd_time_mono_t received_at) { assert(bulb); assert(state); @@ -109,7 +110,7 @@ lifxd_bulb_set_light_state(struct lifxd_bulb *bulb, } void -lifxd_bulb_set_power_state(struct lifxd_bulb *bulb, uint16_t power) +lgtd_lifx_bulb_set_power_state(struct lgtd_lifx_bulb *bulb, uint16_t power) { assert(bulb); bulb->state.power = power; diff --git a/core/bulb.h b/lifx/bulb.h similarity index 61% rename from core/bulb.h rename to lifx/bulb.h index f8e45d2..97fb7be 100644 --- a/core/bulb.h +++ b/lifx/bulb.h @@ -29,52 +29,52 @@ #pragma once -struct lifxd_gateway; +struct lgtd_lifx_gateway; #pragma pack(push, 1) -struct lifxd_light_state { +struct lgtd_lifx_light_state { uint16_t hue; uint16_t saturation; uint16_t brightness; uint16_t kelvin; uint16_t dim; uint16_t power; - char label[LIFXD_LABEL_SIZE]; + char label[LGTD_LIFX_LABEL_SIZE]; uint64_t tags; }; #pragma pack(pop) -struct lifxd_bulb { - RB_ENTRY(lifxd_bulb) link; - SLIST_ENTRY(lifxd_bulb) link_by_gw; - struct lifxd_gateway *gw; - uint8_t addr[LIFXD_ADDR_LENGTH]; - struct lifxd_light_state state; - lifxd_time_mono_t last_light_state_at; +struct lgtd_lifx_bulb { + RB_ENTRY(lgtd_lifx_bulb) link; + SLIST_ENTRY(lgtd_lifx_bulb) link_by_gw; + struct lgtd_lifx_gateway *gw; + uint8_t addr[LGTD_LIFX_ADDR_LENGTH]; + struct lgtd_lifx_light_state state; + lgtd_time_mono_t last_light_state_at; }; -RB_HEAD(lifxd_bulb_map, lifxd_bulb); -SLIST_HEAD(lifxd_bulb_list, lifxd_bulb); +RB_HEAD(lgtd_lifx_bulb_map, lgtd_lifx_bulb); +SLIST_HEAD(lgtd_lifx_bulb_list, lgtd_lifx_bulb); -extern struct lifxd_bulb_map lifxd_bulbs_table; +extern struct lgtd_lifx_bulb_map lgtd_lifx_bulbs_table; static inline int -lifxd_bulb_cmp(const struct lifxd_bulb *a, const struct lifxd_bulb *b) +lgtd_lifx_bulb_cmp(const struct lgtd_lifx_bulb *a, const struct lgtd_lifx_bulb *b) { return memcmp(a->addr, b->addr, sizeof(a->addr)); } RB_GENERATE_STATIC( - lifxd_bulb_map, - lifxd_bulb, + lgtd_lifx_bulb_map, + lgtd_lifx_bulb, link, - lifxd_bulb_cmp + lgtd_lifx_bulb_cmp ); -struct lifxd_bulb *lifxd_bulb_get(struct lifxd_gateway *, const uint8_t *); -struct lifxd_bulb *lifxd_bulb_open(struct lifxd_gateway *, const uint8_t *); -void lifxd_bulb_close(struct lifxd_bulb *); +struct lgtd_lifx_bulb *lgtd_lifx_bulb_get(struct lgtd_lifx_gateway *, const uint8_t *); +struct lgtd_lifx_bulb *lgtd_lifx_bulb_open(struct lgtd_lifx_gateway *, const uint8_t *); +void lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *); -void lifxd_bulb_set_light_state(struct lifxd_bulb *, - const struct lifxd_light_state *, - lifxd_time_mono_t); -void lifxd_bulb_set_power_state(struct lifxd_bulb *, uint16_t); +void lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *, + const struct lgtd_lifx_light_state *, + lgtd_time_mono_t); +void lgtd_lifx_bulb_set_power_state(struct lgtd_lifx_bulb *, uint16_t); diff --git a/core/gateway.c b/lifx/gateway.c similarity index 54% rename from core/gateway.c rename to lifx/gateway.c index 52ae09d..7aaf33c 100644 --- a/core/gateway.c +++ b/lifx/gateway.c @@ -45,18 +45,18 @@ #include #include "wire_proto.h" -#include "time_monotonic.h" +#include "core/time_monotonic.h" #include "bulb.h" #include "gateway.h" #include "broadcast.h" #include "timer.h" -#include "lifxd.h" +#include "core/lightsd.h" -struct lifxd_gateway_list lifxd_gateways = - LIST_HEAD_INITIALIZER(&lifxd_gateways); +struct lgtd_lifx_gateway_list lgtd_lifx_gateways = + LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); void -lifxd_gateway_close(struct lifxd_gateway *gw) +lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) { assert(gw); @@ -69,41 +69,43 @@ lifxd_gateway_close(struct lifxd_gateway *gw) event_free(gw->refresh_ev); event_free(gw->write_ev); evbuffer_free(gw->write_buf); - struct lifxd_bulb *bulb, *next_bulb; + struct lgtd_lifx_bulb *bulb, *next_bulb; SLIST_FOREACH_SAFE(bulb, &gw->bulbs, link_by_gw, next_bulb) { - lifxd_bulb_close(bulb); + lgtd_lifx_bulb_close(bulb); } - lifxd_info( + lgtd_info( "connection with gateway bulb [%s]:%hu closed", gw->ip_addr, gw->port ); free(gw); } static void -lifxd_gateway_write_callback(evutil_socket_t socket, short events, void *ctx) +lgtd_lifx_gateway_write_callback(evutil_socket_t socket, + short events, void *ctx) { (void)socket; assert(ctx); - struct lifxd_gateway *gw = (struct lifxd_gateway *)ctx; + struct lgtd_lifx_gateway *gw = (struct lgtd_lifx_gateway *)ctx; if (events & EV_TIMEOUT) { // Not sure how that could happen in UDP but eh. - lifxd_warn( + lgtd_warn( "lost connection with gateway bulb [%s]:%hu", gw->ip_addr, gw->port ); - lifxd_gateway_close(gw); - if (!lifxd_broadcast_discovery()) { - lifxd_err(1, "can't start auto discovery"); + lgtd_lifx_gateway_close(gw); + if (!lgtd_lifx_broadcast_discovery()) { + lgtd_err(1, "can't start auto discovery"); } return; } if (events & EV_WRITE) { - if (evbuffer_write(gw->write_buf, gw->socket) == -1 && errno != EAGAIN) { - lifxd_warn("can't write to [%s]:%hu", gw->ip_addr, gw->port); - lifxd_gateway_close(gw); - if (!lifxd_broadcast_discovery()) { - lifxd_err(1, "can't start auto discovery"); + int nbytes = evbuffer_write(gw->write_buf, gw->socket); + if (nbytes == -1 && errno != EAGAIN) { + lgtd_warn("can't write to [%s]:%hu", gw->ip_addr, gw->port); + lgtd_lifx_gateway_close(gw); + if (!lgtd_lifx_broadcast_discovery()) { + lgtd_err(1, "can't start auto discovery"); } return; } @@ -112,7 +114,7 @@ lifxd_gateway_write_callback(evutil_socket_t socket, short events, void *ctx) // latency with last_pkt_at < last_req_at, which isn't true since the // pkt will be for an answer the previous write: gw->last_req_at = gw->next_req_at; - gw->next_req_at = lifxd_time_monotonic_msecs(); + gw->next_req_at = lgtd_time_monotonic_msecs(); if (!evbuffer_get_length(gw->write_buf)) { event_del(gw->write_ev); } @@ -120,109 +122,112 @@ lifxd_gateway_write_callback(evutil_socket_t socket, short events, void *ctx) } static void -lifxd_gateway_send_get_all_light_state(struct lifxd_gateway *gw) +lgtd_lifx_gateway_send_get_all_light_state(struct lgtd_lifx_gateway *gw) { assert(gw); - struct lifxd_packet_header hdr; - union lifxd_target target = { .addr = gw->site }; - lifxd_wire_setup_header( - &hdr, LIFXD_TARGET_SITE, target, gw->site, LIFXD_GET_LIGHT_STATE + struct lgtd_lifx_packet_header hdr; + union lgtd_lifx_target target = { .addr = gw->site }; + lgtd_lifx_wire_setup_header( + &hdr, LGTD_LIFX_TARGET_SITE, target, gw->site, LGTD_LIFX_GET_LIGHT_STATE ); - lifxd_debug("GET_LIGHT_STATE --> [%s]:%hu", gw->ip_addr, gw->port); - lifxd_gateway_send_packet(gw, &hdr, NULL, 0); + lgtd_debug("GET_LIGHT_STATE --> [%s]:%hu", gw->ip_addr, gw->port); + lgtd_lifx_gateway_send_packet(gw, &hdr, NULL, 0); } static void -lifxd_gateway_refresh_callback(evutil_socket_t socket, short events, void *ctx) +lgtd_lifx_gateway_refresh_callback(evutil_socket_t socket, + short events, + void *ctx) { (void)socket; (void)events; - lifxd_gateway_send_get_all_light_state((struct lifxd_gateway *)ctx); + lgtd_lifx_gateway_send_get_all_light_state((struct lgtd_lifx_gateway *)ctx); } -static struct lifxd_bulb * -lifxd_gateway_get_or_open_bulb(struct lifxd_gateway *gw, const uint8_t *bulb_addr) +static struct lgtd_lifx_bulb * +lgtd_lifx_gateway_get_or_open_bulb(struct lgtd_lifx_gateway *gw, + const uint8_t *bulb_addr) { assert(gw); assert(bulb_addr); - struct lifxd_bulb *bulb = lifxd_bulb_get(gw, bulb_addr); + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get(gw, bulb_addr); if (!bulb) { - bulb = lifxd_bulb_open(gw, bulb_addr); + bulb = lgtd_lifx_bulb_open(gw, bulb_addr); if (bulb) { SLIST_INSERT_HEAD(&gw->bulbs, bulb, link_by_gw); - lifxd_info( + lgtd_info( "bulb %s on [%s]:%hu", - lifxd_addrtoa(bulb_addr), gw->ip_addr, gw->port + lgtd_addrtoa(bulb_addr), gw->ip_addr, gw->port ); } } return bulb; } -struct lifxd_gateway * -lifxd_gateway_open(const struct sockaddr_storage *peer, - ev_socklen_t addrlen, - const uint8_t *site, - lifxd_time_mono_t received_at) +struct lgtd_lifx_gateway * +lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, + ev_socklen_t addrlen, + const uint8_t *site, + lgtd_time_mono_t received_at) { assert(peer); assert(site); - struct lifxd_gateway *gw = calloc(1, sizeof(*gw)); + struct lgtd_lifx_gateway *gw = calloc(1, sizeof(*gw)); if (!gw) { - lifxd_warn("can't allocate a new gateway bulb"); + lgtd_warn("can't allocate a new gateway bulb"); return false; } gw->socket = socket(peer->ss_family, SOCK_DGRAM, IPPROTO_UDP); if (gw->socket == -1) { - lifxd_warn("can't open a new socket"); + lgtd_warn("can't open a new socket"); goto error_socket; } if (connect(gw->socket, (const struct sockaddr *)peer, addrlen) == -1 || evutil_make_socket_nonblocking(gw->socket) == -1) { - lifxd_warn("can't open a new socket"); + lgtd_warn("can't open a new socket"); goto error_connect; } gw->write_ev = event_new( - lifxd_ev_base, + lgtd_ev_base, gw->socket, EV_WRITE|EV_PERSIST, - lifxd_gateway_write_callback, + lgtd_lifx_gateway_write_callback, gw ); gw->write_buf = evbuffer_new(); gw->refresh_ev = evtimer_new( - lifxd_ev_base, lifxd_gateway_refresh_callback, gw + lgtd_ev_base, lgtd_lifx_gateway_refresh_callback, gw ); memcpy(&gw->peer, peer, sizeof(gw->peer)); - lifxd_sockaddrtoa(peer, gw->ip_addr, sizeof(gw->ip_addr)); - gw->port = lifxd_sockaddrport(peer); + lgtd_sockaddrtoa(peer, gw->ip_addr, sizeof(gw->ip_addr)); + gw->port = lgtd_sockaddrport(peer); memcpy(gw->site, site, sizeof(gw->site)); gw->last_req_at = received_at; gw->next_req_at = received_at; gw->last_pkt_at = received_at; - struct timeval refresh_interval = LIFXD_MSECS_TO_TIMEVAL( - LIFXD_GATEWAY_MIN_REFRESH_INTERVAL_MSECS + struct timeval refresh_interval = LGTD_MSECS_TO_TIMEVAL( + LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS ); if (!gw->write_ev || !gw->write_buf || !gw->refresh_ev || event_add(gw->refresh_ev, &refresh_interval) != 0) { - lifxd_warn("can't allocate a new gateway bulb"); + lgtd_warn("can't allocate a new gateway bulb"); goto error_allocate; } - lifxd_info( + lgtd_info( "gateway for site %s at [%s]:%hu", - lifxd_addrtoa(gw->site), gw->ip_addr, gw->port + lgtd_addrtoa(gw->site), gw->ip_addr, gw->port ); - LIST_INSERT_HEAD(&lifxd_gateways, gw, link); + LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); // In case this is the first bulb (re-)discovered, start the watchdog, it // will stop by itself: - lifxd_timer_start_watchdog(); + lgtd_lifx_timer_start_watchdog(); return gw; @@ -243,13 +248,13 @@ lifxd_gateway_open(const struct sockaddr_storage *peer, return NULL; } -struct lifxd_gateway * -lifxd_gateway_get(const struct sockaddr_storage *peer) +struct lgtd_lifx_gateway * +lgtd_lifx_gateway_get(const struct sockaddr_storage *peer) { assert(peer); - struct lifxd_gateway *gw, *next_gw; - LIST_FOREACH_SAFE(gw, &lifxd_gateways, link, next_gw) { + struct lgtd_lifx_gateway *gw, *next_gw; + LIST_FOREACH_SAFE(gw, &lgtd_lifx_gateways, link, next_gw) { if (peer->ss_family == gw->peer.ss_family && !memcmp(&gw->peer, peer, sizeof(*peer))) { return gw; @@ -260,24 +265,24 @@ lifxd_gateway_get(const struct sockaddr_storage *peer) } void -lifxd_gateway_close_all(void) +lgtd_lifx_gateway_close_all(void) { - struct lifxd_gateway *gw, *next_gw; - LIST_FOREACH_SAFE(gw, &lifxd_gateways, link, next_gw) { - lifxd_gateway_close(gw); + struct lgtd_lifx_gateway *gw, *next_gw; + LIST_FOREACH_SAFE(gw, &lgtd_lifx_gateways, link, next_gw) { + lgtd_lifx_gateway_close(gw); } } void -lifxd_gateway_send_packet(struct lifxd_gateway *gw, - const struct lifxd_packet_header *hdr, - const void *pkt, - int pkt_size) +lgtd_lifx_gateway_send_packet(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const void *pkt, + int pkt_size) { assert(gw); assert(hdr); - assert(pkt_size >= 0 && pkt_size < LIFXD_MAX_PACKET_SIZE); - assert(!memcmp(hdr->site, gw->site, LIFXD_ADDR_LENGTH)); + assert(pkt_size >= 0 && pkt_size < LGTD_LIFX_MAX_PACKET_SIZE); + assert(!memcmp(hdr->site, gw->site, LGTD_LIFX_ADDR_LENGTH)); evbuffer_add(gw->write_buf, hdr, sizeof(*hdr)); if (pkt) { @@ -288,37 +293,37 @@ lifxd_gateway_send_packet(struct lifxd_gateway *gw, } void -lifxd_gateway_handle_pan_gateway(struct lifxd_gateway *gw, - const struct lifxd_packet_header *hdr, - const struct lifxd_packet_pan_gateway *pkt) +lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_pan_gateway *pkt) { assert(gw && hdr && pkt); - lifxd_debug( + lgtd_debug( "SET_PAN_GATEWAY <-- [%s]:%hu - %s site=%s", gw->ip_addr, gw->port, - lifxd_addrtoa(hdr->target.device_addr), - lifxd_addrtoa(hdr->site) + lgtd_addrtoa(hdr->target.device_addr), + lgtd_addrtoa(hdr->site) ); } void -lifxd_gateway_handle_light_status(struct lifxd_gateway *gw, - const struct lifxd_packet_header *hdr, - const struct lifxd_packet_light_status *pkt) +lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_light_status *pkt) { assert(gw && hdr && pkt); - lifxd_debug( + lgtd_debug( "SET_LIGHT_STATE <-- [%s]:%hu - %s " "hue=%#hx, saturation=%#hx, brightness=%#hx, " "kelvin=%d, dim=%#hx, power=%#hx, label=%.*s, tags=%#llx", - gw->ip_addr, gw->port, lifxd_addrtoa(hdr->target.device_addr), + gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), pkt->hue, pkt->saturation, pkt->brightness, pkt->kelvin, - pkt->dim, pkt->power, LIFXD_LABEL_SIZE, pkt->label, pkt->tags + pkt->dim, pkt->power, LGTD_LIFX_LABEL_SIZE, pkt->label, pkt->tags ); - struct lifxd_bulb *b = lifxd_gateway_get_or_open_bulb( + struct lgtd_lifx_bulb *b = lgtd_lifx_gateway_get_or_open_bulb( gw, hdr->target.device_addr ); if (!b) { @@ -326,47 +331,47 @@ lifxd_gateway_handle_light_status(struct lifxd_gateway *gw, } assert(sizeof(*pkt) == sizeof(b->state)); - lifxd_bulb_set_light_state( - b, (const struct lifxd_light_state *)pkt, gw->last_pkt_at + lgtd_lifx_bulb_set_light_state( + b, (const struct lgtd_lifx_light_state *)pkt, gw->last_pkt_at ); int latency = gw->last_pkt_at - gw->last_req_at; - if (latency < LIFXD_GATEWAY_MIN_REFRESH_INTERVAL_MSECS) { - int timeout = LIFXD_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; - struct timeval tv = LIFXD_MSECS_TO_TIMEVAL(timeout); + if (latency < LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS) { + int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; + struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); evtimer_add(gw->refresh_ev, &tv); - lifxd_debug( + lgtd_debug( "[%s]:%hu latency is %dms, scheduling next GET_LIGHT_STATE in %dms", gw->ip_addr, gw->port, latency, timeout ); return; } - lifxd_debug( + lgtd_debug( "[%s]:%hu latency is %dms, sending GET_LIGHT_STATE now", gw->ip_addr, gw->port, latency ); - lifxd_gateway_send_get_all_light_state(gw); + lgtd_lifx_gateway_send_get_all_light_state(gw); } void -lifxd_gateway_handle_power_state(struct lifxd_gateway *gw, - const struct lifxd_packet_header *hdr, - const struct lifxd_packet_power_state *pkt) +lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_power_state *pkt) { assert(gw && hdr && pkt); - lifxd_debug( + lgtd_debug( "SET_POWER_STATE <-- [%s]:%hu - %s power=%#hx", - gw->ip_addr, gw->port, lifxd_addrtoa(hdr->target.device_addr), pkt->power + gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), pkt->power ); - struct lifxd_bulb *b = lifxd_gateway_get_or_open_bulb( + struct lgtd_lifx_bulb *b = lgtd_lifx_gateway_get_or_open_bulb( gw, hdr->target.device_addr ); if (!b) { return; } - lifxd_bulb_set_power_state(b, pkt->power); + lgtd_lifx_bulb_set_power_state(b, pkt->power); } diff --git a/core/gateway.h b/lifx/gateway.h similarity index 53% rename from core/gateway.h rename to lifx/gateway.h index f68bc3f..e8afe5c 100644 --- a/core/gateway.h +++ b/lifx/gateway.h @@ -33,56 +33,56 @@ // according to my own tests, aggressively polling a bulb doesn't raise its // consumption at all (and it's interesting to note that a turned off bulb // still draw about 2W in ZigBee and about 3W in WiFi). -enum { LIFXD_GATEWAY_MIN_REFRESH_INTERVAL_MSECS = 200 }; +enum { LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS = 200 }; -struct lifxd_gateway { - LIST_ENTRY(lifxd_gateway) link; - struct lifxd_bulb_list bulbs; +struct lgtd_lifx_gateway { + LIST_ENTRY(lgtd_lifx_gateway) link; + struct lgtd_lifx_bulb_list bulbs; // Multiple gateways can share the same site (that happens when bulbs are // far away enough that ZigBee can't be used). Moreover the SET_PAN_GATEWAY // packet doesn't include the device address in the header (i.e: site and // device_addr have the same value) so we have no choice but to use the // remote ip address to identify a gateway: - struct sockaddr_storage peer; - char ip_addr[INET6_ADDRSTRLEN]; - uint16_t port; - uint8_t site[LIFXD_ADDR_LENGTH]; - evutil_socket_t socket; + struct sockaddr_storage peer; + char ip_addr[INET6_ADDRSTRLEN]; + uint16_t port; + uint8_t site[LGTD_LIFX_ADDR_LENGTH]; + evutil_socket_t socket; // Those three timers let us measure the latency of the gateway. If we // aren't the only client on the network then this won't be accurate since // we will get pushed packets we didn't ask for, but good enough for our // purpose of rate limiting our requests to the gateway: - lifxd_time_mono_t last_req_at; - lifxd_time_mono_t next_req_at; - lifxd_time_mono_t last_pkt_at; - struct event *write_ev; - struct evbuffer *write_buf; - struct event *refresh_ev; + lgtd_time_mono_t last_req_at; + lgtd_time_mono_t next_req_at; + lgtd_time_mono_t last_pkt_at; + struct event *write_ev; + struct evbuffer *write_buf; + struct event *refresh_ev; }; -LIST_HEAD(lifxd_gateway_list, lifxd_gateway); +LIST_HEAD(lgtd_lifx_gateway_list, lgtd_lifx_gateway); -extern struct lifxd_gateway_list lifxd_gateways; +extern struct lgtd_lifx_gateway_list lgtd_lifx_gateways; -struct lifxd_gateway *lifxd_gateway_get(const struct sockaddr_storage *); -struct lifxd_gateway *lifxd_gateway_open(const struct sockaddr_storage *, - ev_socklen_t, - const uint8_t *, - lifxd_time_mono_t); +struct lgtd_lifx_gateway *lgtd_lifx_gateway_get(const struct sockaddr_storage *); +struct lgtd_lifx_gateway *lgtd_lifx_gateway_open(const struct sockaddr_storage *, + ev_socklen_t, + const uint8_t *, + lgtd_time_mono_t); -void lifxd_gateway_close(struct lifxd_gateway *); -void lifxd_gateway_close_all(void); +void lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *); +void lgtd_lifx_gateway_close_all(void); -void lifxd_gateway_send_packet(struct lifxd_gateway *, - const struct lifxd_packet_header *, - const void *, - int); +void lgtd_lifx_gateway_send_packet(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const void *, + int); -void lifxd_gateway_handle_pan_gateway(struct lifxd_gateway *, - const struct lifxd_packet_header *, - const struct lifxd_packet_pan_gateway *); -void lifxd_gateway_handle_light_status(struct lifxd_gateway *, - const struct lifxd_packet_header *, - const struct lifxd_packet_light_status *); -void lifxd_gateway_handle_power_state(struct lifxd_gateway *, - const struct lifxd_packet_header *, - const struct lifxd_packet_power_state *); +void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_pan_gateway *); +void lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_light_status *); +void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_power_state *); diff --git a/lifx/timer.c b/lifx/timer.c new file mode 100644 index 0000000..8ec4458 --- /dev/null +++ b/lifx/timer.c @@ -0,0 +1,218 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "wire_proto.h" +#include "core/time_monotonic.h" +#include "broadcast.h" +#include "bulb.h" +#include "gateway.h" +#include "timer.h" +#include "core/lightsd.h" + +static struct { + struct event *watchdog_interval_ev; + struct event *discovery_timeout_ev; +} lgtd_lifx_timer_context = { + .watchdog_interval_ev = NULL, + .discovery_timeout_ev = NULL +}; + +static void +lgtd_lifx_timer_discovery_timeout_event_callback(evutil_socket_t socket, + short events, + void *ctx) +{ + (void)socket; + (void)events; + (void)ctx; + + int timeout = LGTD_LIFX_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS; + if (LIST_EMPTY(&lgtd_lifx_gateways)) { + lgtd_debug( + "discovery didn't returned anything in %dms, restarting it", + LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS + ); + timeout = LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS; + } else { + lgtd_debug("sending periodic discovery packet"); + } + + struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); + if (event_add(lgtd_lifx_timer_context.discovery_timeout_ev, &tv) + || !lgtd_lifx_broadcast_discovery()) { + lgtd_err(1, "can't start discovery"); + } +} + +static void +lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, + short events, + void *ctx) +{ + (void)socket; + (void)events; + (void)ctx; + + bool start_discovery = false; + lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); + + struct lgtd_lifx_bulb *bulb, *next_bulb; + RB_FOREACH_SAFE( + bulb, + lgtd_lifx_bulb_map, + &lgtd_lifx_bulbs_table, + next_bulb + ) { + int light_state_lag = now - bulb->last_light_state_at; + if (light_state_lag >= LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS) { + lgtd_info( + "closing bulb \"%.*s\" that hasn't been updated for %dms", + LGTD_LIFX_LABEL_SIZE, bulb->state.label, light_state_lag + ); + lgtd_lifx_bulb_close(bulb); + start_discovery = true; + } + } + + // Repeat for the gateways, we could also look if we are removing the last + // bulb on the gateway but this will also support architectures where + // gateways aren't bulbs themselves: + struct lgtd_lifx_gateway *gw, *next_gw; + LIST_FOREACH_SAFE(gw, &lgtd_lifx_gateways, link, next_gw) { + int gw_lag = now - gw->last_pkt_at; + if (gw_lag >= LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS) { + lgtd_info( + "closing bulb gateway [%s]:%hu that " + "hasn't received traffic for %dms", + gw->ip_addr, gw->port, + gw_lag + ); + lgtd_lifx_gateway_close(gw); + start_discovery = true; + } + } + + // If anything happens restart a discovery right away, maybe something just + // moved on the network: + if (start_discovery) { + lgtd_lifx_broadcast_discovery(); + } +} + +bool +lgtd_lifx_timer_setup(void) +{ + assert(!lgtd_lifx_timer_context.watchdog_interval_ev); + assert(!lgtd_lifx_timer_context.discovery_timeout_ev); + + lgtd_lifx_timer_context.discovery_timeout_ev = event_new( + lgtd_ev_base, + -1, + 0, + lgtd_lifx_timer_discovery_timeout_event_callback, + NULL + ); + lgtd_lifx_timer_context.watchdog_interval_ev = event_new( + lgtd_ev_base, + -1, + EV_PERSIST, + lgtd_lifx_timer_watchdog_timeout_event_callback, + NULL + ); + + if (lgtd_lifx_timer_context.discovery_timeout_ev + && lgtd_lifx_timer_context.watchdog_interval_ev) { + return true; + } + + int errsave = errno; + lgtd_lifx_timer_close(); + errno = errsave; + return false; +} + +void +lgtd_lifx_timer_close(void) +{ + if (lgtd_lifx_timer_context.discovery_timeout_ev) { + event_del(lgtd_lifx_timer_context.discovery_timeout_ev); + event_free(lgtd_lifx_timer_context.discovery_timeout_ev); + lgtd_lifx_timer_context.discovery_timeout_ev = NULL; + } + if (lgtd_lifx_timer_context.watchdog_interval_ev) { + event_del(lgtd_lifx_timer_context.watchdog_interval_ev); + event_free(lgtd_lifx_timer_context.watchdog_interval_ev); + lgtd_lifx_timer_context.watchdog_interval_ev = NULL; + } +} + +void +lgtd_lifx_timer_start_watchdog(void) +{ + assert( + !RB_EMPTY(&lgtd_lifx_bulbs_table) || !LIST_EMPTY(&lgtd_lifx_gateways) + ); + + if (!evtimer_pending(lgtd_lifx_timer_context.watchdog_interval_ev, NULL)) { + struct timeval tv = LGTD_MSECS_TO_TIMEVAL( + LGTD_LIFX_TIMER_WATCHDOG_INTERVAL_MSECS + ); + if (event_add(lgtd_lifx_timer_context.watchdog_interval_ev, &tv)) { + lgtd_err(1, "can't start watchdog"); + } + lgtd_debug("starting watchdog timer"); + } +} + +void +lgtd_lifx_timer_start_discovery(void) +{ + assert(!evtimer_pending( + lgtd_lifx_timer_context.discovery_timeout_ev, NULL + )); + + struct timeval tv = LGTD_MSECS_TO_TIMEVAL( + LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS + ); + if (event_add(lgtd_lifx_timer_context.discovery_timeout_ev, &tv)) { + lgtd_err(1, "can't start discovery timer"); + } + lgtd_debug("starting discovery timer"); +} diff --git a/core/timer.h b/lifx/timer.h similarity index 80% rename from core/timer.h rename to lifx/timer.h index 3e4aec1..57e1cb9 100644 --- a/core/timer.h +++ b/lifx/timer.h @@ -29,12 +29,12 @@ #pragma once -enum { LIFXD_TIMER_WATCHDOG_INTERVAL_MSECS = 200 }; -enum { LIFXD_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000 }; -enum { LIFXD_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000 }; -enum { LIFXD_TIMER_DEVICE_TIMEOUT_MSECS = 2000 }; +enum { LGTD_LIFX_TIMER_WATCHDOG_INTERVAL_MSECS = 200 }; +enum { LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000 }; +enum { LGTD_LIFX_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000 }; +enum { LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS = 2000 }; -bool lifxd_timer_setup(void); -void lifxd_timer_close(void); -void lifxd_timer_start_watchdog(void); -void lifxd_timer_start_discovery(void); +bool lgtd_lifx_timer_setup(void); +void lgtd_lifx_timer_close(void); +void lgtd_lifx_timer_start_watchdog(void); +void lgtd_lifx_timer_start_discovery(void); diff --git a/core/wire_proto.c b/lifx/wire_proto.c similarity index 56% rename from core/wire_proto.c rename to lifx/wire_proto.c index 5973f89..ade1d3f 100644 --- a/core/wire_proto.c +++ b/lifx/wire_proto.c @@ -42,33 +42,33 @@ #include #include "wire_proto.h" -#include "time_monotonic.h" +#include "core/time_monotonic.h" #include "bulb.h" #include "gateway.h" -#include "lifxd.h" +#include "core/lightsd.h" -union lifxd_target LIFXD_UNSPEC_TARGET = { .tags = 0 }; +union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET = { .tags = 0 }; -static struct lifxd_packet_infos_map lifxd_packet_infos = - RB_INITIALIZER(&lifxd_packets_infos); +static struct lgtd_lifx_packet_infos_map lgtd_lifx_packet_infos = + RB_INITIALIZER(&lgtd_lifx_packets_infos); RB_GENERATE_STATIC( - lifxd_packet_infos_map, - lifxd_packet_infos, + lgtd_lifx_packet_infos_map, + lgtd_lifx_packet_infos, link, - lifxd_packet_infos_cmp + lgtd_lifx_packet_infos_cmp ); static void -lifxd_wire_null_packet_encoder_decoder(void *pkt) +lgtd_lifx_wire_null_packet_encoder_decoder(void *pkt) { (void)pkt; } static void -lifxd_wire_null_packet_handler(struct lifxd_gateway *gw, - const struct lifxd_packet_header *hdr, - const void *pkt) +lgtd_lifx_wire_null_packet_handler(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const void *pkt) { (void)gw; (void)hdr; @@ -76,74 +76,76 @@ lifxd_wire_null_packet_handler(struct lifxd_gateway *gw, } void -lifxd_wire_load_packet_infos_map(void) +lgtd_lifx_wire_load_packet_infos_map(void) { #define DECODER(x) ((void (*)(void *))(x)) #define ENCODER(x) ((void (*)(void *))(x)) #define HANDLER(x) \ - ((void (*)(struct lifxd_gateway *, \ - const struct lifxd_packet_header *, \ + ((void (*)(struct lgtd_lifx_gateway *, \ + const struct lgtd_lifx_packet_header *, \ const void *))(x)) #define REQUEST_ONLY \ - .decode = lifxd_wire_null_packet_encoder_decoder, \ - .encode = lifxd_wire_null_packet_encoder_decoder, \ - .handle = lifxd_wire_null_packet_handler + .decode = lgtd_lifx_wire_null_packet_encoder_decoder, \ + .encode = lgtd_lifx_wire_null_packet_encoder_decoder, \ + .handle = lgtd_lifx_wire_null_packet_handler - static struct lifxd_packet_infos packet_table[] = { + static struct lgtd_lifx_packet_infos packet_table[] = { { REQUEST_ONLY, .name = "GET_PAN_GATEWAY", - .type = LIFXD_GET_PAN_GATEWAY + .type = LGTD_LIFX_GET_PAN_GATEWAY }, { .name = "PAN_GATEWAY", - .type = LIFXD_PAN_GATEWAY, - .size = sizeof(struct lifxd_packet_pan_gateway), - .decode = DECODER(lifxd_wire_decode_pan_gateway), - .encode = ENCODER(lifxd_wire_encode_pan_gateway), - .handle = HANDLER(lifxd_gateway_handle_pan_gateway) + .type = LGTD_LIFX_PAN_GATEWAY, + .size = sizeof(struct lgtd_lifx_packet_pan_gateway), + .decode = DECODER(lgtd_lifx_wire_decode_pan_gateway), + .encode = ENCODER(lgtd_lifx_wire_encode_pan_gateway), + .handle = HANDLER(lgtd_lifx_gateway_handle_pan_gateway) }, { REQUEST_ONLY, .name = "GET_LIGHT_STATUS", - .type = LIFXD_GET_LIGHT_STATE + .type = LGTD_LIFX_GET_LIGHT_STATE }, { .name = "LIGHT_STATUS", - .type = LIFXD_LIGHT_STATUS, - .size = sizeof(struct lifxd_packet_light_status), - .decode = DECODER(lifxd_wire_decode_light_status), - .encode = ENCODER(lifxd_wire_encode_light_status), - .handle = HANDLER(lifxd_gateway_handle_light_status) + .type = LGTD_LIFX_LIGHT_STATUS, + .size = sizeof(struct lgtd_lifx_packet_light_status), + .decode = DECODER(lgtd_lifx_wire_decode_light_status), + .encode = ENCODER(lgtd_lifx_wire_encode_light_status), + .handle = HANDLER(lgtd_lifx_gateway_handle_light_status) }, { .name = "POWER_STATE", - .type = LIFXD_POWER_STATE, - .size = sizeof(struct lifxd_packet_power_state), - .decode = DECODER(lifxd_wire_decode_power_state), - .handle = HANDLER(lifxd_gateway_handle_power_state) + .type = LGTD_LIFX_POWER_STATE, + .size = sizeof(struct lgtd_lifx_packet_power_state), + .decode = DECODER(lgtd_lifx_wire_decode_power_state), + .handle = HANDLER(lgtd_lifx_gateway_handle_power_state) } }; - for (int i = 0; i != LIFXD_ARRAY_SIZE(packet_table); ++i) { + for (int i = 0; i != LGTD_ARRAY_SIZE(packet_table); ++i) { RB_INSERT( - lifxd_packet_infos_map, &lifxd_packet_infos, &packet_table[i] + lgtd_lifx_packet_infos_map, + &lgtd_lifx_packet_infos, + &packet_table[i] ); } } -const struct lifxd_packet_infos * -lifxd_wire_get_packet_infos(enum lifxd_packet_type packet_type) +const struct lgtd_lifx_packet_infos * +lgtd_lifx_wire_get_packet_infos(enum lgtd_lifx_packet_type packet_type) { - struct lifxd_packet_infos pkt_infos = { .type = packet_type }; - return RB_FIND(lifxd_packet_infos_map, &lifxd_packet_infos, &pkt_infos); + struct lgtd_lifx_packet_infos pkt_infos = { .type = packet_type }; + return RB_FIND(lgtd_lifx_packet_infos_map, &lgtd_lifx_packet_infos, &pkt_infos); } // Convert all the fields in the header to the host endianness. // // \return The payload size or -1 if the header is invalid. void -lifxd_wire_decode_header(struct lifxd_packet_header *hdr) +lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *hdr) { assert(hdr); @@ -156,53 +158,52 @@ lifxd_wire_decode_header(struct lifxd_packet_header *hdr) hdr->packet_type = le16toh(hdr->packet_type); } -const struct lifxd_packet_infos * -lifxd_wire_setup_header(struct lifxd_packet_header *hdr, - enum lifxd_target_type target_type, - union lifxd_target target, - const uint8_t *site, - enum lifxd_packet_type packet_type) +const struct lgtd_lifx_packet_infos * +lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, + enum lgtd_lifx_target_type target_type, + union lgtd_lifx_target target, + const uint8_t *site, + enum lgtd_lifx_packet_type packet_type) { assert(hdr); - const struct lifxd_packet_infos *pkt_infos = lifxd_wire_get_packet_infos( - packet_type - ); + const struct lgtd_lifx_packet_infos *pkt_infos = + lgtd_lifx_wire_get_packet_infos(packet_type); memset(hdr, 0, sizeof(*hdr)); hdr->size = pkt_infos->size + sizeof(*hdr); - hdr->protocol.version = LIFXD_LIFX_PROTOCOL_V1; + hdr->protocol.version = LGTD_LIFX_PROTOCOL_V1; hdr->packet_type = packet_type; if (site) { memcpy(hdr->site, site, sizeof(hdr->site)); } else { - assert(target_type == LIFXD_TARGET_ALL_DEVICES); + assert(target_type == LGTD_LIFX_TARGET_ALL_DEVICES); } switch (target_type) { - case LIFXD_TARGET_SITE: + case LGTD_LIFX_TARGET_SITE: hdr->protocol.tagged = true; break; - case LIFXD_TARGET_TAGS: + case LGTD_LIFX_TARGET_TAGS: hdr->protocol.tagged = true; hdr->target.tags = target.tags; break; - case LIFXD_TARGET_DEVICE: + case LGTD_LIFX_TARGET_DEVICE: hdr->protocol.addressable = false; - memcpy(hdr->target.device_addr, target.addr, LIFXD_ADDR_LENGTH); + memcpy(hdr->target.device_addr, target.addr, LGTD_LIFX_ADDR_LENGTH); break; - case LIFXD_TARGET_ALL_DEVICES: + case LGTD_LIFX_TARGET_ALL_DEVICES: hdr->protocol.tagged = true; break; } - lifxd_wire_encode_header(hdr); + lgtd_lifx_wire_encode_header(hdr); return pkt_infos; } void -lifxd_wire_encode_header(struct lifxd_packet_header *hdr) +lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr) { assert(hdr); @@ -216,7 +217,7 @@ lifxd_wire_encode_header(struct lifxd_packet_header *hdr) } void -lifxd_wire_decode_pan_gateway(struct lifxd_packet_pan_gateway *pkt) +lgtd_lifx_wire_decode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *pkt) { assert(pkt); @@ -224,7 +225,7 @@ lifxd_wire_decode_pan_gateway(struct lifxd_packet_pan_gateway *pkt) } void -lifxd_wire_encode_pan_gateway(struct lifxd_packet_pan_gateway *pkt) +lgtd_lifx_wire_encode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *pkt) { assert(pkt); @@ -232,7 +233,7 @@ lifxd_wire_encode_pan_gateway(struct lifxd_packet_pan_gateway *pkt) } void -lifxd_wire_decode_light_status(struct lifxd_packet_light_status *pkt) +lgtd_lifx_wire_decode_light_status(struct lgtd_lifx_packet_light_status *pkt) { assert(pkt); @@ -246,7 +247,7 @@ lifxd_wire_decode_light_status(struct lifxd_packet_light_status *pkt) } void -lifxd_wire_encode_light_status(struct lifxd_packet_light_status *pkt) +lgtd_lifx_wire_encode_light_status(struct lgtd_lifx_packet_light_status *pkt) { assert(pkt); @@ -260,7 +261,7 @@ lifxd_wire_encode_light_status(struct lifxd_packet_light_status *pkt) } void -lifxd_wire_decode_power_state(struct lifxd_packet_power_state *pkt) +lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *pkt) { assert(pkt); } diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h new file mode 100644 index 0000000..7d36a72 --- /dev/null +++ b/lifx/wire_proto.h @@ -0,0 +1,225 @@ +// Copyright (c) 2014, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +typedef uint16_t uint16le_t; +typedef uint16_t uint16be_t; +typedef uint32_t uint32le_t; +typedef uint32_t uint32be_t; +typedef uint64_t uint64le_t; +typedef uint64_t uint64be_t; + +enum { LGTD_LIFX_PROTOCOL_PORT = 56700 }; + +enum { LGTD_LIFX_ADDR_LENGTH = 6 }; + +#pragma pack(push, 1) + +struct lgtd_lifx_packet_header { + //! Packet size including the headers (i.e: this structure). + uint16le_t size; + struct { + //! Protocol version should be LGTD_LIFX_LIFX_PROTOCOL_V1. + uint16le_t version:12; + //! True when the target field holds a device address. + uint16le_t addressable:1; + //! True when the target field holds tags. + uint16le_t tagged:1; + //! LIFX internal use should be 0. + uint16le_t origin:2; + } protocol; + //! This seems to be for LIFX internal use only. + uint32le_t source; + union { + //! All targeted tags ORed together. + uint64le_t tags; + //! Address of the targeted device. + uint8_t device_addr[LGTD_LIFX_ADDR_LENGTH]; + } target; + uint8_t site[LGTD_LIFX_ADDR_LENGTH]; + struct { + //! True when a response is required, called acknowledge in lifx-gem... + uint8_t response_required:1; + //! True when an acknowledgement is required, no idea what it means. + uint8_t ack_required:1; + uint8_t reserved:6; + } flags; + //! Wrap-around sequence number, LIFX internal use. + uint8_t seqn; + uint64le_t timestamp; + uint16le_t packet_type; + uint8_t reserved[2]; +}; + +enum { LGTD_LIFX_PACKET_HEADER_SIZE = sizeof(struct lgtd_lifx_packet_header) }; + +enum { LGTD_LIFX_PROTOCOL_V1 = 1024 }; + +// Let's define a maximum packet size just in case somebody sends us weird +// headers: +enum { LGTD_LIFX_MAX_PACKET_SIZE = 4096 }; + +enum lgtd_lifx_packet_type { + LGTD_LIFX_GET_PAN_GATEWAY = 0x02, + LGTD_LIFX_PAN_GATEWAY = 0x03, + LGTD_LIFX_GET_TIME = 0x04, + LGTD_LIFX_SET_TIME = 0x05, + LGTD_LIFX_TIME_STATE = 0x06, + LGTD_LIFX_GET_RESET_SWITCH_STATE = 0x07, + LGTD_LIFX_RESET_SWITCH_STATE = 0x08, + LGTD_LIFX_GET_MESH_INFO = 0x0c, + LGTD_LIFX_MESH_INFO = 0x0d, + LGTD_LIFX_GET_MESH_FIRMWARE = 0x0e, + LGTD_LIFX_MESH_FIRMWARE = 0x0f, + LGTD_LIFX_GET_WIFI_INFO = 0x10, + LGTD_LIFX_WIFI_INFO = 0x11, + LGTD_LIFX_GET_WIFI_FIRMWARE_STATE = 0x12, + LGTD_LIFX_WIFI_FIRMWARE_STATE = 0x13, + LGTD_LIFX_GET_POWER_STATE = 0x14, + LGTD_LIFX_SET_POWER_STATE = 0x15, + LGTD_LIFX_POWER_STATE = 0x16, + LGTD_LIFX_GET_BULB_LABEL = 0x17, + LGTD_LIFX_SET_BULB_LABEL = 0x18, + LGTD_LIFX_BULB_LABEL = 0x19, + LGTD_LIFX_GET_TAGS = 0x1a, + LGTD_LIFX_SET_TAGS = 0x1b, + LGTD_LIFX_TAGS = 0x1c, + LGTD_LIFX_GET_TAG_LABELS = 0x1d, + LGTD_LIFX_SET_TAG_LABELS = 0x1e, + LGTD_LIFX_TAG_LABELS = 0x1f, + LGTD_LIFX_GET_VERSION = 0x20, + LGTD_LIFX_VERSION_STATE = 0x21, + LGTD_LIFX_GET_INFO = 0x22, + LGTD_LIFX_INFO_STATE = 0x23, + LGTD_LIFX_GET_MCU_RAIL_VOLTAGE = 0x24, + LGTD_LIFX_MCU_RAIL_VOLTAGE = 0x25, + LGTD_LIFX_REBOOT = 0x26, + LGTD_LIFX_SET_FACTORY_TEST_MODE = 0x27, + LGTD_LIFX_DISABLE_FACTORY_TEST_MODE = 0x28, + LGTD_LIFX_GET_LIGHT_STATE = 0x65, + LGTD_LIFX_SET_LIGHT_COLOUR = 0x66, + LGTD_LIFX_SET_WAVEFORM = 0x67, + LGTD_LIFX_SET_DIM_ABSOLUTE = 0x68, + LGTD_LIFX_SET_DIM_RELATIVE = 0x69, + LGTD_LIFX_LIGHT_STATUS = 0x6b, + LGTD_LIFX_GET_WIFI_STATE = 0x12d, + LGTD_LIFX_SET_WIFI_STATE = 0x12e, + LGTD_LIFX_WIFI_STATE = 0x12f, + LGTD_LIFX_GET_ACCESS_POINTS = 0x130, + LGTD_LIFX_SET_ACCESS_POINTS = 0x131, + LGTD_LIFX_ACCESS_POINT = 0x132, +}; + +enum { LGTD_LIFX_LABEL_SIZE = 32 }; + +struct lgtd_lifx_packet_light_status { + uint16le_t hue; + uint16le_t saturation; + uint16le_t brightness; + uint16le_t kelvin; + uint16le_t dim; + uint16le_t power; + uint8_t label[LGTD_LIFX_LABEL_SIZE]; + uint64be_t tags; +}; + +enum lgtd_lifx_power_state { + LGTD_LIFX_POWER_OFF = 0, + LGTD_LIFX_POWER_ON = 0xffff +}; + +struct lgtd_lifx_packet_power_state { + uint16_t power; // see enum lgtd_lifx_power_state +}; + +enum lgtd_lifx_service_type { + LGTD_LIFX_SERVICE_TCP = 1, + LGTD_LIFX_SERVICE_UDP = 2 +}; + +struct lgtd_lifx_packet_pan_gateway { + uint8_t service_type; // see enum lgtd_lifx_service_type + uint32le_t port; +}; + +enum lgtd_lifx_target_type { + LGTD_LIFX_TARGET_SITE, + LGTD_LIFX_TARGET_TAGS, + LGTD_LIFX_TARGET_DEVICE, + LGTD_LIFX_TARGET_ALL_DEVICES +}; + +#pragma pack(pop) + +struct lgtd_lifx_gateway; + +struct lgtd_lifx_packet_infos { + RB_ENTRY(lgtd_lifx_packet_infos) link; + const char *name; + enum lgtd_lifx_packet_type type; + unsigned size; + void (*decode)(void *); + void (*encode)(void *); + void (*handle)(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const void *); +}; +RB_HEAD(lgtd_lifx_packet_infos_map, lgtd_lifx_packet_infos); + +static inline int +lgtd_lifx_packet_infos_cmp(struct lgtd_lifx_packet_infos *a, + struct lgtd_lifx_packet_infos *b) +{ + return a->type - b->type; +} + +union lgtd_lifx_target { + uint64_t tags; + const uint8_t *addr; //! site or device address +}; + +extern union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET; + +const struct lgtd_lifx_packet_infos *lgtd_lifx_wire_get_packet_infos(enum lgtd_lifx_packet_type); +void lgtd_lifx_wire_load_packet_infos_map(void); + +const struct lgtd_lifx_packet_infos *lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *, + enum lgtd_lifx_target_type, + union lgtd_lifx_target, + const uint8_t *, + enum lgtd_lifx_packet_type); +void lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *); +void lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *); + +void lgtd_lifx_wire_decode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *); +void lgtd_lifx_wire_encode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *); +void lgtd_lifx_wire_decode_light_status(struct lgtd_lifx_packet_light_status *); +void lgtd_lifx_wire_encode_light_status(struct lgtd_lifx_packet_light_status *); +void lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *); From fd6a68a0ff82d5e44d445168c8dece46c1c6b475 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 24 Jan 2015 18:47:05 +0100 Subject: [PATCH 002/181] Let the timer code restart the discovery on a write error This avoid stupid infinite loops on ENETUNREACH for example. --- lifx/broadcast.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lifx/broadcast.c b/lifx/broadcast.c index b21a238..851ec2a 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -240,7 +240,6 @@ lgtd_lifx_broadcast_event_callback(evutil_socket_t socket, error_reset: lgtd_lifx_broadcast_close(); lgtd_lifx_broadcast_setup(); - lgtd_lifx_broadcast_discovery(); } void From 7116cb852b5540d182fb1935dee7ac0b58272361 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 16 Feb 2015 10:55:24 -0800 Subject: [PATCH 003/181] Use SO_REUSEADDR on the broadcast socket --- lifx/broadcast.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lifx/broadcast.c b/lifx/broadcast.c index 851ec2a..c95338f 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -286,6 +286,12 @@ lgtd_lifx_broadcast_setup(void) if (err) { goto error; } + err = evutil_make_listen_socket_reuseable( + lgtd_lifx_broadcast_endpoint.socket + ); + if (err) { + goto error; + } err = evutil_make_socket_nonblocking(lgtd_lifx_broadcast_endpoint.socket); if (err == -1) { From a9c225823171287feda199b44919807417294d41 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 16 Feb 2015 10:55:24 -0800 Subject: [PATCH 004/181] Re-transmit GET_LIGHT_STATUS instead of waiting for the gateway to timeout --- lifx/gateway.c | 8 ++++++++ lifx/gateway.h | 2 ++ lifx/timer.c | 6 ++++++ lifx/timer.h | 1 + 4 files changed, 17 insertions(+) diff --git a/lifx/gateway.c b/lifx/gateway.c index 7aaf33c..7c4b96b 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -145,6 +145,14 @@ lgtd_lifx_gateway_refresh_callback(evutil_socket_t socket, lgtd_lifx_gateway_send_get_all_light_state((struct lgtd_lifx_gateway *)ctx); } +void +lgtd_lifx_gateway_force_refresh(struct lgtd_lifx_gateway *gw) +{ + assert(gw); + + event_active(gw->refresh_ev, 0, 0); +} + static struct lgtd_lifx_bulb * lgtd_lifx_gateway_get_or_open_bulb(struct lgtd_lifx_gateway *gw, const uint8_t *bulb_addr) diff --git a/lifx/gateway.h b/lifx/gateway.h index e8afe5c..65899cc 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -72,6 +72,8 @@ struct lgtd_lifx_gateway *lgtd_lifx_gateway_open(const struct sockaddr_storage * void lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *); void lgtd_lifx_gateway_close_all(void); +void lgtd_lifx_gateway_force_refresh(struct lgtd_lifx_gateway *); + void lgtd_lifx_gateway_send_packet(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, const void *, diff --git a/lifx/timer.c b/lifx/timer.c index 8ec4458..f8b3c4a 100644 --- a/lifx/timer.c +++ b/lifx/timer.c @@ -126,6 +126,12 @@ lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, ); lgtd_lifx_gateway_close(gw); start_discovery = true; + } else if (gw_lag >= LGTD_LIFX_TIMER_DEVICE_FORCE_REFRESH_MSECS) { + lgtd_info( + "no update on bulb gateway [%s]:%hu for %dms, forcing refresh", + gw->ip_addr, gw->port, gw_lag + ); + lgtd_lifx_gateway_force_refresh(gw); } } diff --git a/lifx/timer.h b/lifx/timer.h index 57e1cb9..765459c 100644 --- a/lifx/timer.h +++ b/lifx/timer.h @@ -33,6 +33,7 @@ enum { LGTD_LIFX_TIMER_WATCHDOG_INTERVAL_MSECS = 200 }; enum { LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000 }; enum { LGTD_LIFX_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000 }; enum { LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS = 2000 }; +enum { LGTD_LIFX_TIMER_DEVICE_FORCE_REFRESH_MSECS = 600 }; bool lgtd_lifx_timer_setup(void); void lgtd_lifx_timer_close(void); From 3969b13f9040ae403dbae522deeae668e61f80fc Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 16 Feb 2015 10:55:29 -0800 Subject: [PATCH 005/181] Start a TCP/JSON-RPC API --- CMakeLists.txt | 6 + CMakeScripts/AddAllSubdirectories.cmake | 8 + CMakeScripts/AddTestFromSources.cmake | 11 + core/CMakeLists.txt | 8 + core/client.c | 177 +++++ core/client.h | 50 ++ core/jsmn.c | 333 +++++++++ core/jsmn.h | 97 +++ core/jsonrpc.c | 664 ++++++++++++++++++ core/jsonrpc.h | 100 +++ core/lightsd.c | 31 +- core/lightsd.h | 7 +- core/listen.c | 147 ++++ core/listen.h | 41 ++ core/log.c | 4 +- core/proto.c | 88 +++ core/proto.h | 34 + core/router.c | 83 +++ core/router.h | 32 + docs/conf.py | 259 +++++++ docs/index.rst | 16 + docs/internal-apis.rst | 23 + docs/protocol.rst | 41 ++ lifx/wire_proto.c | 43 +- lifx/wire_proto.h | 15 +- tests/CMakeLists.txt | 1 + tests/core/CMakeLists.txt | 1 + tests/core/jsonrpc/CMakeLists.txt | 22 + .../test_jsonrpc_check_and_call_power_off.c | 60 ++ ..._check_and_call_power_off_missing_target.c | 49 ++ .../test_jsonrpc_check_and_call_power_on.c | 60 ++ ...c_check_and_call_power_on_missing_target.c | 49 ++ ...onrpc_check_and_call_set_light_from_hsbk.c | 102 +++ ..._call_set_light_from_hsbk_invalid_params.c | 116 +++ .../test_jsonrpc_extract_request_no_params.c | 48 ++ ...c_extract_request_notification_no_params.c | 43 ++ ...est_jsonrpc_extract_request_params_array.c | 54 ++ .../test_jsonrpc_extract_request_params_obj.c | 54 ++ ...onrpc_extract_request_valid_notification.c | 49 ++ tests/core/jsonrpc/test_jsonrpc_send_error.c | 32 + ...onrpc_type_float_between_0_and_1_invalid.c | 26 + ...jsonrpc_type_float_between_0_and_1_valid.c | 25 + ...rpc_type_float_between_0_and_360_invalid.c | 26 + ...onrpc_type_float_between_0_and_360_valid.c | 29 + .../core/jsonrpc/test_jsonrpc_type_integer.c | 20 + ..._jsonrpc_type_integer_invalid_characters.c | 20 + .../test_jsonrpc_type_integer_too_big.c | 20 + .../test_jsonrpc_type_integer_too_small.c | 20 + tests/core/jsonrpc/test_jsonrpc_utils.h | 61 ++ tests/core/utils.c | 18 + tests/lightsc | 51 ++ 51 files changed, 3347 insertions(+), 27 deletions(-) create mode 100644 CMakeScripts/AddAllSubdirectories.cmake create mode 100644 CMakeScripts/AddTestFromSources.cmake create mode 100644 core/client.c create mode 100644 core/client.h create mode 100644 core/jsmn.c create mode 100644 core/jsmn.h create mode 100644 core/jsonrpc.c create mode 100644 core/jsonrpc.h create mode 100644 core/listen.c create mode 100644 core/listen.h create mode 100644 core/proto.c create mode 100644 core/proto.h create mode 100644 core/router.c create mode 100644 core/router.h create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/internal-apis.rst create mode 100644 docs/protocol.rst create mode 100644 tests/CMakeLists.txt create mode 100644 tests/core/CMakeLists.txt create mode 100644 tests/core/jsonrpc/CMakeLists.txt create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_send_error.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_type_integer.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_utils.h create mode 100644 tests/core/utils.c create mode 100755 tests/lightsc diff --git a/CMakeLists.txt b/CMakeLists.txt index be8599e..78edc12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,8 @@ MESSAGE(STATUS "Source directory: ${LIGHTSD_SOURCE_DIR}") SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${LIGHTSD_SOURCE_DIR}/CMakeScripts) +ENABLE_TESTING() + ### Platform checks ############################################################ # TODO: we need at least 2.0.19-stable because of the logging defines @@ -29,6 +31,9 @@ ADD_DEFINITIONS("-DLGTD_SUSECONDS_T_SIZE=${SUSECONDS_T_SIZE}") ### Global definitions ######################################################### +INCLUDE(AddAllSubdirectories) +INCLUDE(AddTestFromSources) + SET(CMAKE_C_FLAGS "-pipe -Wextra -Wall -Wstrict-prototypes -std=c99") # Only relevant for the GNU libc: @@ -49,3 +54,4 @@ INCLUDE_DIRECTORIES(${LIGHTSD_SOURCE_DIR}/compat/generic ${LIGHTSD_BINARY_DIR}/c ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(lifx) +ADD_SUBDIRECTORY(tests) diff --git a/CMakeScripts/AddAllSubdirectories.cmake b/CMakeScripts/AddAllSubdirectories.cmake new file mode 100644 index 0000000..f9c7158 --- /dev/null +++ b/CMakeScripts/AddAllSubdirectories.cmake @@ -0,0 +1,8 @@ +FUNCTION(ADD_ALL_SUBDIRECTORIES) + FILE(GLOB SUBDIRECTORIES "*") + FOREACH (ENTRY ${SUBDIRECTORIES}) + IF (IS_DIRECTORY ${ENTRY}) + ADD_SUBDIRECTORY(${ENTRY}) + ENDIF () + ENDFOREACH () +ENDFUNCTION() diff --git a/CMakeScripts/AddTestFromSources.cmake b/CMakeScripts/AddTestFromSources.cmake new file mode 100644 index 0000000..c13c588 --- /dev/null +++ b/CMakeScripts/AddTestFromSources.cmake @@ -0,0 +1,11 @@ +FUNCTION(ADD_TEST_FROM_C_SOURCES TEST_SOURCE TEST_LIB) + STRING(LENGTH ${TEST_SOURCE} TEST_NAME_LEN) + STRING(LENGTH "test_" PREFIX_LEN) + MATH(EXPR TEST_NAME_LEN "${TEST_NAME_LEN} - 2 - ${PREFIX_LEN}") + STRING(SUBSTRING ${ARGV0} ${PREFIX_LEN} ${TEST_NAME_LEN} TEST_NAME) + ADD_EXECUTABLE(${TEST_NAME} ${TEST_SOURCE} ${ARGN}) + IF (TEST_LIB) + TARGET_LINK_LIBRARIES(${TEST_NAME} ${TEST_LIB}) + ENDIF () + ADD_TEST(test_${TEST_NAME} ${TEST_NAME}) +ENDFUNCTION() diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a72fc26..a5e982f 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -10,10 +10,18 @@ INCLUDE_DIRECTORIES( CONFIGURE_FILE(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) +ADD_DEFINITIONS("-DJSMN_STRICT=1") + ADD_EXECUTABLE( lightsd + client.c + jsmn.c + jsonrpc.c + listen.c lightsd.c log.c + proto.c + router.c ${TIME_MONOTONIC_IMPL} ) diff --git a/core/client.c b/core/client.c new file mode 100644 index 0000000..d100d3a --- /dev/null +++ b/core/client.c @@ -0,0 +1,177 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "jsmn.h" +#include "client.h" +#include "jsonrpc.h" +#include "lightsd.h" + +struct lgtd_client_list lgtd_clients = LIST_HEAD_INITIALIZER(&lgtd_clients); + +static void +lgtd_client_close(struct lgtd_client *client) +{ + assert(client); + assert(client->io); + + LIST_REMOVE(client, link); + bufferevent_free(client->io); + free(client); +} + +void +lgtd_client_close_all(void) +{ + struct lgtd_client *client, *next_client; + LIST_FOREACH_SAFE(client, &lgtd_clients, link, next_client) { + lgtd_client_close(client); + } +} + +static void +lgtd_client_read_callback(struct bufferevent *bev, void *ctx) +{ + assert(ctx); + + struct lgtd_client *client = ctx; + + struct evbuffer *input = bufferevent_get_input(bev); + size_t bufsz = evbuffer_get_contiguous_space(input); + // Get the actual pointer to the beginning of the evbuf: + const char *buf = (char *)evbuffer_pullup(input, bufsz); + jsmnerr_t rv; + +retry_after_pullup: + rv = jsmn_parse( + &client->jsmn_ctx, + buf, + bufsz, + client->jsmn_tokens, + LGTD_ARRAY_SIZE(client->jsmn_tokens) + ); + switch (rv) { + case JSMN_ERROR_NOMEM: + lgtd_warnx( + "dropping client [%s]:%hu: request too big, not " + "enough parser tokens", client->ip_addr, client->port + ); + lgtd_client_close(client); + break; + case JSMN_ERROR_INVAL: + lgtd_warnx( + "dropping client [%s]:%hu: invalid json", + client->ip_addr, client->port + ); + // TODO: consume remaining data and send a proper error instead of + // closing the connection: + lgtd_client_close(client); + break; + case JSMN_ERROR_PART: + if (evbuffer_get_length(input) > LGTD_CLIENT_MAX_REQUEST_BUF_SIZE) { + lgtd_warnx( + "dropping client [%s]:%hu: request too big", + client->ip_addr, client->port + ); + lgtd_client_close(client); + break; + } else if (bufsz >= evbuffer_get_length(input)) { + break; // We pulled up everything already, wait for more data + } + // pullup and resume parsing: + buf = (char *)evbuffer_pullup(input, -1); + bufsz = evbuffer_get_length(input); + goto retry_after_pullup; + default: + lgtd_jsonrpc_dispatch_request(client, buf, rv); + evbuffer_drain(input, bufsz); + jsmn_init(&client->jsmn_ctx); + break; + } +} + +static void +lgtd_client_event_callback(struct bufferevent *bev, short events, void *ctx) +{ + (void)bev; + assert(ctx); + + struct lgtd_client *client = ctx; + + if (events & (BEV_EVENT_ERROR|BEV_EVENT_EOF)) { + lgtd_info( + "lost connection with client [%s]:%hu", + client->ip_addr, client->port + ); + lgtd_client_close(client); + } +} + +struct lgtd_client * +lgtd_client_open(evutil_socket_t peer, const struct sockaddr_storage *peer_addr) +{ + assert(peer != -1); + assert(peer_addr); + + struct lgtd_client *client = calloc(1, sizeof(*client)); + if (!client) { + return NULL; + } + client->io = bufferevent_socket_new( + lgtd_ev_base, peer, BEV_OPT_CLOSE_ON_FREE + ); + if (!client->io) { + return NULL; + } + bufferevent_setcb( + client->io, + lgtd_client_read_callback, + NULL, + lgtd_client_event_callback, + client + ); + lgtd_sockaddrtoa(peer_addr, client->ip_addr, sizeof(client->ip_addr)); + client->port = lgtd_sockaddrport(peer_addr); + jsmn_init(&client->jsmn_ctx); + bufferevent_enable(client->io, EV_READ|EV_WRITE|EV_TIMEOUT); + + LIST_INSERT_HEAD(&lgtd_clients, client, link); + + return client; +} diff --git a/core/client.h b/core/client.h new file mode 100644 index 0000000..3dbd8ad --- /dev/null +++ b/core/client.h @@ -0,0 +1,50 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +enum { LGTD_CLIENT_JSMN_TOKENS_NUM = 48 }; +enum { LGTD_CLIENT_MAX_REQUEST_BUF_SIZE = 2048 }; + +struct lgtd_client { + LIST_ENTRY(lgtd_client) link; + struct bufferevent *io; + char ip_addr[INET6_ADDRSTRLEN]; + uint16_t port; + jsmn_parser jsmn_ctx; + jsmntok_t jsmn_tokens[LGTD_CLIENT_JSMN_TOKENS_NUM]; +}; +LIST_HEAD(lgtd_client_list, lgtd_client); + +#define LGTD_CLIENT_WRITE_STRING(client, s) do { \ + bufferevent_write((client)->io, s, strlen((s))); \ +} while(0) + +struct lgtd_client *lgtd_client_open(evutil_socket_t, const struct sockaddr_storage *); +void lgtd_client_close_all(void); diff --git a/core/jsmn.c b/core/jsmn.c new file mode 100644 index 0000000..0efa8c3 --- /dev/null +++ b/core/jsmn.c @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2010 Serge A. Zaitsev + * + * 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. + */ + +#include + +#include "jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Filsl next token with JSON string. + */ +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + jsmnerr_t r; + int i; + jsmntok_t *token; + int count = 0; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + diff --git a/core/jsmn.h b/core/jsmn.h new file mode 100644 index 0000000..48a07c1 --- /dev/null +++ b/core/jsmn.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2010 Serge A. Zaitsev + * + * 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 __JSMN_H_ +#define __JSMN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 +} jsmntype_t; + +typedef enum { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +} jsmnerr_t; + +/** + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_H_ */ diff --git a/core/jsonrpc.c b/core/jsonrpc.c new file mode 100644 index 0000000..d1260b2 --- /dev/null +++ b/core/jsonrpc.c @@ -0,0 +1,664 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "jsmn.h" +#include "client.h" +#include "jsonrpc.h" +#include "proto.h" +#include "lightsd.h" + +static bool +lgtd_jsonrpc_type_integer(const jsmntok_t *t, const char *json) +{ + if (t->type != JSMN_PRIMITIVE) { + return false; + } + + const char *endptr = NULL; + errno = 0; + strtol(&json[t->start], (char **)&endptr, 10); + return endptr == json + t->end && errno != ERANGE; +} + +static bool +lgtd_jsonrpc_type_float_between_0_and_1(const jsmntok_t *t, + const char *json) +{ + if (t->type != JSMN_PRIMITIVE) { + return false; + } + + int i = t->start; + bool dot_seen = false; + bool first_digit_is_one = false; + for (; i < t->end; i++) { + if (json[i] == '.') { + if (dot_seen) { + return false; + } + dot_seen = true; + } else if (dot_seen) { + if (json[i] < '0' || json[i] > '9') { + return false; + } if (first_digit_is_one && json[i] != '0') { + return false; + } + } else { + if (first_digit_is_one) { + return false; + } else if (json[i] == '1') { + first_digit_is_one = true; + } else if (json[i] != '0') { + return false; + } + } + } + + return true; +} + +static bool +lgtd_jsonrpc_type_float_between_0_and_360(const jsmntok_t *t, + const char *json) +{ + if (t->type != JSMN_PRIMITIVE) { + return false; + } + + char c = json[t->start]; + if (c != '-' && (c > '9' || c < '0')) { + return false; + } + + const char *endptr = NULL; + errno = 0; + long intpart = strtol(&json[t->start], (char **)&endptr, 10); + if ((endptr != json + t->end && *endptr != '.') + || errno == ERANGE || intpart < 0 || intpart > 360 + || (intpart == 0 && c == '-')) { + return false; + } + if (endptr == json + t->end) { + return true; + } + long fracpart = strtol(++endptr, (char **)&endptr, 10); + return endptr == json + t->end && errno != ERANGE + && fracpart >= 0 && (intpart < 360 || fracpart == 0); +} + +static bool +lgtd_jsonrpc_type_number(const jsmntok_t *t, const char *json) +{ + if (t->type != JSMN_PRIMITIVE) { + return false; + } + + char c = json[t->start]; + return c == '-' || (c >= '0' && c <= '9'); +} + +static bool __attribute__((unused)) +lgtd_jsonrpc_type_bool(const jsmntok_t *t, const char *json) +{ + if (t->type != JSMN_PRIMITIVE) { + return false; + } + + char c = json[t->start]; + return c == 't' || c == 'f'; +} + +static bool +lgtd_jsonrpc_type_null(const jsmntok_t *t, const char *json) +{ + return memcmp( + &json[t->start], "null", LGTD_MIN(t->size, (int)sizeof("null")) + ); +} + +static bool +lgtd_jsonrpc_type_string(const jsmntok_t *t, const char *json) +{ + (void)json; + return t->type == JSMN_STRING; +} + +static bool +lgtd_jsonrpc_type_object_or_array(const jsmntok_t *t, const char *json) +{ + (void)json; + return t->type == JSMN_OBJECT || t->type == JSMN_ARRAY; +} + +static bool +lgtd_jsonrpc_type_string_number_or_null(const jsmntok_t *t, + const char *json) +{ + return lgtd_jsonrpc_type_number(t, json) + || lgtd_jsonrpc_type_null(t, json) + || t->type == JSMN_STRING; +} + +static int +lgtd_jsonrpc_consume_object_or_array(const jsmntok_t *tokens, + int ti, + int parsed, + const char *json) +{ + assert(tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY); + assert(ti < parsed); + + int obj_size = tokens[ti++].size; + while (obj_size-- && ti < parsed) { + if (tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY) { + ti = lgtd_jsonrpc_consume_object_or_array(tokens, ti, parsed, json); + } else { + ti += tokens[ti].size + 1; + } + } + return ti; +} + +static int +lgtd_jsonrpc_float_range_to_uint16(const char *s, int len, int start, int stop) +{ + assert(s); + assert(len > 0); + assert(start < stop); + + int range = stop * 10E5 - start * 10E5; + const char *dot = NULL; + long fracpart = 0; + long intpart = strtol(s, (char **)&dot, 10) * 10E5; + if (dot - s != len && *dot == '.') { + for (int i = dot - s + 1, multiplier = 10E4; + i != len && multiplier != 0; + i++, multiplier /= 10) { + fracpart += (s[i] - '0') * multiplier; + } + } + return ((intpart + fracpart) * UINT16_MAX) / range; +} + +static bool +lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, + const struct lgtd_jsonrpc_node *schema, + int schema_size, + const jsmntok_t *tokens, + int ntokens, + const char *json) +{ + if (tokens[0].type != JSMN_OBJECT) { + return false; + } + + for (int ti = 1; ti < ntokens;) { + // make sure it's a key: + if (tokens[ti].type != JSMN_STRING) { + return false; + } + + for (int si = 0;; si++) { + if (si == schema_size) { + ti++; // nothing matched, skip the key + break; + } + + int tokenlen = LGTD_JSONRPC_TOKEN_LEN(&tokens[ti]); + if (schema[si].keylen != tokenlen) { + continue; + } + int diff = memcmp( + schema[si].key, &json[tokens[ti].start], tokenlen + ); + if (!diff) { + ti++; // keys looks good, move to the value + if (!schema[si].type_cmp(&tokens[ti], json)) { + lgtd_debug( + "jsonrpc client sent an invalid value for %s", + schema[si].key + ); + return false; + } + if (schema[si].value_offset != -1) { + const jsmntok_t *seen = LGTD_JSONRPC_GET_JSMNTOK( + output, schema[si].value_offset + ); + if (seen) { // duplicate key + lgtd_debug( + "jsonrpc client sent duplicate parameter %s", + schema[si].key + ); + return false; + } + LGTD_JSONRPC_SET_JSMNTOK( + output, schema[si].value_offset, &tokens[ti] + ); + } + break; + } + } + + // skip the value, if it's an object or an array we need to + // skip everything in it: + if (tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY) { + ti = lgtd_jsonrpc_consume_object_or_array( + tokens, ti, ntokens, json + ); + } else { + ti++; + } + } + + for (int si = 0; si != schema_size; si++) { + if (!schema[si].optional) { + const jsmntok_t *seen = LGTD_JSONRPC_GET_JSMNTOK( + output, schema[si].value_offset + ); + if (!seen) { + lgtd_debug("missing jsonrpc parameter %s", schema[si].key); + return false; + } + lgtd_debug("got jsonrpc parameter %s", schema[si].key); + } + } + + return true; +} + +static void +lgtd_jsonrpc_write_id(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json) +{ + int start, stop; + if (request->id->type == JSMN_STRING) { // get the quotes + start = request->id->start - 1; + stop = request->id->end + 1; + } else { + start = request->id->start; + stop = request->id->end; + } + bufferevent_write(client->io, &json[start], stop - start); +} + +void +lgtd_jsonrpc_send_error(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json, + enum lgtd_jsonrpc_error_code code, + const char *message) +{ + LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); + lgtd_jsonrpc_write_id(client, request, json); + LGTD_CLIENT_WRITE_STRING(client, ", \"error\": {\"code\": "); + char str_code[8] = { 0 }; + snprintf(str_code, sizeof(str_code), "%d", code); + LGTD_CLIENT_WRITE_STRING(client, str_code); + LGTD_CLIENT_WRITE_STRING(client, ", \"message\": \""); + LGTD_CLIENT_WRITE_STRING(client, message); + LGTD_CLIENT_WRITE_STRING(client, "\"}}"); +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json, + const char *result) +{ + LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); + lgtd_jsonrpc_write_id(client, request, json); + LGTD_CLIENT_WRITE_STRING(client, ", \"result\": "); + LGTD_CLIENT_WRITE_STRING(client, result); + LGTD_CLIENT_WRITE_STRING(client, "}"); +} + +static bool +lgtd_jsonrpc_check_and_extract_request(struct lgtd_jsonrpc_request *request, + const jsmntok_t *tokens, + int ntokens, + const char *json) +{ + static const struct lgtd_jsonrpc_node request_schema[] = { + LGTD_JSONRPC_NODE( + "jsonrpc", -1, lgtd_jsonrpc_type_string, false + ), + LGTD_JSONRPC_NODE( + "method", + offsetof(struct lgtd_jsonrpc_request, method), + lgtd_jsonrpc_type_string, + false + ), + LGTD_JSONRPC_NODE( + "params", + offsetof(struct lgtd_jsonrpc_request, params), + lgtd_jsonrpc_type_object_or_array, + true + ), + LGTD_JSONRPC_NODE( + "id", + offsetof(struct lgtd_jsonrpc_request, id), + lgtd_jsonrpc_type_string_number_or_null, + true + ) + }; + + bool ok = lgtd_jsonrpc_extract_values_from_schema_and_dict( + request, + request_schema, + LGTD_ARRAY_SIZE(request_schema), + tokens, + ntokens, + json + ); + if (ok) { + // XXX We already do that from extract_values_from_schema_and_dict: + if (request->params) { + const jsmntok_t *params = request->params; + int params_ti = params - tokens; + while (params[request->params_ntokens].start < params->end + && params_ti + request->params_ntokens < ntokens) { + request->params_ntokens++; + } + } + return true; + } + + return false; +} + +static char * +lgtd_jsonrpc_dup_target(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json, + const jsmntok_t *t) +{ + char *target = strndup( + &json[t->start], LGTD_JSONRPC_TOKEN_LEN(t) + ); + if (!target) { + lgtd_jsonrpc_send_error( + client, request, json, LGTD_JSONRPC_INTERNAL_ERROR, + "Shit's on fire, yo" + ); + } + return target; +} + +static void +lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json) +{ + struct lgtd_jsonrpc_set_brightness_args { + const jsmntok_t *target; + const jsmntok_t *h; + const jsmntok_t *s; + const jsmntok_t *b; + const jsmntok_t *k; + } params = { NULL, NULL, NULL, NULL, NULL }; + static const struct lgtd_jsonrpc_node schema[] = { + LGTD_JSONRPC_NODE( + "target", + offsetof(struct lgtd_jsonrpc_set_brightness_args, target), + lgtd_jsonrpc_type_string, + false + ), + LGTD_JSONRPC_NODE( + "hue", + offsetof(struct lgtd_jsonrpc_set_brightness_args, h), + lgtd_jsonrpc_type_float_between_0_and_360, + false + ), + LGTD_JSONRPC_NODE( + "saturation", + offsetof(struct lgtd_jsonrpc_set_brightness_args, s), + lgtd_jsonrpc_type_float_between_0_and_1, + false + ), + LGTD_JSONRPC_NODE( + "brightness", + offsetof(struct lgtd_jsonrpc_set_brightness_args, b), + lgtd_jsonrpc_type_float_between_0_and_1, + false + ), + LGTD_JSONRPC_NODE( + "kelvin", + offsetof(struct lgtd_jsonrpc_set_brightness_args, k), + lgtd_jsonrpc_type_integer, + false + ), + }; + + bool ok = lgtd_jsonrpc_extract_values_from_schema_and_dict( + ¶ms, + schema, + LGTD_ARRAY_SIZE(schema), + request->params, + request->params_ntokens, + json + ); + if (!ok) { + goto error_invalid_params; + } + + int h = lgtd_jsonrpc_float_range_to_uint16( + &json[params.h->start], LGTD_JSONRPC_TOKEN_LEN(params.h), 0, 360 + ); + int s = lgtd_jsonrpc_float_range_to_uint16( + &json[params.s->start], LGTD_JSONRPC_TOKEN_LEN(params.s), 0, 1 + ); + int b = lgtd_jsonrpc_float_range_to_uint16( + &json[params.b->start], LGTD_JSONRPC_TOKEN_LEN(params.b), 0, 1 + ); + errno = 0; + int k = strtol(&json[params.k->start], NULL, 10); + if (k < 2500 || k > 9000 || errno == ERANGE) { + goto error_invalid_params; + } + + char *t = lgtd_jsonrpc_dup_target(client, request, json, params.target); + if (!t) { + return; + } + + ok = lgtd_proto_set_light_from_hsbk(t, h, s, b, k, 0); + free(t); + if (ok) { + lgtd_jsonrpc_send_response(client, request, json, "true"); + return; + } + +error_invalid_params: + lgtd_jsonrpc_send_error( + client, request, json, LGTD_JSONRPC_INVALID_PARAMS, + "Invalid parameters" + ); +} + +static char * +lgtd_jsonrpc_extract_target_only(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json) +{ + const jsmntok_t *target = NULL; + static const struct lgtd_jsonrpc_node schema[] = { + LGTD_JSONRPC_NODE("target", 0, lgtd_jsonrpc_type_string, false) + }; + + bool ok = lgtd_jsonrpc_extract_values_from_schema_and_dict( + &target, schema, 1, request->params, request->params_ntokens, json + ); + if (!ok) { + lgtd_jsonrpc_send_error( + client, request, json, LGTD_JSONRPC_INVALID_PARAMS, + "Invalid parameters" + ); + return NULL; + } + + return lgtd_jsonrpc_dup_target(client, request, json, target); +} + +static void +lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json) +{ + + char *target = lgtd_jsonrpc_extract_target_only(client, request, json); + if (!target) { + return; + } + + bool ok = lgtd_proto_power_on(target); + free(target); + if (ok) { + lgtd_jsonrpc_send_response(client, request, json, "true"); + return; + } + + lgtd_jsonrpc_send_error( + client, request, json, LGTD_JSONRPC_INVALID_PARAMS, + "Invalid parameters" + ); +} + +static void +lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json) +{ + + char *target = lgtd_jsonrpc_extract_target_only(client, request, json); + if (!target) { + return; + } + + bool ok = lgtd_proto_power_off(target); + free(target); + if (ok) { + lgtd_jsonrpc_send_response(client, request, json, "true"); + return; + } + + lgtd_jsonrpc_send_error( + client, request, json, LGTD_JSONRPC_INVALID_PARAMS, + "Invalid parameters" + ); +} + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, + const char *json, + int parsed) +{ + static const struct lgtd_jsonrpc_method methods[] = { + LGTD_JSONRPC_METHOD( + "power_on", 1, // t + lgtd_jsonrpc_check_and_call_power_on + ), + LGTD_JSONRPC_METHOD( + "power_off", 1, // t + lgtd_jsonrpc_check_and_call_power_off + ), + LGTD_JSONRPC_METHOD( + "set_light_from_hsbk", 5, // t, h, s, b, k + lgtd_jsonrpc_check_and_call_set_light_from_hsbk + ), + }; + + assert(client); + assert(parsed > 0); + + const jsmntok_t *tokens = client->jsmn_tokens; + + // TODO: batch requests + + struct lgtd_jsonrpc_request request; + memset(&request, 0, sizeof(request)); + bool ok = lgtd_jsonrpc_check_and_extract_request( + &request, + tokens, + parsed, + json + ); + if (!ok) { + lgtd_jsonrpc_send_error( + client, &request, json, LGTD_JSONRPC_INVALID_REQUEST, + "Invalid request" + ); + return; + } + + assert(request.method); + assert(request.id); + + for (int i = 0; i != LGTD_ARRAY_SIZE(methods); i++) { + int parsed_method_namelen = LGTD_JSONRPC_TOKEN_LEN(request.method); + if (parsed_method_namelen != methods[i].namelen) { + continue; + } + int diff = memcmp( + methods[i].name, &json[request.method->start], methods[i].namelen + ); + if (!diff) { + int params_count = request.params->size; + if (params_count != methods[i].params_count) { + lgtd_jsonrpc_send_error( + client, &request, json, LGTD_JSONRPC_INVALID_PARAMS, + "Invalid number of parameters" + ); + return; + } + methods[i].method(client, &request, json); + return; + } + } + + lgtd_jsonrpc_send_error( + client, &request, json, LGTD_JSONRPC_METHOD_NOT_FOUND, + "Method not found" + ); +} diff --git a/core/jsonrpc.h b/core/jsonrpc.h new file mode 100644 index 0000000..ee6bf44 --- /dev/null +++ b/core/jsonrpc.h @@ -0,0 +1,100 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +struct lgtd_jsonrpc_request { + const jsmntok_t *method; + const jsmntok_t *params; + int params_ntokens; + const jsmntok_t *id; +}; + +struct lgtd_jsonrpc_node { + const char *key; + int keylen; + int value_offset; + bool (*type_cmp)(const jsmntok_t *, const char *); + bool optional; +}; + +#define LGTD_JSONRPC_SET_JSMNTOK(object, value_offset, value) do { \ + *(const jsmntok_t **)(&(((char *)(object))[value_offset])) = (value); \ +} while (0) + +#define LGTD_JSONRPC_GET_JSMNTOK(object, value_offset) \ + *(const jsmntok_t **)(&((char *)(object))[value_offset]); \ + +#define LGTD_JSONRPC_NODE(key_, value_offset_, fn_type_cmp, optional_) { \ + .key = (key_), \ + .keylen = sizeof((key_)) - 1, \ + .value_offset = (value_offset_), \ + .type_cmp = (fn_type_cmp), \ + .optional = (optional_) \ +} + +#define LGTD_JSONRPC_TOKEN_LEN(t) ((t)->end - (t)->start) + +struct lgtd_jsonrpc_method { + const char *name; + int namelen; + int params_count; + void (*method)(struct lgtd_client *, + const struct lgtd_jsonrpc_request *, + const char *); +}; + +#define LGTD_JSONRPC_METHOD(name_, params_count_, method_) { \ + .name = (name_), \ + .namelen = sizeof((name_)) -1, \ + .params_count = (params_count_), \ + .method = (method_) \ +} + +enum lgtd_jsonrpc_error_code { + LGTD_JSONRPC_SUCCESS = 0, + LGTD_JSONRPC_PARSE_ERROR = -32700, + LGTD_JSONRPC_INVALID_REQUEST = -32600, + LGTD_JSONRPC_METHOD_NOT_FOUND = -32601, + LGTD_JSONRPC_INVALID_PARAMS = -32602, + LGTD_JSONRPC_INTERNAL_ERROR = -32603, + LGTD_JSONRPC_SERVER_ERROR = -32000 // (to -32099) +}; + +void lgtd_jsonrpc_dispatch_request(struct lgtd_client *, const char *, int); + +void lgtd_jsonrpc_send_error(struct lgtd_client *, + const struct lgtd_jsonrpc_request *, + const char *, + enum lgtd_jsonrpc_error_code, + const char *); +void lgtd_jsonrpc_send_response(struct lgtd_client *, + const struct lgtd_jsonrpc_request *, + const char *, + const char *); diff --git a/core/lightsd.c b/core/lightsd.c index 8a8024f..92573a3 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -51,8 +51,11 @@ #include "lifx/bulb.h" #include "lifx/gateway.h" #include "lifx/broadcast.h" -#include "version.h" #include "lifx/timer.h" +#include "version.h" +#include "jsmn.h" +#include "client.h" +#include "listen.h" #include "lightsd.h" struct lgtd_opts lgtd_opts = { @@ -66,6 +69,8 @@ struct event_base *lgtd_ev_base = NULL; void lgtd_cleanup(void) { + lgtd_listen_close_all(); + lgtd_client_close_all(); lgtd_lifx_timer_close(); lgtd_lifx_broadcast_close(); lgtd_lifx_gateway_close_all(); @@ -131,7 +136,8 @@ static void lgtd_usage(const char *progname) { printf( - "Usage: %s [-v debug|info|warning|error] [-f] [-t] [-h] [-V]\n", + "Usage: %s -l addr:port [-l ...] [-f] [-t] [-h] [-V] " + "[-v debug|info|warning|error]\n", progname ); exit(0); @@ -140,7 +146,11 @@ lgtd_usage(const char *progname) int main(int argc, char *argv[]) { + lgtd_configure_libevent(); + lgtd_configure_signal_handling(); + static const struct option long_opts[] = { + {"listen", required_argument, NULL, 'l'}, {"foreground", no_argument, NULL, 'f'}, {"no-timestamps", no_argument, NULL, 't'}, {"help", no_argument, NULL, 'h'}, @@ -148,12 +158,22 @@ main(int argc, char *argv[]) {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} }; - const char short_opts[] = "fthv:V"; + const char short_opts[] = "l:fthv:V"; for (int rv = getopt_long(argc, argv, short_opts, long_opts, NULL); rv != -1; rv = getopt_long(argc, argv, short_opts, long_opts, NULL)) { switch (rv) { + case 'l': + (void)0; + char *sep = strrchr(optarg, ':'); + if (!sep || !sep[1]) { + lgtd_usage(argv[0]); + } + *sep = '\0'; + if (!lgtd_listen_open(optarg, sep + 1)) { + exit(1); + } case 'f': lgtd_opts.foreground = true; break; @@ -187,12 +207,9 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; - lgtd_configure_libevent(); - lgtd_configure_signal_handling(); - lgtd_lifx_wire_load_packet_infos_map(); if (!lgtd_lifx_timer_setup() || !lgtd_lifx_broadcast_setup()) { - lgtd_err(1, "can't setup lgtd_lifx"); + lgtd_err(1, "can't setup lightsd"); } lgtd_lifx_timer_start_discovery(); diff --git a/core/lightsd.h b/core/lightsd.h index d5bdae0..ab2b448 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -34,6 +34,7 @@ #endif #define LGTD_ABS(v) ((v) >= 0 ? (v) : (v) * -1) +#define LGTD_MIN(a, b) ((a) < (b) ? (a) : (b)) #define LGTD_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #define LGTD_MSECS_TO_TIMEVAL(v) { \ .tv_sec = (v) / 1000, \ @@ -50,9 +51,9 @@ enum lgtd_verbosity { enum { LGTD_ERROR_MSG_BUFSIZE = 2048 }; struct lgtd_opts { - bool foreground; - bool log_timestamps; - enum lgtd_verbosity verbosity; + bool foreground; + bool log_timestamps; + enum lgtd_verbosity verbosity; }; extern struct lgtd_opts lgtd_opts; diff --git a/core/listen.c b/core/listen.c new file mode 100644 index 0000000..44d3358 --- /dev/null +++ b/core/listen.c @@ -0,0 +1,147 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "jsmn.h" +#include "client.h" +#include "listen.h" +#include "lightsd.h" + +struct lgtd_listen_list lgtd_listeners = + SLIST_HEAD_INITIALIZER(&lgtd_listeners); + +static void +lgtd_listen_accept_new_client(struct evconnlistener *evlistener, + evutil_socket_t peer, + struct sockaddr *peer_addr, + int addrlen, + void *ctx) +{ + (void)evlistener; + (void)addrlen; + + struct lgtd_listen *listener = ctx; + struct lgtd_client *client = lgtd_client_open( + peer, (struct sockaddr_storage *)peer_addr + ); + if (client) { + lgtd_info( + "accepted new client [%s]:%hu", client->ip_addr, client->port + ); + return; + } + lgtd_warn( + "can't accept new client on %s:%s", listener->addr, listener->port + ); +} + +void +lgtd_listen_close_all(void) +{ + while (!SLIST_EMPTY(&lgtd_listeners)) { + struct lgtd_listen *listener = SLIST_FIRST(&lgtd_listeners); + SLIST_REMOVE_HEAD(&lgtd_listeners, link); + evconnlistener_free(listener->evlistener); + free(listener); + } +} + +bool +lgtd_listen_open(const char *addr, const char *port) +{ + assert(addr); + assert(port); + + struct evutil_addrinfo *res = NULL, hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_protocol = IPPROTO_TCP, + .ai_flags = EVUTIL_AI_NUMERICSERV|EVUTIL_AI_PASSIVE + }; + + int err = evutil_getaddrinfo(addr, port, &hints, &res); + if (err) { + lgtd_warnx( + "can't listen on %s:%s: %s", addr, port, evutil_gai_strerror(err) + ); + return false; + } + + struct lgtd_listen *listener; + struct evconnlistener *evlistener; + for (struct evutil_addrinfo *it = res; it; it = it->ai_next) { + evlistener = NULL; + listener = calloc(1, sizeof(*listener)); + if (!listener) { + goto error; + } + evlistener = evconnlistener_new_bind( + lgtd_ev_base, + lgtd_listen_accept_new_client, + listener, + LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, + -1, + it->ai_addr, + it->ai_addrlen + ); + if (!evlistener) { + goto error; + } + listener->evlistener = evlistener; + listener->addr = addr; + listener->port = port; + SLIST_INSERT_HEAD(&lgtd_listeners, listener, link); + lgtd_info( + "listening on %s:%s (%s)", + addr, port, it->ai_family == AF_INET ? "IPv4" : "IPv6" + ); + } + + evutil_freeaddrinfo(res); + + return true; + +error: + lgtd_warn("can't listen on %s:%s", addr, port); + if (evlistener) { + evconnlistener_free(evlistener); + } + free(listener); + evutil_freeaddrinfo(res); + return false; +} diff --git a/core/listen.h b/core/listen.h new file mode 100644 index 0000000..9f86f22 --- /dev/null +++ b/core/listen.h @@ -0,0 +1,41 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +struct lgtd_listen { + SLIST_ENTRY(lgtd_listen) link; + const char *addr; + const char *port; + struct evconnlistener *evlistener; +}; +SLIST_HEAD(lgtd_listen_list, lgtd_listen); + +bool lgtd_listen_open(const char *, const char *); +void lgtd_listen_close_all(void); diff --git a/core/log.c b/core/log.c index 6966b2a..48bd761 100644 --- a/core/log.c +++ b/core/log.c @@ -83,11 +83,11 @@ lgtd_log_header(const char *loglvl, bool showprogname) lgtd_isotime_now(timestr, sizeof(timestr)); fprintf( stderr, "[%s] [%s] %s", - timestr, loglvl, showprogname ? "lightsd " : "" + timestr, loglvl, showprogname ? "lightsd: " : "" ); return; } - fprintf(stderr, "[%s] %s", loglvl, showprogname ? "lightsd " : ""); + fprintf(stderr, "[%s] %s", loglvl, showprogname ? "lightsd: " : ""); } const char * diff --git a/core/proto.c b/core/proto.c new file mode 100644 index 0000000..529fe55 --- /dev/null +++ b/core/proto.c @@ -0,0 +1,88 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lifx/wire_proto.h" +#include "router.h" +#include "lightsd.h" + +bool +lgtd_proto_power_on(const char *target) +{ + assert(target); + + struct lgtd_lifx_packet_power_state pkt = { .power = LGTD_LIFX_POWER_ON }; + return lgtd_router_send(target, LGTD_LIFX_SET_POWER_STATE, &pkt); +} + +bool +lgtd_proto_power_off(const char *target) +{ + assert(target); + + struct lgtd_lifx_packet_power_state pkt = { .power = LGTD_LIFX_POWER_OFF }; + return lgtd_router_send(target, LGTD_LIFX_SET_POWER_STATE, &pkt); +} + +bool +lgtd_proto_set_light_from_hsbk(const char *target, + int hue, + int saturation, + int brightness, + int kelvin, + int transition_msecs) +{ + assert(target); + assert(hue >= 0 && hue <= UINT16_MAX); + assert(saturation >= 0 && saturation <= UINT16_MAX); + assert(brightness >= 0 && brightness <= UINT16_MAX); + assert(kelvin >= 2500 && kelvin <= 9000); + assert(transition_msecs >= 0); + + struct lgtd_lifx_packet_light_color pkt = { + .stream = 0, + .hue = hue, + .saturation = saturation, + .brightness = brightness, + .kelvin = kelvin, + .transition = transition_msecs + }; + lgtd_lifx_wire_encode_light_color(&pkt); + return lgtd_router_send(target, LGTD_LIFX_SET_LIGHT_COLOR, &pkt); +} diff --git a/core/proto.h b/core/proto.h new file mode 100644 index 0000000..a69ff14 --- /dev/null +++ b/core/proto.h @@ -0,0 +1,34 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +bool lgtd_proto_set_light_from_hsbk(const char *, int, int, int, int, int); +bool lgtd_proto_power_on(const char *); +bool lgtd_proto_power_off(const char *); diff --git a/core/router.c b/core/router.c new file mode 100644 index 0000000..1779b67 --- /dev/null +++ b/core/router.c @@ -0,0 +1,83 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lifx/wire_proto.h" +#include "time_monotonic.h" +#include "lifx/bulb.h" +#include "lifx/gateway.h" +#include "lightsd.h" + +static void +lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) +{ + struct lgtd_lifx_packet_header hdr; + union lgtd_lifx_target target = { .tags = 0 }; + + const struct lgtd_lifx_packet_infos *pkt_infos = NULL; + struct lgtd_lifx_gateway *gw; + LIST_FOREACH(gw, &lgtd_lifx_gateways, link) { + pkt_infos = lgtd_lifx_wire_setup_header( + &hdr, LGTD_LIFX_TARGET_ALL_DEVICES, target, gw->site, pkt_type + ); + assert(pkt_infos); + lgtd_lifx_gateway_send_packet(gw, &hdr, pkt, pkt_infos->size); + } + + if (pkt_infos) { + lgtd_debug("broadcasting %s", pkt_infos->name); + } +} + +bool +lgtd_router_send(const char *target, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + assert(target); + + if (!strcmp(target, "*")) { + lgtd_router_broadcast(pkt_type, pkt); + return true; + } + + return false; +} diff --git a/core/router.h b/core/router.h new file mode 100644 index 0000000..c427fbd --- /dev/null +++ b/core/router.h @@ -0,0 +1,32 @@ +// Copyright (c) 2015, Louis Opter +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +bool lgtd_router_send(const char *, enum lgtd_lifx_packet_type, void *); diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..5688dca --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# lightsd documentation build configuration file, created by +# sphinx-quickstart on Wed Jan 28 00:04:24 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'lightsd' +copyright = '2015, Louis Opter' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.0.1' +# The full version, including alpha/beta/rc tags. +release = '0.0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'nature' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'lightsddoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'lightsd.tex', 'lightsd Documentation', + 'Louis Opter', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'lightsd', 'lightsd Documentation', + ['Louis Opter'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'lightsd', 'lightsd Documentation', + 'Louis Opter', 'lightsd', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..441da99 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,16 @@ +The lights daemon documentation +=============================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + protocol + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/internal-apis.rst b/docs/internal-apis.rst new file mode 100644 index 0000000..b659011 --- /dev/null +++ b/docs/internal-apis.rst @@ -0,0 +1,23 @@ +lightds internal APIs +===================== + +lgtd_proto_* +------------ + +.. function:: lgtd_proto_power_off(target) + + Turns off the targeted bulbs. + +.. function:: lgtd_proto_power_on(target) + + Turns on the targeted bulbs. + +.. function:: lgtd_proto_set_light_from_hsbk(target, h, s, b, k) + + :param string target: targeted bulbs; + :param int h: Hue from 0 (0°) to 65535 (360°); + :param int s: Saturation from 0 to 65535; + :param int b: Brightness from 0 to 65535; + :param int k: Temperature in Kelvin (max 9000K). + +.. vim: set tw=80 spelllang=en spell: diff --git a/docs/protocol.rst b/docs/protocol.rst new file mode 100644 index 0000000..3625672 --- /dev/null +++ b/docs/protocol.rst @@ -0,0 +1,41 @@ +The lights daemon protocol +========================== + +The lightsd protocol is implemented on top of `JSON-RPC 2.0`_. + +.. _JSON-RPC 2.0: http://www.jsonrpc.org/specification + +Targeting bulbs +--------------- + +Commands that manipulate bulbs will take a *target* argument to define on which +bulb(s) the operation should apply: + ++-----------------------------+--------------------------------------------+ +| ``\*`` | targets all bulbs | ++-----------------------------+--------------------------------------------+ +| ``#TagName`` | targets bulbs tagged with *TagName* | ++-----------------------------+--------------------------------------------+ +| ``124f31a5`` | directly target the bulb with the given id | ++-----------------------------+--------------------------------------------+ +| ``[\*, #Kitchen, 123456]`` | compose different targets together | ++-----------------------------+--------------------------------------------+ + +Available methods +----------------- + +.. function:: power_off(target) + + Power off the given bulb(s). + +.. function:: power_on(target) + + Power on the given bulb(s). + +.. function:: set_light_from_hsbk(target, h, s, b, k) + + :param float h: Hue from 0 to 360. + :param float s: Saturation from 0 to 1. + :param float b: Brightness from 0 to 1. + +.. vim: set tw=80 spelllang=en spell: diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index ade1d3f..661befa 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -84,14 +84,18 @@ lgtd_lifx_wire_load_packet_infos_map(void) ((void (*)(struct lgtd_lifx_gateway *, \ const struct lgtd_lifx_packet_header *, \ const void *))(x)) -#define REQUEST_ONLY \ +#define NO_PAYLOAD \ + .encode = lgtd_lifx_wire_null_packet_encoder_decoder +#define RESPONSE_ONLY \ + .encode = lgtd_lifx_wire_null_packet_encoder_decoder +#define REQUEST_ONLY \ .decode = lgtd_lifx_wire_null_packet_encoder_decoder, \ - .encode = lgtd_lifx_wire_null_packet_encoder_decoder, \ .handle = lgtd_lifx_wire_null_packet_handler static struct lgtd_lifx_packet_infos packet_table[] = { { REQUEST_ONLY, + NO_PAYLOAD, .name = "GET_PAN_GATEWAY", .type = LGTD_LIFX_GET_PAN_GATEWAY }, @@ -105,23 +109,38 @@ lgtd_lifx_wire_load_packet_infos_map(void) }, { REQUEST_ONLY, + NO_PAYLOAD, .name = "GET_LIGHT_STATUS", .type = LGTD_LIFX_GET_LIGHT_STATE }, { + RESPONSE_ONLY, .name = "LIGHT_STATUS", .type = LGTD_LIFX_LIGHT_STATUS, .size = sizeof(struct lgtd_lifx_packet_light_status), .decode = DECODER(lgtd_lifx_wire_decode_light_status), - .encode = ENCODER(lgtd_lifx_wire_encode_light_status), .handle = HANDLER(lgtd_lifx_gateway_handle_light_status) }, + { + REQUEST_ONLY, + NO_PAYLOAD, // well it has a payload, but it's just 0 or 1... + .size = sizeof(struct lgtd_lifx_packet_power_state), + .name = "SET_POWER_STATE", + .type = LGTD_LIFX_SET_POWER_STATE, + }, { .name = "POWER_STATE", .type = LGTD_LIFX_POWER_STATE, .size = sizeof(struct lgtd_lifx_packet_power_state), .decode = DECODER(lgtd_lifx_wire_decode_power_state), .handle = HANDLER(lgtd_lifx_gateway_handle_power_state) + }, + { + REQUEST_ONLY, + .name = "SET_LIGHT_COLOR", + .type = LGTD_LIFX_SET_LIGHT_COLOR, + .size = sizeof(struct lgtd_lifx_packet_light_color), + .encode = ENCODER(lgtd_lifx_wire_encode_light_color) } }; @@ -247,21 +266,19 @@ lgtd_lifx_wire_decode_light_status(struct lgtd_lifx_packet_light_status *pkt) } void -lgtd_lifx_wire_encode_light_status(struct lgtd_lifx_packet_light_status *pkt) +lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *pkt) { assert(pkt); - - pkt->hue = htole16(pkt->hue); - pkt->saturation = htole16(pkt->saturation); - pkt->brightness = htole16(pkt->brightness); - pkt->kelvin = htole16(pkt->kelvin); - pkt->dim = htole16(pkt->dim); - pkt->power = htole16(pkt->power); - pkt->tags = htole64(pkt->tags); } void -lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *pkt) +lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *pkt) { assert(pkt); + + pkt->hue = htole16(pkt->hue); + pkt->saturation = htole16(pkt->saturation); + pkt->brightness = htole16(pkt->brightness); + pkt->kelvin = htole16(pkt->kelvin); + pkt->transition = htole32(pkt->transition); } diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 7d36a72..a6a599c 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -124,7 +124,7 @@ enum lgtd_lifx_packet_type { LGTD_LIFX_SET_FACTORY_TEST_MODE = 0x27, LGTD_LIFX_DISABLE_FACTORY_TEST_MODE = 0x28, LGTD_LIFX_GET_LIGHT_STATE = 0x65, - LGTD_LIFX_SET_LIGHT_COLOUR = 0x66, + LGTD_LIFX_SET_LIGHT_COLOR = 0x66, LGTD_LIFX_SET_WAVEFORM = 0x67, LGTD_LIFX_SET_DIM_ABSOLUTE = 0x68, LGTD_LIFX_SET_DIM_RELATIVE = 0x69, @@ -176,6 +176,15 @@ enum lgtd_lifx_target_type { LGTD_LIFX_TARGET_ALL_DEVICES }; +struct lgtd_lifx_packet_light_color { + uint8_t stream; // should be 0 + uint16le_t hue; + uint16le_t saturation; + uint16le_t brightness; + uint16le_t kelvin; + uint32le_t transition; // transition time to the color in msecs +}; + #pragma pack(pop) struct lgtd_lifx_gateway; @@ -195,7 +204,7 @@ RB_HEAD(lgtd_lifx_packet_infos_map, lgtd_lifx_packet_infos); static inline int lgtd_lifx_packet_infos_cmp(struct lgtd_lifx_packet_infos *a, - struct lgtd_lifx_packet_infos *b) + struct lgtd_lifx_packet_infos *b) { return a->type - b->type; } @@ -223,3 +232,5 @@ void lgtd_lifx_wire_encode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *); void lgtd_lifx_wire_decode_light_status(struct lgtd_lifx_packet_light_status *); void lgtd_lifx_wire_encode_light_status(struct lgtd_lifx_packet_light_status *); void lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *); + +void lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..5e02551 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1 @@ +ADD_ALL_SUBDIRECTORIES() diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt new file mode 100644 index 0000000..5e02551 --- /dev/null +++ b/tests/core/CMakeLists.txt @@ -0,0 +1 @@ +ADD_ALL_SUBDIRECTORIES() diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt new file mode 100644 index 0000000..b9d9bdc --- /dev/null +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -0,0 +1,22 @@ +INCLUDE_DIRECTORIES( + ${LIGHTSD_SOURCE_DIR} + ${LIGHTSD_SOURCE_DIR}/core/ + ${LIGHTSD_BINARY_DIR} + ${LIGHTSD_BINARY_DIR}/core/ +) + +ADD_LIBRARY( + test_core_jsonrpc STATIC + ${LIGHTSD_SOURCE_DIR}/core/jsmn.c + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${CMAKE_CURRENT_SOURCE_DIR}/../utils.c +) + +FUNCTION(ADD_JSONRPC_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_jsonrpc) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_JSONRPC_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c new file mode 100644 index 0000000..cf92dff --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c @@ -0,0 +1,60 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_POWER_OFF +#include "test_jsonrpc_utils.h" + +static bool power_off_called = false; + +bool +lgtd_proto_power_off(const char *target) +{ + if (strcmp(target, "*")) { + errx(1, "Invalid target [%s] (expected=[*])", target); + } + power_off_called = true; + return true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"power_off\"," + "\"params\": {\"target\": \"*\"}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_power_off(&client, &req, json); + + const char response[] = ("{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"42\", " + "\"result\": true" + "}"); + + if (strcmp(client_write_buf, response)) { + errx( + 1, "invalid response: %s (expected: %s)", + client_write_buf, response + ); + } + + if (!power_off_called) { + errx(1, "lgtd_proto_power_off wasn't called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c new file mode 100644 index 0000000..67ebfb9 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c @@ -0,0 +1,49 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_POWER_OFF +#include "test_jsonrpc_utils.h" + +static bool power_off_called = false; + +bool +lgtd_proto_power_off(const char *target) +{ + (void)target; + power_off_called = true; + return true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"power_off\"," + "\"params\": {\"fensjk\": \"*\"}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_power_off(&client, &req, json); + + if (!strstr(client_write_buf, "-32602")) { + errx(1, "no error returned"); + } + + if (power_off_called) { + errx(1, "lgtd_proto_power_off was called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c new file mode 100644 index 0000000..79e3d79 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c @@ -0,0 +1,60 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_POWER_ON +#include "test_jsonrpc_utils.h" + +static bool power_on_called = false; + +bool +lgtd_proto_power_on(const char *target) +{ + if (strcmp(target, "*")) { + errx(1, "Invalid target [%s] (expected=[*])", target); + } + power_on_called = true; + return true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"power_on\"," + "\"params\": {\"target\": \"*\"}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_power_on(&client, &req, json); + + const char response[] = ("{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"42\", " + "\"result\": true" + "}"); + + if (strcmp(client_write_buf, response)) { + errx( + 1, "invalid response: %s (expected: %s)", + client_write_buf, response + ); + } + + if (!power_on_called) { + errx(1, "lgtd_proto_power_on wasn't called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c new file mode 100644 index 0000000..14e2b7c --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c @@ -0,0 +1,49 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_POWER_ON +#include "test_jsonrpc_utils.h" + +static bool power_on_called = false; + +bool +lgtd_proto_power_on(const char *target) +{ + (void)target; + power_on_called = true; + return true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"power_on\"," + "\"params\": {}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_power_on(&client, &req, json); + + if (!strstr(client_write_buf, "-32602")) { + errx(1, "no error returned"); + } + + if (power_on_called) { + errx(1, "lgtd_proto_power_off was called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c new file mode 100644 index 0000000..89fb8c8 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -0,0 +1,102 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_SET_LIGHT_FROM_HSBK +#include "test_jsonrpc_utils.h" + +static bool set_light_called = false; + +bool +lgtd_proto_set_light_from_hsbk(const char *target, + int hue, + int saturation, + int brightness, + int kelvin, + int transition_msecs) +{ + if (strcmp(target, "*")) { + errx(1, "Invalid target [%s] (expected=[*])", target); + } + int expected_hue = lgtd_jsonrpc_float_range_to_uint16( + "324.2341", strlen("324.2341"), 0, 360 + ); + if (hue != expected_hue) { + errx(1, "Invalid hue: %d, expected: %d", hue, expected_hue); + } + int expected_saturation = lgtd_jsonrpc_float_range_to_uint16( + "0.234", strlen("0.234"), 0, 1 + ); + if (saturation != expected_saturation) { + errx( + 1, "Invalid saturation: %d, expected: %d", + saturation, expected_saturation + ); + } + if (brightness != UINT16_MAX) { + errx( + 1, "Invalid brightness: %d, expected: %d", + brightness, UINT16_MAX + ); + } + if (kelvin != 4200) { + errx( + 1, "Invalid temperature: %d, expected: 4200", kelvin + ); + } + if (transition_msecs != 0) { + errx( + 1, "Invalid transition duration: %d, expected: 0", transition_msecs + ); + } + set_light_called = true; + return true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_light_from_hsbk\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 324.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200" + "}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client, &req, json); + + const char response[] = ("{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"42\", " + "\"result\": true" + "}"); + + if (strcmp(client_write_buf, response)) { + errx( + 1, "invalid response: %s (expected: %s)", + client_write_buf, response + ); + } + + if (!set_light_called) { + errx(1, "lgtd_proto_set_light_from_hsbk wasn't called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c new file mode 100644 index 0000000..d76b8fe --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -0,0 +1,116 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_SET_LIGHT_FROM_HSBK +#include "test_jsonrpc_utils.h" + +static bool set_light_called = false; + +bool +lgtd_proto_set_light_from_hsbk(const char *target, + int hue, + int saturation, + int brightness, + int kelvin, + int transition_msecs) +{ + (void)target; + (void)hue; + (void)saturation; + (void)brightness; + (void)kelvin; + (void)transition_msecs; + set_light_called = true; + return true; +} + +static void +test_request(const char *json) +{ + jsmntok_t tokens[32]; + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client, &req, json); + + if (!strstr(client_write_buf, "-32602")) { + errx(1, "no error returned, client_write_buf=[%s]", client_write_buf); + } + + if (set_light_called) { + errx(1, "lgtd_proto_power_off was called"); + } + + memset(client_write_buf, 0, sizeof(client_write_buf)); + client_write_buf_idx = 0; +} + +int +main(void) +{ + // invalid temperature: + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_light_from_hsbk\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 324.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": -4200" + "}," + "\"id\": \"42\"" + "}"); + + // saturation to big + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_light_from_hsbk\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 324.2341514, " + "\"saturation\": 3.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200" + "}," + "\"id\": \"42\"" + "}"); + + // hue too big + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_light_from_hsbk\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 424.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200" + "}," + "\"id\": \"42\"" + "}"); + + // brightness too small + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_light_from_hsbk\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": -1.0, " + "\"kelvin\": 4200" + "}," + "\"id\": \"42\"" + "}"); + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c new file mode 100644 index 0000000..9027f15 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c @@ -0,0 +1,48 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"hello\"," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + bool ok = lgtd_jsonrpc_check_and_extract_request( + &req, tokens, parsed, json + ); + + if (!ok) { + errx(1, "return value should be true"); + } + + if (!req.method) { + errx(1, "missing method"); + } + if (req.method->end - req.method->start != sizeof("hello") - 1 + || memcmp(&json[req.method->start], "hello", sizeof("hello") - 1)) { + errx(1, "wrong method name"); + } + + if (req.params) { + errx(1, "params should be null"); + } + + if (!req.id) { + errx(1, "missing id"); + } + if (req.id->end - req.id->start != 2 + || memcmp(&json[req.id->start], "42", 2)) { + errx(1, "wrong id"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c new file mode 100644 index 0000000..72eae61 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c @@ -0,0 +1,43 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"hello\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + bool ok = lgtd_jsonrpc_check_and_extract_request( + &req, tokens, parsed, json + ); + + if (!ok) { + errx(1, "return value should be true"); + } + + if (!req.method) { + errx(1, "missing method"); + } + if (req.method->end - req.method->start != sizeof("hello") - 1 + || memcmp(&json[req.method->start], "hello", sizeof("hello") - 1)) { + errx(1, "wrong method name"); + } + + if (req.params) { + errx(1, "params should be null"); + } + + if (req.id) { + errx(1, "id should be null"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c new file mode 100644 index 0000000..10a4065 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c @@ -0,0 +1,54 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"hello\"," + "\"params\": [\"on\", 12345, null, {\"lol\": \"wut\"}]," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + bool ok = lgtd_jsonrpc_check_and_extract_request( + &req, tokens, parsed, json + ); + + if (!ok) { + errx(1, "return value should be true"); + } + + if (!req.method) { + errx(1, "missing method"); + } + if (req.method->end - req.method->start != sizeof("hello") - 1 + || memcmp(&json[req.method->start], "hello", sizeof("hello") - 1)) { + errx(1, "wrong method name"); + } + + if (!req.params) { + errx(1, "missing params"); + } + const char params[] = "[\"on\", 12345, null, {\"lol\": \"wut\"}]"; + if (req.params->end - req.params->start != sizeof(params) - 1 + || memcmp(&json[req.params->start], params, req.params->size)) { + errx(1, "wrong params"); + } + + if (!req.id) { + errx(1, "missing id"); + } + if (req.id->end - req.id->start != 2 + || memcmp(&json[req.id->start], "42", 2)) { + errx(1, "wrong id"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c new file mode 100644 index 0000000..00a7018 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c @@ -0,0 +1,54 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"hello\"," + "\"params\": {\"on\": true}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + bool ok = lgtd_jsonrpc_check_and_extract_request( + &req, tokens, parsed, json + ); + + if (!ok) { + errx(1, "return value should be true"); + } + + if (!req.method) { + errx(1, "missing method"); + } + if (req.method->end - req.method->start != sizeof("hello") - 1 + || memcmp(&json[req.method->start], "hello", sizeof("hello") - 1)) { + errx(1, "wrong method name"); + } + + if (!req.params) { + errx(1, "missing params"); + } + const char params[] = "{\"on\": true}"; + if (req.params->end - req.params->start != sizeof(params) - 1 + || memcmp(&json[req.params->start], params, req.params->size)) { + errx(1, "wrong params"); + } + + if (!req.id) { + errx(1, "missing id"); + } + if (req.id->end - req.id->start != 2 + || memcmp(&json[req.id->start], "42", 2)) { + errx(1, "wrong id"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c new file mode 100644 index 0000000..d7250b1 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c @@ -0,0 +1,49 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"hello\"," + "\"params\": {\"on\": true}" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + bool ok = lgtd_jsonrpc_check_and_extract_request( + &req, tokens, parsed, json + ); + + if (!ok) { + errx(1, "return value should be true"); + } + + if (!req.method) { + errx(1, "missing method"); + } + if (req.method->end - req.method->start != sizeof("hello") - 1 + || memcmp(&json[req.method->start], "hello", sizeof("hello") - 1)) { + errx(1, "wrong method name"); + } + + if (!req.params) { + errx(1, "missing params"); + } + const char params[] = "{\"on\": true}"; + if (req.params->end - req.params->start != sizeof(params) - 1 + || memcmp(&json[req.params->start], params, req.params->size)) { + errx(1, "wrong params"); + } + + if (req.id) { + errx(1, "id should be NULL"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c new file mode 100644 index 0000000..311829a --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c @@ -0,0 +1,32 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + struct lgtd_client client = { .io = NULL }; + jsmntok_t token = { .start = 1, .end = 3, .type = JSMN_STRING }; + struct lgtd_jsonrpc_request req = { .id = &token }; + + lgtd_jsonrpc_send_error( + &client, &req, "\"42\"",LGTD_JSONRPC_INVALID_REQUEST, "Invalid Request" + ); + + const char *expected = ( + "{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"42\", " + "\"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}" + "}" + ); + + int diff = memcmp(client_write_buf, expected, strlen(expected)); + if (diff) { + printf("expected: %s\n", expected); + printf("received: %s\n", client_write_buf); + return 1; + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c new file mode 100644 index 0000000..2e84a21 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c @@ -0,0 +1,26 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +static void +test_float(const char *json) +{ + jsmntok_t tokens[8]; + parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); + if (lgtd_jsonrpc_type_float_between_0_and_1(tokens, json)) { + errx(1, "%s was considered as a valid float >= 0 and <= 1", json); + } +} + +int +main(void) +{ + test_float("1.1234"); + test_float("-0.1234"); + test_float("1.00000001"); + test_float("2.0000"); + test_float("10"); + test_float("0.0.1"); + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c new file mode 100644 index 0000000..7a7b2f6 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c @@ -0,0 +1,25 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +static void +test_float(const char *json) +{ + jsmntok_t tokens[8]; + parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); + if (!lgtd_jsonrpc_type_float_between_0_and_1(tokens, json)) { + errx(1, "%s wasn't considered as a valid float >= 0 and <= 1", json); + } +} + +int +main(void) +{ + test_float("0.1234"); + test_float("1.0000000"); + test_float("0.9999"); + test_float("0.01"); + test_float("000.01"); + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c new file mode 100644 index 0000000..d056fba --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c @@ -0,0 +1,26 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +static void +test_float(const char *json) +{ + jsmntok_t tokens[8]; + parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); + if (lgtd_jsonrpc_type_float_between_0_and_360(tokens, json)) { + errx(1, "%s was considered as a valid float >= 0 and <= 360", json); + } +} + +int +main(void) +{ + test_float("-1.1234"); + test_float("-0.1234"); + test_float("0.1.234"); + test_float("0.1a234"); + test_float("360a"); + test_float("360.1"); + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c new file mode 100644 index 0000000..322cb1c --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c @@ -0,0 +1,29 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +static void +test_float(const char *json) +{ + jsmntok_t tokens[8]; + parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); + if (!lgtd_jsonrpc_type_float_between_0_and_360(tokens, json)) { + errx(1, "%s wasn't considered as a valid float >= 0 and <= 360", json); + } +} + +int +main(void) +{ + test_float("1.1234"); + test_float("1.00000001"); + test_float("2.0000"); + test_float("10"); + test_float("0.1"); + test_float("0"); + test_float("231."); + test_float("359.1"); + test_float("360"); + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer.c b/tests/core/jsonrpc/test_jsonrpc_type_integer.c new file mode 100644 index 0000000..8b04da8 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer.c @@ -0,0 +1,20 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + const char *json = "1234"; + jsmntok_t tokens[8]; + + parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); + + bool ok = lgtd_jsonrpc_type_integer(tokens, json); + + if (!ok) { + errx(1, "%s wasn't considered as a valid integer", json); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c new file mode 100644 index 0000000..0abeea0 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c @@ -0,0 +1,20 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + const char *json = "-1a"; + jsmntok_t tokens[8]; + + parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); + + bool ok = lgtd_jsonrpc_type_integer(tokens, json); + + if (ok) { + errx(1, "%s wasn't considered invalid", json); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c new file mode 100644 index 0000000..c780464 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c @@ -0,0 +1,20 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + const char *json = "9999999999"; + jsmntok_t tokens[8]; + + parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); + + bool ok = lgtd_jsonrpc_type_integer(tokens, json); + + if (ok) { + errx(1, "%s wasn't considered invalid", json); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c new file mode 100644 index 0000000..d154c9a --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c @@ -0,0 +1,20 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + const char *json = "-9999999999"; + jsmntok_t tokens[8]; + + parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); + + bool ok = lgtd_jsonrpc_type_integer(tokens, json); + + if (ok) { + errx(1, "%s wasn't considered invalid", json); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h new file mode 100644 index 0000000..a72837f --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -0,0 +1,61 @@ +#pragma once + +#define TEST_REQUEST_INITIALIZER { NULL, NULL, 0, NULL } + +static inline int +parse_json(jsmntok_t *tokens, size_t capacity, const char *json , size_t len) +{ + jsmn_parser ctx; + jsmn_init(&ctx); + return jsmn_parse(&ctx, json, len, tokens, capacity); +} + +static char client_write_buf[4096] = { 0 }; +static int client_write_buf_idx = 0; + +int +bufferevent_write(struct bufferevent *bev, const void *data, size_t nbytes) +{ + (void)bev; + int to_write = LGTD_MIN(nbytes, sizeof(client_write_buf)); + memcpy(&client_write_buf[client_write_buf_idx], data, to_write); + client_write_buf_idx += to_write; + return 0; +} + +#ifndef LGTD_TESTING_SET_LIGHT_FROM_HSBK +bool +lgtd_proto_set_light_from_hsbk(const char *target, + int hue, + int saturation, + int brightness, + int kelvin, + int transition_msecs) +{ + (void)target; + (void)hue; + (void)saturation; + (void)brightness; + (void)kelvin; + (void)transition_msecs; + return true; +} +#endif + +#ifndef LGTD_TESTING_POWER_ON +bool +lgtd_proto_power_on(const char *target) +{ + (void)target; + return true; +} +#endif + +#ifndef LGTD_TESTING_POWER_OFF +bool +lgtd_proto_power_off(const char *target) +{ + (void)target; + return true; +} +#endif diff --git a/tests/core/utils.c b/tests/core/utils.c new file mode 100644 index 0000000..72a613b --- /dev/null +++ b/tests/core/utils.c @@ -0,0 +1,18 @@ +#include + +#include + +#include "lightsd.h" + +struct lgtd_opts lgtd_opts = { + .foreground = false, + .log_timestamps = false, + .verbosity = LGTD_DEBUG +}; + +struct event_base *lgtd_ev_base = NULL; + +void +lgtd_cleanup(void) +{ +} diff --git a/tests/lightsc b/tests/lightsc new file mode 100755 index 0000000..1a4ffc3 --- /dev/null +++ b/tests/lightsc @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +import json +import socket +import time + + +def jsonrpc_call(socket, id, method, params): + payload = { + "method": method, + "params": params, + "jsonrpc": "2.0", + "id": id, + } + socket.send(json.dumps(payload).encode("ascii")) + response = socket.recv(2048) + response = json.loads(response.decode("ascii")) + print(response) + + +def set_light_from_hsbk(socket, id, h, s, b, k): + jsonrpc_call(socket, id, "set_light_from_hsbk", { + "target": "*", + "hue": h, + "saturation": s, + "brightness": b, + "kelvin": k + }) + + +def power_on(socket, id): + jsonrpc_call(socket, id, "power_on", {"target": "*"}) + + +def power_off(socket, id): + jsonrpc_call(socket, id, "power_off", {"target": "*"}) + +if __name__ == "__main__": + s = socket.create_connection(("localhost", 1234)) + h = 0 + id = 0 + try: + power_on(s, id) + while True: + h = (h + 1) % 360 + id += 1 + set_light_from_hsbk(s, id, h, 0.7, 0.02, 2500) + time.sleep(0.1) + finally: + power_off(s, id) + s.close() From f93ac5fd8626505b71c163afa2bb9474215db9ef Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Tue, 17 Feb 2015 23:30:01 -0800 Subject: [PATCH 006/181] Add support for passing JSONRPC params as arrays --- core/jsonrpc.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++---- tests/lightsc | 10 +++----- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/core/jsonrpc.c b/core/jsonrpc.c index d1260b2..ecdb0e8 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -229,9 +229,7 @@ lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, int ntokens, const char *json) { - if (tokens[0].type != JSMN_OBJECT) { - return false; - } + assert(tokens[0].type == JSMN_OBJECT); for (int ti = 1; ti < ntokens;) { // make sure it's a key: @@ -307,6 +305,66 @@ lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, return true; } +static bool +lgtd_jsonrpc_extract_values_from_schema_and_array(void *output, + const struct lgtd_jsonrpc_node *schema, + int schema_size, + const jsmntok_t *tokens, + int ntokens, + const char *json) +{ + assert(tokens[0].type == JSMN_ARRAY); + + int si, ti; + for (si = 0, ti = 1; si < schema_size && ti < ntokens; si++) { + if (!schema[si].type_cmp(&tokens[ti], json)) { + lgtd_debug( + "jsonrpc client sent an invalid value for %s", + schema[si].key + ); + return false; + } + if (schema[si].value_offset != -1) { + LGTD_JSONRPC_SET_JSMNTOK( + output, schema[si].value_offset, &tokens[ti] + ); + } + // skip the value, if it's an object or an array we need to + // skip everything in it: + if (tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY) { + ti = lgtd_jsonrpc_consume_object_or_array( + tokens, ti, ntokens, json + ); + } else { + ti++; + } + } + + return si == schema_size; +} + +static bool +lgtd_jsonrpc_extract_and_validate_params_against_schema(void *output, + const struct lgtd_jsonrpc_node *schema, + int schema_size, + const jsmntok_t *tokens, + int ntokens, + const char *json) +{ + switch (tokens[0].type) { + case JSMN_OBJECT: + return lgtd_jsonrpc_extract_values_from_schema_and_dict( + output, schema, schema_size, tokens, ntokens, json + ); + case JSMN_ARRAY: + return lgtd_jsonrpc_extract_values_from_schema_and_array( + output, schema, schema_size, tokens, ntokens, json + ); + default: + return false; + } +} + static void lgtd_jsonrpc_write_id(struct lgtd_client *client, const struct lgtd_jsonrpc_request *request, @@ -471,7 +529,7 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, ), }; - bool ok = lgtd_jsonrpc_extract_values_from_schema_and_dict( + bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( ¶ms, schema, LGTD_ARRAY_SIZE(schema), @@ -527,7 +585,7 @@ lgtd_jsonrpc_extract_target_only(struct lgtd_client *client, LGTD_JSONRPC_NODE("target", 0, lgtd_jsonrpc_type_string, false) }; - bool ok = lgtd_jsonrpc_extract_values_from_schema_and_dict( + bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( &target, schema, 1, request->params, request->params_ntokens, json ); if (!ok) { diff --git a/tests/lightsc b/tests/lightsc index 1a4ffc3..1d5aa68 100755 --- a/tests/lightsc +++ b/tests/lightsc @@ -19,13 +19,9 @@ def jsonrpc_call(socket, id, method, params): def set_light_from_hsbk(socket, id, h, s, b, k): - jsonrpc_call(socket, id, "set_light_from_hsbk", { - "target": "*", - "hue": h, - "saturation": s, - "brightness": b, - "kelvin": k - }) + jsonrpc_call(socket, id, "set_light_from_hsbk", [ + "*", h, s, b, k + ]) def power_on(socket, id): From b0a06bb0c10021d9a57afdf6d807ed00dbcb5fa5 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Tue, 17 Feb 2015 23:45:27 -0800 Subject: [PATCH 007/181] Fix build warning Use the j modifier when printing 64bits numbers. --- lifx/gateway.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lifx/gateway.c b/lifx/gateway.c index 7c4b96b..aaf3c60 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -325,7 +325,7 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, lgtd_debug( "SET_LIGHT_STATE <-- [%s]:%hu - %s " "hue=%#hx, saturation=%#hx, brightness=%#hx, " - "kelvin=%d, dim=%#hx, power=%#hx, label=%.*s, tags=%#llx", + "kelvin=%d, dim=%#hx, power=%#hx, label=%.*s, tags=%#jx", gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), pkt->hue, pkt->saturation, pkt->brightness, pkt->kelvin, pkt->dim, pkt->power, LGTD_LIFX_LABEL_SIZE, pkt->label, pkt->tags From 69c1d97770523e512b323adf57197f404c9ef139 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Tue, 17 Feb 2015 23:51:18 -0800 Subject: [PATCH 008/181] Fix the previous fix on Mac OS --- lifx/gateway.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lifx/gateway.c b/lifx/gateway.c index aaf3c60..21a28dc 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -328,7 +328,8 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, "kelvin=%d, dim=%#hx, power=%#hx, label=%.*s, tags=%#jx", gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), pkt->hue, pkt->saturation, pkt->brightness, pkt->kelvin, - pkt->dim, pkt->power, LGTD_LIFX_LABEL_SIZE, pkt->label, pkt->tags + pkt->dim, pkt->power, LGTD_LIFX_LABEL_SIZE, pkt->label, + (uintmax_t)pkt->tags ); struct lgtd_lifx_bulb *b = lgtd_lifx_gateway_get_or_open_bulb( From eb6bdf42c9a135c723328158c5e482fc59107406 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Fri, 20 Feb 2015 00:11:52 -0800 Subject: [PATCH 009/181] Remove code and reduce discovery time by 2s Just call the callback for the discovery timeout event right away, it starts the discovery and setup everything already... --- lifx/timer.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lifx/timer.c b/lifx/timer.c index f8b3c4a..6c3c7a7 100644 --- a/lifx/timer.c +++ b/lifx/timer.c @@ -214,11 +214,6 @@ lgtd_lifx_timer_start_discovery(void) lgtd_lifx_timer_context.discovery_timeout_ev, NULL )); - struct timeval tv = LGTD_MSECS_TO_TIMEVAL( - LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS - ); - if (event_add(lgtd_lifx_timer_context.discovery_timeout_ev, &tv)) { - lgtd_err(1, "can't start discovery timer"); - } + lgtd_lifx_timer_discovery_timeout_event_callback(-1, 0, NULL); lgtd_debug("starting discovery timer"); } From 96c93290980c7b79e4a904a38fdac11aed2e4f29 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 010/181] Change the license to GPLv3 A quick look around showed me that this project can perfectly run on iOS and Android and it changed my perspectives. --- COPYING | 621 ++++++++++++++++++++++++++++++++ compat/Darwin/time_monotonic.c | 34 +- compat/Darwin/time_monotonic.h | 34 +- compat/generic/time_monotonic.c | 34 +- compat/generic/time_monotonic.h | 34 +- core/client.c | 34 +- core/client.h | 34 +- core/jsonrpc.c | 34 +- core/jsonrpc.h | 34 +- core/lightsd.c | 34 +- core/lightsd.h | 34 +- core/listen.c | 34 +- core/listen.h | 34 +- core/log.c | 34 +- core/proto.c | 34 +- core/proto.h | 34 +- core/router.c | 34 +- core/router.h | 34 +- lifx/broadcast.c | 34 +- lifx/broadcast.h | 34 +- lifx/bulb.c | 34 +- lifx/bulb.h | 34 +- lifx/gateway.c | 34 +- lifx/gateway.h | 34 +- lifx/timer.c | 34 +- lifx/timer.h | 34 +- lifx/wire_proto.c | 34 +- lifx/wire_proto.h | 34 +- 28 files changed, 918 insertions(+), 621 deletions(-) create mode 100644 COPYING diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a0453 --- /dev/null +++ b/COPYING @@ -0,0 +1,621 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/compat/Darwin/time_monotonic.c b/compat/Darwin/time_monotonic.c index 9405893..e8b8045 100644 --- a/compat/Darwin/time_monotonic.c +++ b/compat/Darwin/time_monotonic.c @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . // This is pretty much Listing 2 from: // diff --git a/compat/Darwin/time_monotonic.h b/compat/Darwin/time_monotonic.h index 170c600..0e249cd 100644 --- a/compat/Darwin/time_monotonic.h +++ b/compat/Darwin/time_monotonic.h @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/compat/generic/time_monotonic.c b/compat/generic/time_monotonic.c index 54d7e2d..7bbf789 100644 --- a/compat/generic/time_monotonic.c +++ b/compat/generic/time_monotonic.c @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include diff --git a/compat/generic/time_monotonic.h b/compat/generic/time_monotonic.h index 170c600..0e249cd 100644 --- a/compat/generic/time_monotonic.h +++ b/compat/generic/time_monotonic.h @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/core/client.c b/core/client.c index d100d3a..967ce06 100644 --- a/core/client.c +++ b/core/client.c @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/core/client.h b/core/client.h index 3dbd8ad..e7439de 100644 --- a/core/client.h +++ b/core/client.h @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/core/jsonrpc.c b/core/jsonrpc.c index ecdb0e8..2a06a48 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/core/jsonrpc.h b/core/jsonrpc.h index ee6bf44..df86b2a 100644 --- a/core/jsonrpc.h +++ b/core/jsonrpc.h @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/core/lightsd.c b/core/lightsd.c index 92573a3..295dd7c 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/core/lightsd.h b/core/lightsd.h index ab2b448..d364fe9 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/core/listen.c b/core/listen.c index 44d3358..faebdbf 100644 --- a/core/listen.c +++ b/core/listen.c @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/core/listen.h b/core/listen.h index 9f86f22..fe61951 100644 --- a/core/listen.h +++ b/core/listen.h @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/core/log.c b/core/log.c index 48bd761..9d5a3c9 100644 --- a/core/log.c +++ b/core/log.c @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/core/proto.c b/core/proto.c index 529fe55..bcdac98 100644 --- a/core/proto.c +++ b/core/proto.c @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/core/proto.h b/core/proto.h index a69ff14..119745b 100644 --- a/core/proto.h +++ b/core/proto.h @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/core/router.c b/core/router.c index 1779b67..1e06e9c 100644 --- a/core/router.c +++ b/core/router.c @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/core/router.h b/core/router.h index c427fbd..ffb9048 100644 --- a/core/router.h +++ b/core/router.h @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/lifx/broadcast.c b/lifx/broadcast.c index c95338f..3b38325 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/lifx/broadcast.h b/lifx/broadcast.h index 3c1ed8d..451acca 100644 --- a/lifx/broadcast.h +++ b/lifx/broadcast.h @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/lifx/bulb.c b/lifx/bulb.c index f9b425c..550cf77 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/lifx/bulb.h b/lifx/bulb.h index 97fb7be..2f304fb 100644 --- a/lifx/bulb.h +++ b/lifx/bulb.h @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/lifx/gateway.c b/lifx/gateway.c index 21a28dc..eca123f 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/lifx/gateway.h b/lifx/gateway.h index 65899cc..9a13f97 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/lifx/timer.c b/lifx/timer.c index 6c3c7a7..2d16596 100644 --- a/lifx/timer.c +++ b/lifx/timer.c @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/lifx/timer.h b/lifx/timer.h index 765459c..70cff79 100644 --- a/lifx/timer.h +++ b/lifx/timer.h @@ -1,31 +1,19 @@ // Copyright (c) 2015, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 661befa..38fe10e 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #include #include diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index a6a599c..803bc89 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -1,31 +1,19 @@ // Copyright (c) 2014, Louis Opter -// All rights reserved. // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// This file is part of lighstd. // -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. // -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. // -// 3. Neither the name of the copyright holder nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . #pragma once From 88d3bd2755899b30c9ff431c5777ec8202c0b3d3 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 011/181] Remove useless file --- compat/generic/time_monotonic.h | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 compat/generic/time_monotonic.h diff --git a/compat/generic/time_monotonic.h b/compat/generic/time_monotonic.h deleted file mode 100644 index 0e249cd..0000000 --- a/compat/generic/time_monotonic.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2015, Louis Opter -// -// This file is part of lighstd. -// -// lighstd is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// lighstd is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with lighstd. If not, see . - -#pragma once - -typedef time_t lgtd_time_mono_t; - -lgtd_time_mono_t lgtd_time_monotonic_msecs(void); From 8965c8473389e77a5bf9ff00eb521d050ed10921 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 012/181] Forgot to add this unit test earlier --- ..._and_call_set_light_from_hsbk_from_array.c | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c new file mode 100644 index 0000000..3cdecc8 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -0,0 +1,98 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_SET_LIGHT_FROM_HSBK +#include "test_jsonrpc_utils.h" + +static bool set_light_called = false; + +bool +lgtd_proto_set_light_from_hsbk(const char *target, + int hue, + int saturation, + int brightness, + int kelvin, + int transition_msecs) +{ + if (strcmp(target, "*")) { + errx(1, "Invalid target [%s] (expected=[*])", target); + } + int expected_hue = lgtd_jsonrpc_float_range_to_uint16( + "324.2341", strlen("324.2341"), 0, 360 + ); + if (hue != expected_hue) { + errx(1, "Invalid hue: %d, expected: %d", hue, expected_hue); + } + int expected_saturation = lgtd_jsonrpc_float_range_to_uint16( + "0.234", strlen("0.234"), 0, 1 + ); + if (saturation != expected_saturation) { + errx( + 1, "Invalid saturation: %d, expected: %d", + saturation, expected_saturation + ); + } + if (brightness != UINT16_MAX) { + errx( + 1, "Invalid brightness: %d, expected: %d", + brightness, UINT16_MAX + ); + } + if (kelvin != 4200) { + errx( + 1, "Invalid temperature: %d, expected: 4200", kelvin + ); + } + if (transition_msecs != 0) { + errx( + 1, "Invalid transition duration: %d, expected: 0", transition_msecs + ); + } + set_light_called = true; + return true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_light_from_hsbk\"," + "\"params\": [" + "\"*\", 324.2341514, 0.234, 1.0, 4200" + "]," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client, &req, json); + + const char response[] = ("{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"42\", " + "\"result\": true" + "}"); + + if (strcmp(client_write_buf, response)) { + errx( + 1, "invalid response: %s (expected: %s)", + client_write_buf, response + ); + } + + if (!set_light_called) { + errx(1, "lgtd_proto_set_light_from_hsbk wasn't called"); + } + + return 0; +} From 1dca385b798989e91656f0d016c33974c9b354ae Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 013/181] Don't segfault when sending back a JSONRPC error without an id --- core/jsonrpc.c | 5 +++ ..._call_set_light_from_hsbk_invalid_params.c | 3 +- tests/core/jsonrpc/test_jsonrpc_send_error.c | 31 +++++++++++++++---- tests/core/jsonrpc/test_jsonrpc_utils.h | 7 +++++ 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 2a06a48..8ddabb2 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -358,6 +358,11 @@ lgtd_jsonrpc_write_id(struct lgtd_client *client, const struct lgtd_jsonrpc_request *request, const char *json) { + if (!request->id) { + LGTD_CLIENT_WRITE_STRING(client, "null"); + return; + } + int start, stop; if (request->id->type == JSMN_STRING) { // get the quotes start = request->id->start - 1; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c index d76b8fe..b950916 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -49,8 +49,7 @@ test_request(const char *json) errx(1, "lgtd_proto_power_off was called"); } - memset(client_write_buf, 0, sizeof(client_write_buf)); - client_write_buf_idx = 0; + reset_client_write_buf(); } int diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c index 311829a..2bb1a4e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_error.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c @@ -6,13 +6,10 @@ int main(void) { struct lgtd_client client = { .io = NULL }; + + const char *json = "\"42\""; jsmntok_t token = { .start = 1, .end = 3, .type = JSMN_STRING }; struct lgtd_jsonrpc_request req = { .id = &token }; - - lgtd_jsonrpc_send_error( - &client, &req, "\"42\"",LGTD_JSONRPC_INVALID_REQUEST, "Invalid Request" - ); - const char *expected = ( "{" "\"jsonrpc\": \"2.0\", " @@ -20,7 +17,9 @@ main(void) "\"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}" "}" ); - + lgtd_jsonrpc_send_error( + &client, &req, json, LGTD_JSONRPC_INVALID_REQUEST, "Invalid Request" + ); int diff = memcmp(client_write_buf, expected, strlen(expected)); if (diff) { printf("expected: %s\n", expected); @@ -28,5 +27,25 @@ main(void) return 1; } + reset_client_write_buf(); + + req.id = NULL; + expected = ( + "{" + "\"jsonrpc\": \"2.0\", " + "\"id\": null, " + "\"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}" + "}" + ); + lgtd_jsonrpc_send_error( + &client, &req, NULL, LGTD_JSONRPC_INVALID_REQUEST, "Invalid Request" + ); + diff = memcmp(client_write_buf, expected, strlen(expected)); + if (diff) { + printf("expected: %s\n", expected); + printf("received: %s\n", client_write_buf); + return 1; + } + return 0; } diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index a72837f..ab7a2cc 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -13,6 +13,13 @@ parse_json(jsmntok_t *tokens, size_t capacity, const char *json , size_t len) static char client_write_buf[4096] = { 0 }; static int client_write_buf_idx = 0; +static inline void +reset_client_write_buf(void) +{ + memset(client_write_buf, 0, sizeof(client_write_buf)); + client_write_buf_idx = 0; +} + int bufferevent_write(struct bufferevent *bev, const void *data, size_t nbytes) { From 657618f5f25e4288f34d2b84915d9da6d4af5d09 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 014/181] Experiment some retry logic on power_on/power_off --- core/router.c | 9 +++++++++ lifx/bulb.h | 2 ++ lifx/gateway.c | 15 +++++++++++++++ lifx/timer.c | 1 + 4 files changed, 27 insertions(+) diff --git a/core/router.c b/core/router.c index 1e06e9c..18796e4 100644 --- a/core/router.c +++ b/core/router.c @@ -48,6 +48,15 @@ lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) ); assert(pkt_infos); lgtd_lifx_gateway_send_packet(gw, &hdr, pkt, pkt_infos->size); + struct lgtd_lifx_bulb *bulb; + lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); + SLIST_FOREACH(bulb, &gw->bulbs, link_by_gw) { + if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { + bulb->dirty_at = now; + struct lgtd_lifx_packet_power_state *payload = pkt; + bulb->expected_power_on = payload->power; + } + } } if (pkt_infos) { diff --git a/lifx/bulb.h b/lifx/bulb.h index 2f304fb..8809778 100644 --- a/lifx/bulb.h +++ b/lifx/bulb.h @@ -39,6 +39,8 @@ struct lgtd_lifx_bulb { uint8_t addr[LGTD_LIFX_ADDR_LENGTH]; struct lgtd_lifx_light_state state; lgtd_time_mono_t last_light_state_at; + lgtd_time_mono_t dirty_at; + uint16_t expected_power_on; }; RB_HEAD(lgtd_lifx_bulb_map, lgtd_lifx_bulb); SLIST_HEAD(lgtd_lifx_bulb_list, lgtd_lifx_bulb); diff --git a/lifx/gateway.c b/lifx/gateway.c index eca123f..043cc8e 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -332,6 +332,21 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, b, (const struct lgtd_lifx_light_state *)pkt, gw->last_pkt_at ); + if (b->dirty_at + && b->last_light_state_at > b->dirty_at + && b->gw->last_pkt_at - b->dirty_at > 400) { + if (b->expected_power_on == b->state.power) { + lgtd_warnx("clearing dirty_at on %s", b->state.label); + b->dirty_at = 0; + } else if (b->expected_power_on) { + lgtd_warnx("retransmiting power on %s", b->state.label); + lgtd_proto_power_on("*"); + } else { + lgtd_warnx("retransmiting power off on %s", b->state.label); + lgtd_proto_power_off("*"); + } + } + int latency = gw->last_pkt_at - gw->last_req_at; if (latency < LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS) { int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; diff --git a/lifx/timer.c b/lifx/timer.c index 2d16596..2aec8b3 100644 --- a/lifx/timer.c +++ b/lifx/timer.c @@ -96,6 +96,7 @@ lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, ); lgtd_lifx_bulb_close(bulb); start_discovery = true; + continue; } } From a8e93115d239ec5022cd05663de81c5e5f0393ac Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 015/181] Do not enqueue multiple GET_LIGHT_STATE for all bulbs on a single gateway lgtd_gateway_handle_light_status is called per bulb and not per gateway and we trigger the refresh of all the bulbs on the gateway from this function. This means that we could potentially enqueue multiple GET_LIGHT_STATE packets for all bulbs on the same gateway within a single libevent iteration. With this changeset each gateway now keep track of when the last GET_LIGHT_STATE for all bulbs was sent. If it's more recent than when the last packet was sent then we know that one has already been enqueued. --- core/router.c | 3 +++ lifx/gateway.c | 41 ++++++++++++++++++++++++++++------------- lifx/gateway.h | 1 + 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/core/router.c b/core/router.c index 18796e4..5804c23 100644 --- a/core/router.c +++ b/core/router.c @@ -19,7 +19,10 @@ #include #include #include +#include +#include #include +#include #include #include #include diff --git a/lifx/gateway.c b/lifx/gateway.c index 043cc8e..af1437a 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -38,6 +38,7 @@ #include "gateway.h" #include "broadcast.h" #include "timer.h" +#include "core/proto.h" #include "core/lightsd.h" struct lgtd_lifx_gateway_list lgtd_lifx_gateways = @@ -100,9 +101,13 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, // Callbacks are called in any order, so we keep two timers to make // sure we can get the latency right, otherwise we could be compute the // latency with last_pkt_at < last_req_at, which isn't true since the - // pkt will be for an answer the previous write: + // pkt will be for an answer to the previous write: gw->last_req_at = gw->next_req_at; gw->next_req_at = lgtd_time_monotonic_msecs(); + // XXX this isn't perfect because we don't know what we just sent, I + // just assume that everything pending will alway be transmitted in a + // single call: + gw->pending_refresh_req = false; if (!evbuffer_get_length(gw->write_buf)) { event_del(gw->write_ev); } @@ -121,6 +126,7 @@ lgtd_lifx_gateway_send_get_all_light_state(struct lgtd_lifx_gateway *gw) ); lgtd_debug("GET_LIGHT_STATE --> [%s]:%hu", gw->ip_addr, gw->port); lgtd_lifx_gateway_send_packet(gw, &hdr, NULL, 0); + gw->pending_refresh_req = true; } static void @@ -349,21 +355,30 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, int latency = gw->last_pkt_at - gw->last_req_at; if (latency < LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS) { - int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; - struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); - evtimer_add(gw->refresh_ev, &tv); - lgtd_debug( - "[%s]:%hu latency is %dms, scheduling next GET_LIGHT_STATE in %dms", - gw->ip_addr, gw->port, latency, timeout - ); + if (!event_pending(gw->refresh_ev, EV_TIMEOUT, NULL)) { + int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; + struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); + evtimer_add(gw->refresh_ev, &tv); + lgtd_info( + "[%s]:%hu latency is %dms, scheduling next GET_LIGHT_STATE in %dms", + gw->ip_addr, gw->port, latency, timeout + ); + } return; } - lgtd_debug( - "[%s]:%hu latency is %dms, sending GET_LIGHT_STATE now", - gw->ip_addr, gw->port, latency - ); - lgtd_lifx_gateway_send_get_all_light_state(gw); + if (!gw->pending_refresh_req) { + lgtd_info( + "[%s]:%hu latency is %dms, sending GET_LIGHT_STATE now", + gw->ip_addr, gw->port, latency + ); + lgtd_lifx_gateway_send_get_all_light_state(gw); + } else { + lgtd_debug( + "[%s]:%hu GET_LIGHT_STATE for all bulbs on this gw has already " + "been enqueued", gw->ip_addr, gw->port + ); + } } void diff --git a/lifx/gateway.h b/lifx/gateway.h index 9a13f97..f541bfe 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -45,6 +45,7 @@ struct lgtd_lifx_gateway { lgtd_time_mono_t last_pkt_at; struct event *write_ev; struct evbuffer *write_buf; + bool pending_refresh_req; struct event *refresh_ev; }; LIST_HEAD(lgtd_lifx_gateway_list, lgtd_lifx_gateway); From bc7e61892b151816a3cc71cd65dd8106c3e48264 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 016/181] Fix crash on newline in the jsonrpc code --- core/jsonrpc.c | 10 +++++++--- .../test_jsonrpc_extract_request_params_array.c | 7 +++++++ .../jsonrpc/test_jsonrpc_extract_request_params_obj.c | 7 +++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 8ddabb2..435a27d 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -217,7 +217,9 @@ lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, int ntokens, const char *json) { - assert(tokens[0].type == JSMN_OBJECT); + if (!ntokens || tokens[0].type != JSMN_OBJECT) { + return false; + } for (int ti = 1; ti < ntokens;) { // make sure it's a key: @@ -301,7 +303,9 @@ lgtd_jsonrpc_extract_values_from_schema_and_array(void *output, int ntokens, const char *json) { - assert(tokens[0].type == JSMN_ARRAY); + if (!ntokens || tokens[0].type != JSMN_ARRAY) { + return false; + } int si, ti; for (si = 0, ti = 1; si < schema_size && ti < ntokens; si++) { @@ -661,7 +665,7 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, }; assert(client); - assert(parsed > 0); + assert(parsed >= 0); const jsmntok_t *tokens = client->jsmn_tokens; diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c index 10a4065..0d1d7f3 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c @@ -50,5 +50,12 @@ main(void) errx(1, "wrong id"); } + ok = lgtd_jsonrpc_check_and_extract_request( + &req, tokens, 0, json + ); + if (ok) { + errx(1, "empty request should return false"); + } + return 0; } diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c index 00a7018..fdd6178 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c @@ -50,5 +50,12 @@ main(void) errx(1, "wrong id"); } + ok = lgtd_jsonrpc_check_and_extract_request( + &req, tokens, 0, json + ); + if (ok) { + errx(1, "empty request should return false"); + } + return 0; } From 716d38b3bf4e8ed300f4c85beedac4d9e0351756 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 017/181] Add the ability to target a specific device via its address --- core/router.c | 47 +++++++++++++++ lifx/bulb.c | 6 +- lifx/bulb.h | 2 +- lifx/gateway.c | 2 +- lifx/wire_proto.c | 2 +- tests/core/jsonrpc/CMakeLists.txt | 5 +- tests/core/router/CMakeLists.txt | 33 +++++++++++ tests/core/router/test_router_broadcast.c | 54 +++++++++++++++++ tests/core/router/test_router_device.c | 58 +++++++++++++++++++ .../core/router/test_router_invalid_targets.c | 39 +++++++++++++ tests/core/router/tests_router_utils.h | 45 ++++++++++++++ tests/core/tests_shims.c | 58 +++++++++++++++++++ tests/core/tests_utils.c | 47 +++++++++++++++ tests/core/tests_utils.h | 2 + tests/core/utils.c | 18 ------ tests/lightsc | 22 ++++--- 16 files changed, 406 insertions(+), 34 deletions(-) create mode 100644 tests/core/router/CMakeLists.txt create mode 100644 tests/core/router/test_router_broadcast.c create mode 100644 tests/core/router/test_router_device.c create mode 100644 tests/core/router/test_router_invalid_targets.c create mode 100644 tests/core/router/tests_router_utils.h create mode 100644 tests/core/tests_shims.c create mode 100644 tests/core/tests_utils.c create mode 100644 tests/core/tests_utils.h delete mode 100644 tests/core/utils.c diff --git a/core/router.c b/core/router.c index 5804c23..6347eab 100644 --- a/core/router.c +++ b/core/router.c @@ -67,6 +67,33 @@ lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) } } +static void +lgtd_router_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + assert(bulb); + + struct lgtd_lifx_packet_header hdr; + union lgtd_lifx_target target = { .addr = bulb->addr }; + + const struct lgtd_lifx_packet_infos *pkt_infos; + pkt_infos = lgtd_lifx_wire_setup_header( + &hdr, LGTD_LIFX_TARGET_DEVICE, target, bulb->gw->site, pkt_type + ); + assert(pkt_infos); + + lgtd_lifx_gateway_send_packet(bulb->gw, &hdr, pkt, pkt_infos->size); + + if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { + bulb->dirty_at = lgtd_time_monotonic_msecs(); + struct lgtd_lifx_packet_power_state *payload = pkt; + bulb->expected_power_on = payload->power; + } + + lgtd_debug("sending %s to %s", pkt_infos->name, lgtd_addrtoa(bulb->addr)); +} + bool lgtd_router_send(const char *target, enum lgtd_lifx_packet_type pkt_type, @@ -79,5 +106,25 @@ lgtd_router_send(const char *target, return true; } + if (isxdigit(target[0])) { + const char *endptr = NULL; + errno = 0; + long long device = strtoll(target, (char **)&endptr, 16); + if (*endptr || errno == ERANGE) { + lgtd_debug("invalid target device %s", target); + return false; + } + device = htobe64(device); + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get( + (uint8_t *)&device + sizeof(device) - LGTD_LIFX_ADDR_LENGTH + ); + if (!bulb) { + lgtd_debug("target device %#llx not found", device); + return false; + } + lgtd_router_device(bulb, pkt_type, pkt); + return true; + } + return false; } diff --git a/lifx/bulb.c b/lifx/bulb.c index 550cf77..1492bdc 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -37,9 +37,8 @@ struct lgtd_lifx_bulb_map lgtd_lifx_bulbs_table = RB_INITIALIZER(&lgtd_lifx_bulbs_table); struct lgtd_lifx_bulb * -lgtd_lifx_bulb_get(struct lgtd_lifx_gateway *gw, const uint8_t *addr) +lgtd_lifx_bulb_get(const uint8_t *addr) { - assert(gw); assert(addr); struct lgtd_lifx_bulb bulb; @@ -77,9 +76,10 @@ lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *bulb) RB_REMOVE(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, bulb); SLIST_REMOVE(&bulb->gw->bulbs, bulb, lgtd_lifx_bulb, link_by_gw); lgtd_info( - "closed bulb \"%.*s\" on [%s]:%hu", + "closed bulb \"%.*s\" (%s) on [%s]:%hu", LGTD_LIFX_LABEL_SIZE, bulb->state.label, + lgtd_addrtoa(bulb->addr), bulb->gw->ip_addr, bulb->gw->port ); diff --git a/lifx/bulb.h b/lifx/bulb.h index 8809778..4b5dba1 100644 --- a/lifx/bulb.h +++ b/lifx/bulb.h @@ -60,7 +60,7 @@ RB_GENERATE_STATIC( lgtd_lifx_bulb_cmp ); -struct lgtd_lifx_bulb *lgtd_lifx_bulb_get(struct lgtd_lifx_gateway *, const uint8_t *); +struct lgtd_lifx_bulb *lgtd_lifx_bulb_get(const uint8_t *); struct lgtd_lifx_bulb *lgtd_lifx_bulb_open(struct lgtd_lifx_gateway *, const uint8_t *); void lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *); diff --git a/lifx/gateway.c b/lifx/gateway.c index af1437a..c299431 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -154,7 +154,7 @@ lgtd_lifx_gateway_get_or_open_bulb(struct lgtd_lifx_gateway *gw, assert(gw); assert(bulb_addr); - struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get(gw, bulb_addr); + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get(bulb_addr); if (!bulb) { bulb = lgtd_lifx_bulb_open(gw, bulb_addr); if (bulb) { diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 38fe10e..2681071 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -196,7 +196,7 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, hdr->target.tags = target.tags; break; case LGTD_LIFX_TARGET_DEVICE: - hdr->protocol.addressable = false; + hdr->protocol.addressable = true; memcpy(hdr->target.device_addr, target.addr, LGTD_LIFX_ADDR_LENGTH); break; case LGTD_LIFX_TARGET_ALL_DEVICES: diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt index b9d9bdc..4fa3a6a 100644 --- a/tests/core/jsonrpc/CMakeLists.txt +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -1,7 +1,9 @@ INCLUDE_DIRECTORIES( ${LIGHTSD_SOURCE_DIR} + ${LIGHTSD_SOURCE_DIR}/../ ${LIGHTSD_SOURCE_DIR}/core/ ${LIGHTSD_BINARY_DIR} + ${LIGHTSD_BINARY_DIR}/../ ${LIGHTSD_BINARY_DIR}/core/ ) @@ -9,7 +11,8 @@ ADD_LIBRARY( test_core_jsonrpc STATIC ${LIGHTSD_SOURCE_DIR}/core/jsmn.c ${LIGHTSD_SOURCE_DIR}/core/log.c - ${CMAKE_CURRENT_SOURCE_DIR}/../utils.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) FUNCTION(ADD_JSONRPC_TEST TEST_SOURCE) diff --git a/tests/core/router/CMakeLists.txt b/tests/core/router/CMakeLists.txt new file mode 100644 index 0000000..12f5984 --- /dev/null +++ b/tests/core/router/CMakeLists.txt @@ -0,0 +1,33 @@ +INCLUDE_DIRECTORIES( + ${LIGHTSD_SOURCE_DIR} + ${LIGHTSD_SOURCE_DIR}/../ + ${LIGHTSD_SOURCE_DIR}/core/ + ${LIGHTSD_SOURCE_DIR}/tests/core/ + ${LIGHTSD_BINARY_DIR} + ${LIGHTSD_BINARY_DIR}/../ + ${LIGHTSD_BINARY_DIR}/core/ + ${LIGHTSD_BINARY_DIR}/tests/core/ +) + +ADD_LIBRARY( + test_core_router STATIC + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/proto.c + ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/timer.c + ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c + ${TIME_MONOTONIC_IMPL} +) + +TARGET_LINK_LIBRARIES(test_core_router ${EVENT2_CORE_LIBRARY}) + +FUNCTION(ADD_ROUTER_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_router) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_ROUTER_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/core/router/test_router_broadcast.c b/tests/core/router/test_router_broadcast.c new file mode 100644 index 0000000..9151a6e --- /dev/null +++ b/tests/core/router/test_router_broadcast.c @@ -0,0 +1,54 @@ +#include "router.c" + +#include "tests_utils.h" +#include "tests_router_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + lgtd_tests_insert_mock_gateway(2); + lgtd_tests_insert_mock_gateway(1); + + struct lgtd_lifx_packet_power_state payload = { + .power = LGTD_LIFX_POWER_ON + }; + lgtd_router_send("*", LGTD_LIFX_SET_POWER_STATE, &payload); + + if (lgtd_tests_gw_pkt_queue_size != 2) { + lgtd_errx(1, "2 packets should have been sent"); + } + + for (int i = lgtd_tests_gw_pkt_queue_size; i--;) { + struct lgtd_lifx_gateway *gw = lgtd_tests_gw_pkt_queue[i].gw; + if (gw->socket != i + 1) { + lgtd_errx( + 1, "packet was sent to wrong gateway (expected %d, got %d)", + i + 1, gw->socket + ); + } + const struct lgtd_lifx_packet_header *hdr; + hdr = lgtd_tests_gw_pkt_queue[i].hdr; + if (!hdr->protocol.tagged || hdr->protocol.addressable) { + lgtd_errx(1, "packet header doesn't have the right bits set"); + } + if (hdr->target.tags != 0) { + lgtd_errx(1, "tags should be 0 for broadcast"); + } + if (memcmp(gw->site, hdr->site, sizeof(hdr->site))) { + lgtd_errx(1, "sites don't match"); + } + if (lgtd_tests_gw_pkt_queue[i].pkt != &payload) { + lgtd_errx(1, "the payload has been improperly set"); + } + if (lgtd_tests_gw_pkt_queue[i].pkt_size != sizeof(payload)) { + lgtd_errx( + 1, "unexpected pkt size %d (expected %ld)", + lgtd_tests_gw_pkt_queue[i].pkt_size, sizeof(payload) + ); + } + } + + return 0; +} diff --git a/tests/core/router/test_router_device.c b/tests/core/router/test_router_device.c new file mode 100644 index 0000000..3d25154 --- /dev/null +++ b/tests/core/router/test_router_device.c @@ -0,0 +1,58 @@ +#include "router.c" + +#include "tests_utils.h" +#include "tests_router_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); + struct lgtd_lifx_bulb *bulb_1 = lgtd_tests_insert_mock_bulb(gw_1, 1); + struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); + lgtd_tests_insert_mock_bulb(gw_2, 2); + + struct lgtd_lifx_packet_power_state payload = { + .power = LGTD_LIFX_POWER_ON + }; + lgtd_router_send("1", LGTD_LIFX_SET_POWER_STATE, &payload); + + if (lgtd_tests_gw_pkt_queue_size != 1) { + lgtd_errx(1, "1 packet should have been sent"); + } + + struct lgtd_lifx_gateway *recpt_gw = lgtd_tests_gw_pkt_queue[0].gw; + struct lgtd_lifx_packet_header *hdr_queued = lgtd_tests_gw_pkt_queue[0].hdr; + const void *pkt_queued = lgtd_tests_gw_pkt_queue[0].pkt; + int pkt_size = lgtd_tests_gw_pkt_queue[0].pkt_size; + + if (recpt_gw != gw_1) { + lgtd_errx(1, "the packet has been sent to the wrong gateway"); + } + + if (!hdr_queued->protocol.addressable || hdr_queued->protocol.tagged) { + lgtd_errx(1, "the packet header doesn't have the right protocol flags"); + } + + if (memcmp(hdr_queued->target.device_addr, bulb_1->addr, sizeof(bulb_1->addr))) { + lgtd_errx(1, "the packet header doesn't have the right target address"); + } + + if (memcmp(gw_1->site, hdr_queued->site, sizeof(hdr_queued->site))) { + lgtd_errx(1, "incorrect site in the headers"); + } + + if (pkt_queued != &payload) { + lgtd_errx(1, "invalid payload"); + } + + if (pkt_size != sizeof(payload)) { + lgtd_errx( + 1, "unexpected pkt size %d (expected %ld)", + pkt_size, sizeof(payload) + ); + } + + return 0; +} diff --git a/tests/core/router/test_router_invalid_targets.c b/tests/core/router/test_router_invalid_targets.c new file mode 100644 index 0000000..b19559e --- /dev/null +++ b/tests/core/router/test_router_invalid_targets.c @@ -0,0 +1,39 @@ +#include "router.c" + +#include "tests_utils.h" +#include "tests_router_utils.h" + +void +test_target(const char *target) +{ + struct lgtd_lifx_packet_power_state payload = { + .power = LGTD_LIFX_POWER_ON + }; + bool ok = lgtd_router_send(target, LGTD_LIFX_SET_POWER_STATE, &payload); + if (ok) { + lgtd_errx(1, "router_send didn't return false for an unknown device"); + } + if (lgtd_tests_gw_pkt_queue_size) { + lgtd_errx(1, "no packets should have been sent"); + } +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); + lgtd_tests_insert_mock_bulb(gw_1, 1); + struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); + lgtd_tests_insert_mock_bulb(gw_2, 2); + + test_target("4"); + test_target("-1"); + test_target("blabla"); + test_target("**"); + test_target("ffffffffffffffffffffffffff"); + test_target(""); + + return 0; +} diff --git a/tests/core/router/tests_router_utils.h b/tests/core/router/tests_router_utils.h new file mode 100644 index 0000000..9c3f1f5 --- /dev/null +++ b/tests/core/router/tests_router_utils.h @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lifx/wire_proto.h" +#include "core/time_monotonic.h" +#include "lifx/bulb.h" +#include "lifx/gateway.h" +#include "tests_utils.h" + +int lgtd_tests_gw_pkt_queue_size = 0; +struct { + struct lgtd_lifx_gateway *gw; + struct lgtd_lifx_packet_header *hdr; + const void *pkt; + int pkt_size; +} lgtd_tests_gw_pkt_queue[16] = { { NULL, NULL, NULL, 0}, }; + +void +lgtd_lifx_gateway_send_packet(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const void *pkt, + int pkt_size) +{ + lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].gw = gw; + // headers are created on the stack so we need to dup them: + lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].hdr = malloc( + sizeof(*hdr) + ); + memcpy( + lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].hdr, + hdr, + sizeof(*hdr) + ); + lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].pkt = pkt; + lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].pkt_size = pkt_size; + lgtd_tests_gw_pkt_queue_size++; +} diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c new file mode 100644 index 0000000..08b31a3 --- /dev/null +++ b/tests/core/tests_shims.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lifx/wire_proto.h" +#include "core/time_monotonic.h" +#include "lifx/bulb.h" +#include "lifx/gateway.h" +#include "lightsd.h" + +struct lgtd_opts lgtd_opts = { + .foreground = false, + .log_timestamps = false, + .verbosity = LGTD_DEBUG +}; + +struct event_base *lgtd_ev_base = NULL; + +void +lgtd_cleanup(void) +{ +} + + +void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_pan_gateway *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} + +void lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_light_status *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} + +void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_power_state *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c new file mode 100644 index 0000000..037a661 --- /dev/null +++ b/tests/core/tests_utils.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lifx/wire_proto.h" +#include "core/time_monotonic.h" +#include "lifx/bulb.h" +#include "lifx/gateway.h" +#include "tests_utils.h" + +struct lgtd_lifx_gateway_list lgtd_lifx_gateways = + LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); + +struct lgtd_lifx_gateway * +lgtd_tests_insert_mock_gateway(int id) +{ + struct lgtd_lifx_gateway *gw = calloc(1, sizeof(*gw)); + + gw->socket = id; + gw->site[0] = id; + + LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); + + return gw; +} + +struct lgtd_lifx_bulb * +lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *gw, uint64_t addr) +{ + assert(gw); + + union { + uint8_t as_array[LGTD_LIFX_ADDR_LENGTH]; + uint64_t as_scalar; + } bulb_addr = { .as_scalar = htobe64(addr) >> 16 }; + + return lgtd_lifx_bulb_open(gw, bulb_addr.as_array); +} diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h new file mode 100644 index 0000000..8e17abf --- /dev/null +++ b/tests/core/tests_utils.h @@ -0,0 +1,2 @@ +struct lgtd_lifx_gateway *lgtd_tests_insert_mock_gateway(int); +struct lgtd_lifx_bulb *lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *, uint64_t); diff --git a/tests/core/utils.c b/tests/core/utils.c deleted file mode 100644 index 72a613b..0000000 --- a/tests/core/utils.c +++ /dev/null @@ -1,18 +0,0 @@ -#include - -#include - -#include "lightsd.h" - -struct lgtd_opts lgtd_opts = { - .foreground = false, - .log_timestamps = false, - .verbosity = LGTD_DEBUG -}; - -struct event_base *lgtd_ev_base = NULL; - -void -lgtd_cleanup(void) -{ -} diff --git a/tests/lightsc b/tests/lightsc index 1d5aa68..2a19916 100755 --- a/tests/lightsc +++ b/tests/lightsc @@ -18,30 +18,34 @@ def jsonrpc_call(socket, id, method, params): print(response) -def set_light_from_hsbk(socket, id, h, s, b, k): +def set_light_from_hsbk(socket, id, target, h, s, b, k): jsonrpc_call(socket, id, "set_light_from_hsbk", [ - "*", h, s, b, k + target, h, s, b, k ]) -def power_on(socket, id): - jsonrpc_call(socket, id, "power_on", {"target": "*"}) +def power_on(socket, id, target): + jsonrpc_call(socket, id, "power_on", {"target": target}) -def power_off(socket, id): - jsonrpc_call(socket, id, "power_off", {"target": "*"}) +def power_off(socket, id, target): + jsonrpc_call(socket, id, "power_off", {"target": target}) if __name__ == "__main__": s = socket.create_connection(("localhost", 1234)) h = 0 id = 0 + nb = "d073d501a0d5" + fugu = "d073d500603b" + neko = "d073d5018fb6" + target = "*" try: - power_on(s, id) + power_on(s, id, target) while True: h = (h + 1) % 360 id += 1 - set_light_from_hsbk(s, id, h, 0.7, 0.02, 2500) + set_light_from_hsbk(s, id, target, h, 0.7, 0.02, 2500) time.sleep(0.1) finally: - power_off(s, id) + power_off(s, id, target) s.close() From 568f82d61678961c7458a37ede9eadebc776d191 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Mar 2015 23:19:37 -0800 Subject: [PATCH 018/181] Re-transmit power_on & power_off via device address --- lifx/gateway.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lifx/gateway.c b/lifx/gateway.c index c299431..2e92c13 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -344,12 +344,24 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, if (b->expected_power_on == b->state.power) { lgtd_warnx("clearing dirty_at on %s", b->state.label); b->dirty_at = 0; - } else if (b->expected_power_on) { - lgtd_warnx("retransmiting power on %s", b->state.label); - lgtd_proto_power_on("*"); } else { - lgtd_warnx("retransmiting power off on %s", b->state.label); - lgtd_proto_power_off("*"); + char target[LGTD_LIFX_ADDR_LENGTH * 2 + 1]; + snprintf( + target, sizeof(target), "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", + b->addr[0], b->addr[1], b->addr[2], + b->addr[3], b->addr[4], b->addr[5] + ); + if (b->expected_power_on) { + lgtd_warnx( + "retransmiting power on %s (0x%s)", b->state.label, target + ); + lgtd_proto_power_on(target); + } else { + lgtd_warnx( + "retransmiting power off %s (0x%s)", b->state.label, target + ); + lgtd_proto_power_off(target); + } } } From 7fd73c684dab18ea968f8c7d8d3361b0a6cb5289 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 8 Mar 2015 01:34:19 -0800 Subject: [PATCH 019/181] Add the transition duration (in ms) argument to set_light_from_hsbk --- core/jsonrpc.c | 24 ++++++++++++----- docs/protocol.rst | 5 +++- ...onrpc_check_and_call_set_light_from_hsbk.c | 7 ++--- ..._and_call_set_light_from_hsbk_from_array.c | 2 +- ..._call_set_light_from_hsbk_invalid_params.c | 26 ++++++++++++++++--- tests/lightsc | 10 +++---- 6 files changed, 54 insertions(+), 20 deletions(-) diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 435a27d..02b8538 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -492,7 +492,8 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, const jsmntok_t *s; const jsmntok_t *b; const jsmntok_t *k; - } params = { NULL, NULL, NULL, NULL, NULL }; + const jsmntok_t *t; + } params = { NULL, NULL, NULL, NULL, NULL, NULL }; static const struct lgtd_jsonrpc_node schema[] = { LGTD_JSONRPC_NODE( "target", @@ -524,6 +525,12 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, lgtd_jsonrpc_type_integer, false ), + LGTD_JSONRPC_NODE( + "transition", + offsetof(struct lgtd_jsonrpc_set_brightness_args, t), + lgtd_jsonrpc_type_integer, + false + ), }; bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( @@ -552,14 +559,19 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, if (k < 2500 || k > 9000 || errno == ERANGE) { goto error_invalid_params; } + int t = strtol(&json[params.t->start], NULL, 10); + if (t < 0 || errno == ERANGE) { + goto error_invalid_params; + } - char *t = lgtd_jsonrpc_dup_target(client, request, json, params.target); - if (!t) { + char *target; + target = lgtd_jsonrpc_dup_target(client, request, json, params.target); + if (!target) { return; } - ok = lgtd_proto_set_light_from_hsbk(t, h, s, b, k, 0); - free(t); + ok = lgtd_proto_set_light_from_hsbk(target, h, s, b, k, t); + free(target); if (ok) { lgtd_jsonrpc_send_response(client, request, json, "true"); return; @@ -659,7 +671,7 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, lgtd_jsonrpc_check_and_call_power_off ), LGTD_JSONRPC_METHOD( - "set_light_from_hsbk", 5, // t, h, s, b, k + "set_light_from_hsbk", 6, // t, h, s, b, k, t lgtd_jsonrpc_check_and_call_set_light_from_hsbk ), }; diff --git a/docs/protocol.rst b/docs/protocol.rst index 3625672..6eff16c 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -32,10 +32,13 @@ Available methods Power on the given bulb(s). -.. function:: set_light_from_hsbk(target, h, s, b, k) +.. function:: set_light_from_hsbk(target, h, s, b, k, t) :param float h: Hue from 0 to 360. :param float s: Saturation from 0 to 1. :param float b: Brightness from 0 to 1. + :param int k: Temperature in Kelvin from 2500 to 9000. + :param int t: Transition duration to this color in ms. + .. vim: set tw=80 spelllang=en spell: diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c index 89fb8c8..86fe2a4 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -42,9 +42,9 @@ lgtd_proto_set_light_from_hsbk(const char *target, 1, "Invalid temperature: %d, expected: 4200", kelvin ); } - if (transition_msecs != 0) { + if (transition_msecs != 60) { errx( - 1, "Invalid transition duration: %d, expected: 0", transition_msecs + 1, "Invalid transition duration: %d, expected: 60", transition_msecs ); } set_light_called = true; @@ -63,7 +63,8 @@ main(void) "\"hue\": 324.2341514, " "\"saturation\": 0.234, " "\"brightness\": 1.0, " - "\"kelvin\": 4200" + "\"kelvin\": 4200," + "\"transition\": 60" "}," "\"id\": \"42\"" "}"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c index 3cdecc8..a04a12c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -59,7 +59,7 @@ main(void) "\"jsonrpc\": \"2.0\"," "\"method\": \"set_light_from_hsbk\"," "\"params\": [" - "\"*\", 324.2341514, 0.234, 1.0, 4200" + "\"*\", 324.2341514, 0.234, 1.0, 4200, 0" "]," "\"id\": \"42\"" "}"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c index b950916..d83d76c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -64,7 +64,8 @@ main(void) "\"hue\": 324.2341514, " "\"saturation\": 0.234, " "\"brightness\": 1.0, " - "\"kelvin\": -4200" + "\"kelvin\": -4200," + "\"transition\": 42" "}," "\"id\": \"42\"" "}"); @@ -78,7 +79,8 @@ main(void) "\"hue\": 324.2341514, " "\"saturation\": 3.234, " "\"brightness\": 1.0, " - "\"kelvin\": 4200" + "\"kelvin\": 4200," + "\"transition\": 42" "}," "\"id\": \"42\"" "}"); @@ -92,7 +94,8 @@ main(void) "\"hue\": 424.2341514, " "\"saturation\": 0.234, " "\"brightness\": 1.0, " - "\"kelvin\": 4200" + "\"kelvin\": 4200," + "\"transition\": 42" "}," "\"id\": \"42\"" "}"); @@ -106,10 +109,25 @@ main(void) "\"hue\": 224.2341514, " "\"saturation\": 0.234, " "\"brightness\": -1.0, " - "\"kelvin\": 4200" + "\"kelvin\": 4200," + "\"transition\": 42" "}," "\"id\": \"42\"" "}"); + // negative transition + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_light_from_hsbk\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": -1.0, " + "\"kelvin\": 4200," + "\"transition\": -42" + "}," + "\"id\": \"42\"" + "}"); return 0; } diff --git a/tests/lightsc b/tests/lightsc index 2a19916..1016fbe 100755 --- a/tests/lightsc +++ b/tests/lightsc @@ -18,9 +18,9 @@ def jsonrpc_call(socket, id, method, params): print(response) -def set_light_from_hsbk(socket, id, target, h, s, b, k): +def set_light_from_hsbk(socket, id, target, h, s, b, k, t): jsonrpc_call(socket, id, "set_light_from_hsbk", [ - target, h, s, b, k + target, h, s, b, k, t ]) @@ -42,10 +42,10 @@ if __name__ == "__main__": try: power_on(s, id, target) while True: - h = (h + 1) % 360 + h = (h + 5) % 360 id += 1 - set_light_from_hsbk(s, id, target, h, 0.7, 0.02, 2500) - time.sleep(0.1) + set_light_from_hsbk(s, id, target, h, 0.8, 0.1, 2500, 450) + time.sleep(0.5) finally: power_off(s, id, target) s.close() From 6a505de184919f25174d1888e66bc9702435ea60 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Fri, 13 Mar 2015 23:11:19 -0700 Subject: [PATCH 020/181] Backed out changeset c109b3351e99 Huuu we do need this file on linux, and I do need to step continuous testing. --- compat/generic/time_monotonic.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 compat/generic/time_monotonic.h diff --git a/compat/generic/time_monotonic.h b/compat/generic/time_monotonic.h new file mode 100644 index 0000000..0e249cd --- /dev/null +++ b/compat/generic/time_monotonic.h @@ -0,0 +1,22 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#pragma once + +typedef time_t lgtd_time_mono_t; + +lgtd_time_mono_t lgtd_time_monotonic_msecs(void); From ec98bb375a08557e8b05a4b9fb6be869dcc675dd Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 15 Mar 2015 00:57:30 -0700 Subject: [PATCH 021/181] Stop being retarded on includes Fix build warnings on apple/clang. --- tests/core/CMakeLists.txt | 9 +++++++++ tests/core/jsonrpc/CMakeLists.txt | 8 ++------ tests/core/router/CMakeLists.txt | 10 ++-------- tests/core/router/tests_router_utils.h | 17 ----------------- 4 files changed, 13 insertions(+), 31 deletions(-) diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 5e02551..edd23dd 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -1 +1,10 @@ +INCLUDE_DIRECTORIES( + ${LIGHTSD_SOURCE_DIR} + ${LIGHTSD_SOURCE_DIR}/core/ + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIGHTSD_BINARY_DIR} + ${LIGHTSD_BINARY_DIR}/core/ + ${CMAKE_CURRENT_BINARY_DIR} +) + ADD_ALL_SUBDIRECTORIES() diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt index 4fa3a6a..b41cb58 100644 --- a/tests/core/jsonrpc/CMakeLists.txt +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -1,10 +1,6 @@ INCLUDE_DIRECTORIES( - ${LIGHTSD_SOURCE_DIR} - ${LIGHTSD_SOURCE_DIR}/../ - ${LIGHTSD_SOURCE_DIR}/core/ - ${LIGHTSD_BINARY_DIR} - ${LIGHTSD_BINARY_DIR}/../ - ${LIGHTSD_BINARY_DIR}/core/ + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} ) ADD_LIBRARY( diff --git a/tests/core/router/CMakeLists.txt b/tests/core/router/CMakeLists.txt index 12f5984..04c851b 100644 --- a/tests/core/router/CMakeLists.txt +++ b/tests/core/router/CMakeLists.txt @@ -1,12 +1,6 @@ INCLUDE_DIRECTORIES( - ${LIGHTSD_SOURCE_DIR} - ${LIGHTSD_SOURCE_DIR}/../ - ${LIGHTSD_SOURCE_DIR}/core/ - ${LIGHTSD_SOURCE_DIR}/tests/core/ - ${LIGHTSD_BINARY_DIR} - ${LIGHTSD_BINARY_DIR}/../ - ${LIGHTSD_BINARY_DIR}/core/ - ${LIGHTSD_BINARY_DIR}/tests/core/ + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} ) ADD_LIBRARY( diff --git a/tests/core/router/tests_router_utils.h b/tests/core/router/tests_router_utils.h index 9c3f1f5..36f03d2 100644 --- a/tests/core/router/tests_router_utils.h +++ b/tests/core/router/tests_router_utils.h @@ -1,20 +1,3 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "lifx/wire_proto.h" -#include "core/time_monotonic.h" -#include "lifx/bulb.h" -#include "lifx/gateway.h" -#include "tests_utils.h" - int lgtd_tests_gw_pkt_queue_size = 0; struct { struct lgtd_lifx_gateway *gw; From e4add61f33a9240dc2a9b00a8a2ecc97659d2fc7 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 15 Mar 2015 00:57:30 -0700 Subject: [PATCH 022/181] Add the set_waveform command --- .hgignore | 2 +- core/jsonrpc.c | 175 +++++++++++- core/proto.c | 34 +++ core/proto.h | 4 + docs/protocol.rst | 35 ++- lifx/wire_proto.c | 50 ++++ lifx/wire_proto.h | 33 +++ tests/core/jsonrpc/CMakeLists.txt | 1 + ...test_jsonrpc_check_and_call_set_waveform.c | 120 +++++++++ ...eck_and_call_set_waveform_invalid_params.c | 251 ++++++++++++++++++ tests/core/jsonrpc/test_jsonrpc_utils.h | 22 ++ tests/core/tests_utils.h | 2 + tests/lifx/CMakeLists.txt | 10 + tests/lifx/wire_proto/CMakeLists.txt | 13 + tests/lifx/wire_proto/test_wire_proto_utils.h | 28 ++ .../test_wire_proto_waveform_table.c | 57 ++++ tests/lightsc | 38 ++- 17 files changed, 857 insertions(+), 18 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c create mode 100644 tests/lifx/CMakeLists.txt create mode 100644 tests/lifx/wire_proto/CMakeLists.txt create mode 100644 tests/lifx/wire_proto/test_wire_proto_utils.h create mode 100644 tests/lifx/wire_proto/test_wire_proto_waveform_table.c diff --git a/.hgignore b/.hgignore index 7ec60a4..fd55498 100644 --- a/.hgignore +++ b/.hgignore @@ -1,3 +1,3 @@ -.*\.sw[p-z]$ +.*\.sw[a-z]$ .*\.py[co]$ ^build diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 02b8538..82b0df9 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -16,6 +16,7 @@ // along with lighstd. If not, see . #include +#include #include #include #include @@ -30,6 +31,7 @@ #include #include +#include "lifx/wire_proto.h" #include "jsmn.h" #include "client.h" #include "jsonrpc.h" @@ -486,7 +488,7 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, const struct lgtd_jsonrpc_request *request, const char *json) { - struct lgtd_jsonrpc_set_brightness_args { + struct lgtd_jsonrpc_set_light_from_hsbk_args { const jsmntok_t *target; const jsmntok_t *h; const jsmntok_t *s; @@ -497,37 +499,37 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, static const struct lgtd_jsonrpc_node schema[] = { LGTD_JSONRPC_NODE( "target", - offsetof(struct lgtd_jsonrpc_set_brightness_args, target), + offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, target), lgtd_jsonrpc_type_string, false ), LGTD_JSONRPC_NODE( "hue", - offsetof(struct lgtd_jsonrpc_set_brightness_args, h), + offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, h), lgtd_jsonrpc_type_float_between_0_and_360, false ), LGTD_JSONRPC_NODE( "saturation", - offsetof(struct lgtd_jsonrpc_set_brightness_args, s), + offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, s), lgtd_jsonrpc_type_float_between_0_and_1, false ), LGTD_JSONRPC_NODE( "brightness", - offsetof(struct lgtd_jsonrpc_set_brightness_args, b), + offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, b), lgtd_jsonrpc_type_float_between_0_and_1, false ), LGTD_JSONRPC_NODE( "kelvin", - offsetof(struct lgtd_jsonrpc_set_brightness_args, k), + offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, k), lgtd_jsonrpc_type_integer, false ), LGTD_JSONRPC_NODE( "transition", - offsetof(struct lgtd_jsonrpc_set_brightness_args, t), + offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, t), lgtd_jsonrpc_type_integer, false ), @@ -632,6 +634,160 @@ lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client, ); } +static void +lgtd_jsonrpc_check_and_call_set_waveform(struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const char *json) +{ + struct lgtd_jsonrpc_set_waveform_args { + const jsmntok_t *target; + const jsmntok_t *waveform; + const jsmntok_t *h; + const jsmntok_t *s; + const jsmntok_t *b; + const jsmntok_t *k; + const jsmntok_t *period; + const jsmntok_t *cycles; + const jsmntok_t *skew_ratio; + const jsmntok_t *transient; + } params = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + static const struct lgtd_jsonrpc_node schema[] = { + LGTD_JSONRPC_NODE( + "target", + offsetof(struct lgtd_jsonrpc_set_waveform_args, target), + lgtd_jsonrpc_type_string, + false + ), + LGTD_JSONRPC_NODE( + "waveform", + offsetof(struct lgtd_jsonrpc_set_waveform_args, waveform), + lgtd_jsonrpc_type_string, + false + ), + LGTD_JSONRPC_NODE( + "hue", + offsetof(struct lgtd_jsonrpc_set_waveform_args, h), + lgtd_jsonrpc_type_float_between_0_and_360, + false + ), + LGTD_JSONRPC_NODE( + "saturation", + offsetof(struct lgtd_jsonrpc_set_waveform_args, s), + lgtd_jsonrpc_type_float_between_0_and_1, + false + ), + LGTD_JSONRPC_NODE( + "brightness", + offsetof(struct lgtd_jsonrpc_set_waveform_args, b), + lgtd_jsonrpc_type_float_between_0_and_1, + false + ), + LGTD_JSONRPC_NODE( + "kelvin", + offsetof(struct lgtd_jsonrpc_set_waveform_args, k), + lgtd_jsonrpc_type_integer, + false + ), + LGTD_JSONRPC_NODE( + "period", + offsetof(struct lgtd_jsonrpc_set_waveform_args, period), + lgtd_jsonrpc_type_integer, + false + ), + LGTD_JSONRPC_NODE( + "cycles", + offsetof(struct lgtd_jsonrpc_set_waveform_args, cycles), + lgtd_jsonrpc_type_integer, + false + ), + LGTD_JSONRPC_NODE( + "skew_ratio", + offsetof(struct lgtd_jsonrpc_set_waveform_args, skew_ratio), + lgtd_jsonrpc_type_float_between_0_and_1, + false + ), + LGTD_JSONRPC_NODE( + "transient", + offsetof(struct lgtd_jsonrpc_set_waveform_args, transient), + lgtd_jsonrpc_type_bool, + false + ), + }; + + bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( + ¶ms, + schema, + LGTD_ARRAY_SIZE(schema), + request->params, + request->params_ntokens, + json + ); + if (!ok) { + goto error_invalid_params; + } + + enum lgtd_lifx_waveform_type waveform; + waveform = lgtd_lifx_wire_waveform_string_id_to_type( + &json[params.waveform->start], LGTD_JSONRPC_TOKEN_LEN(params.waveform) + ); + if (waveform == LGTD_LIFX_WAVEFORM_INVALID) { + goto error_invalid_params; + } + + int h = lgtd_jsonrpc_float_range_to_uint16( + &json[params.h->start], LGTD_JSONRPC_TOKEN_LEN(params.h), 0, 360 + ); + int s = lgtd_jsonrpc_float_range_to_uint16( + &json[params.s->start], LGTD_JSONRPC_TOKEN_LEN(params.s), 0, 1 + ); + int b = lgtd_jsonrpc_float_range_to_uint16( + &json[params.b->start], LGTD_JSONRPC_TOKEN_LEN(params.b), 0, 1 + ); + errno = 0; + int k = strtol(&json[params.k->start], NULL, 10); + if (k < 2500 || k > 9000 || errno == ERANGE) { + goto error_invalid_params; + } + int period = strtol(&json[params.period->start], NULL, 10); + if (period <= 0 || errno == ERANGE) { + goto error_invalid_params; + } + int cycles = strtol(&json[params.cycles->start], NULL, 10); + if (cycles <= 0 || errno == ERANGE) { + goto error_invalid_params; + } + int skew_ratio = lgtd_jsonrpc_float_range_to_uint16( + &json[params.skew_ratio->start], + LGTD_JSONRPC_TOKEN_LEN(params.skew_ratio), + 0, 1 + ); + skew_ratio -= UINT16_MAX / 2; + bool transient = json[params.transient->start] == 't'; + + + char *target; + target = lgtd_jsonrpc_dup_target(client, request, json, params.target); + if (!target) { + return; + } + + lgtd_proto_set_waveform( + target, waveform, h, s, b, k, period, cycles, skew_ratio, transient + ); + + free(target); + if (ok) { + lgtd_jsonrpc_send_response(client, request, json, "true"); + return; + } + +error_invalid_params: + lgtd_jsonrpc_send_error( + client, request, json, LGTD_JSONRPC_INVALID_PARAMS, + "Invalid parameters" + ); +} + static void lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client, const struct lgtd_jsonrpc_request *request, @@ -674,6 +830,11 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, "set_light_from_hsbk", 6, // t, h, s, b, k, t lgtd_jsonrpc_check_and_call_set_light_from_hsbk ), + LGTD_JSONRPC_METHOD( + // t, waveform, h, s, b, k, period, cycles, skew_ratio, transient + "set_waveform", 10, + lgtd_jsonrpc_check_and_call_set_waveform + ), }; assert(client); diff --git a/core/proto.c b/core/proto.c index bcdac98..5dcd93b 100644 --- a/core/proto.c +++ b/core/proto.c @@ -74,3 +74,37 @@ lgtd_proto_set_light_from_hsbk(const char *target, lgtd_lifx_wire_encode_light_color(&pkt); return lgtd_router_send(target, LGTD_LIFX_SET_LIGHT_COLOR, &pkt); } + +bool lgtd_proto_set_waveform(const char *target, + enum lgtd_lifx_waveform_type waveform, + int hue, int saturation, + int brightness, int kelvin, + int period, float cycles, + int skew_ratio, bool transient) +{ + assert(target); + assert(hue >= 0 && hue <= UINT16_MAX); + assert(saturation >= 0 && saturation <= UINT16_MAX); + assert(brightness >= 0 && brightness <= UINT16_MAX); + assert(kelvin >= 2500 && kelvin <= 9000); + assert(waveform <= LGTD_LIFX_WAVEFORM_PULSE); + assert(skew_ratio >= -32767 && skew_ratio <= 32768); + assert(period >= 0); + assert(cycles >= 0); + + struct lgtd_lifx_packet_waveform pkt = { + .stream = 0, + .transient = transient, + .hue = hue, + .saturation = saturation, + .brightness = brightness, + .kelvin = kelvin, + .period = period, + .cycles = cycles, + .skew_ratio = skew_ratio, + .waveform = waveform + }; + + lgtd_lifx_wire_encode_waveform(&pkt); + return lgtd_router_send(target, LGTD_LIFX_SET_WAVEFORM, &pkt); +} diff --git a/core/proto.h b/core/proto.h index 119745b..8b525bf 100644 --- a/core/proto.h +++ b/core/proto.h @@ -18,5 +18,9 @@ #pragma once bool lgtd_proto_set_light_from_hsbk(const char *, int, int, int, int, int); +bool lgtd_proto_set_waveform(const char *, + enum lgtd_lifx_waveform_type, + int, int, int, int, + int, float, int, bool); bool lgtd_proto_power_on(const char *); bool lgtd_proto_power_off(const char *); diff --git a/docs/protocol.rst b/docs/protocol.rst index 6eff16c..70db0e8 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -11,15 +11,18 @@ Targeting bulbs Commands that manipulate bulbs will take a *target* argument to define on which bulb(s) the operation should apply: -+-----------------------------+--------------------------------------------+ -| ``\*`` | targets all bulbs | -+-----------------------------+--------------------------------------------+ -| ``#TagName`` | targets bulbs tagged with *TagName* | -+-----------------------------+--------------------------------------------+ -| ``124f31a5`` | directly target the bulb with the given id | -+-----------------------------+--------------------------------------------+ -| ``[\*, #Kitchen, 123456]`` | compose different targets together | -+-----------------------------+--------------------------------------------+ ++-----------------------------+-----------------------------------------------+ +| ``\*`` | targets all bulbs | ++-----------------------------+-----------------------------------------------+ +| ``#TagName`` | targets bulbs tagged with *TagName* | ++-----------------------------+-----------------------------------------------+ +| ``124f31a5`` | directly target the bulb with the given id | ++-----------------------------+-----------------------------------------------+ +| ``label`` | directly target the bulb with the given label | ++-----------------------------+-----------------------------------------------+ + +You can use JSON-RPC's batch feature to send a command to multiple targets at +the same time. Available methods ----------------- @@ -40,5 +43,19 @@ Available methods :param int k: Temperature in Kelvin from 2500 to 9000. :param int t: Transition duration to this color in ms. +.. function:: set_waveform(target, waveform, h, s, b, k, period, cycles, skew_ratio, transient) + + :param string waveform: One of ``SAW``, ``SINE``, ``HALF_SINE``, + ``TRIANGLE``, ``PULSE``. + :param float h: Hue from 0 to 360. + :param float s: Saturation from 0 to 1. + :param float b: Brightness from 0 to 1. + :param int k: Temperature in Kelvin from 2500 to 9000. + :param int period: milliseconds per cycle. + :param int cycles: number of cycles. + :param float skew_ratio: from 0 to 1. + :param bool transient: if true the target will keep the color it has at the + end of the waveform, otherwise it will revert back to + its original state. .. vim: set tw=80 spelllang=en spell: diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 2681071..b1dcb9c 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -129,6 +129,13 @@ lgtd_lifx_wire_load_packet_infos_map(void) .type = LGTD_LIFX_SET_LIGHT_COLOR, .size = sizeof(struct lgtd_lifx_packet_light_color), .encode = ENCODER(lgtd_lifx_wire_encode_light_color) + }, + { + REQUEST_ONLY, + .name = "SET_WAVEFORM", + .type = LGTD_LIFX_SET_WAVEFORM, + .size = sizeof(struct lgtd_lifx_packet_waveform), + .encode = ENCODER(lgtd_lifx_wire_encode_waveform) } }; @@ -148,6 +155,34 @@ lgtd_lifx_wire_get_packet_infos(enum lgtd_lifx_packet_type packet_type) return RB_FIND(lgtd_lifx_packet_infos_map, &lgtd_lifx_packet_infos, &pkt_infos); } + +#define WAVEFORM_ENTRY(e) { .str = e, .len = sizeof(e) - 1 } +const struct lgtd_lifx_waveform_string_id lgtd_lifx_waveform_table[] = { + WAVEFORM_ENTRY("SAW"), + WAVEFORM_ENTRY("SINE"), + WAVEFORM_ENTRY("HALF_SINE"), + WAVEFORM_ENTRY("TRIANGLE"), + WAVEFORM_ENTRY("PULSE"), + WAVEFORM_ENTRY("INVALID") +}; + +enum lgtd_lifx_waveform_type +lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) +{ + assert(s); + assert(len >= 0); + + for (int i = 0; i != LGTD_ARRAY_SIZE(lgtd_lifx_waveform_table); i++) { + const struct lgtd_lifx_waveform_string_id *entry; + entry = &lgtd_lifx_waveform_table[i]; + if (entry->len == len && !memcmp(entry->str, s, len)) { + return i; + } + } + + return LGTD_LIFX_WAVEFORM_INVALID; +} + // Convert all the fields in the header to the host endianness. // // \return The payload size or -1 if the header is invalid. @@ -177,6 +212,8 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_infos *pkt_infos = lgtd_lifx_wire_get_packet_infos(packet_type); + assert(pkt_infos); + memset(hdr, 0, sizeof(*hdr)); hdr->size = pkt_infos->size + sizeof(*hdr); hdr->protocol.version = LGTD_LIFX_PROTOCOL_V1; @@ -270,3 +307,16 @@ lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *pkt) pkt->kelvin = htole16(pkt->kelvin); pkt->transition = htole32(pkt->transition); } + +void +lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) +{ + assert(pkt); + + pkt->hue = htole16(pkt->hue); + pkt->saturation = htole16(pkt->saturation); + pkt->brightness = htole16(pkt->brightness); + pkt->kelvin = htole16(pkt->kelvin); + pkt->period = htole16(pkt->period); + pkt->skew_ratio = htole16(pkt->skew_ratio); +} diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 803bc89..e9de993 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -173,8 +173,38 @@ struct lgtd_lifx_packet_light_color { uint32le_t transition; // transition time to the color in msecs }; +// note: those can be used as indexes for lgtd_lifx_waveform_table +enum lgtd_lifx_waveform_type { + LGTD_LIFX_WAVEFORM_SAW = 0, + LGTD_LIFX_WAVEFORM_SINE = 1, + LGTD_LIFX_WAVEFORM_HALF_SINE = 2, + LGTD_LIFX_WAVEFORM_TRIANGLE = 3, + LGTD_LIFX_WAVEFORM_PULSE = 4, + LGTD_LIFX_WAVEFORM_INVALID = 5, +}; + +struct lgtd_lifx_packet_waveform { + uint8_t stream; + uint8_t transient; + uint16le_t hue; + uint16le_t saturation; + uint16le_t brightness; + uint16le_t kelvin; + uint32le_t period; // milliseconds + float cycles; // yes, this value is really encoded as a float. + uint16le_t skew_ratio; + uint8_t waveform; // see enum lgtd_lifx_waveform_type +}; + #pragma pack(pop) +struct lgtd_lifx_waveform_string_id { + const char *str; + int len; +}; + +extern const struct lgtd_lifx_waveform_string_id lgtd_lifx_waveform_table[]; + struct lgtd_lifx_gateway; struct lgtd_lifx_packet_infos { @@ -204,6 +234,8 @@ union lgtd_lifx_target { extern union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET; +enum lgtd_lifx_waveform_type lgtd_lifx_wire_waveform_string_id_to_type(const char *, int); + const struct lgtd_lifx_packet_infos *lgtd_lifx_wire_get_packet_infos(enum lgtd_lifx_packet_type); void lgtd_lifx_wire_load_packet_infos_map(void); @@ -222,3 +254,4 @@ void lgtd_lifx_wire_encode_light_status(struct lgtd_lifx_packet_light_status *); void lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *); void lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *); +void lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt); diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt index b41cb58..df78e50 100644 --- a/tests/core/jsonrpc/CMakeLists.txt +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -7,6 +7,7 @@ ADD_LIBRARY( test_core_jsonrpc STATIC ${LIGHTSD_SOURCE_DIR}/core/jsmn.c ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c new file mode 100644 index 0000000..91d88d9 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c @@ -0,0 +1,120 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_SET_WAVEFORM +#include "test_jsonrpc_utils.h" + +static bool set_waveform_called = false; + +bool +lgtd_proto_set_waveform(const char *target, + enum lgtd_lifx_waveform_type waveform, + int hue, int saturation, + int brightness, int kelvin, + int period, float cycles, + int skew_ratio, bool transient) +{ + if (strcmp(target, "*")) { + errx(1, "Invalid target [%s] (expected=[*])", target); + } + int expected_hue = lgtd_jsonrpc_float_range_to_uint16( + "324.2341", strlen("324.2341"), 0, 360 + ); + if (hue != expected_hue) { + errx(1, "Invalid hue: %d, expected: %d", hue, expected_hue); + } + int expected_saturation = lgtd_jsonrpc_float_range_to_uint16( + "0.234", strlen("0.234"), 0, 1 + ); + if (saturation != expected_saturation) { + errx( + 1, "Invalid saturation: %d, expected: %d", + saturation, expected_saturation + ); + } + if (brightness != UINT16_MAX) { + errx( + 1, "Invalid brightness: %d, expected: %d", + brightness, UINT16_MAX + ); + } + if (kelvin != 4200) { + errx( + 1, "Invalid temperature: %d, expected: 4200", kelvin + ); + } + if (period != 1000) { + errx(1, "Invalid period: %d, expected: 1000", period); + } + if (cycles != 10) { + errx(1, "Invalid cycles: %d, expected: 10", (int)cycles); + } + if (skew_ratio != 0) { + errx(1, "Invalid skew_ratio: %d, expected: 0", skew_ratio); + } + if (!transient) { + errx(1, "transient is false instead of true"); + } + if (waveform != LGTD_LIFX_WAVEFORM_SAW) { + errx( + 1, "Invalid waveform %d: expected: %d", + waveform, LGTD_LIFX_WAVEFORM_SAW + ); + } + set_waveform_called = true; + return true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 324.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": 0.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_set_waveform(&client, &req, json); + + const char response[] = ("{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"42\", " + "\"result\": true" + "}"); + + if (strcmp(client_write_buf, response)) { + errx( + 1, "invalid response: %s (expected: %s)", + client_write_buf, response + ); + } + + if (!set_waveform_called) { + errx(1, "lgtd_proto_set_waveform wasn't called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c new file mode 100644 index 0000000..d8e24a3 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c @@ -0,0 +1,251 @@ +#include "jsonrpc.c" + +#define LGTD_TESTING_SET_WAVEFORM +#include "test_jsonrpc_utils.h" + +static bool set_waveform_called = false; + +bool +lgtd_proto_set_waveform(const char *target, + enum lgtd_lifx_waveform_type waveform, + int hue, int saturation, + int brightness, int kelvin, + int period, float cycles, + int skew_ratio, bool transient) +{ + (void)target; + (void)waveform; + (void)hue; + (void)saturation; + (void)brightness; + (void)kelvin; + (void)period; + (void)cycles; + (void)skew_ratio; + (void)transient; + set_waveform_called = true; + return true; +} + +static void +test_request(const char *json) +{ + jsmntok_t tokens[32]; + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json) + ); + + bool ok; + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_set_waveform(&client, &req, json); + + if (!strstr(client_write_buf, "-32602")) { + errx(1, "no error returned, client_write_buf=[%s]", client_write_buf); + } + + if (set_waveform_called) { + errx(1, "lgtd_proto_power_off was called"); + } + + reset_client_write_buf(); +} + +int +main(void) +{ + // invalid temperature: + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 324.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": -4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": 0.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + + // saturation to big + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 324.2341514, " + "\"saturation\": 3.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": 0.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + + // hue too big + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 424.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": 0.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + + // brightness too small + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": -1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": 0.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + + // cycles must be > 0 + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 0," + "\"period\": 1000," + "\"skew_ratio\": 0.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + + // skew ratio too big + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": 1.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + + // skew ratio too small + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": -1.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + + // invalid waveform + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": 0.5," + "\"transient\": true," + "\"waveform\": \"TEST\"" + "}," + "\"id\": \"42\"" + "}"); + + // invalid transient + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 1000," + "\"skew_ratio\": 0.5," + "\"transient\": 42," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); + + // period must be > 0 + test_request("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_waveform\"," + "\"params\": {" + "\"target\": \"*\", " + "\"hue\": 224.2341514, " + "\"saturation\": 0.234, " + "\"brightness\": 1.0, " + "\"kelvin\": 4200," + "\"cycles\": 10," + "\"period\": 0," + "\"skew_ratio\": 0.5," + "\"transient\": true," + "\"waveform\": \"SAW\"" + "}," + "\"id\": \"42\"" + "}"); +} diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index ab7a2cc..2adc6bd 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -66,3 +66,25 @@ lgtd_proto_power_off(const char *target) return true; } #endif + +#ifndef LGTD_TESTING_SET_WAVEFORM +bool lgtd_proto_set_waveform(const char *target, + enum lgtd_lifx_waveform_type waveform, + int hue, int saturation, + int brightness, int kelvin, + int period, float cycles, + int skew_ratio, bool transient) +{ + (void)target; + (void)waveform; + (void)hue; + (void)saturation; + (void)brightness; + (void)kelvin; + (void)period; + (void)cycles; + (void)skew_ratio; + (void)transient; + return true; +} +#endif diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index 8e17abf..a4a43b1 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -1,2 +1,4 @@ +#pragma once + struct lgtd_lifx_gateway *lgtd_tests_insert_mock_gateway(int); struct lgtd_lifx_bulb *lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *, uint64_t); diff --git a/tests/lifx/CMakeLists.txt b/tests/lifx/CMakeLists.txt new file mode 100644 index 0000000..da2ad41 --- /dev/null +++ b/tests/lifx/CMakeLists.txt @@ -0,0 +1,10 @@ +INCLUDE_DIRECTORIES( + ${LIGHTSD_SOURCE_DIR} + ${LIGHTSD_SOURCE_DIR}/lifx/ + ${CMAKE_CURRENT_SOURCE_DIR} + ${LIGHTSD_BINARY_DIR} + ${LIGHTSD_BINARY_DIR}/lifx/ + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_ALL_SUBDIRECTORIES() diff --git a/tests/lifx/wire_proto/CMakeLists.txt b/tests/lifx/wire_proto/CMakeLists.txt new file mode 100644 index 0000000..a3d3b8f --- /dev/null +++ b/tests/lifx/wire_proto/CMakeLists.txt @@ -0,0 +1,13 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +FUNCTION(ADD_WIRE_PROTO_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} FALSE) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_WIRE_PROTO_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/lifx/wire_proto/test_wire_proto_utils.h b/tests/lifx/wire_proto/test_wire_proto_utils.h new file mode 100644 index 0000000..b143e4a --- /dev/null +++ b/tests/lifx/wire_proto/test_wire_proto_utils.h @@ -0,0 +1,28 @@ +#pragma once + +void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_pan_gateway *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} + +void lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_light_status *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} + +void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_power_state *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} diff --git a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c new file mode 100644 index 0000000..1e4f5e4 --- /dev/null +++ b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c @@ -0,0 +1,57 @@ +#include "wire_proto.c" + +#include "test_wire_proto_utils.h" + +int +main(void) +{ + enum lgtd_lifx_waveform_type rv = LGTD_LIFX_WAVEFORM_INVALID; + + rv = lgtd_lifx_wire_waveform_string_id_to_type("SAW", 3); + if (rv != LGTD_LIFX_WAVEFORM_SAW) { + errx(1, "Expected WAVEFORM_SAW"); + } + + rv = lgtd_lifx_wire_waveform_string_id_to_type("SINE", 4); + if (rv != LGTD_LIFX_WAVEFORM_SINE) { + errx(1, "Expected WAVEFORM_SINE"); + } + + rv = lgtd_lifx_wire_waveform_string_id_to_type("HALF_SINE", 9); + if (rv != LGTD_LIFX_WAVEFORM_HALF_SINE) { + errx(1, "Expected WAVEFORM_HALF_SINE"); + } + + rv = lgtd_lifx_wire_waveform_string_id_to_type("TRIANGLE", 8); + if (rv != LGTD_LIFX_WAVEFORM_TRIANGLE) { + errx(1, "Expected WAVEFORM_TRIANGLE"); + } + + rv = lgtd_lifx_wire_waveform_string_id_to_type("PULSE", 5); + if (rv != LGTD_LIFX_WAVEFORM_PULSE) { + errx(1, "Expected WAVEFORM_PULSE"); + } + + rv = lgtd_lifx_wire_waveform_string_id_to_type("TEST", 4); + if (rv != LGTD_LIFX_WAVEFORM_INVALID) { + errx(1, "Expected WAVEFORM_INVALID"); + } + + rv = lgtd_lifx_wire_waveform_string_id_to_type("", 0); + if (rv != LGTD_LIFX_WAVEFORM_INVALID) { + errx(1, "Expected WAVEFORM_INVALID"); + } + + int cmp = strcmp( + lgtd_lifx_waveform_table[LGTD_LIFX_WAVEFORM_INVALID].str, + "INVALID" + ); + if (cmp) { + errx( + 1, "Expected INVALID got %s", + lgtd_lifx_waveform_table[LGTD_LIFX_WAVEFORM_INVALID].str + ); + } + + return 0; +} diff --git a/tests/lightsc b/tests/lightsc index 1016fbe..635377e 100755 --- a/tests/lightsc +++ b/tests/lightsc @@ -2,8 +2,11 @@ import json import socket +import sys import time +from IPython.terminal.embed import InteractiveShellEmbed + def jsonrpc_call(socket, id, method, params): payload = { @@ -24,6 +27,34 @@ def set_light_from_hsbk(socket, id, target, h, s, b, k, t): ]) +def set_waveform(socket, id, target, waveform, + h, s, b, k, + period, cycles, skew_ratio, transient): + jsonrpc_call(socket, id, "set_waveform", [ + target, waveform, h, s, b, k, period, cycles, skew_ratio, transient + ]) + + +def saw(socket, id, target, h, s, b, k, period, cycles, transient=True): + set_waveform( + socket, id, target, "SAW", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=0.5, + transient=transient + ) + + +def sine(socket, id, target, h, s, b, k, period, cycles, peak=0.5, transient=True): + set_waveform( + socket, id, target, "SINE", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=peak, + transient=transient + ) + + def power_on(socket, id, target): jsonrpc_call(socket, id, "power_on", {"target": target}) @@ -38,14 +69,19 @@ if __name__ == "__main__": nb = "d073d501a0d5" fugu = "d073d500603b" neko = "d073d5018fb6" + middle = "d073d502e530" target = "*" try: + if len(sys.argv) == 2 and sys.argv[1] == "shell": + ipshell = InteractiveShellEmbed() + ipshell() + sys.exit(0) power_on(s, id, target) while True: h = (h + 5) % 360 id += 1 set_light_from_hsbk(s, id, target, h, 0.8, 0.1, 2500, 450) time.sleep(0.5) - finally: power_off(s, id, target) + finally: s.close() From 74c73ab610b12334edc7f3b54a9c61dc0ae426b4 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 1 Apr 2015 22:59:38 -0700 Subject: [PATCH 023/181] Log device latency at debug only --- lifx/gateway.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lifx/gateway.c b/lifx/gateway.c index 2e92c13..0d3cc45 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -371,7 +371,7 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); evtimer_add(gw->refresh_ev, &tv); - lgtd_info( + lgtd_debug( "[%s]:%hu latency is %dms, scheduling next GET_LIGHT_STATE in %dms", gw->ip_addr, gw->port, latency, timeout ); @@ -380,7 +380,7 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, } if (!gw->pending_refresh_req) { - lgtd_info( + lgtd_debug( "[%s]:%hu latency is %dms, sending GET_LIGHT_STATE now", gw->ip_addr, gw->port, latency ); From c869f8a22a56c4d9aef2214d97dffde04086b9dc Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 1 Apr 2015 23:06:51 -0700 Subject: [PATCH 024/181] Rename the LIFX header timestamp field to at_time Readings of lifx-gem & LIFXKit revealed the actual purpose of this field. --- lifx/wire_proto.c | 4 ++-- lifx/wire_proto.h | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index b1dcb9c..73a6c41 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -196,7 +196,7 @@ lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *hdr) if (hdr->protocol.tagged) { le64toh(hdr->target.tags); } - hdr->timestamp = le64toh(hdr->timestamp); + hdr->at_time = le64toh(hdr->at_time); hdr->packet_type = le16toh(hdr->packet_type); } @@ -256,7 +256,7 @@ lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr) if (hdr->protocol.tagged) { le64toh(hdr->target.tags); } - hdr->timestamp = htole64(hdr->timestamp); + hdr->at_time = htole64(hdr->at_time); hdr->packet_type = htole16(hdr->packet_type); } diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index e9de993..e20dec6 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -61,7 +61,9 @@ struct lgtd_lifx_packet_header { } flags; //! Wrap-around sequence number, LIFX internal use. uint8_t seqn; - uint64le_t timestamp; + //! Apparently this is a unix epoch timestamp in milliseconds at which the + //! payload should be run. + uint64le_t at_time; uint16le_t packet_type; uint8_t reserved[2]; }; From 2dafe1a1b490c1dc04a4654e3c7a4fe163a30997 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 6 Apr 2015 01:43:34 -0700 Subject: [PATCH 025/181] Allow the target argument to be a list We actually need that to support tagging. --- core/jsonrpc.c | 267 ++++++++++++------ core/jsonrpc.h | 20 +- core/proto.c | 38 ++- core/proto.h | 18 +- core/router.c | 69 ++--- core/router.h | 4 +- docs/protocol.rst | 6 +- lifx/gateway.c | 24 +- .../jsonrpc/test_jsonrpc_build_target_list.c | 64 +++++ .../test_jsonrpc_check_and_call_power_off.c | 9 +- ..._check_and_call_power_off_missing_target.c | 4 +- .../test_jsonrpc_check_and_call_power_on.c | 9 +- ...c_check_and_call_power_on_missing_target.c | 4 +- ...onrpc_check_and_call_set_light_from_hsbk.c | 9 +- ..._and_call_set_light_from_hsbk_from_array.c | 9 +- ..._call_set_light_from_hsbk_invalid_params.c | 4 +- ...test_jsonrpc_check_and_call_set_waveform.c | 9 +- ...eck_and_call_set_waveform_invalid_params.c | 4 +- tests/core/jsonrpc/test_jsonrpc_utils.h | 21 +- tests/core/router/test_router_broadcast.c | 4 +- tests/core/router/test_router_device.c | 4 +- .../core/router/test_router_invalid_targets.c | 4 +- tests/core/tests_utils.c | 27 ++ tests/core/tests_utils.h | 1 + 24 files changed, 444 insertions(+), 188 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_build_target_list.c diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 82b0df9..e7b4e84 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -1,4 +1,3 @@ -// Copyright (c) 2015, Louis Opter // // This file is part of lighstd. // @@ -154,6 +153,13 @@ lgtd_jsonrpc_type_string(const jsmntok_t *t, const char *json) return t->type == JSMN_STRING; } +static bool +lgtd_jsonrpc_type_array(const jsmntok_t *t, const char *json) +{ + (void)json; + return t->type == JSMN_ARRAY; +} + static bool lgtd_jsonrpc_type_object_or_array(const jsmntok_t *t, const char *json) { @@ -167,27 +173,30 @@ lgtd_jsonrpc_type_string_number_or_null(const jsmntok_t *t, { return lgtd_jsonrpc_type_number(t, json) || lgtd_jsonrpc_type_null(t, json) - || t->type == JSMN_STRING; + || lgtd_jsonrpc_type_string(t, json); } -static int -lgtd_jsonrpc_consume_object_or_array(const jsmntok_t *tokens, - int ti, - int parsed, - const char *json) +static bool +lgtd_jsonrpc_type_string_or_number(const jsmntok_t *t, + const char *json) { - assert(tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY); - assert(ti < parsed); + return lgtd_jsonrpc_type_string(t, json) + || lgtd_jsonrpc_type_number(t, json); +} - int obj_size = tokens[ti++].size; - while (obj_size-- && ti < parsed) { - if (tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY) { - ti = lgtd_jsonrpc_consume_object_or_array(tokens, ti, parsed, json); - } else { - ti += tokens[ti].size + 1; - } - } - return ti; +static bool __attribute__((unused)) +lgtd_jsonrpc_type_string_or_array(const jsmntok_t *t, const char *json) +{ + return lgtd_jsonrpc_type_string(t, json) + || lgtd_jsonrpc_type_array(t, json); +} + +static bool +lgtd_jsonrpc_type_string_number_or_array(const jsmntok_t *t, const char *json) +{ + return lgtd_jsonrpc_type_string(t, json) + || lgtd_jsonrpc_type_number(t, json) + || lgtd_jsonrpc_type_array(t, json); } static int @@ -211,6 +220,30 @@ lgtd_jsonrpc_float_range_to_uint16(const char *s, int len, int start, int stop) return ((intpart + fracpart) * UINT16_MAX) / range; } +static int +lgtd_jsonrpc_consume_object_or_array(const jsmntok_t *tokens, + int ti, + int parsed, + const char *json) +{ + assert(tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY); + assert(ti < parsed); + + int objsize = tokens[ti].size; + if (tokens[ti++].type == JSMN_OBJECT) { + ti++; // move to the value + } + while (objsize-- && ti < parsed) { + if (tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY) { + ti = lgtd_jsonrpc_consume_object_or_array(tokens, ti, parsed, json); + } else { + ti += tokens[ti].size + 1; // move to the next value + } + } + + return ti; +} + static bool lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, const struct lgtd_jsonrpc_node *schema, @@ -229,7 +262,8 @@ lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, return false; } - for (int si = 0;; si++) { + int si = 0; + for (;; si++) { if (si == schema_size) { ti++; // nothing matched, skip the key break; @@ -272,6 +306,7 @@ lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, // skip the value, if it's an object or an array we need to // skip everything in it: + int value_ntokens = ti; if (tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY) { ti = lgtd_jsonrpc_consume_object_or_array( tokens, ti, ntokens, json @@ -279,6 +314,12 @@ lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, } else { ti++; } + value_ntokens = ti - value_ntokens; + if (si < schema_size && schema[si].ntokens_offset != -1) { + LGTD_JSONRPC_SET_NTOKENS( + output, schema[si].ntokens_offset, value_ntokens + ); + } } for (int si = 0; si != schema_size; si++) { @@ -325,6 +366,7 @@ lgtd_jsonrpc_extract_values_from_schema_and_array(void *output, } // skip the value, if it's an object or an array we need to // skip everything in it: + int value_ntokens = ti; if (tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY) { ti = lgtd_jsonrpc_consume_object_or_array( tokens, ti, ntokens, json @@ -332,6 +374,12 @@ lgtd_jsonrpc_extract_values_from_schema_and_array(void *output, } else { ti++; } + value_ntokens = ti - value_ntokens; + if (schema[si].ntokens_offset != -1) { + LGTD_JSONRPC_SET_NTOKENS( + output, schema[si].ntokens_offset, value_ntokens + ); + } } return si == schema_size; @@ -419,29 +467,32 @@ lgtd_jsonrpc_check_and_extract_request(struct lgtd_jsonrpc_request *request, { static const struct lgtd_jsonrpc_node request_schema[] = { LGTD_JSONRPC_NODE( - "jsonrpc", -1, lgtd_jsonrpc_type_string, false + "jsonrpc", -1, -1, lgtd_jsonrpc_type_string, false ), LGTD_JSONRPC_NODE( "method", offsetof(struct lgtd_jsonrpc_request, method), + -1, lgtd_jsonrpc_type_string, false ), LGTD_JSONRPC_NODE( "params", offsetof(struct lgtd_jsonrpc_request, params), + offsetof(struct lgtd_jsonrpc_request, params_ntokens), lgtd_jsonrpc_type_object_or_array, true ), LGTD_JSONRPC_NODE( "id", offsetof(struct lgtd_jsonrpc_request, id), + -1, lgtd_jsonrpc_type_string_number_or_null, true ) }; - bool ok = lgtd_jsonrpc_extract_values_from_schema_and_dict( + return lgtd_jsonrpc_extract_values_from_schema_and_dict( request, request_schema, LGTD_ARRAY_SIZE(request_schema), @@ -449,38 +500,60 @@ lgtd_jsonrpc_check_and_extract_request(struct lgtd_jsonrpc_request *request, ntokens, json ); - if (ok) { - // XXX We already do that from extract_values_from_schema_and_dict: - if (request->params) { - const jsmntok_t *params = request->params; - int params_ti = params - tokens; - while (params[request->params_ntokens].start < params->end - && params_ti + request->params_ntokens < ntokens) { - request->params_ntokens++; +} + +static bool +lgtd_jsonrpc_build_target_list(struct lgtd_proto_target_list *targets, + struct lgtd_client *client, + const struct lgtd_jsonrpc_request *request, + const jsmntok_t *target, + int target_ntokens, + const char *json) +{ + assert(targets); + assert(client); + assert(request); + assert(target); + assert(target_ntokens >= 1); + assert(json); + + if (lgtd_jsonrpc_type_array(target, json)) { + target_ntokens -= 1; + target++; + } + + for (int ti = target_ntokens; ti--;) { + int token_len = LGTD_JSONRPC_TOKEN_LEN(&target[ti]); + if (lgtd_jsonrpc_type_string_or_number(&target[ti], json)) { + struct lgtd_proto_target *t = malloc(sizeof(*t) + token_len + 1); + if (!t) { + lgtd_warn("can't allocate a new target"); + lgtd_jsonrpc_send_error( + client, request, json, LGTD_JSONRPC_INTERNAL_ERROR, + "Can't allocate memory" + ); + goto error; } + memcpy(t->target, json + target[ti].start, token_len); + t->target[token_len] = '\0'; + SLIST_INSERT_HEAD(targets, t, link); + } else { + lgtd_debug( + "invalid target value %.*s", token_len, json + target[ti].start + ); + lgtd_jsonrpc_send_error( + client, request, json, LGTD_JSONRPC_INVALID_PARAMS, + "Invalid parameters" + ); + goto error; } - return true; } - return false; -} + return true; -static char * -lgtd_jsonrpc_dup_target(struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json, - const jsmntok_t *t) -{ - char *target = strndup( - &json[t->start], LGTD_JSONRPC_TOKEN_LEN(t) - ); - if (!target) { - lgtd_jsonrpc_send_error( - client, request, json, LGTD_JSONRPC_INTERNAL_ERROR, - "Shit's on fire, yo" - ); - } - return target; +error: + lgtd_proto_target_list_clear(targets); + return false; } static void @@ -490,46 +563,53 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, { struct lgtd_jsonrpc_set_light_from_hsbk_args { const jsmntok_t *target; + int target_ntokens; const jsmntok_t *h; const jsmntok_t *s; const jsmntok_t *b; const jsmntok_t *k; const jsmntok_t *t; - } params = { NULL, NULL, NULL, NULL, NULL, NULL }; + } params = { NULL, 0, NULL, NULL, NULL, NULL, NULL }; static const struct lgtd_jsonrpc_node schema[] = { LGTD_JSONRPC_NODE( "target", offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, target), - lgtd_jsonrpc_type_string, + offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, target_ntokens), + lgtd_jsonrpc_type_string_number_or_array, false ), LGTD_JSONRPC_NODE( "hue", offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, h), + -1, lgtd_jsonrpc_type_float_between_0_and_360, false ), LGTD_JSONRPC_NODE( "saturation", offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, s), + -1, lgtd_jsonrpc_type_float_between_0_and_1, false ), LGTD_JSONRPC_NODE( "brightness", offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, b), + -1, lgtd_jsonrpc_type_float_between_0_and_1, false ), LGTD_JSONRPC_NODE( "kelvin", offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, k), + -1, lgtd_jsonrpc_type_integer, false ), LGTD_JSONRPC_NODE( "transition", offsetof(struct lgtd_jsonrpc_set_light_from_hsbk_args, t), + -1, lgtd_jsonrpc_type_integer, false ), @@ -566,14 +646,16 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, goto error_invalid_params; } - char *target; - target = lgtd_jsonrpc_dup_target(client, request, json, params.target); - if (!target) { + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); + ok = lgtd_jsonrpc_build_target_list( + &targets, client, request, params.target, params.target_ntokens, json + ); + if (!ok) { return; } - ok = lgtd_proto_set_light_from_hsbk(target, h, s, b, k, t); - free(target); + ok = lgtd_proto_set_light_from_hsbk(&targets, h, s, b, k, t); + lgtd_proto_target_list_clear(&targets); if (ok) { lgtd_jsonrpc_send_response(client, request, json, "true"); return; @@ -586,28 +668,40 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, ); } -static char * -lgtd_jsonrpc_extract_target_only(struct lgtd_client *client, +static bool +lgtd_jsonrpc_extract_target_list(struct lgtd_proto_target_list *targets, + struct lgtd_client *client, const struct lgtd_jsonrpc_request *request, const char *json) { - const jsmntok_t *target = NULL; + struct lgtd_jsonrpc_target_args { + const jsmntok_t *target; + int target_ntokens; + } params = { NULL, 0 }; static const struct lgtd_jsonrpc_node schema[] = { - LGTD_JSONRPC_NODE("target", 0, lgtd_jsonrpc_type_string, false) + LGTD_JSONRPC_NODE( + "target", + offsetof(struct lgtd_jsonrpc_target_args, target), + offsetof(struct lgtd_jsonrpc_target_args, target_ntokens), + lgtd_jsonrpc_type_string_number_or_array, + false + ) }; bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( - &target, schema, 1, request->params, request->params_ntokens, json + ¶ms, schema, 1, request->params, request->params_ntokens, json ); if (!ok) { lgtd_jsonrpc_send_error( client, request, json, LGTD_JSONRPC_INVALID_PARAMS, "Invalid parameters" ); - return NULL; + return false; } - return lgtd_jsonrpc_dup_target(client, request, json, target); + return lgtd_jsonrpc_build_target_list( + targets, client, request, params.target, params.target_ntokens, json + ); } static void @@ -616,13 +710,14 @@ lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client, const char *json) { - char *target = lgtd_jsonrpc_extract_target_only(client, request, json); - if (!target) { + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); + bool ok = lgtd_jsonrpc_extract_target_list(&targets, client, request, json); + if (!ok) { return; } - bool ok = lgtd_proto_power_on(target); - free(target); + ok = lgtd_proto_power_on(&targets); + lgtd_proto_target_list_clear(&targets); if (ok) { lgtd_jsonrpc_send_response(client, request, json, "true"); return; @@ -641,6 +736,7 @@ lgtd_jsonrpc_check_and_call_set_waveform(struct lgtd_client *client, { struct lgtd_jsonrpc_set_waveform_args { const jsmntok_t *target; + int target_ntokens; const jsmntok_t *waveform; const jsmntok_t *h; const jsmntok_t *s; @@ -650,65 +746,75 @@ lgtd_jsonrpc_check_and_call_set_waveform(struct lgtd_client *client, const jsmntok_t *cycles; const jsmntok_t *skew_ratio; const jsmntok_t *transient; - } params = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + } params = { NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static const struct lgtd_jsonrpc_node schema[] = { LGTD_JSONRPC_NODE( "target", offsetof(struct lgtd_jsonrpc_set_waveform_args, target), - lgtd_jsonrpc_type_string, + offsetof(struct lgtd_jsonrpc_set_waveform_args, target_ntokens), + lgtd_jsonrpc_type_string_number_or_array, false ), LGTD_JSONRPC_NODE( "waveform", offsetof(struct lgtd_jsonrpc_set_waveform_args, waveform), + -1, lgtd_jsonrpc_type_string, false ), LGTD_JSONRPC_NODE( "hue", offsetof(struct lgtd_jsonrpc_set_waveform_args, h), + -1, lgtd_jsonrpc_type_float_between_0_and_360, false ), LGTD_JSONRPC_NODE( "saturation", offsetof(struct lgtd_jsonrpc_set_waveform_args, s), + -1, lgtd_jsonrpc_type_float_between_0_and_1, false ), LGTD_JSONRPC_NODE( "brightness", offsetof(struct lgtd_jsonrpc_set_waveform_args, b), + -1, lgtd_jsonrpc_type_float_between_0_and_1, false ), LGTD_JSONRPC_NODE( "kelvin", offsetof(struct lgtd_jsonrpc_set_waveform_args, k), + -1, lgtd_jsonrpc_type_integer, false ), LGTD_JSONRPC_NODE( "period", offsetof(struct lgtd_jsonrpc_set_waveform_args, period), + -1, lgtd_jsonrpc_type_integer, false ), LGTD_JSONRPC_NODE( "cycles", offsetof(struct lgtd_jsonrpc_set_waveform_args, cycles), + -1, lgtd_jsonrpc_type_integer, false ), LGTD_JSONRPC_NODE( "skew_ratio", offsetof(struct lgtd_jsonrpc_set_waveform_args, skew_ratio), + -1, lgtd_jsonrpc_type_float_between_0_and_1, false ), LGTD_JSONRPC_NODE( "transient", offsetof(struct lgtd_jsonrpc_set_waveform_args, transient), + -1, lgtd_jsonrpc_type_bool, false ), @@ -764,18 +870,18 @@ lgtd_jsonrpc_check_and_call_set_waveform(struct lgtd_client *client, skew_ratio -= UINT16_MAX / 2; bool transient = json[params.transient->start] == 't'; - - char *target; - target = lgtd_jsonrpc_dup_target(client, request, json, params.target); - if (!target) { + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); + ok = lgtd_jsonrpc_build_target_list( + &targets, client, request, params.target, params.target_ntokens, json + ); + if (!ok) { return; } - lgtd_proto_set_waveform( - target, waveform, h, s, b, k, period, cycles, skew_ratio, transient + ok = lgtd_proto_set_waveform( + &targets, waveform, h, s, b, k, period, cycles, skew_ratio, transient ); - - free(target); + lgtd_proto_target_list_clear(&targets); if (ok) { lgtd_jsonrpc_send_response(client, request, json, "true"); return; @@ -794,13 +900,14 @@ lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client, const char *json) { - char *target = lgtd_jsonrpc_extract_target_only(client, request, json); - if (!target) { + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); + bool ok = lgtd_jsonrpc_extract_target_list(&targets, client, request, json); + if (!ok) { return; } - bool ok = lgtd_proto_power_off(target); - free(target); + ok = lgtd_proto_power_off(&targets); + lgtd_proto_target_list_clear(&targets); if (ok) { lgtd_jsonrpc_send_response(client, request, json, "true"); return; diff --git a/core/jsonrpc.h b/core/jsonrpc.h index df86b2a..cf29872 100644 --- a/core/jsonrpc.h +++ b/core/jsonrpc.h @@ -28,6 +28,7 @@ struct lgtd_jsonrpc_node { const char *key; int keylen; int value_offset; + int ntokens_offset; bool (*type_cmp)(const jsmntok_t *, const char *); bool optional; }; @@ -37,14 +38,19 @@ struct lgtd_jsonrpc_node { } while (0) #define LGTD_JSONRPC_GET_JSMNTOK(object, value_offset) \ - *(const jsmntok_t **)(&((char *)(object))[value_offset]); \ + *(const jsmntok_t **)(&((char *)(object))[value_offset]); -#define LGTD_JSONRPC_NODE(key_, value_offset_, fn_type_cmp, optional_) { \ - .key = (key_), \ - .keylen = sizeof((key_)) - 1, \ - .value_offset = (value_offset_), \ - .type_cmp = (fn_type_cmp), \ - .optional = (optional_) \ +#define LGTD_JSONRPC_SET_NTOKENS(object, ntokens_offset, ntokens) do { \ + *(int *)(&(((char *)(object))[ntokens_offset])) = (ntokens); \ +} while (0) + +#define LGTD_JSONRPC_NODE(key_, value_offset_, ntokens_offset_, fn_type_cmp, optional_) { \ + .key = (key_), \ + .keylen = sizeof((key_)) - 1, \ + .value_offset = (value_offset_), \ + .ntokens_offset = (ntokens_offset_), \ + .type_cmp = (fn_type_cmp), \ + .optional = (optional_) \ } #define LGTD_JSONRPC_TOKEN_LEN(t) ((t)->end - (t)->start) diff --git a/core/proto.c b/core/proto.c index 5dcd93b..bf4e294 100644 --- a/core/proto.c +++ b/core/proto.c @@ -27,36 +27,50 @@ #include #include "lifx/wire_proto.h" +#include "time_monotonic.h" +#include "lifx/bulb.h" +#include "proto.h" #include "router.h" #include "lightsd.h" +void lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) +{ + assert(targets); + + while (!SLIST_EMPTY(targets)) { + struct lgtd_proto_target *target = SLIST_FIRST(targets); + SLIST_REMOVE_HEAD(targets, link); + free(target); + } +} + bool -lgtd_proto_power_on(const char *target) +lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) { - assert(target); + assert(targets); struct lgtd_lifx_packet_power_state pkt = { .power = LGTD_LIFX_POWER_ON }; - return lgtd_router_send(target, LGTD_LIFX_SET_POWER_STATE, &pkt); + return lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &pkt); } bool -lgtd_proto_power_off(const char *target) +lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) { - assert(target); + assert(targets); struct lgtd_lifx_packet_power_state pkt = { .power = LGTD_LIFX_POWER_OFF }; - return lgtd_router_send(target, LGTD_LIFX_SET_POWER_STATE, &pkt); + return lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &pkt); } bool -lgtd_proto_set_light_from_hsbk(const char *target, +lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { - assert(target); + assert(targets); assert(hue >= 0 && hue <= UINT16_MAX); assert(saturation >= 0 && saturation <= UINT16_MAX); assert(brightness >= 0 && brightness <= UINT16_MAX); @@ -72,17 +86,17 @@ lgtd_proto_set_light_from_hsbk(const char *target, .transition = transition_msecs }; lgtd_lifx_wire_encode_light_color(&pkt); - return lgtd_router_send(target, LGTD_LIFX_SET_LIGHT_COLOR, &pkt); + return lgtd_router_send(targets, LGTD_LIFX_SET_LIGHT_COLOR, &pkt); } -bool lgtd_proto_set_waveform(const char *target, +bool lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_waveform_type waveform, int hue, int saturation, int brightness, int kelvin, int period, float cycles, int skew_ratio, bool transient) { - assert(target); + assert(targets); assert(hue >= 0 && hue <= UINT16_MAX); assert(saturation >= 0 && saturation <= UINT16_MAX); assert(brightness >= 0 && brightness <= UINT16_MAX); @@ -106,5 +120,5 @@ bool lgtd_proto_set_waveform(const char *target, }; lgtd_lifx_wire_encode_waveform(&pkt); - return lgtd_router_send(target, LGTD_LIFX_SET_WAVEFORM, &pkt); + return lgtd_router_send(targets, LGTD_LIFX_SET_WAVEFORM, &pkt); } diff --git a/core/proto.h b/core/proto.h index 8b525bf..452a86f 100644 --- a/core/proto.h +++ b/core/proto.h @@ -17,10 +17,20 @@ #pragma once -bool lgtd_proto_set_light_from_hsbk(const char *, int, int, int, int, int); -bool lgtd_proto_set_waveform(const char *, +struct lgtd_proto_target { + SLIST_ENTRY(lgtd_proto_target) link; + char target[]; +}; +SLIST_HEAD(lgtd_proto_target_list, lgtd_proto_target); + +void lgtd_proto_target_list_clear(struct lgtd_proto_target_list *); +const struct lgtd_proto_target *lgtd_proto_target_list_add(struct lgtd_proto_target_list *, + const char *, int); + +bool lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *, int, int, int, int, int); +bool lgtd_proto_set_waveform(const struct lgtd_proto_target_list *, enum lgtd_lifx_waveform_type, int, int, int, int, int, float, int, bool); -bool lgtd_proto_power_on(const char *); -bool lgtd_proto_power_off(const char *); +bool lgtd_proto_power_on(const struct lgtd_proto_target_list *); +bool lgtd_proto_power_off(const struct lgtd_proto_target_list *); diff --git a/core/router.c b/core/router.c index 6347eab..be3d5be 100644 --- a/core/router.c +++ b/core/router.c @@ -34,10 +34,12 @@ #include "lifx/wire_proto.h" #include "time_monotonic.h" #include "lifx/bulb.h" +#include "proto.h" +#include "router.h" #include "lifx/gateway.h" #include "lightsd.h" -static void +void lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) { struct lgtd_lifx_packet_header hdr; @@ -63,14 +65,14 @@ lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) } if (pkt_infos) { - lgtd_debug("broadcasting %s", pkt_infos->name); + lgtd_info("broadcasting %s", pkt_infos->name); } } -static void -lgtd_router_device(struct lgtd_lifx_bulb *bulb, - enum lgtd_lifx_packet_type pkt_type, - void *pkt) +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) { assert(bulb); @@ -91,40 +93,43 @@ lgtd_router_device(struct lgtd_lifx_bulb *bulb, bulb->expected_power_on = payload->power; } - lgtd_debug("sending %s to %s", pkt_infos->name, lgtd_addrtoa(bulb->addr)); + lgtd_info("sending %s to %s", pkt_infos->name, lgtd_addrtoa(bulb->addr)); } bool -lgtd_router_send(const char *target, +lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, void *pkt) { - assert(target); - - if (!strcmp(target, "*")) { - lgtd_router_broadcast(pkt_type, pkt); - return true; - } - - if (isxdigit(target[0])) { - const char *endptr = NULL; - errno = 0; - long long device = strtoll(target, (char **)&endptr, 16); - if (*endptr || errno == ERANGE) { - lgtd_debug("invalid target device %s", target); - return false; - } - device = htobe64(device); - struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get( - (uint8_t *)&device + sizeof(device) - LGTD_LIFX_ADDR_LENGTH - ); - if (!bulb) { + assert(targets); + + bool rv = true; + + const struct lgtd_proto_target *target; + SLIST_FOREACH(target, targets, link) { + if (!strcmp(target->target, "*")) { + lgtd_router_broadcast(pkt_type, pkt); + continue; + } else if (isxdigit(target->target[0])) { + const char *endptr = NULL; + errno = 0; + long long device = strtoll(target->target, (char **)&endptr, 16); + if (*endptr || errno == ERANGE) { + lgtd_debug("invalid target device %s", target->target); + rv = false; + } + device = htobe64(device); + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get( + (uint8_t *)&device + sizeof(device) - LGTD_LIFX_ADDR_LENGTH + ); + if (bulb) { + lgtd_router_send_to_device(bulb, pkt_type, pkt); + continue; + } lgtd_debug("target device %#llx not found", device); - return false; } - lgtd_router_device(bulb, pkt_type, pkt); - return true; + rv = false; } - return false; + return rv; } diff --git a/core/router.h b/core/router.h index ffb9048..643551f 100644 --- a/core/router.h +++ b/core/router.h @@ -17,4 +17,6 @@ #pragma once -bool lgtd_router_send(const char *, enum lgtd_lifx_packet_type, void *); +bool lgtd_router_send(const struct lgtd_proto_target_list *, enum lgtd_lifx_packet_type, void *); +void lgtd_router_send_to_device(struct lgtd_lifx_bulb *, enum lgtd_lifx_packet_type, void *); +void lgtd_router_broadcast(enum lgtd_lifx_packet_type, void *); diff --git a/docs/protocol.rst b/docs/protocol.rst index 70db0e8..33737c1 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -20,9 +20,11 @@ bulb(s) the operation should apply: +-----------------------------+-----------------------------------------------+ | ``label`` | directly target the bulb with the given label | +-----------------------------+-----------------------------------------------+ +| ``[#TagName, 123f31a5]`` | composite target (JSON array) | ++-----------------------------+-----------------------------------------------+ -You can use JSON-RPC's batch feature to send a command to multiple targets at -the same time. +A target is either a string, a hexadecimal number (without any prefix like 0x) +or an array of targets. Available methods ----------------- diff --git a/lifx/gateway.c b/lifx/gateway.c index 0d3cc45..6ab2190 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -39,6 +39,7 @@ #include "broadcast.h" #include "timer.h" #include "core/proto.h" +#include "core/router.h" #include "core/lightsd.h" struct lgtd_lifx_gateway_list lgtd_lifx_gateways = @@ -345,23 +346,14 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, lgtd_warnx("clearing dirty_at on %s", b->state.label); b->dirty_at = 0; } else { - char target[LGTD_LIFX_ADDR_LENGTH * 2 + 1]; - snprintf( - target, sizeof(target), "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", - b->addr[0], b->addr[1], b->addr[2], - b->addr[3], b->addr[4], b->addr[5] + lgtd_warnx( + "retransmiting power %s to %s", + b->expected_power_on ? "on" : "off", b->state.label ); - if (b->expected_power_on) { - lgtd_warnx( - "retransmiting power on %s (0x%s)", b->state.label, target - ); - lgtd_proto_power_on(target); - } else { - lgtd_warnx( - "retransmiting power off %s (0x%s)", b->state.label, target - ); - lgtd_proto_power_off(target); - } + struct lgtd_lifx_packet_power_state pkt; + pkt.power = b->expected_power_on ? + LGTD_LIFX_POWER_ON : LGTD_LIFX_POWER_OFF; + lgtd_router_send_to_device(b, LGTD_LIFX_SET_POWER_STATE, &pkt); } } diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c new file mode 100644 index 0000000..62da903 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c @@ -0,0 +1,64 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +static void +test_params(const char *json, const char **expected_targets) +{ + struct lgtd_client client = { .io = NULL }; + struct lgtd_jsonrpc_request request = { .id = NULL }; + + jsmntok_t tokens[32]; + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json) + ); + + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); + + reset_client_write_buf(); + + bool ok = lgtd_jsonrpc_build_target_list( + &targets, &client, &request, tokens, parsed, json + ); + + if (!expected_targets && !SLIST_EMPTY(&targets)) { + if (ok) { + errx(1, "lgtd_jsonrpc_build_target_list returned true on an error"); + } + return; + } + + struct lgtd_proto_target *target; + int i = 0; + SLIST_FOREACH(target, &targets, link) { + if (!expected_targets[i]) { + errx(1, "unexpected target %s", target->target); + } + if (strcmp(target->target, expected_targets[i])) { + errx( + 1, "target mismatch got %s but expected %s", + target->target, expected_targets[i] + ); + } + i++; + } +} + +int +main(void) +{ + const char *expected_1[] = {"on", "12345", "6789", NULL}; + test_params("[\"on\", 12345, \"6789\"]", expected_1); + + const char *expected_2[] = {"#tower", NULL}; + test_params("#tower", expected_2); + + test_params("{\"key\": 42}", NULL); + + test_params("null", NULL); + + const char *expected_3[] = {NULL}; + test_params("[]", expected_3); + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c index cf92dff..647b72e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c @@ -6,10 +6,13 @@ static bool power_off_called = false; bool -lgtd_proto_power_off(const char *target) +lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) { - if (strcmp(target, "*")) { - errx(1, "Invalid target [%s] (expected=[*])", target); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); } power_off_called = true; return true; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c index 67ebfb9..5493284 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c @@ -6,9 +6,9 @@ static bool power_off_called = false; bool -lgtd_proto_power_off(const char *target) +lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) { - (void)target; + (void)targets; power_off_called = true; return true; } diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c index 79e3d79..d74ef75 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c @@ -6,10 +6,13 @@ static bool power_on_called = false; bool -lgtd_proto_power_on(const char *target) +lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) { - if (strcmp(target, "*")) { - errx(1, "Invalid target [%s] (expected=[*])", target); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); } power_on_called = true; return true; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c index 14e2b7c..4305307 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c @@ -6,9 +6,9 @@ static bool power_on_called = false; bool -lgtd_proto_power_on(const char *target) +lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) { - (void)target; + (void)targets; power_on_called = true; return true; } diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c index 86fe2a4..b1028fb 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -6,15 +6,18 @@ static bool set_light_called = false; bool -lgtd_proto_set_light_from_hsbk(const char *target, +lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { - if (strcmp(target, "*")) { - errx(1, "Invalid target [%s] (expected=[*])", target); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); } int expected_hue = lgtd_jsonrpc_float_range_to_uint16( "324.2341", strlen("324.2341"), 0, 360 diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c index a04a12c..29a156f 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -6,15 +6,18 @@ static bool set_light_called = false; bool -lgtd_proto_set_light_from_hsbk(const char *target, +lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { - if (strcmp(target, "*")) { - errx(1, "Invalid target [%s] (expected=[*])", target); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); } int expected_hue = lgtd_jsonrpc_float_range_to_uint16( "324.2341", strlen("324.2341"), 0, 360 diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c index d83d76c..d0a3e35 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -6,14 +6,14 @@ static bool set_light_called = false; bool -lgtd_proto_set_light_from_hsbk(const char *target, +lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { - (void)target; + (void)targets; (void)hue; (void)saturation; (void)brightness; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c index 91d88d9..56ac89e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c @@ -6,15 +6,18 @@ static bool set_waveform_called = false; bool -lgtd_proto_set_waveform(const char *target, +lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_waveform_type waveform, int hue, int saturation, int brightness, int kelvin, int period, float cycles, int skew_ratio, bool transient) { - if (strcmp(target, "*")) { - errx(1, "Invalid target [%s] (expected=[*])", target); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); } int expected_hue = lgtd_jsonrpc_float_range_to_uint16( "324.2341", strlen("324.2341"), 0, 360 diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c index d8e24a3..936528d 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c @@ -6,14 +6,14 @@ static bool set_waveform_called = false; bool -lgtd_proto_set_waveform(const char *target, +lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_waveform_type waveform, int hue, int saturation, int brightness, int kelvin, int period, float cycles, int skew_ratio, bool transient) { - (void)target; + (void)targets; (void)waveform; (void)hue; (void)saturation; diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index 2adc6bd..27ad8db 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -30,16 +30,21 @@ bufferevent_write(struct bufferevent *bev, const void *data, size_t nbytes) return 0; } +void lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) +{ + assert(targets); +} + #ifndef LGTD_TESTING_SET_LIGHT_FROM_HSBK bool -lgtd_proto_set_light_from_hsbk(const char *target, +lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { - (void)target; + (void)targets; (void)hue; (void)saturation; (void)brightness; @@ -51,31 +56,31 @@ lgtd_proto_set_light_from_hsbk(const char *target, #ifndef LGTD_TESTING_POWER_ON bool -lgtd_proto_power_on(const char *target) +lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) { - (void)target; + (void)targets; return true; } #endif #ifndef LGTD_TESTING_POWER_OFF bool -lgtd_proto_power_off(const char *target) +lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) { - (void)target; + (void)targets; return true; } #endif #ifndef LGTD_TESTING_SET_WAVEFORM -bool lgtd_proto_set_waveform(const char *target, +bool lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_waveform_type waveform, int hue, int saturation, int brightness, int kelvin, int period, float cycles, int skew_ratio, bool transient) { - (void)target; + (void)targets; (void)waveform; (void)hue; (void)saturation; diff --git a/tests/core/router/test_router_broadcast.c b/tests/core/router/test_router_broadcast.c index 9151a6e..08b44b7 100644 --- a/tests/core/router/test_router_broadcast.c +++ b/tests/core/router/test_router_broadcast.c @@ -14,7 +14,9 @@ main(void) struct lgtd_lifx_packet_power_state payload = { .power = LGTD_LIFX_POWER_ON }; - lgtd_router_send("*", LGTD_LIFX_SET_POWER_STATE, &payload); + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &payload); if (lgtd_tests_gw_pkt_queue_size != 2) { lgtd_errx(1, "2 packets should have been sent"); diff --git a/tests/core/router/test_router_device.c b/tests/core/router/test_router_device.c index 3d25154..af080ec 100644 --- a/tests/core/router/test_router_device.c +++ b/tests/core/router/test_router_device.c @@ -16,7 +16,9 @@ main(void) struct lgtd_lifx_packet_power_state payload = { .power = LGTD_LIFX_POWER_ON }; - lgtd_router_send("1", LGTD_LIFX_SET_POWER_STATE, &payload); + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("1", NULL); + lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &payload); if (lgtd_tests_gw_pkt_queue_size != 1) { lgtd_errx(1, "1 packet should have been sent"); diff --git a/tests/core/router/test_router_invalid_targets.c b/tests/core/router/test_router_invalid_targets.c index b19559e..7d912e8 100644 --- a/tests/core/router/test_router_invalid_targets.c +++ b/tests/core/router/test_router_invalid_targets.c @@ -9,7 +9,9 @@ test_target(const char *target) struct lgtd_lifx_packet_power_state payload = { .power = LGTD_LIFX_POWER_ON }; - bool ok = lgtd_router_send(target, LGTD_LIFX_SET_POWER_STATE, &payload); + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list(target, NULL); + bool ok = lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &payload); if (ok) { lgtd_errx(1, "router_send didn't return false for an unknown device"); } diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index 037a661..1df0d05 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,7 @@ #include "lifx/wire_proto.h" #include "core/time_monotonic.h" +#include "core/proto.h" #include "lifx/bulb.h" #include "lifx/gateway.h" #include "tests_utils.h" @@ -45,3 +47,28 @@ lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *gw, uint64_t addr) return lgtd_lifx_bulb_open(gw, bulb_addr.as_array); } + +struct lgtd_proto_target_list * +lgtd_tests_build_target_list(const char *target, ...) +{ + struct lgtd_proto_target_list *targets = malloc(sizeof(*targets)); + SLIST_INIT(targets); + + struct lgtd_proto_target *tail = malloc( + sizeof(*tail) + strlen(target) + 1 + ); + strcpy(tail->target, target); + SLIST_INSERT_HEAD(targets, tail, link); + + va_list ap; + va_start(ap, target); + while ((target = va_arg(ap, const char *))) { + struct lgtd_proto_target *t = malloc(sizeof(*t) + strlen(target) + 1); + strcpy(t->target, target); + SLIST_INSERT_AFTER(tail, t, link); + tail = t; + } + va_end(ap); + + return targets; +} diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index a4a43b1..88aff54 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -2,3 +2,4 @@ struct lgtd_lifx_gateway *lgtd_tests_insert_mock_gateway(int); struct lgtd_lifx_bulb *lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *, uint64_t); +struct lgtd_proto_target_list *lgtd_tests_build_target_list(const char *, ...); From 68552e50fa026c4b3d27813b86a3ce637af32e00 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 11 Apr 2015 13:05:49 -0700 Subject: [PATCH 026/181] Return responses and errors from lgtd_proto We'll need this for debugging soon enough and to return real responses as well. --- core/client.c | 23 +- core/client.h | 27 +- core/jsonrpc.c | 274 ++++++++---------- core/jsonrpc.h | 12 +- core/lightsd.c | 1 + core/listen.c | 1 + core/proto.c | 58 ++-- core/proto.h | 14 +- core/router.c | 3 + docs/protocol.rst | 4 + lifx/gateway.c | 3 + .../jsonrpc/test_jsonrpc_build_target_list.c | 8 +- .../test_jsonrpc_check_and_call_power_off.c | 27 +- ..._check_and_call_power_off_missing_target.c | 13 +- .../test_jsonrpc_check_and_call_power_on.c | 27 +- ...c_check_and_call_power_on_missing_target.c | 13 +- ...onrpc_check_and_call_set_light_from_hsbk.c | 27 +- ..._and_call_set_light_from_hsbk_from_array.c | 27 +- ..._call_set_light_from_hsbk_invalid_params.c | 17 +- ...test_jsonrpc_check_and_call_set_waveform.c | 27 +- ...eck_and_call_set_waveform_invalid_params.c | 17 +- tests/core/jsonrpc/test_jsonrpc_send_error.c | 9 +- tests/core/jsonrpc/test_jsonrpc_utils.h | 40 +-- tests/core/tests_utils.c | 3 + 24 files changed, 334 insertions(+), 341 deletions(-) diff --git a/core/client.c b/core/client.c index 967ce06..5a2c588 100644 --- a/core/client.c +++ b/core/client.c @@ -16,6 +16,7 @@ // along with lighstd. If not, see . #include +#include #include #include #include @@ -26,9 +27,11 @@ #include #include +#include "lifx/wire_proto.h" #include "jsmn.h" -#include "client.h" #include "jsonrpc.h" +#include "client.h" +#include "proto.h" #include "lightsd.h" struct lgtd_client_list lgtd_clients = LIST_HEAD_INITIALIZER(&lgtd_clients); @@ -107,7 +110,9 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) bufsz = evbuffer_get_length(input); goto retry_after_pullup; default: - lgtd_jsonrpc_dispatch_request(client, buf, rv); + client->json = buf; + lgtd_jsonrpc_dispatch_request(client, rv); + client->json = NULL; evbuffer_drain(input, bufsz); jsmn_init(&client->jsmn_ctx); break; @@ -131,6 +136,20 @@ lgtd_client_event_callback(struct bufferevent *bev, short events, void *ctx) } } +void +lgtd_client_send_response(struct lgtd_client *client, const char *msg) +{ + lgtd_jsonrpc_send_response(client, msg); +} + +void +lgtd_client_send_error(struct lgtd_client *client, + enum lgtd_client_error_code error, + const char *msg) +{ + lgtd_jsonrpc_send_error(client, (enum lgtd_jsonrpc_error_code)error, msg); +} + struct lgtd_client * lgtd_client_open(evutil_socket_t peer, const struct sockaddr_storage *peer_addr) { diff --git a/core/client.h b/core/client.h index e7439de..bf1307b 100644 --- a/core/client.h +++ b/core/client.h @@ -20,13 +20,25 @@ enum { LGTD_CLIENT_JSMN_TOKENS_NUM = 48 }; enum { LGTD_CLIENT_MAX_REQUEST_BUF_SIZE = 2048 }; +enum lgtd_client_error_code { + LGTD_CLIENT_SUCCESS = LGTD_JSONRPC_SUCCESS, + LGTD_CLIENT_PARSE_ERROR = LGTD_JSONRPC_PARSE_ERROR, + LGTD_CLIENT_INVALID_REQUEST = LGTD_JSONRPC_INVALID_REQUEST, + LGTD_CLIENT_METHOD_NOT_FOUND = LGTD_JSONRPC_METHOD_NOT_FOUND, + LGTD_CLIENT_INVALID_PARAMS = LGTD_JSONRPC_INVALID_PARAMS, + LGTD_CLIENT_INTERNAL_ERROR = LGTD_JSONRPC_INTERNAL_ERROR, + LGTD_CLIENT_SERVER_ERROR = LGTD_JSONRPC_SERVER_ERROR +}; + struct lgtd_client { - LIST_ENTRY(lgtd_client) link; - struct bufferevent *io; - char ip_addr[INET6_ADDRSTRLEN]; - uint16_t port; - jsmn_parser jsmn_ctx; - jsmntok_t jsmn_tokens[LGTD_CLIENT_JSMN_TOKENS_NUM]; + LIST_ENTRY(lgtd_client) link; + struct bufferevent *io; + char ip_addr[INET6_ADDRSTRLEN]; + uint16_t port; + jsmn_parser jsmn_ctx; + jsmntok_t jsmn_tokens[LGTD_CLIENT_JSMN_TOKENS_NUM]; + const char *json; + struct lgtd_jsonrpc_request *current_request; }; LIST_HEAD(lgtd_client_list, lgtd_client); @@ -36,3 +48,6 @@ LIST_HEAD(lgtd_client_list, lgtd_client); struct lgtd_client *lgtd_client_open(evutil_socket_t, const struct sockaddr_storage *); void lgtd_client_close_all(void); + +void lgtd_client_send_response(struct lgtd_client *, const char *); +void lgtd_client_send_error(struct lgtd_client *, enum lgtd_client_error_code, const char *); diff --git a/core/jsonrpc.c b/core/jsonrpc.c index e7b4e84..bb05dc7 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -32,8 +32,8 @@ #include "lifx/wire_proto.h" #include "jsmn.h" -#include "client.h" #include "jsonrpc.h" +#include "client.h" #include "proto.h" #include "lightsd.h" @@ -408,35 +408,31 @@ lgtd_jsonrpc_extract_and_validate_params_against_schema(void *output, } static void -lgtd_jsonrpc_write_id(struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json) +lgtd_jsonrpc_write_id(struct lgtd_client *client) { - if (!request->id) { + if (!client->current_request->id) { LGTD_CLIENT_WRITE_STRING(client, "null"); return; } int start, stop; - if (request->id->type == JSMN_STRING) { // get the quotes - start = request->id->start - 1; - stop = request->id->end + 1; + if (client->current_request->id->type == JSMN_STRING) { // get the quotes + start = client->current_request->id->start - 1; + stop = client->current_request->id->end + 1; } else { - start = request->id->start; - stop = request->id->end; + start = client->current_request->id->start; + stop = client->current_request->id->end; } - bufferevent_write(client->io, &json[start], stop - start); + bufferevent_write(client->io, &client->json[start], stop - start); } void lgtd_jsonrpc_send_error(struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json, enum lgtd_jsonrpc_error_code code, const char *message) { LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); - lgtd_jsonrpc_write_id(client, request, json); + lgtd_jsonrpc_write_id(client); LGTD_CLIENT_WRITE_STRING(client, ", \"error\": {\"code\": "); char str_code[8] = { 0 }; snprintf(str_code, sizeof(str_code), "%d", code); @@ -448,12 +444,10 @@ lgtd_jsonrpc_send_error(struct lgtd_client *client, void lgtd_jsonrpc_send_response(struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json, const char *result) { LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); - lgtd_jsonrpc_write_id(client, request, json); + lgtd_jsonrpc_write_id(client); LGTD_CLIENT_WRITE_STRING(client, ", \"result\": "); LGTD_CLIENT_WRITE_STRING(client, result); LGTD_CLIENT_WRITE_STRING(client, "}"); @@ -505,45 +499,41 @@ lgtd_jsonrpc_check_and_extract_request(struct lgtd_jsonrpc_request *request, static bool lgtd_jsonrpc_build_target_list(struct lgtd_proto_target_list *targets, struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, const jsmntok_t *target, - int target_ntokens, - const char *json) + int target_ntokens) { assert(targets); assert(client); - assert(request); assert(target); assert(target_ntokens >= 1); - assert(json); - if (lgtd_jsonrpc_type_array(target, json)) { + if (lgtd_jsonrpc_type_array(target, client->json)) { target_ntokens -= 1; target++; } for (int ti = target_ntokens; ti--;) { int token_len = LGTD_JSONRPC_TOKEN_LEN(&target[ti]); - if (lgtd_jsonrpc_type_string_or_number(&target[ti], json)) { + if (lgtd_jsonrpc_type_string_or_number(&target[ti], client->json)) { struct lgtd_proto_target *t = malloc(sizeof(*t) + token_len + 1); if (!t) { lgtd_warn("can't allocate a new target"); lgtd_jsonrpc_send_error( - client, request, json, LGTD_JSONRPC_INTERNAL_ERROR, - "Can't allocate memory" + client, LGTD_JSONRPC_INTERNAL_ERROR, "Can't allocate memory" ); goto error; } - memcpy(t->target, json + target[ti].start, token_len); + memcpy(t->target, client->json + target[ti].start, token_len); t->target[token_len] = '\0'; SLIST_INSERT_HEAD(targets, t, link); } else { lgtd_debug( - "invalid target value %.*s", token_len, json + target[ti].start + "invalid target value %.*s", + token_len, + client->json + target[ti].start ); lgtd_jsonrpc_send_error( - client, request, json, LGTD_JSONRPC_INVALID_PARAMS, - "Invalid parameters" + client, LGTD_JSONRPC_INVALID_PARAMS, "Invalid parameters" ); goto error; } @@ -557,9 +547,7 @@ lgtd_jsonrpc_build_target_list(struct lgtd_proto_target_list *targets, } static void -lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json) +lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client) { struct lgtd_jsonrpc_set_light_from_hsbk_args { const jsmntok_t *target; @@ -619,120 +607,53 @@ lgtd_jsonrpc_check_and_call_set_light_from_hsbk(struct lgtd_client *client, ¶ms, schema, LGTD_ARRAY_SIZE(schema), - request->params, - request->params_ntokens, - json + client->current_request->params, + client->current_request->params_ntokens, + client->json ); if (!ok) { goto error_invalid_params; } int h = lgtd_jsonrpc_float_range_to_uint16( - &json[params.h->start], LGTD_JSONRPC_TOKEN_LEN(params.h), 0, 360 + &client->json[params.h->start], LGTD_JSONRPC_TOKEN_LEN(params.h), 0, 360 ); int s = lgtd_jsonrpc_float_range_to_uint16( - &json[params.s->start], LGTD_JSONRPC_TOKEN_LEN(params.s), 0, 1 + &client->json[params.s->start], LGTD_JSONRPC_TOKEN_LEN(params.s), 0, 1 ); int b = lgtd_jsonrpc_float_range_to_uint16( - &json[params.b->start], LGTD_JSONRPC_TOKEN_LEN(params.b), 0, 1 + &client->json[params.b->start], LGTD_JSONRPC_TOKEN_LEN(params.b), 0, 1 ); errno = 0; - int k = strtol(&json[params.k->start], NULL, 10); + int k = strtol(&client->json[params.k->start], NULL, 10); if (k < 2500 || k > 9000 || errno == ERANGE) { goto error_invalid_params; } - int t = strtol(&json[params.t->start], NULL, 10); + int t = strtol(&client->json[params.t->start], NULL, 10); if (t < 0 || errno == ERANGE) { goto error_invalid_params; } struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); ok = lgtd_jsonrpc_build_target_list( - &targets, client, request, params.target, params.target_ntokens, json + &targets, client, params.target, params.target_ntokens ); if (!ok) { return; } - ok = lgtd_proto_set_light_from_hsbk(&targets, h, s, b, k, t); + lgtd_proto_set_light_from_hsbk(client, &targets, h, s, b, k, t); lgtd_proto_target_list_clear(&targets); - if (ok) { - lgtd_jsonrpc_send_response(client, request, json, "true"); - return; - } + return; error_invalid_params: lgtd_jsonrpc_send_error( - client, request, json, LGTD_JSONRPC_INVALID_PARAMS, - "Invalid parameters" - ); -} - -static bool -lgtd_jsonrpc_extract_target_list(struct lgtd_proto_target_list *targets, - struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json) -{ - struct lgtd_jsonrpc_target_args { - const jsmntok_t *target; - int target_ntokens; - } params = { NULL, 0 }; - static const struct lgtd_jsonrpc_node schema[] = { - LGTD_JSONRPC_NODE( - "target", - offsetof(struct lgtd_jsonrpc_target_args, target), - offsetof(struct lgtd_jsonrpc_target_args, target_ntokens), - lgtd_jsonrpc_type_string_number_or_array, - false - ) - }; - - bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( - ¶ms, schema, 1, request->params, request->params_ntokens, json - ); - if (!ok) { - lgtd_jsonrpc_send_error( - client, request, json, LGTD_JSONRPC_INVALID_PARAMS, - "Invalid parameters" - ); - return false; - } - - return lgtd_jsonrpc_build_target_list( - targets, client, request, params.target, params.target_ntokens, json - ); -} - -static void -lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json) -{ - - struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); - bool ok = lgtd_jsonrpc_extract_target_list(&targets, client, request, json); - if (!ok) { - return; - } - - ok = lgtd_proto_power_on(&targets); - lgtd_proto_target_list_clear(&targets); - if (ok) { - lgtd_jsonrpc_send_response(client, request, json, "true"); - return; - } - - lgtd_jsonrpc_send_error( - client, request, json, LGTD_JSONRPC_INVALID_PARAMS, - "Invalid parameters" + client, LGTD_JSONRPC_INVALID_PARAMS, "Invalid parameters" ); } static void -lgtd_jsonrpc_check_and_call_set_waveform(struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json) +lgtd_jsonrpc_check_and_call_set_waveform(struct lgtd_client *client) { struct lgtd_jsonrpc_set_waveform_args { const jsmntok_t *target; @@ -824,9 +745,9 @@ lgtd_jsonrpc_check_and_call_set_waveform(struct lgtd_client *client, ¶ms, schema, LGTD_ARRAY_SIZE(schema), - request->params, - request->params_ntokens, - json + client->current_request->params, + client->current_request->params_ntokens, + client->json ); if (!ok) { goto error_invalid_params; @@ -834,95 +755,128 @@ lgtd_jsonrpc_check_and_call_set_waveform(struct lgtd_client *client, enum lgtd_lifx_waveform_type waveform; waveform = lgtd_lifx_wire_waveform_string_id_to_type( - &json[params.waveform->start], LGTD_JSONRPC_TOKEN_LEN(params.waveform) + &client->json[params.waveform->start], LGTD_JSONRPC_TOKEN_LEN(params.waveform) ); if (waveform == LGTD_LIFX_WAVEFORM_INVALID) { goto error_invalid_params; } int h = lgtd_jsonrpc_float_range_to_uint16( - &json[params.h->start], LGTD_JSONRPC_TOKEN_LEN(params.h), 0, 360 + &client->json[params.h->start], LGTD_JSONRPC_TOKEN_LEN(params.h), 0, 360 ); int s = lgtd_jsonrpc_float_range_to_uint16( - &json[params.s->start], LGTD_JSONRPC_TOKEN_LEN(params.s), 0, 1 + &client->json[params.s->start], LGTD_JSONRPC_TOKEN_LEN(params.s), 0, 1 ); int b = lgtd_jsonrpc_float_range_to_uint16( - &json[params.b->start], LGTD_JSONRPC_TOKEN_LEN(params.b), 0, 1 + &client->json[params.b->start], LGTD_JSONRPC_TOKEN_LEN(params.b), 0, 1 ); errno = 0; - int k = strtol(&json[params.k->start], NULL, 10); + int k = strtol(&client->json[params.k->start], NULL, 10); if (k < 2500 || k > 9000 || errno == ERANGE) { goto error_invalid_params; } - int period = strtol(&json[params.period->start], NULL, 10); + int period = strtol(&client->json[params.period->start], NULL, 10); if (period <= 0 || errno == ERANGE) { goto error_invalid_params; } - int cycles = strtol(&json[params.cycles->start], NULL, 10); + int cycles = strtol(&client->json[params.cycles->start], NULL, 10); if (cycles <= 0 || errno == ERANGE) { goto error_invalid_params; } int skew_ratio = lgtd_jsonrpc_float_range_to_uint16( - &json[params.skew_ratio->start], + &client->json[params.skew_ratio->start], LGTD_JSONRPC_TOKEN_LEN(params.skew_ratio), 0, 1 ); skew_ratio -= UINT16_MAX / 2; - bool transient = json[params.transient->start] == 't'; + bool transient = client->json[params.transient->start] == 't'; struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); ok = lgtd_jsonrpc_build_target_list( - &targets, client, request, params.target, params.target_ntokens, json + &targets, client, params.target, params.target_ntokens ); if (!ok) { return; } - ok = lgtd_proto_set_waveform( - &targets, waveform, h, s, b, k, period, cycles, skew_ratio, transient + lgtd_proto_set_waveform( + client, &targets, + waveform, h, s, b, k, + period, cycles, skew_ratio, transient ); lgtd_proto_target_list_clear(&targets); - if (ok) { - lgtd_jsonrpc_send_response(client, request, json, "true"); - return; - } + return; error_invalid_params: lgtd_jsonrpc_send_error( - client, request, json, LGTD_JSONRPC_INVALID_PARAMS, - "Invalid parameters" + client, LGTD_JSONRPC_INVALID_PARAMS, "Invalid parameters" + ); +} + +static bool +lgtd_jsonrpc_extract_target_list(struct lgtd_proto_target_list *targets, + struct lgtd_client *client) +{ + struct lgtd_jsonrpc_target_args { + const jsmntok_t *target; + int target_ntokens; + } params = { NULL, 0 }; + static const struct lgtd_jsonrpc_node schema[] = { + LGTD_JSONRPC_NODE( + "target", + offsetof(struct lgtd_jsonrpc_target_args, target), + offsetof(struct lgtd_jsonrpc_target_args, target_ntokens), + lgtd_jsonrpc_type_string_number_or_array, + false + ) + }; + + struct lgtd_jsonrpc_request *req = client->current_request; + bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( + ¶ms, schema, 1, req->params, req->params_ntokens, client->json + ); + if (!ok) { + lgtd_jsonrpc_send_error( + client, LGTD_JSONRPC_INVALID_PARAMS, "Invalid parameters" + ); + return false; + } + + return lgtd_jsonrpc_build_target_list( + targets, client, params.target, params.target_ntokens ); } static void -lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client, - const struct lgtd_jsonrpc_request *request, - const char *json) +lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client) { struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); - bool ok = lgtd_jsonrpc_extract_target_list(&targets, client, request, json); + bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); if (!ok) { return; } - ok = lgtd_proto_power_off(&targets); + lgtd_proto_power_on(client, &targets); lgtd_proto_target_list_clear(&targets); - if (ok) { - lgtd_jsonrpc_send_response(client, request, json, "true"); +} + +static void +lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client) +{ + + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); + bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); + if (!ok) { return; } - lgtd_jsonrpc_send_error( - client, request, json, LGTD_JSONRPC_INVALID_PARAMS, - "Invalid parameters" - ); + lgtd_proto_power_off(client, &targets); + lgtd_proto_target_list_clear(&targets); } void -lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, - const char *json, - int parsed) +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) { static const struct lgtd_jsonrpc_method methods[] = { LGTD_JSONRPC_METHOD( @@ -947,22 +901,20 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, assert(client); assert(parsed >= 0); - const jsmntok_t *tokens = client->jsmn_tokens; - // TODO: batch requests struct lgtd_jsonrpc_request request; memset(&request, 0, sizeof(request)); bool ok = lgtd_jsonrpc_check_and_extract_request( &request, - tokens, + client->jsmn_tokens, parsed, - json + client->json ); + client->current_request = &request; if (!ok) { lgtd_jsonrpc_send_error( - client, &request, json, LGTD_JSONRPC_INVALID_REQUEST, - "Invalid request" + client, LGTD_JSONRPC_INVALID_REQUEST, "Invalid request" ); return; } @@ -976,24 +928,26 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, continue; } int diff = memcmp( - methods[i].name, &json[request.method->start], methods[i].namelen + methods[i].name, &client->json[request.method->start], methods[i].namelen ); if (!diff) { int params_count = request.params->size; if (params_count != methods[i].params_count) { lgtd_jsonrpc_send_error( - client, &request, json, LGTD_JSONRPC_INVALID_PARAMS, + client, LGTD_JSONRPC_INVALID_PARAMS, "Invalid number of parameters" ); - return; + goto error; } - methods[i].method(client, &request, json); + methods[i].method(client); + client->current_request = NULL; return; } } lgtd_jsonrpc_send_error( - client, &request, json, LGTD_JSONRPC_METHOD_NOT_FOUND, - "Method not found" + client, LGTD_JSONRPC_METHOD_NOT_FOUND, "Method not found" ); +error: + client->current_request = NULL; } diff --git a/core/jsonrpc.h b/core/jsonrpc.h index cf29872..709ce70 100644 --- a/core/jsonrpc.h +++ b/core/jsonrpc.h @@ -17,6 +17,8 @@ #pragma once +struct lgtd_client; + struct lgtd_jsonrpc_request { const jsmntok_t *method; const jsmntok_t *params; @@ -59,9 +61,7 @@ struct lgtd_jsonrpc_method { const char *name; int namelen; int params_count; - void (*method)(struct lgtd_client *, - const struct lgtd_jsonrpc_request *, - const char *); + void (*method)(struct lgtd_client *); }; #define LGTD_JSONRPC_METHOD(name_, params_count_, method_) { \ @@ -81,14 +81,10 @@ enum lgtd_jsonrpc_error_code { LGTD_JSONRPC_SERVER_ERROR = -32000 // (to -32099) }; -void lgtd_jsonrpc_dispatch_request(struct lgtd_client *, const char *, int); +void lgtd_jsonrpc_dispatch_request(struct lgtd_client *, int); void lgtd_jsonrpc_send_error(struct lgtd_client *, - const struct lgtd_jsonrpc_request *, - const char *, enum lgtd_jsonrpc_error_code, const char *); void lgtd_jsonrpc_send_response(struct lgtd_client *, - const struct lgtd_jsonrpc_request *, - const char *, const char *); diff --git a/core/lightsd.c b/core/lightsd.c index 295dd7c..2397932 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -42,6 +42,7 @@ #include "lifx/timer.h" #include "version.h" #include "jsmn.h" +#include "jsonrpc.h" #include "client.h" #include "listen.h" #include "lightsd.h" diff --git a/core/listen.c b/core/listen.c index faebdbf..773c2fe 100644 --- a/core/listen.c +++ b/core/listen.c @@ -26,6 +26,7 @@ #include #include "jsmn.h" +#include "jsonrpc.h" #include "client.h" #include "listen.h" #include "lightsd.h" diff --git a/core/proto.c b/core/proto.c index bf4e294..7df6bd1 100644 --- a/core/proto.c +++ b/core/proto.c @@ -26,14 +26,24 @@ #include #include +#include + #include "lifx/wire_proto.h" #include "time_monotonic.h" #include "lifx/bulb.h" +#include "jsmn.h" +#include "jsonrpc.h" +#include "client.h" #include "proto.h" #include "router.h" #include "lightsd.h" -void lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) +#define SEND_RESULT(client, ok) do { \ + lgtd_jsonrpc_send_response((client), (ok) ? "true" : "false"); \ +} while(0) + +void +lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) { assert(targets); @@ -44,26 +54,33 @@ void lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) } } -bool -lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) { assert(targets); struct lgtd_lifx_packet_power_state pkt = { .power = LGTD_LIFX_POWER_ON }; - return lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &pkt); + SEND_RESULT( + client, lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &pkt) + ); } -bool -lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) +void +lgtd_proto_power_off(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) { assert(targets); struct lgtd_lifx_packet_power_state pkt = { .power = LGTD_LIFX_POWER_OFF }; - return lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &pkt); + SEND_RESULT( + client, lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &pkt) + ); } -bool -lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, +void +lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, @@ -85,16 +102,21 @@ lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, .kelvin = kelvin, .transition = transition_msecs }; + lgtd_lifx_wire_encode_light_color(&pkt); - return lgtd_router_send(targets, LGTD_LIFX_SET_LIGHT_COLOR, &pkt); + SEND_RESULT( + client, lgtd_router_send(targets, LGTD_LIFX_SET_LIGHT_COLOR, &pkt)) + ; } -bool lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, - enum lgtd_lifx_waveform_type waveform, - int hue, int saturation, - int brightness, int kelvin, - int period, float cycles, - int skew_ratio, bool transient) +void +lgtd_proto_set_waveform(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_waveform_type waveform, + int hue, int saturation, + int brightness, int kelvin, + int period, float cycles, + int skew_ratio, bool transient) { assert(targets); assert(hue >= 0 && hue <= UINT16_MAX); @@ -120,5 +142,7 @@ bool lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, }; lgtd_lifx_wire_encode_waveform(&pkt); - return lgtd_router_send(targets, LGTD_LIFX_SET_WAVEFORM, &pkt); + SEND_RESULT( + client, lgtd_router_send(targets, LGTD_LIFX_SET_WAVEFORM, &pkt) + ); } diff --git a/core/proto.h b/core/proto.h index 452a86f..677f780 100644 --- a/core/proto.h +++ b/core/proto.h @@ -24,13 +24,17 @@ struct lgtd_proto_target { SLIST_HEAD(lgtd_proto_target_list, lgtd_proto_target); void lgtd_proto_target_list_clear(struct lgtd_proto_target_list *); -const struct lgtd_proto_target *lgtd_proto_target_list_add(struct lgtd_proto_target_list *, +const struct lgtd_proto_target *lgtd_proto_target_list_add(struct lgtd_client *, + struct lgtd_proto_target_list *, const char *, int); -bool lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *, int, int, int, int, int); -bool lgtd_proto_set_waveform(const struct lgtd_proto_target_list *, +void lgtd_proto_set_light_from_hsbk(struct lgtd_client *, + const struct lgtd_proto_target_list *, + int, int, int, int, int); +void lgtd_proto_set_waveform(struct lgtd_client *, + const struct lgtd_proto_target_list *, enum lgtd_lifx_waveform_type, int, int, int, int, int, float, int, bool); -bool lgtd_proto_power_on(const struct lgtd_proto_target_list *); -bool lgtd_proto_power_off(const struct lgtd_proto_target_list *); +void lgtd_proto_power_on(struct lgtd_client *, const struct lgtd_proto_target_list *); +void lgtd_proto_power_off(struct lgtd_client *, const struct lgtd_proto_target_list *); diff --git a/core/router.c b/core/router.c index be3d5be..d52dc74 100644 --- a/core/router.c +++ b/core/router.c @@ -34,6 +34,9 @@ #include "lifx/wire_proto.h" #include "time_monotonic.h" #include "lifx/bulb.h" +#include "jsmn.h" +#include "jsonrpc.h" +#include "client.h" #include "proto.h" #include "router.h" #include "lifx/gateway.h" diff --git a/docs/protocol.rst b/docs/protocol.rst index 33737c1..8ef08e3 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -60,4 +60,8 @@ Available methods end of the waveform, otherwise it will revert back to its original state. +.. function:: tag_list(tag) + + Return an array of labels or adresses of the devices having the given tag. + .. vim: set tw=80 spelllang=en spell: diff --git a/lifx/gateway.c b/lifx/gateway.c index 6ab2190..f58e647 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -38,6 +38,9 @@ #include "gateway.h" #include "broadcast.h" #include "timer.h" +#include "core/jsmn.h" +#include "core/jsonrpc.h" +#include "core/client.h" #include "core/proto.h" #include "core/router.h" #include "core/lightsd.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c index 62da903..efdeca1 100644 --- a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c +++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c @@ -5,8 +5,10 @@ static void test_params(const char *json, const char **expected_targets) { - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request request = { .id = NULL }; + struct lgtd_client client = { + .io = NULL, .json = json, .current_request = &request + }; jsmntok_t tokens[32]; int parsed = parse_json( @@ -17,9 +19,7 @@ test_params(const char *json, const char **expected_targets) reset_client_write_buf(); - bool ok = lgtd_jsonrpc_build_target_list( - &targets, &client, &request, tokens, parsed, json - ); + bool ok = lgtd_jsonrpc_build_target_list(&targets, &client, tokens, parsed); if (!expected_targets && !SLIST_EMPTY(&targets)) { if (ok) { diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c index 647b72e..782dea0 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c @@ -5,9 +5,12 @@ static bool power_off_called = false; -bool -lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) +void +lgtd_proto_power_off(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) { + assert(client); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( 1, "Invalid target [%s] (expected=[*])", @@ -15,7 +18,6 @@ lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) ); } power_off_called = true; - return true; } int @@ -33,27 +35,16 @@ main(void) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_power_off(&client, &req, json); - - const char response[] = ("{" - "\"jsonrpc\": \"2.0\", " - "\"id\": \"42\", " - "\"result\": true" - "}"); - - if (strcmp(client_write_buf, response)) { - errx( - 1, "invalid response: %s (expected: %s)", - client_write_buf, response - ); - } + lgtd_jsonrpc_check_and_call_power_off(&client); if (!power_off_called) { errx(1, "lgtd_proto_power_off wasn't called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c index 5493284..7ed7a85 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c @@ -5,12 +5,13 @@ static bool power_off_called = false; -bool -lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) +void +lgtd_proto_power_off(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) { (void)targets; + (void)client; power_off_called = true; - return true; } int @@ -28,14 +29,16 @@ main(void) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_power_off(&client, &req, json); + lgtd_jsonrpc_check_and_call_power_off(&client); if (!strstr(client_write_buf, "-32602")) { errx(1, "no error returned"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c index d74ef75..302dbe3 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c @@ -5,9 +5,12 @@ static bool power_on_called = false; -bool -lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) { + assert(client); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( 1, "Invalid target [%s] (expected=[*])", @@ -15,7 +18,6 @@ lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) ); } power_on_called = true; - return true; } int @@ -33,27 +35,16 @@ main(void) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_power_on(&client, &req, json); - - const char response[] = ("{" - "\"jsonrpc\": \"2.0\", " - "\"id\": \"42\", " - "\"result\": true" - "}"); - - if (strcmp(client_write_buf, response)) { - errx( - 1, "invalid response: %s (expected: %s)", - client_write_buf, response - ); - } + lgtd_jsonrpc_check_and_call_power_on(&client); if (!power_on_called) { errx(1, "lgtd_proto_power_on wasn't called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c index 4305307..7bb6c3d 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c @@ -5,12 +5,13 @@ static bool power_on_called = false; -bool -lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) { + (void)client; (void)targets; power_on_called = true; - return true; } int @@ -28,14 +29,16 @@ main(void) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_power_on(&client, &req, json); + lgtd_jsonrpc_check_and_call_power_on(&client); if (!strstr(client_write_buf, "-32602")) { errx(1, "no error returned"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c index b1028fb..639a22b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -5,14 +5,17 @@ static bool set_light_called = false; -bool -lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, +void +lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { + assert(client); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( 1, "Invalid target [%s] (expected=[*])", @@ -51,7 +54,6 @@ lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, ); } set_light_called = true; - return true; } int @@ -76,27 +78,16 @@ main(void) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client, &req, json); - - const char response[] = ("{" - "\"jsonrpc\": \"2.0\", " - "\"id\": \"42\", " - "\"result\": true" - "}"); - - if (strcmp(client_write_buf, response)) { - errx( - 1, "invalid response: %s (expected: %s)", - client_write_buf, response - ); - } + lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client); if (!set_light_called) { errx(1, "lgtd_proto_set_light_from_hsbk wasn't called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c index 29a156f..ebedd88 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -5,14 +5,17 @@ static bool set_light_called = false; -bool -lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, +void +lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { + assert(client); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( 1, "Invalid target [%s] (expected=[*])", @@ -51,7 +54,6 @@ lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, ); } set_light_called = true; - return true; } int @@ -71,27 +73,16 @@ main(void) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client, &req, json); - - const char response[] = ("{" - "\"jsonrpc\": \"2.0\", " - "\"id\": \"42\", " - "\"result\": true" - "}"); - - if (strcmp(client_write_buf, response)) { - errx( - 1, "invalid response: %s (expected: %s)", - client_write_buf, response - ); - } + lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client); if (!set_light_called) { errx(1, "lgtd_proto_set_light_from_hsbk wasn't called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c index d0a3e35..520f17e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -5,14 +5,16 @@ static bool set_light_called = false; -bool -lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, +void +lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { + (void)client; (void)targets; (void)hue; (void)saturation; @@ -20,7 +22,6 @@ lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, (void)kelvin; (void)transition_msecs; set_light_called = true; - return true; } static void @@ -32,18 +33,16 @@ test_request(const char *json) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client, &req, json); - - if (!strstr(client_write_buf, "-32602")) { - errx(1, "no error returned, client_write_buf=[%s]", client_write_buf); - } + lgtd_jsonrpc_check_and_call_set_light_from_hsbk(&client); if (set_light_called) { errx(1, "lgtd_proto_power_off was called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c index 56ac89e..5f32d46 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c @@ -5,14 +5,17 @@ static bool set_waveform_called = false; -bool -lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, +void +lgtd_proto_set_waveform(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, enum lgtd_lifx_waveform_type waveform, int hue, int saturation, int brightness, int kelvin, int period, float cycles, int skew_ratio, bool transient) { + assert(client); + if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( 1, "Invalid target [%s] (expected=[*])", @@ -64,7 +67,6 @@ lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, ); } set_waveform_called = true; - return true; } int @@ -93,27 +95,16 @@ main(void) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_set_waveform(&client, &req, json); - - const char response[] = ("{" - "\"jsonrpc\": \"2.0\", " - "\"id\": \"42\", " - "\"result\": true" - "}"); - - if (strcmp(client_write_buf, response)) { - errx( - 1, "invalid response: %s (expected: %s)", - client_write_buf, response - ); - } + lgtd_jsonrpc_check_and_call_set_waveform(&client); if (!set_waveform_called) { errx(1, "lgtd_proto_set_waveform wasn't called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c index 936528d..64d3650 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c @@ -5,14 +5,16 @@ static bool set_waveform_called = false; -bool -lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, +void +lgtd_proto_set_waveform(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, enum lgtd_lifx_waveform_type waveform, int hue, int saturation, int brightness, int kelvin, int period, float cycles, int skew_ratio, bool transient) { + (void)client; (void)targets; (void)waveform; (void)hue; @@ -24,7 +26,6 @@ lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, (void)skew_ratio; (void)transient; set_waveform_called = true; - return true; } static void @@ -36,18 +37,16 @@ test_request(const char *json) ); bool ok; - struct lgtd_client client = { .io = NULL }; struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); if (!ok) { errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_set_waveform(&client, &req, json); - - if (!strstr(client_write_buf, "-32602")) { - errx(1, "no error returned, client_write_buf=[%s]", client_write_buf); - } + lgtd_jsonrpc_check_and_call_set_waveform(&client); if (set_waveform_called) { errx(1, "lgtd_proto_power_off was called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c index 2bb1a4e..cbd3184 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_error.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c @@ -5,11 +5,12 @@ int main(void) { - struct lgtd_client client = { .io = NULL }; - const char *json = "\"42\""; jsmntok_t token = { .start = 1, .end = 3, .type = JSMN_STRING }; struct lgtd_jsonrpc_request req = { .id = &token }; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; const char *expected = ( "{" "\"jsonrpc\": \"2.0\", " @@ -18,7 +19,7 @@ main(void) "}" ); lgtd_jsonrpc_send_error( - &client, &req, json, LGTD_JSONRPC_INVALID_REQUEST, "Invalid Request" + &client, LGTD_JSONRPC_INVALID_REQUEST, "Invalid Request" ); int diff = memcmp(client_write_buf, expected, strlen(expected)); if (diff) { @@ -38,7 +39,7 @@ main(void) "}" ); lgtd_jsonrpc_send_error( - &client, &req, NULL, LGTD_JSONRPC_INVALID_REQUEST, "Invalid Request" + &client, LGTD_JSONRPC_INVALID_REQUEST, "Invalid Request" ); diff = memcmp(client_write_buf, expected, strlen(expected)); if (diff) { diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index 27ad8db..06a5e0a 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -30,56 +30,63 @@ bufferevent_write(struct bufferevent *bev, const void *data, size_t nbytes) return 0; } -void lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) +void +lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) { assert(targets); } #ifndef LGTD_TESTING_SET_LIGHT_FROM_HSBK -bool -lgtd_proto_set_light_from_hsbk(const struct lgtd_proto_target_list *targets, +void +lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, int hue, int saturation, int brightness, int kelvin, int transition_msecs) { + (void)client; (void)targets; (void)hue; (void)saturation; (void)brightness; (void)kelvin; (void)transition_msecs; - return true; } #endif #ifndef LGTD_TESTING_POWER_ON -bool -lgtd_proto_power_on(const struct lgtd_proto_target_list *targets) +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) { + (void)client; (void)targets; - return true; } #endif #ifndef LGTD_TESTING_POWER_OFF -bool -lgtd_proto_power_off(const struct lgtd_proto_target_list *targets) +void +lgtd_proto_power_off(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) { + (void)client; (void)targets; - return true; } #endif #ifndef LGTD_TESTING_SET_WAVEFORM -bool lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, - enum lgtd_lifx_waveform_type waveform, - int hue, int saturation, - int brightness, int kelvin, - int period, float cycles, - int skew_ratio, bool transient) +void +lgtd_proto_set_waveform(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_waveform_type waveform, + int hue, int saturation, + int brightness, int kelvin, + int period, float cycles, + int skew_ratio, bool transient) { + (void)client; (void)targets; (void)waveform; (void)hue; @@ -90,6 +97,5 @@ bool lgtd_proto_set_waveform(const struct lgtd_proto_target_list *targets, (void)cycles; (void)skew_ratio; (void)transient; - return true; } #endif diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index 1df0d05..0fecb68 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -14,6 +14,9 @@ #include "lifx/wire_proto.h" #include "core/time_monotonic.h" +#include "core/jsmn.h" +#include "core/jsonrpc.h" +#include "core/client.h" #include "core/proto.h" #include "lifx/bulb.h" #include "lifx/gateway.h" From e3e108dcc91aa96743a6464703b455ebcd122c82 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 11 Apr 2015 17:26:16 -0700 Subject: [PATCH 027/181] Beef-up the test suite by testing the proto module --- core/jsonrpc.c | 6 ++ .../core/jsonrpc/test_jsonrpc_send_response.c | 44 +++++++++ tests/core/proto/CMakeLists.txt | 26 ++++++ tests/core/proto/test_proto_power_off.c | 55 +++++++++++ .../test_proto_power_off_routing_error.c | 56 +++++++++++ tests/core/proto/test_proto_power_on.c | 55 +++++++++++ .../proto/test_proto_power_on_routing_error.c | 55 +++++++++++ .../proto/test_proto_set_light_from_hsbk.c | 77 +++++++++++++++ ...oto_set_light_from_hsbk_on_routing_error.c | 77 +++++++++++++++ tests/core/proto/test_proto_set_waveform.c | 93 +++++++++++++++++++ ...test_proto_set_waveform_on_routing_error.c | 93 +++++++++++++++++++ 11 files changed, 637 insertions(+) create mode 100644 tests/core/jsonrpc/test_jsonrpc_send_response.c create mode 100644 tests/core/proto/CMakeLists.txt create mode 100644 tests/core/proto/test_proto_power_off.c create mode 100644 tests/core/proto/test_proto_power_off_routing_error.c create mode 100644 tests/core/proto/test_proto_power_on.c create mode 100644 tests/core/proto/test_proto_power_on_routing_error.c create mode 100644 tests/core/proto/test_proto_set_light_from_hsbk.c create mode 100644 tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c create mode 100644 tests/core/proto/test_proto_set_waveform.c create mode 100644 tests/core/proto/test_proto_set_waveform_on_routing_error.c diff --git a/core/jsonrpc.c b/core/jsonrpc.c index bb05dc7..6183c7d 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -431,6 +431,9 @@ lgtd_jsonrpc_send_error(struct lgtd_client *client, enum lgtd_jsonrpc_error_code code, const char *message) { + assert(client); + assert(message); + LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); lgtd_jsonrpc_write_id(client); LGTD_CLIENT_WRITE_STRING(client, ", \"error\": {\"code\": "); @@ -446,6 +449,9 @@ void lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *result) { + assert(client); + assert(result); + LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); lgtd_jsonrpc_write_id(client); LGTD_CLIENT_WRITE_STRING(client, ", \"result\": "); diff --git a/tests/core/jsonrpc/test_jsonrpc_send_response.c b/tests/core/jsonrpc/test_jsonrpc_send_response.c new file mode 100644 index 0000000..d03a5af --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_send_response.c @@ -0,0 +1,44 @@ +#include "jsonrpc.c" + +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + const char *json = "\"42\""; + jsmntok_t token = { .start = 1, .end = 3, .type = JSMN_STRING }; + struct lgtd_jsonrpc_request req = { .id = &token }; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; + const char *expected = ("{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"42\", " + "\"result\": false" + "}"); + lgtd_jsonrpc_send_response(&client, "false"); + int diff = memcmp(client_write_buf, expected, strlen(expected)); + if (diff) { + printf("expected: %s\n", expected); + printf("received: %s\n", client_write_buf); + return 1; + } + + reset_client_write_buf(); + + req.id = NULL; + expected = ("{" + "\"jsonrpc\": \"2.0\", " + "\"id\": null, " + "\"result\": true" + "}"); + lgtd_jsonrpc_send_response(&client, "true"); + diff = memcmp(client_write_buf, expected, strlen(expected)); + if (diff) { + printf("expected: %s\n", expected); + printf("received: %s\n", client_write_buf); + return 1; + } + + return 0; +} diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt new file mode 100644 index 0000000..a7e0463 --- /dev/null +++ b/tests/core/proto/CMakeLists.txt @@ -0,0 +1,26 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_LIBRARY( + test_core_proto STATIC + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/timer.c + ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c + ${TIME_MONOTONIC_IMPL} +) + +TARGET_LINK_LIBRARIES(test_core_proto ${EVENT2_CORE_LIBRARY}) + +FUNCTION(ADD_ROUTER_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_proto) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_ROUTER_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/core/proto/test_proto_power_off.c b/tests/core/proto/test_proto_power_off.c new file mode 100644 index 0000000..86ca09e --- /dev/null +++ b/tests/core/proto/test_proto_power_off.c @@ -0,0 +1,55 @@ +#include "proto.c" + +#include "tests_utils.h" + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_POWER_STATE) { + errx(1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_POWER_STATE + ); + } + + struct lgtd_lifx_packet_power_state *power_off = pkt; + if (power_off->power != LGTD_LIFX_POWER_OFF) { + errx(1, "invalid power state %hx (expected %x)", + power_off->power, LGTD_LIFX_POWER_OFF); + } + + return true; +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't ne NULL"); + } + + if (strcmp(msg, "true")) { + errx(1, "unexpected response [%s] (expected [true])", msg); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client; + + lgtd_proto_power_off(&client, targets); + + return 0; +} diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c new file mode 100644 index 0000000..63324e1 --- /dev/null +++ b/tests/core/proto/test_proto_power_off_routing_error.c @@ -0,0 +1,56 @@ +#include "proto.c" + +#include "tests_utils.h" + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_POWER_STATE) { + errx(1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_POWER_STATE + ); + } + + struct lgtd_lifx_packet_power_state *power_off = pkt; + if (power_off->power != LGTD_LIFX_POWER_OFF) { + errx(1, "invalid power state %hx (expected %x)", + power_off->power, LGTD_LIFX_POWER_OFF); + } + + return false; +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't ne NULL"); + } + + if (strcmp(msg, "false")) { + errx(1, "unexpected response [%s] (expected [false])", msg); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client; + + lgtd_proto_power_off(&client, targets); + + return 0; +} + diff --git a/tests/core/proto/test_proto_power_on.c b/tests/core/proto/test_proto_power_on.c new file mode 100644 index 0000000..beb28d8 --- /dev/null +++ b/tests/core/proto/test_proto_power_on.c @@ -0,0 +1,55 @@ +#include "proto.c" + +#include "tests_utils.h" + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_POWER_STATE) { + errx(1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_POWER_STATE + ); + } + + struct lgtd_lifx_packet_power_state *power_on = pkt; + if (power_on->power != LGTD_LIFX_POWER_ON) { + errx(1, "invalid power state %hx (expected %x)", + power_on->power, LGTD_LIFX_POWER_ON); + } + + return true; +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't ne NULL"); + } + + if (strcmp(msg, "true")) { + errx(1, "unexpected response [%s] (expected [true])", msg); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client; + + lgtd_proto_power_on(&client, targets); + + return 0; +} diff --git a/tests/core/proto/test_proto_power_on_routing_error.c b/tests/core/proto/test_proto_power_on_routing_error.c new file mode 100644 index 0000000..df54d02 --- /dev/null +++ b/tests/core/proto/test_proto_power_on_routing_error.c @@ -0,0 +1,55 @@ +#include "proto.c" + +#include "tests_utils.h" + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_POWER_STATE) { + errx(1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_POWER_STATE + ); + } + + struct lgtd_lifx_packet_power_state *power_on = pkt; + if (power_on->power != LGTD_LIFX_POWER_ON) { + errx(1, "invalid power state %hx (expected %x)", + power_on->power, LGTD_LIFX_POWER_ON); + } + + return false; +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't ne NULL"); + } + + if (strcmp(msg, "false")) { + errx(1, "unexpected response [%s] (expected [false])", msg); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client; + + lgtd_proto_power_on(&client, targets); + + return 0; +} diff --git a/tests/core/proto/test_proto_set_light_from_hsbk.c b/tests/core/proto/test_proto_set_light_from_hsbk.c new file mode 100644 index 0000000..e560851 --- /dev/null +++ b/tests/core/proto/test_proto_set_light_from_hsbk.c @@ -0,0 +1,77 @@ +#include + +#include "proto.c" + +#include "tests_utils.h" + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_LIGHT_COLOR) { + errx( + 1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_LIGHT_COLOR + ); + } + + struct lgtd_lifx_packet_light_color *light_color = pkt; + int hue = le16toh(light_color->hue); + int saturation = le16toh(light_color->saturation); + int brightness = le16toh(light_color->brightness); + int kelvin = le16toh(light_color->kelvin); + int transition = htole32(light_color->transition); + + if (hue != 42) { + errx(1, "got hue = %d (expected 42)", hue); + } + if (saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", saturation); + } + if (brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", brightness); + } + if (kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", kelvin); + } + if (transition != 150) { + errx(1, "got transition = %d (expected 150)", transition); + } + + return true; +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't ne NULL"); + } + + if (strcmp(msg, "true")) { + errx(1, "unexpected response [%s] (expected [true])", msg); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client; + + lgtd_proto_set_light_from_hsbk( + &client, targets, 42, 10000, 20000, 4500, 150 + ); + + return 0; +} diff --git a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c new file mode 100644 index 0000000..2728623 --- /dev/null +++ b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c @@ -0,0 +1,77 @@ +#include + +#include "proto.c" + +#include "tests_utils.h" + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_LIGHT_COLOR) { + errx( + 1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_LIGHT_COLOR + ); + } + + struct lgtd_lifx_packet_light_color *light_color = pkt; + int hue = le16toh(light_color->hue); + int saturation = le16toh(light_color->saturation); + int brightness = le16toh(light_color->brightness); + int kelvin = le16toh(light_color->kelvin); + int transition = htole32(light_color->transition); + + if (hue != 42) { + errx(1, "got hue = %d (expected 42)", hue); + } + if (saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", saturation); + } + if (brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", brightness); + } + if (kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", kelvin); + } + if (transition != 150) { + errx(1, "got transition = %d (expected 150)", transition); + } + + return false; +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't ne NULL"); + } + + if (strcmp(msg, "false")) { + errx(1, "unexpected response [%s] (expected [false])", msg); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client; + + lgtd_proto_set_light_from_hsbk( + &client, targets, 42, 10000, 20000, 4500, 150 + ); + + return 0; +} diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c new file mode 100644 index 0000000..6a669a0 --- /dev/null +++ b/tests/core/proto/test_proto_set_waveform.c @@ -0,0 +1,93 @@ +#include + +#include "proto.c" + +#include "tests_utils.h" + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_WAVEFORM) { + errx( + 1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_WAVEFORM + ); + } + + struct lgtd_lifx_packet_waveform *waveform = pkt; + enum lgtd_lifx_waveform_type waveform_type = waveform->waveform; + int hue = le16toh(waveform->hue); + int saturation = le16toh(waveform->saturation); + int brightness = le16toh(waveform->brightness); + int kelvin = le16toh(waveform->kelvin); + int period = le16toh(waveform->period); + float cycles = waveform->cycles; + int skew_ratio = le16toh(waveform->skew_ratio); + + if (waveform_type != LGTD_LIFX_WAVEFORM_SAW) { + errx( + 1, "got waveform = %d (expected %d)", + waveform_type, LGTD_LIFX_WAVEFORM_SAW + ); + } + if (hue != 42) { + errx(1, "got hue = %d (expected 42)", hue); + } + if (saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", saturation); + } + if (brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", brightness); + } + if (kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", kelvin); + } + if (period != 200) { + errx(1, "got period = %d (expected 200)", period); + } + if (cycles != 10.) { + errx(1, "got cycles = %f (expected 10)", cycles); + } + if (skew_ratio != 0) { + errx(1, "got skew_ratio = %d (expected 0)", skew_ratio); + } + + return true; +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't ne NULL"); + } + + if (strcmp(msg, "true")) { + errx(1, "unexpected response [%s] (expected [true])", msg); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client; + + lgtd_proto_set_waveform( + &client, targets, LGTD_LIFX_WAVEFORM_SAW, + 42, 10000, 20000, 4500, 200, 10., 0, false + ); + + return 0; +} diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c new file mode 100644 index 0000000..46788df --- /dev/null +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -0,0 +1,93 @@ +#include + +#include "proto.c" + +#include "tests_utils.h" + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_WAVEFORM) { + errx( + 1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_WAVEFORM + ); + } + + struct lgtd_lifx_packet_waveform *waveform = pkt; + enum lgtd_lifx_waveform_type waveform_type = waveform->waveform; + int hue = le16toh(waveform->hue); + int saturation = le16toh(waveform->saturation); + int brightness = le16toh(waveform->brightness); + int kelvin = le16toh(waveform->kelvin); + int period = le16toh(waveform->period); + float cycles = waveform->cycles; + int skew_ratio = le16toh(waveform->skew_ratio); + + if (waveform_type != LGTD_LIFX_WAVEFORM_SAW) { + errx( + 1, "got waveform = %d (expected %d)", + waveform_type, LGTD_LIFX_WAVEFORM_SAW + ); + } + if (hue != 42) { + errx(1, "got hue = %d (expected 42)", hue); + } + if (saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", saturation); + } + if (brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", brightness); + } + if (kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", kelvin); + } + if (period != 200) { + errx(1, "got period = %d (expected 200)", period); + } + if (cycles != 10.) { + errx(1, "got cycles = %f (expected 10)", cycles); + } + if (skew_ratio != 0) { + errx(1, "got skew_ratio = %d (expected 0)", skew_ratio); + } + + return false; +} + +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't ne NULL"); + } + + if (strcmp(msg, "false")) { + errx(1, "unexpected response [%s] (expected [false])", msg); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client; + + lgtd_proto_set_waveform( + &client, targets, LGTD_LIFX_WAVEFORM_SAW, + 42, 10000, 20000, 4500, 200, 10., 0, false + ); + + return 0; +} From fc38d30208ad3cceb433963776dc74ac3c96456a Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 9 May 2015 01:23:17 -0700 Subject: [PATCH 028/181] Remove useless type size instrospection for suseconds_t By using intmax_t and %jd in lgtd_isotime_now. --- CMakeLists.txt | 4 ---- core/log.c | 8 ++------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78edc12..93894d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,10 +25,6 @@ FIND_PACKAGE(Event2 REQUIRED COMPONENTS core) FIND_PACKAGE(Endian REQUIRED) INCLUDE(CompatTimeMonotonic) -INCLUDE(CheckTypeSize) -CHECK_TYPE_SIZE(suseconds_t SUSECONDS_T_SIZE) -ADD_DEFINITIONS("-DLGTD_SUSECONDS_T_SIZE=${SUSECONDS_T_SIZE}") - ### Global definitions ######################################################### INCLUDE(AddAllSubdirectories) diff --git a/core/log.c b/core/log.c index 9d5a3c9..8c9fefa 100644 --- a/core/log.c +++ b/core/log.c @@ -48,14 +48,10 @@ lgtd_isotime_now(char *strbuf, int bufsz) } // '2015-01-02T10:13:16.132222+00:00' snprintf( -#if LGTD_SUSECONDS_T_SIZE == 4 - strbuf, bufsz, "%d-%02d-%02dT%02d:%02d:%02d.%d%c%02ld:%02ld", -#else - strbuf, bufsz, "%d-%02d-%02dT%02d:%02d:%02d.%ld%c%02ld:%02ld", -#endif + strbuf, bufsz, "%d-%02d-%02dT%02d:%02d:%02d.%jd%c%02ld:%02ld", 1900 + tm_now.tm_year, 1 + tm_now.tm_mon, tm_now.tm_mday, tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec, - now.tv_usec, tm_now.tm_gmtoff >= 0 ? '+' : '-', // %+02ld doesn't work + (intmax_t)now.tv_usec, tm_now.tm_gmtoff >= 0 ? '+' : '-', // %+02ld doesn't work LGTD_ABS(tm_now.tm_gmtoff / 60 / 60), tm_now.tm_gmtoff % (60 * 60) ); return; From 310b5e009a1493627b6a5332e3c8fce4022d10a7 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 9 May 2015 01:23:17 -0700 Subject: [PATCH 029/181] Turn lifx_gateway.site into an union with an uint64_t This is will be needed to support tagging. --- core/router.c | 20 ++++++++++++++++---- lifx/gateway.c | 18 ++++++++++++------ lifx/gateway.h | 6 +++++- tests/core/router/test_router_broadcast.c | 2 +- tests/core/router/test_router_device.c | 2 +- tests/core/tests_utils.c | 2 +- 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/core/router.c b/core/router.c index d52dc74..2cd9b3e 100644 --- a/core/router.c +++ b/core/router.c @@ -52,10 +52,16 @@ lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) struct lgtd_lifx_gateway *gw; LIST_FOREACH(gw, &lgtd_lifx_gateways, link) { pkt_infos = lgtd_lifx_wire_setup_header( - &hdr, LGTD_LIFX_TARGET_ALL_DEVICES, target, gw->site, pkt_type + &hdr, + LGTD_LIFX_TARGET_ALL_DEVICES, + target, + gw->site.as_array, + pkt_type ); assert(pkt_infos); - lgtd_lifx_gateway_send_packet(gw, &hdr, pkt, pkt_infos->size); + lgtd_lifx_gateway_enqueue_packet( + gw, &hdr, pkt_type, pkt, pkt_infos->size + ); struct lgtd_lifx_bulb *bulb; lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); SLIST_FOREACH(bulb, &gw->bulbs, link_by_gw) { @@ -84,11 +90,17 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, const struct lgtd_lifx_packet_infos *pkt_infos; pkt_infos = lgtd_lifx_wire_setup_header( - &hdr, LGTD_LIFX_TARGET_DEVICE, target, bulb->gw->site, pkt_type + &hdr, + LGTD_LIFX_TARGET_DEVICE, + target, + bulb->gw->site.as_array, + pkt_type ); assert(pkt_infos); - lgtd_lifx_gateway_send_packet(bulb->gw, &hdr, pkt, pkt_infos->size); + lgtd_lifx_gateway_enqueue_packet( + bulb->gw, &hdr, pkt_type, pkt, pkt_infos->size + ); if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { bulb->dirty_at = lgtd_time_monotonic_msecs(); diff --git a/lifx/gateway.c b/lifx/gateway.c index f58e647..dc66f00 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -68,7 +68,8 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) } lgtd_info( - "connection with gateway bulb [%s]:%hu closed", gw->ip_addr, gw->port + "connection with gateway bulb [%s]:%hu (site %s) closed", + gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) ); free(gw); } @@ -124,9 +125,14 @@ lgtd_lifx_gateway_send_get_all_light_state(struct lgtd_lifx_gateway *gw) assert(gw); struct lgtd_lifx_packet_header hdr; - union lgtd_lifx_target target = { .addr = gw->site }; + union lgtd_lifx_target target = { .addr = gw->site.as_array }; + lgtd_lifx_wire_setup_header( - &hdr, LGTD_LIFX_TARGET_SITE, target, gw->site, LGTD_LIFX_GET_LIGHT_STATE + &hdr, + LGTD_LIFX_TARGET_SITE, + target, + gw->site.as_array, + LGTD_LIFX_GET_LIGHT_STATE ); lgtd_debug("GET_LIGHT_STATE --> [%s]:%hu", gw->ip_addr, gw->port); lgtd_lifx_gateway_send_packet(gw, &hdr, NULL, 0); @@ -210,7 +216,7 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, memcpy(&gw->peer, peer, sizeof(gw->peer)); lgtd_sockaddrtoa(peer, gw->ip_addr, sizeof(gw->ip_addr)); gw->port = lgtd_sockaddrport(peer); - memcpy(gw->site, site, sizeof(gw->site)); + memcpy(gw->site.as_array, site, sizeof(gw->site.as_array)); gw->last_req_at = received_at; gw->next_req_at = received_at; gw->last_pkt_at = received_at; @@ -227,7 +233,7 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, lgtd_info( "gateway for site %s at [%s]:%hu", - lgtd_addrtoa(gw->site), gw->ip_addr, gw->port + lgtd_addrtoa(gw->site.as_array), gw->ip_addr, gw->port ); LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); @@ -288,7 +294,7 @@ lgtd_lifx_gateway_send_packet(struct lgtd_lifx_gateway *gw, assert(gw); assert(hdr); assert(pkt_size >= 0 && pkt_size < LGTD_LIFX_MAX_PACKET_SIZE); - assert(!memcmp(hdr->site, gw->site, LGTD_LIFX_ADDR_LENGTH)); + assert(!memcmp(hdr->site, gw->site.as_array, LGTD_LIFX_ADDR_LENGTH)); evbuffer_add(gw->write_buf, hdr, sizeof(*hdr)); if (pkt) { diff --git a/lifx/gateway.h b/lifx/gateway.h index f541bfe..b3ba208 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -34,7 +34,11 @@ struct lgtd_lifx_gateway { struct sockaddr_storage peer; char ip_addr[INET6_ADDRSTRLEN]; uint16_t port; - uint8_t site[LGTD_LIFX_ADDR_LENGTH]; + // TODO: just use an integer and rename it to site_id: + union { + uint8_t as_array[LGTD_LIFX_ADDR_LENGTH]; + uint64_t as_integer; + } site; evutil_socket_t socket; // Those three timers let us measure the latency of the gateway. If we // aren't the only client on the network then this won't be accurate since diff --git a/tests/core/router/test_router_broadcast.c b/tests/core/router/test_router_broadcast.c index 08b44b7..a821a26 100644 --- a/tests/core/router/test_router_broadcast.c +++ b/tests/core/router/test_router_broadcast.c @@ -38,7 +38,7 @@ main(void) if (hdr->target.tags != 0) { lgtd_errx(1, "tags should be 0 for broadcast"); } - if (memcmp(gw->site, hdr->site, sizeof(hdr->site))) { + if (memcmp(gw->site.as_array, hdr->site, sizeof(hdr->site))) { lgtd_errx(1, "sites don't match"); } if (lgtd_tests_gw_pkt_queue[i].pkt != &payload) { diff --git a/tests/core/router/test_router_device.c b/tests/core/router/test_router_device.c index af080ec..c620756 100644 --- a/tests/core/router/test_router_device.c +++ b/tests/core/router/test_router_device.c @@ -41,7 +41,7 @@ main(void) lgtd_errx(1, "the packet header doesn't have the right target address"); } - if (memcmp(gw_1->site, hdr_queued->site, sizeof(hdr_queued->site))) { + if (memcmp(gw_1->site.as_array, hdr_queued->site, sizeof(hdr_queued->site))) { lgtd_errx(1, "incorrect site in the headers"); } diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index 0fecb68..a990d13 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -31,7 +31,7 @@ lgtd_tests_insert_mock_gateway(int id) struct lgtd_lifx_gateway *gw = calloc(1, sizeof(*gw)); gw->socket = id; - gw->site[0] = id; + gw->site.as_array[0] = id; LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); From bff30dd3ddc32093667580350f7511ddc55916a1 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 9 May 2015 01:23:17 -0700 Subject: [PATCH 030/181] Fix broadcast and multicast (aka tagged) routing to LIFX bulbs --- lifx/wire_proto.c | 6 +++--- lifx/wire_proto.h | 3 ++- tests/core/router/test_router_broadcast.c | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 73a6c41..9cf5c79 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -226,19 +226,19 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, switch (target_type) { case LGTD_LIFX_TARGET_SITE: + case LGTD_LIFX_TARGET_ALL_DEVICES: hdr->protocol.tagged = true; + hdr->protocol.addressable = true; break; case LGTD_LIFX_TARGET_TAGS: hdr->protocol.tagged = true; + hdr->protocol.addressable = true; hdr->target.tags = target.tags; break; case LGTD_LIFX_TARGET_DEVICE: hdr->protocol.addressable = true; memcpy(hdr->target.device_addr, target.addr, LGTD_LIFX_ADDR_LENGTH); break; - case LGTD_LIFX_TARGET_ALL_DEVICES: - hdr->protocol.tagged = true; - break; } lgtd_lifx_wire_encode_header(hdr); diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index e20dec6..f4d68df 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -43,7 +43,8 @@ struct lgtd_lifx_packet_header { //! LIFX internal use should be 0. uint16le_t origin:2; } protocol; - //! This seems to be for LIFX internal use only. + //! Here is what LIFXKit says about it, maybe it's related to zigbee: + //! Message source identifier from NAT table (Internal LIFX use) uint32le_t source; union { //! All targeted tags ORed together. diff --git a/tests/core/router/test_router_broadcast.c b/tests/core/router/test_router_broadcast.c index a821a26..5eb67b3 100644 --- a/tests/core/router/test_router_broadcast.c +++ b/tests/core/router/test_router_broadcast.c @@ -32,7 +32,7 @@ main(void) } const struct lgtd_lifx_packet_header *hdr; hdr = lgtd_tests_gw_pkt_queue[i].hdr; - if (!hdr->protocol.tagged || hdr->protocol.addressable) { + if (!hdr->protocol.tagged || !hdr->protocol.addressable) { lgtd_errx(1, "packet header doesn't have the right bits set"); } if (hdr->target.tags != 0) { From 7967ea430953fa7f14dcda193822484dcbe2adda Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 9 May 2015 01:23:17 -0700 Subject: [PATCH 031/181] Send only one LIFX packet per UDP datagram Turns out the bulbs can't do that... This changeset also fixes rate limiting, status refresh and improve the latency measurement a bit. This is necessary to support tagging. --- lifx/gateway.c | 69 ++++++-- lifx/gateway.h | 25 ++- tests/core/router/tests_router_utils.h | 9 +- tests/lifx/gateway/CMakeLists.txt | 26 +++ .../gateway/test_gateway_enqueue_packet.c | 65 +++++++ .../test_gateway_enqueue_packet_ring_full.c | 89 ++++++++++ ...t_gateway_enqueue_packet_ring_wraparound.c | 71 ++++++++ tests/lifx/gateway/test_gateway_utils.h | 162 ++++++++++++++++++ .../gateway/test_gateway_write_callback.c | 77 +++++++++ ...way_write_callback_clears_ring_full_flag.c | 80 +++++++++ ...teway_write_callback_last_packet_on_ring.c | 76 ++++++++ ...est_gateway_write_callback_partial_write.c | 101 +++++++++++ ...t_gateway_write_callback_ring_wraparound.c | 79 +++++++++ tests/lifx/tests_shims.c | 37 ++++ 14 files changed, 945 insertions(+), 21 deletions(-) create mode 100644 tests/lifx/gateway/CMakeLists.txt create mode 100644 tests/lifx/gateway/test_gateway_enqueue_packet.c create mode 100644 tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c create mode 100644 tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c create mode 100644 tests/lifx/gateway/test_gateway_utils.h create mode 100644 tests/lifx/gateway/test_gateway_write_callback.c create mode 100644 tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c create mode 100644 tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c create mode 100644 tests/lifx/gateway/test_gateway_write_callback_partial_write.c create mode 100644 tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c create mode 100644 tests/lifx/tests_shims.c diff --git a/lifx/gateway.c b/lifx/gateway.c index dc66f00..e0e5ca4 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -83,6 +83,7 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, assert(ctx); struct lgtd_lifx_gateway *gw = (struct lgtd_lifx_gateway *)ctx; + if (events & EV_TIMEOUT) { // Not sure how that could happen in UDP but eh. lgtd_warn( "lost connection with gateway bulb [%s]:%hu", gw->ip_addr, gw->port @@ -93,8 +94,13 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, } return; } + if (events & EV_WRITE) { - int nbytes = evbuffer_write(gw->write_buf, gw->socket); + assert(gw->pkt_ring_tail >= 0); + assert(gw->pkt_ring_tail < (int)LGTD_ARRAY_SIZE(gw->pkt_ring)); + + int to_write = gw->pkt_ring[gw->pkt_ring_tail].size; + int nbytes = evbuffer_write_atmost(gw->write_buf, gw->socket, to_write); if (nbytes == -1 && errno != EAGAIN) { lgtd_warn("can't write to [%s]:%hu", gw->ip_addr, gw->port); lgtd_lifx_gateway_close(gw); @@ -103,16 +109,33 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, } return; } + // Callbacks are called in any order, so we keep two timers to make // sure we can get the latency right, otherwise we could be compute the // latency with last_pkt_at < last_req_at, which isn't true since the // pkt will be for an answer to the previous write: gw->last_req_at = gw->next_req_at; gw->next_req_at = lgtd_time_monotonic_msecs(); - // XXX this isn't perfect because we don't know what we just sent, I - // just assume that everything pending will alway be transmitted in a - // single call: - gw->pending_refresh_req = false; + + gw->pkt_ring[gw->pkt_ring_tail].size -= nbytes; + if (gw->pkt_ring[gw->pkt_ring_tail].size == 0) { + enum lgtd_lifx_packet_type type; + type = gw->pkt_ring[gw->pkt_ring_tail].type; + if (type == LGTD_LIFX_GET_TAG_LABELS) { + gw->pending_refresh_req = false; + } + if (lgtd_opts.verbosity <= LGTD_DEBUG) { + const struct lgtd_lifx_packet_infos *pkt_infos = + lgtd_lifx_wire_get_packet_infos(type); + lgtd_debug( + "%s --> [%s]:%hu", pkt_infos->name, gw->ip_addr, gw->port + ); + } + gw->pkt_ring[gw->pkt_ring_tail].type = 0; + LGTD_LIFX_GATEWAY_INC_MESSAGE_RING_INDEX(gw->pkt_ring_tail); + gw->pkt_ring_full = false; + } + if (!evbuffer_get_length(gw->write_buf)) { event_del(gw->write_ev); } @@ -134,8 +157,10 @@ lgtd_lifx_gateway_send_get_all_light_state(struct lgtd_lifx_gateway *gw) gw->site.as_array, LGTD_LIFX_GET_LIGHT_STATE ); - lgtd_debug("GET_LIGHT_STATE --> [%s]:%hu", gw->ip_addr, gw->port); - lgtd_lifx_gateway_send_packet(gw, &hdr, NULL, 0); + lgtd_lifx_gateway_enqueue_packet( + gw, &hdr, LGTD_LIFX_GET_LIGHT_STATE, NULL, 0 + ); + gw->pending_refresh_req = true; } @@ -286,21 +311,39 @@ lgtd_lifx_gateway_close_all(void) } void -lgtd_lifx_gateway_send_packet(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const void *pkt, - int pkt_size) +lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt, + int pkt_size) { assert(gw); assert(hdr); assert(pkt_size >= 0 && pkt_size < LGTD_LIFX_MAX_PACKET_SIZE); assert(!memcmp(hdr->site, gw->site.as_array, LGTD_LIFX_ADDR_LENGTH)); + assert(gw->pkt_ring_head >= 0); + assert(gw->pkt_ring_head < (int)LGTD_ARRAY_SIZE(gw->pkt_ring)); + + if (gw->pkt_ring_full) { + lgtd_warnx( + "dropping packet type %s: packet queue on [%s]:%hu is full", + lgtd_lifx_wire_get_packet_infos(pkt_type)->name, + gw->ip_addr, gw->port + ); + return; + } evbuffer_add(gw->write_buf, hdr, sizeof(*hdr)); if (pkt) { assert((unsigned)pkt_size == le16toh(hdr->size) - sizeof(*hdr)); evbuffer_add(gw->write_buf, pkt, pkt_size); } + gw->pkt_ring[gw->pkt_ring_head].size = sizeof(*hdr) + pkt_size; + gw->pkt_ring[gw->pkt_ring_head].type = pkt_type; + LGTD_LIFX_GATEWAY_INC_MESSAGE_RING_INDEX(gw->pkt_ring_head); + if (gw->pkt_ring_head == gw->pkt_ring_tail) { + gw->pkt_ring_full = true; + } event_add(gw->write_ev, NULL); } @@ -352,10 +395,10 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, && b->last_light_state_at > b->dirty_at && b->gw->last_pkt_at - b->dirty_at > 400) { if (b->expected_power_on == b->state.power) { - lgtd_warnx("clearing dirty_at on %s", b->state.label); + lgtd_debug("clearing dirty_at on %s", b->state.label); b->dirty_at = 0; } else { - lgtd_warnx( + lgtd_info( "retransmiting power %s to %s", b->expected_power_on ? "on" : "off", b->state.label ); diff --git a/lifx/gateway.h b/lifx/gateway.h index b3ba208..a2d45e6 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -23,6 +23,14 @@ // still draw about 2W in ZigBee and about 3W in WiFi). enum { LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS = 200 }; +// You can't send more than one lifx packet per UDP datagram. +enum { LGTD_LIFX_GATEWAY_PACKET_RING_SIZE = 16 }; + +struct lgtd_lifx_message { + enum lgtd_lifx_packet_type type; + int size; +}; + struct lgtd_lifx_gateway { LIST_ENTRY(lgtd_lifx_gateway) link; struct lgtd_lifx_bulb_list bulbs; @@ -47,6 +55,14 @@ struct lgtd_lifx_gateway { lgtd_time_mono_t last_req_at; lgtd_time_mono_t next_req_at; lgtd_time_mono_t last_pkt_at; + struct lgtd_lifx_message pkt_ring[LGTD_LIFX_GATEWAY_PACKET_RING_SIZE]; +#define LGTD_LIFX_GATEWAY_INC_MESSAGE_RING_INDEX(idx) do { \ + (idx) += 1; \ + (idx) %= LGTD_LIFX_GATEWAY_PACKET_RING_SIZE; \ +} while(0) + int pkt_ring_head; + int pkt_ring_tail; + bool pkt_ring_full; struct event *write_ev; struct evbuffer *write_buf; bool pending_refresh_req; @@ -67,10 +83,11 @@ void lgtd_lifx_gateway_close_all(void); void lgtd_lifx_gateway_force_refresh(struct lgtd_lifx_gateway *); -void lgtd_lifx_gateway_send_packet(struct lgtd_lifx_gateway *, - const struct lgtd_lifx_packet_header *, - const void *, - int); +void lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + enum lgtd_lifx_packet_type, + const void *, + int); void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, diff --git a/tests/core/router/tests_router_utils.h b/tests/core/router/tests_router_utils.h index 36f03d2..3a16faa 100644 --- a/tests/core/router/tests_router_utils.h +++ b/tests/core/router/tests_router_utils.h @@ -7,10 +7,11 @@ struct { } lgtd_tests_gw_pkt_queue[16] = { { NULL, NULL, NULL, 0}, }; void -lgtd_lifx_gateway_send_packet(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const void *pkt, - int pkt_size) +lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt, + int pkt_size) { lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].gw = gw; // headers are created on the stack so we need to dup them: diff --git a/tests/lifx/gateway/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt new file mode 100644 index 0000000..2a85fd2 --- /dev/null +++ b/tests/lifx/gateway/CMakeLists.txt @@ -0,0 +1,26 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_LIBRARY( + test_lifx_gateway STATIC + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/proto.c + ${LIGHTSD_SOURCE_DIR}/core/router.c + ${LIGHTSD_SOURCE_DIR}/lifx/broadcast.c + ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/timer.c + ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${TIME_MONOTONIC_IMPL} +) + +FUNCTION(ADD_GATEWAY_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_gateway) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_GATEWAY_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet.c b/tests/lifx/gateway/test_gateway_enqueue_packet.c new file mode 100644 index 0000000..f96422a --- /dev/null +++ b/tests/lifx/gateway/test_gateway_enqueue_packet.c @@ -0,0 +1,65 @@ +#include "gateway.c" + +#include "test_gateway_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + gw.write_ev = (void *)42; + + struct lgtd_lifx_packet_power_state pkt; + pkt.power = LGTD_LIFX_POWER_ON; + + union lgtd_lifx_target target = { .tags = 0 }; + + struct lgtd_lifx_packet_header hdr; + lgtd_lifx_wire_setup_header( + &hdr, + LGTD_LIFX_TARGET_ALL_DEVICES, + target, + gw.site.as_array, + LGTD_LIFX_SET_POWER_STATE + ); + + lgtd_lifx_gateway_enqueue_packet( + &gw, &hdr, LGTD_LIFX_SET_POWER_STATE, &pkt, sizeof(pkt) + ); + + if (memcmp(gw_write_buf, &hdr, sizeof(hdr))) { + errx(1, "header incorrectly buffered"); + } + + if (memcmp(&gw_write_buf[sizeof(hdr)], &pkt, sizeof(pkt))) { + errx(1, "pkt incorrectly buffered"); + } + + if (gw.pkt_ring[0].type != LGTD_LIFX_SET_POWER_STATE) { + errx(1, "packet type incorrectly enqueued"); + } + + if (gw.pkt_ring[0].size != sizeof(pkt) + sizeof(hdr)) { + errx(1, "packet size incorrectly enqueued"); + } + + if (gw.pkt_ring_head != 1) { + errx(1, "packet ring head should be on index 1"); + } + + if (gw.pkt_ring_tail != 0) { + errx(1, "packet ring tail should be on index 0"); + } + + if (gw.pkt_ring_full == true) { + errx(1, "packet ring shouldn't be full"); + } + + if (last_event_passed_to_event_add != gw.write_ev) { + errx(1, "event_add should have been called with gw.write_ev"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c new file mode 100644 index 0000000..97a940f --- /dev/null +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c @@ -0,0 +1,89 @@ +#include "gateway.c" + +#include "test_gateway_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + gw.write_ev = (void *)42; + + struct lgtd_lifx_packet_power_state pkt; + pkt.power = LGTD_LIFX_POWER_ON; + + union lgtd_lifx_target target = { .tags = 0 }; + + struct lgtd_lifx_packet_header hdr; + lgtd_lifx_wire_setup_header( + &hdr, + LGTD_LIFX_TARGET_ALL_DEVICES, + target, + gw.site.as_array, + LGTD_LIFX_SET_POWER_STATE + ); + + // set the head so it catches up the tail: + gw.pkt_ring_head = 1; + gw.pkt_ring_tail = 2; + + lgtd_lifx_gateway_enqueue_packet( + &gw, &hdr, LGTD_LIFX_SET_POWER_STATE, &pkt, sizeof(pkt) + ); + + if (memcmp(gw_write_buf, &hdr, sizeof(hdr))) { + errx(1, "header incorrectly buffered"); + } + + if (memcmp(&gw_write_buf[sizeof(hdr)], &pkt, sizeof(pkt))) { + errx(1, "pkt incorrectly buffered"); + } + + if (gw.pkt_ring[1].type != LGTD_LIFX_SET_POWER_STATE) { + errx(1, "packet type incorrectly enqueued"); + } + + if (gw.pkt_ring[1].size != sizeof(pkt) + sizeof(hdr)) { + errx(1, "packet size incorrectly enqueued"); + } + + if (gw.pkt_ring_head != 2) { + errx(1, "packet ring head should be on index 2"); + } + + if (gw.pkt_ring_tail != 2) { + errx(1, "packet ring tail should be on index 2"); + } + + if (gw.pkt_ring_full != true) { + errx(1, "packet ring should be full"); + } + + if (last_event_passed_to_event_add != gw.write_ev) { + errx(1, "event_add should have been called with gw.write_ev"); + } + + lgtd_lifx_gateway_enqueue_packet( + &gw, &hdr, LGTD_LIFX_SET_POWER_STATE, &pkt, sizeof(pkt) + ); + + if (gw_write_buf_idx != sizeof(pkt) + sizeof(hdr)) { + errx(1, "nothing should have been buffered"); + } + + if (gw.pkt_ring_head != 2) { + errx(1, "packet ring head should be on index 2"); + } + + if (gw.pkt_ring_tail != 2) { + errx(1, "packet ring tail should be on index 2"); + } + + if (gw.pkt_ring_full != true) { + errx(1, "packet ring should be full"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c new file mode 100644 index 0000000..680c753 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c @@ -0,0 +1,71 @@ +#include "gateway.c" + +#include "test_gateway_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + gw.write_ev = (void *)42; + + struct lgtd_lifx_packet_power_state pkt; + pkt.power = LGTD_LIFX_POWER_ON; + + union lgtd_lifx_target target = { .tags = 0 }; + + struct lgtd_lifx_packet_header hdr; + lgtd_lifx_wire_setup_header( + &hdr, + LGTD_LIFX_TARGET_ALL_DEVICES, + target, + gw.site.as_array, + LGTD_LIFX_SET_POWER_STATE + ); + + int pkt_ring_last_idx = LGTD_ARRAY_SIZE(gw.pkt_ring) - 1; + + // set the head so it has to wrap-around and set the tail somewhere: + gw.pkt_ring_head = pkt_ring_last_idx; + gw.pkt_ring_tail = 2; + + lgtd_lifx_gateway_enqueue_packet( + &gw, &hdr, LGTD_LIFX_SET_POWER_STATE, &pkt, sizeof(pkt) + ); + + if (memcmp(gw_write_buf, &hdr, sizeof(hdr))) { + errx(1, "header incorrectly buffered"); + } + + if (memcmp(&gw_write_buf[sizeof(hdr)], &pkt, sizeof(pkt))) { + errx(1, "pkt incorrectly buffered"); + } + + if (gw.pkt_ring[pkt_ring_last_idx].type != LGTD_LIFX_SET_POWER_STATE) { + errx(1, "packet type incorrectly enqueued"); + } + + if (gw.pkt_ring[pkt_ring_last_idx].size != sizeof(pkt) + sizeof(hdr)) { + errx(1, "packet size incorrectly enqueued"); + } + + if (gw.pkt_ring_head != 0) { + errx(1, "packet ring head should have wrapped around to index 0"); + } + + if (gw.pkt_ring_tail != 2) { + errx(1, "packet ring tail should be on index 2"); + } + + if (gw.pkt_ring_full == true) { + errx(1, "packet ring shouldn't be full"); + } + + if (last_event_passed_to_event_add != gw.write_ev) { + errx(1, "event_add should have been called with gw.write_ev"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_utils.h b/tests/lifx/gateway/test_gateway_utils.h new file mode 100644 index 0000000..cefd2fb --- /dev/null +++ b/tests/lifx/gateway/test_gateway_utils.h @@ -0,0 +1,162 @@ +#pragma once + +static char gw_write_buf[4096] = { 0 }; +static int gw_write_buf_idx = 0; + +static inline void +reset_gw_write_buf(void) +{ + memset(gw_write_buf, 0, sizeof(gw_write_buf)); + gw_write_buf_idx = 0; +} + +int +evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen) +{ + (void)buf; + int to_write = LGTD_MIN( + datlen, sizeof(gw_write_buf) - gw_write_buf_idx + ); + memcpy(&gw_write_buf[gw_write_buf_idx], data, to_write); + gw_write_buf_idx += to_write; + return 0; +} + +struct lgtd_lifx_tag_list lgtd_lifx_tags = LIST_HEAD_INITIALIZER(&lgtd_lifx_tags); + +#ifndef MOCKED_EVBUFFER_GET_LENGTH +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + (void)buf; + return gw_write_buf_idx + 1; +} +#endif + +#ifndef MOCKED_EVBUFFER_WRITE_ATMOST +int +evbuffer_write_atmost(struct evbuffer *buf, + evutil_socket_t fd, + ev_ssize_t howmuch) +{ + (void)buf; + (void)fd; + (void)howmuch; + return howmuch; +} +#endif + +#ifndef MOCKED_LIFX_TAGGING_INCREF +struct lgtd_lifx_tag * +lgtd_lifx_tagging_incref(const char *label, const struct lgtd_lifx_gateway *gw) +{ + struct lgtd_lifx_tag *tag = calloc(1, sizeof(*tag)); + strcpy(tag->label, label); + LIST_INSERT_HEAD(&lgtd_lifx_tags, tag, link); + struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); + site->gw = gw; + LIST_INSERT_HEAD(&tag->sites, site, link); + return tag; +} +#endif + +#ifndef MOCKED_LIFX_TAGGING_DECREF +void +lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, + const struct lgtd_lifx_gateway *gw) +{ + (void)tag; + (void)gw; +} +#endif + +struct evbuffer * +evbuffer_new(void) +{ + return NULL; +} + +void +evbuffer_free(struct evbuffer *buf) +{ + (void)buf; +} + + +static struct event *last_event_passed_to_event_add = NULL; + +int +event_add(struct event *ev, const struct timeval *timeout) +{ + (void)timeout; + last_event_passed_to_event_add = ev; + return 0; +} + +static struct event *last_event_passed_to_event_del = NULL; + +int +event_del(struct event *ev) +{ + last_event_passed_to_event_del = ev; + return 0; +} + +void +event_active(struct event *ev, int res, short ncalls) +{ + (void)ev; + (void)res; + (void)ncalls; +} + +struct event * +event_new(struct event_base *evbase, + evutil_socket_t sock, + short events, + event_callback_fn cb, + void *ctx) +{ + (void)evbase; + (void)sock; + (void)events; + (void)cb; + (void)ctx; + return NULL; +} + +void +event_free(struct event *ev) +{ + (void)ev; +} + +int +event_pending(const struct event *ev, short events, struct timeval *tv) +{ + (void)ev; + (void)events; + (void)tv; + return 0; +} + +int +evutil_closesocket(evutil_socket_t sock) +{ + (void)sock; + return 0; +} + +int +evutil_make_socket_nonblocking(evutil_socket_t sock) +{ + (void)sock; + return 0; +} + +int +evutil_make_listen_socket_reuseable(evutil_socket_t sock) +{ + (void)sock; + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_write_callback.c b/tests/lifx/gateway/test_gateway_write_callback.c new file mode 100644 index 0000000..adc749e --- /dev/null +++ b/tests/lifx/gateway/test_gateway_write_callback.c @@ -0,0 +1,77 @@ +#include "gateway.c" + +#define MOCKED_EVBUFFER_WRITE_ATMOST +#define MOCKED_EVBUFFER_GET_LENGTH +#include "test_gateway_utils.h" + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)42) { + errx(1, "didn't get the expected evbuffer"); + } + + // fake another packet to write: + return sizeof(struct lgtd_lifx_packet_header); +} + +int +evbuffer_write_atmost(struct evbuffer *buf, + evutil_socket_t fd, + ev_ssize_t howmuch) +{ + if (fd != 25) { + errx(1, "evbuffer_write_atmost didn't get the expected socket"); + } + + if (buf != (void *)42) { + errx(1, "evbuffer_write_atmost didn't get the expected evbuffer"); + } + + int expected = sizeof(struct lgtd_lifx_packet_header); + expected += sizeof(struct lgtd_lifx_packet_power_state); + if (howmuch != expected) { + errx( + 1, "evbuffer_write_atmost expected %d but got %ld", + expected, howmuch + ); + } + + return howmuch; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + // fake some values: + gw.socket = 25; + gw.write_ev = (void *)21; + gw.write_buf = (void *)42; + + gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_header); + gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_power_state); + gw.pkt_ring[0].type = LGTD_LIFX_SET_POWER_STATE; + gw.pkt_ring_head++; + gw.pkt_ring_head++; + + lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + + if (gw.pkt_ring[0].size != 0 || gw.pkt_ring[0].type != 0) { + errx(1, "the ring entry should have been reset"); + } + + if (gw.pkt_ring_tail != 1) { + errx(1, "the tail shoud have been moved by one"); + } + + if (last_event_passed_to_event_del != NULL) { + errx(1, "event_del shouldn't have ben called"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c new file mode 100644 index 0000000..02fdd7b --- /dev/null +++ b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c @@ -0,0 +1,80 @@ +#include "gateway.c" + +#define MOCKED_EVBUFFER_WRITE_ATMOST +#define MOCKED_EVBUFFER_GET_LENGTH +#include "test_gateway_utils.h" + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)42) { + errx(1, "didn't get the expected evbuffer"); + } + + // fake another packet to write: + return sizeof(struct lgtd_lifx_packet_header); +} + +int +evbuffer_write_atmost(struct evbuffer *buf, + evutil_socket_t fd, + ev_ssize_t howmuch) +{ + if (fd != 25) { + errx(1, "evbuffer_write_atmost didn't get the expected socket"); + } + + if (buf != (void *)42) { + errx(1, "evbuffer_write_atmost didn't get the expected evbuffer"); + } + + int expected = sizeof(struct lgtd_lifx_packet_header); + expected += sizeof(struct lgtd_lifx_packet_power_state); + if (howmuch != expected) { + errx( + 1, "evbuffer_write_atmost expected %d but got %ld", + expected, howmuch + ); + } + + return howmuch; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + // fake some values: + gw.socket = 25; + gw.write_ev = (void *)21; + gw.write_buf = (void *)42; + + gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_header); + gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_power_state); + gw.pkt_ring[0].type = LGTD_LIFX_SET_POWER_STATE; + gw.pkt_ring_full = true; + + lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + + if (gw.pkt_ring[0].size != 0 || gw.pkt_ring[0].type != 0) { + errx(1, "the ring entry should have been reset"); + } + + if (gw.pkt_ring_tail != 1) { + errx(1, "the tail shoud have been moved by one"); + } + + if (last_event_passed_to_event_del != NULL) { + errx(1, "event_del shouldn't have ben called"); + } + + if (gw.pkt_ring_full) { + errx(1, "the ring full flag should have been cleared out"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c new file mode 100644 index 0000000..4ec0713 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c @@ -0,0 +1,76 @@ +#include "gateway.c" + +#define MOCKED_EVBUFFER_WRITE_ATMOST +#define MOCKED_EVBUFFER_GET_LENGTH +#include "test_gateway_utils.h" + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)42) { + errx(1, "didn't get the expected evbuffer"); + } + + return 0; +} + +int +evbuffer_write_atmost(struct evbuffer *buf, + evutil_socket_t fd, + ev_ssize_t howmuch) +{ + if (fd != 25) { + errx(1, "evbuffer_write_atmost didn't get the expected socket"); + } + + if (buf != (void *)42) { + errx(1, "evbuffer_write_atmost didn't get the expected evbuffer"); + } + + int expected = sizeof(struct lgtd_lifx_packet_header); + expected += sizeof(struct lgtd_lifx_packet_power_state); + if (howmuch != expected) { + errx( + 1, "evbuffer_write_atmost expected %d but got %ld", + expected, howmuch + ); + } + + return howmuch; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + // fake some values: + gw.socket = 25; + gw.write_ev = (void *)21; + gw.write_buf = (void *)42; + + gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_header); + gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_power_state); + gw.pkt_ring[0].type = LGTD_LIFX_SET_POWER_STATE; + gw.pkt_ring_head++; + gw.pkt_ring_head++; + + lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + + if (gw.pkt_ring[0].size != 0 || gw.pkt_ring[0].type != 0) { + errx(1, "the ring entry should have been reset"); + } + + if (gw.pkt_ring_tail != 1) { + errx(1, "the tail shoud have been moved by one"); + } + + if (last_event_passed_to_event_del != (void *)21) { + errx(1, "event_del should have ben called"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c new file mode 100644 index 0000000..39da792 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c @@ -0,0 +1,101 @@ +#include "gateway.c" + +#define MOCKED_EVBUFFER_WRITE_ATMOST +#define MOCKED_EVBUFFER_GET_LENGTH +#include "test_gateway_utils.h" + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)42) { + errx(1, "didn't get the expected evbuffer"); + } + + // fake another packet to write: + return sizeof(struct lgtd_lifx_packet_header); +} + +int +evbuffer_write_atmost(struct evbuffer *buf, + evutil_socket_t fd, + ev_ssize_t howmuch) +{ + if (fd != 25) { + errx(1, "evbuffer_write_atmost didn't get the expected socket"); + } + + if (buf != (void *)42) { + errx(1, "evbuffer_write_atmost didn't get the expected evbuffer"); + } + + static int expected = ( + sizeof(struct lgtd_lifx_packet_header) + + sizeof(struct lgtd_lifx_packet_power_state) + ); + if (howmuch != expected) { + errx( + 1, "evbuffer_write_atmost expected %d but got %ld", + expected, howmuch + ); + } + + if (expected != sizeof(struct lgtd_lifx_packet_power_state)) { + expected -= sizeof(struct lgtd_lifx_packet_header); + return sizeof(struct lgtd_lifx_packet_header); + } + + return howmuch; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + // fake some values: + gw.socket = 25; + gw.write_buf = (void *)42; + + gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_header); + gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_power_state); + gw.pkt_ring[0].type = LGTD_LIFX_SET_POWER_STATE; + gw.pkt_ring_head++; + gw.pkt_ring_head++; + + lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + + if (gw.pkt_ring[0].type != LGTD_LIFX_SET_POWER_STATE) { + errx(1, "the ring entry doesn't have the right packet type"); + } + + if (gw.pkt_ring[0].size != sizeof(struct lgtd_lifx_packet_power_state)) { + errx(1, "the ring entry doesn't have the right size value"); + } + + if (gw.pkt_ring_tail != 0) { + errx(1, "the tail shoudn't have been moved by one"); + } + + if (last_event_passed_to_event_del != NULL) { + errx(1, "event_del shouldn't have ben called"); + } + + lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + + if (gw.pkt_ring[0].size != 0 || gw.pkt_ring[0].type != 0) { + errx(1, "the ring entry should have been reset"); + } + + if (gw.pkt_ring_tail != 1) { + errx(1, "the tail shoud have been moved by one"); + } + + if (last_event_passed_to_event_del != NULL) { + errx(1, "event_del shouldn't have ben called"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c new file mode 100644 index 0000000..f4ddf11 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c @@ -0,0 +1,79 @@ +#include "gateway.c" + +#define MOCKED_EVBUFFER_WRITE_ATMOST +#define MOCKED_EVBUFFER_GET_LENGTH +#include "test_gateway_utils.h" + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)42) { + errx(1, "didn't get the expected evbuffer"); + } + + // fake another packet to write: + return sizeof(struct lgtd_lifx_packet_header); +} + +int +evbuffer_write_atmost(struct evbuffer *buf, + evutil_socket_t fd, + ev_ssize_t howmuch) +{ + if (fd != 25) { + errx(1, "evbuffer_write_atmost didn't get the expected socket"); + } + + if (buf != (void *)42) { + errx(1, "evbuffer_write_atmost didn't get the expected evbuffer"); + } + + int expected = sizeof(struct lgtd_lifx_packet_header); + expected += sizeof(struct lgtd_lifx_packet_power_state); + if (howmuch != expected) { + errx( + 1, "evbuffer_write_atmost expected %d but got %ld", + expected, howmuch + ); + } + + return howmuch; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + // fake some values: + gw.socket = 25; + gw.write_ev = (void *)21; + gw.write_buf = (void *)42; + + int pkt_ring_last_idx = LGTD_ARRAY_SIZE(gw.pkt_ring) - 1; + gw.pkt_ring_tail = pkt_ring_last_idx; + + gw.pkt_ring[pkt_ring_last_idx].size += sizeof(struct lgtd_lifx_packet_header); + gw.pkt_ring[pkt_ring_last_idx].size += sizeof(struct lgtd_lifx_packet_power_state); + gw.pkt_ring[pkt_ring_last_idx].type = LGTD_LIFX_SET_POWER_STATE; + + lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + + if (gw.pkt_ring[pkt_ring_last_idx].size != 0 + || gw.pkt_ring[pkt_ring_last_idx].type != 0) { + errx(1, "the ring entry should have been reset"); + } + + if (gw.pkt_ring_tail != 0) { + errx(1, "the tail shoud have wrapped around to 0"); + } + + if (last_event_passed_to_event_del != NULL) { + errx(1, "event_del shouldn't have ben called"); + } + + return 0; +} diff --git a/tests/lifx/tests_shims.c b/tests/lifx/tests_shims.c new file mode 100644 index 0000000..4d6a313 --- /dev/null +++ b/tests/lifx/tests_shims.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include +#include + +#include "core/lightsd.h" + +struct lgtd_opts lgtd_opts = { + .foreground = false, + .log_timestamps = false, + .verbosity = LGTD_DEBUG +}; + +struct event_base *lgtd_ev_base = NULL; + +void +lgtd_cleanup(void) +{ +} + +short +lgtd_sockaddrport(const struct sockaddr_storage *peer) +{ + if (!peer) { + return -1; + } + + if (peer->ss_family == AF_INET) { + const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; + return ntohs(in_peer->sin_port); + } else { + const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; + return ntohs(in6_peer->sin6_port); + } +} From a70d23ad0d43e66997dff207aa3b6f5ff494f3ce Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 9 May 2015 01:23:17 -0700 Subject: [PATCH 032/181] Handle the LIFX tag labels traffic First step towards full tagging support. --- CMakeLists.txt | 2 + lifx/gateway.c | 125 ++++++++++++++++ lifx/gateway.h | 10 ++ lifx/tagging.c | 135 ++++++++++++++++++ lifx/tagging.h | 37 +++++ lifx/wire_proto.c | 24 ++++ lifx/wire_proto.h | 10 ++ tests/core/router/tests_router_utils.h | 4 + tests/core/tests_shims.c | 9 ++ tests/core/tests_utils.c | 14 ++ tests/core/tests_utils.h | 1 + ...ateway_allocate_tag_id_from_lifx_network.c | 122 ++++++++++++++++ ...eway_deallocate_tag_id_from_lifx_network.c | 72 ++++++++++ .../gateway/test_gateway_handle_tag_labels.c | 78 ++++++++++ tests/lifx/tagging/CMakeLists.txt | 19 +++ tests/lifx/tagging/test_tagging_decref.c | 67 +++++++++ tests/lifx/tagging/test_tagging_incref.c | 65 +++++++++ tests/lifx/wire_proto/test_wire_proto_utils.h | 9 ++ 18 files changed, 803 insertions(+) create mode 100644 lifx/tagging.c create mode 100644 lifx/tagging.h create mode 100644 tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c create mode 100644 tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c create mode 100644 tests/lifx/gateway/test_gateway_handle_tag_labels.c create mode 100644 tests/lifx/tagging/CMakeLists.txt create mode 100644 tests/lifx/tagging/test_tagging_decref.c create mode 100644 tests/lifx/tagging/test_tagging_incref.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 93894d2..7b5399a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,8 @@ INCLUDE(AddTestFromSources) SET(CMAKE_C_FLAGS "-pipe -Wextra -Wall -Wstrict-prototypes -std=c99") +ADD_DEFINITIONS("-DLGTD_SIZEOF_VOID_P=${CMAKE_SIZEOF_VOID_P}") + # Only relevant for the GNU libc: ADD_DEFINITIONS( "-D_POSIX_C_SOURCE=200809L" diff --git a/lifx/gateway.c b/lifx/gateway.c index e0e5ca4..c833753 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -38,6 +38,7 @@ #include "gateway.h" #include "broadcast.h" #include "timer.h" +#include "tagging.h" #include "core/jsmn.h" #include "core/jsonrpc.h" #include "core/client.h" @@ -48,6 +49,26 @@ struct lgtd_lifx_gateway_list lgtd_lifx_gateways = LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); +// Kim Walisch (2012) +// http://chessprogramming.wikispaces.com/BitScan#DeBruijnMultiplation +static inline int +lgtd_lifx_bitscan64_forward(uint64_t n) +{ + enum { DEBRUIJN_NUMBER = 0x03f79d71b4cb0a89 }; + static const int DEBRUIJN_SEQUENCE[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, + 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, + 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, + 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, + 13, 18, 8, 12, 7, 6, 5, 63 + }; + + return n ? DEBRUIJN_SEQUENCE[((n ^ (n - 1)) * DEBRUIJN_NUMBER) >> 58] : -1; +} + void lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) { @@ -62,6 +83,11 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) event_free(gw->refresh_ev); event_free(gw->write_ev); evbuffer_free(gw->write_buf); + for (int i = 0; i != LGTD_LIFX_GATEWAY_MAX_TAGS; i++) { + if (gw->tags[i]) { + lgtd_lifx_tagging_decref(gw->tags[i], gw); + } + } struct lgtd_lifx_bulb *bulb, *next_bulb; SLIST_FOREACH_SAFE(bulb, &gw->bulbs, link_by_gw, next_bulb) { lgtd_lifx_bulb_close(bulb); @@ -161,6 +187,18 @@ lgtd_lifx_gateway_send_get_all_light_state(struct lgtd_lifx_gateway *gw) gw, &hdr, LGTD_LIFX_GET_LIGHT_STATE, NULL, 0 ); + struct lgtd_lifx_packet_get_tag_labels pkt = { .tags = LGTD_LIFX_ALL_TAGS }; + lgtd_lifx_wire_setup_header( + &hdr, + LGTD_LIFX_TARGET_SITE, + target, + gw->site.as_array, + LGTD_LIFX_GET_TAG_LABELS + ); + lgtd_lifx_gateway_enqueue_packet( + gw, &hdr, LGTD_LIFX_GET_TAG_LABELS, &pkt, sizeof(pkt) + ); + gw->pending_refresh_req = true; } @@ -458,3 +496,90 @@ lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, lgtd_lifx_bulb_set_power_state(b, pkt->power); } + +#if LGTD_SIZEOF_VOID_P == 8 +# define TAG_ID_TO_VALUE(x) (1UL << (x)) +#elif LGTD_SIZEOF_VOID_P == 4 +# define TAG_ID_TO_VALUE(x) (1ULL << (x)) +#endif + +int +lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, + int tag_id, + const char *tag_label) +{ + assert(gw); + assert(tag_label); + // allocating a new tag_id (tag_id == -1) isn't supported yet: + assert(tag_id >= 0); + assert(tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); + + if (!(gw->tag_ids & TAG_ID_TO_VALUE(tag_id))) { + struct lgtd_lifx_tag *tag; + tag = lgtd_lifx_tagging_incref(tag_label, gw); + if (!tag) { + lgtd_warn( + "couldn't allocate a new reference to tag [%s] (site %s)", + tag_label, lgtd_addrtoa(gw->site.as_array) + ); + return -1; + } + lgtd_debug( + "tag_id %d allocated for tag [%s] on gw [%s]:%hu (site %s)", + tag_id, tag_label, gw->ip_addr, gw->port, + lgtd_addrtoa(gw->site.as_array) + ); + gw->tag_ids |= TAG_ID_TO_VALUE(tag_id); + gw->tags[tag_id] = tag; + } + + return tag_id; +} + +void +lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id) +{ + assert(gw); + assert(tag_id >= 0); + assert(tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); + + if (gw->tag_ids & TAG_ID_TO_VALUE(tag_id)) { + lgtd_debug( + "tag_id %d deallocated for tag [%s] on gw [%s]:%hu (site %s)", + tag_id, gw->tags[tag_id]->label, + gw->ip_addr, gw->port, + lgtd_addrtoa(gw->site.as_array) + ); + lgtd_lifx_tagging_decref(gw->tags[tag_id], gw); + gw->tag_ids &= ~TAG_ID_TO_VALUE(tag_id); + gw->tags[tag_id] = NULL; + } +} + +void +lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_tag_labels *pkt) +{ + assert(gw && hdr && pkt); + + lgtd_debug( + "SET_TAG_LABELS <-- [%s]:%hu - %s label=%s, tags=%jx", + gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), + pkt->label, (uintmax_t)pkt->tags + ); + + uint64_t tags = pkt->tags; + while (true) { + int tag_id = lgtd_lifx_bitscan64_forward(tags); + if (tag_id == -1) { + break; + } + if (pkt->label[0]) { + lgtd_lifx_gateway_allocate_tag_id(gw, tag_id, pkt->label); + } else if (gw->tags[tag_id]) { + lgtd_lifx_gateway_deallocate_tag_id(gw, tag_id); + } + tags &= ~TAG_ID_TO_VALUE(tag_id); + } +} diff --git a/lifx/gateway.h b/lifx/gateway.h index a2d45e6..9e472ec 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -26,6 +26,8 @@ enum { LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS = 200 }; // You can't send more than one lifx packet per UDP datagram. enum { LGTD_LIFX_GATEWAY_PACKET_RING_SIZE = 16 }; +enum { LGTD_LIFX_GATEWAY_MAX_TAGS = 64 }; + struct lgtd_lifx_message { enum lgtd_lifx_packet_type type; int size; @@ -47,6 +49,8 @@ struct lgtd_lifx_gateway { uint8_t as_array[LGTD_LIFX_ADDR_LENGTH]; uint64_t as_integer; } site; + uint64_t tag_ids; + struct lgtd_lifx_tag *tags[LGTD_LIFX_GATEWAY_MAX_TAGS]; evutil_socket_t socket; // Those three timers let us measure the latency of the gateway. If we // aren't the only client on the network then this won't be accurate since @@ -89,6 +93,9 @@ void lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *, const void *, int); +int lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *, int, const char *); +void lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *, int); + void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, const struct lgtd_lifx_packet_pan_gateway *); @@ -98,3 +105,6 @@ void lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *, void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, const struct lgtd_lifx_packet_power_state *); +void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_tag_labels *); diff --git a/lifx/tagging.c b/lifx/tagging.c new file mode 100644 index 0000000..34101cb --- /dev/null +++ b/lifx/tagging.c @@ -0,0 +1,135 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wire_proto.h" +#include "core/time_monotonic.h" +#include "bulb.h" +#include "gateway.h" +#include "tagging.h" +#include "core/lightsd.h" + +struct lgtd_lifx_tag_list lgtd_lifx_tags = + LIST_HEAD_INITIALIZER(&lgtd_lifx_tags); + +static struct lgtd_lifx_tag * +lgtd_lifx_tagging_find_tag(const char *tag_label) +{ + struct lgtd_lifx_tag *tag = NULL; + LIST_FOREACH(tag, &lgtd_lifx_tags, link) { + if (!strcmp(tag->label, tag_label)) { + break; + } + } + return tag; +} + +static struct lgtd_lifx_site * +lgtd_lifx_tagging_find_site(struct lgtd_lifx_site_list *sites, + const struct lgtd_lifx_gateway *gw) +{ + struct lgtd_lifx_site *site = NULL; + LIST_FOREACH(site, sites, link) { + if (site->gw == gw) { + break; + } + } + return site; +} + +struct lgtd_lifx_tag * +lgtd_lifx_tagging_incref(const char *tag_label, + const struct lgtd_lifx_gateway *gw) +{ + assert(strlen(tag_label) < LGTD_LIFX_LABEL_SIZE); + assert(gw); + + bool dealloc_tag = false; + struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(tag_label); + if (!tag) { + tag = calloc(1, sizeof(*tag)); + if (!tag) { + return NULL; + } + strncpy(tag->label, tag_label, sizeof(tag->label) - 1); + LIST_INSERT_HEAD(&lgtd_lifx_tags, tag, link); + dealloc_tag = true; + } + + struct lgtd_lifx_site *site = lgtd_lifx_tagging_find_site(&tag->sites, gw); + if (!site) { + site = calloc(1, sizeof(*site)); + if (!site) { + if (dealloc_tag) { + LIST_REMOVE(tag, link); + free(tag); + } + errno = ENOMEM; + return NULL; + } + if (dealloc_tag) { + lgtd_info("discovered tag [%s]", tag_label); + } + lgtd_debug( + "tag [%s] added to gw [%s]:%hu (site %s)", + tag_label, gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) + ); + site->gw = gw; + LIST_INSERT_HEAD(&tag->sites, site, link); + } + + return tag; +} + +void +lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, + const struct lgtd_lifx_gateway *gw) +{ + assert(tag); + assert(gw); + + struct lgtd_lifx_site *site; + site = lgtd_lifx_tagging_find_site(&tag->sites, gw); + if (site) { + lgtd_debug( + "tag [%s] removed from gw [%s]:%hu (site %s)", + tag->label, gw->ip_addr, gw->port, + lgtd_addrtoa(gw->site.as_array) + ); + LIST_REMOVE(site, link); + free(site); + } + if (LIST_EMPTY(&tag->sites)) { + LIST_REMOVE(tag, link); + lgtd_info("forgetting unused tag [%s]", tag->label); + free(tag); + } +} diff --git a/lifx/tagging.h b/lifx/tagging.h new file mode 100644 index 0000000..11aaadf --- /dev/null +++ b/lifx/tagging.h @@ -0,0 +1,37 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#pragma once + +extern struct lgtd_lifx_tag_list lgtd_lifx_tags; + +struct lgtd_lifx_site { + LIST_ENTRY(lgtd_lifx_site) link; + const struct lgtd_lifx_gateway *gw; +}; +LIST_HEAD(lgtd_lifx_site_list, lgtd_lifx_site); + +struct lgtd_lifx_tag { + LIST_ENTRY(lgtd_lifx_tag) link; + char label[LGTD_LIFX_LABEL_SIZE]; + struct lgtd_lifx_site_list sites; +}; +LIST_HEAD(lgtd_lifx_tag_list, lgtd_lifx_tag); + +struct lgtd_lifx_tag *lgtd_lifx_tagging_incref(const char *, + const struct lgtd_lifx_gateway *); +void lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *, const struct lgtd_lifx_gateway *); diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 9cf5c79..c426343 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -136,6 +136,21 @@ lgtd_lifx_wire_load_packet_infos_map(void) .type = LGTD_LIFX_SET_WAVEFORM, .size = sizeof(struct lgtd_lifx_packet_waveform), .encode = ENCODER(lgtd_lifx_wire_encode_waveform) + }, + { + REQUEST_ONLY, + .name = "GET_TAG_LABELS", + .type = LGTD_LIFX_GET_TAG_LABELS, + .size = sizeof(struct lgtd_lifx_packet_get_tag_labels), + .encode = lgtd_lifx_wire_null_packet_encoder_decoder + }, + { + RESPONSE_ONLY, + .name = "TAG_LABELS", + .type = LGTD_LIFX_TAG_LABELS, + .size = sizeof(struct lgtd_lifx_packet_tag_labels), + .decode = DECODER(lgtd_lifx_wire_decode_tag_labels), + .handle = HANDLER(lgtd_lifx_gateway_handle_tag_labels) } }; @@ -320,3 +335,12 @@ lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) pkt->period = htole16(pkt->period); pkt->skew_ratio = htole16(pkt->skew_ratio); } + +void +lgtd_lifx_wire_decode_tag_labels(struct lgtd_lifx_packet_tag_labels *pkt) +{ + assert(pkt); + + pkt->label[sizeof(pkt->label) - 1] = '\0'; + pkt->tags = le64toh(pkt->tags); +} diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index f4d68df..7e2158a 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -199,6 +199,16 @@ struct lgtd_lifx_packet_waveform { uint8_t waveform; // see enum lgtd_lifx_waveform_type }; +enum { LGTD_LIFX_ALL_TAGS = ~0 }; +struct lgtd_lifx_packet_get_tag_labels { + uint64le_t tags; +}; + +struct lgtd_lifx_packet_tag_labels { + uint64le_t tags; + char label[LGTD_LIFX_LABEL_SIZE]; +}; + #pragma pack(pop) struct lgtd_lifx_waveform_string_id { diff --git a/tests/core/router/tests_router_utils.h b/tests/core/router/tests_router_utils.h index 3a16faa..6901dc5 100644 --- a/tests/core/router/tests_router_utils.h +++ b/tests/core/router/tests_router_utils.h @@ -1,3 +1,5 @@ +#pragma once + int lgtd_tests_gw_pkt_queue_size = 0; struct { struct lgtd_lifx_gateway *gw; @@ -13,6 +15,8 @@ lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, const void *pkt, int pkt_size) { + (void)pkt_type; + lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].gw = gw; // headers are created on the stack so we need to dup them: lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].hdr = malloc( diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c index 08b31a3..4203ef4 100644 --- a/tests/core/tests_shims.c +++ b/tests/core/tests_shims.c @@ -56,3 +56,12 @@ void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, (void)hdr; (void)pkt; } + +void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_tag_labels *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index a990d13..d713e64 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -14,6 +14,7 @@ #include "lifx/wire_proto.h" #include "core/time_monotonic.h" +#include "lifx/tagging.h" #include "core/jsmn.h" #include "core/jsonrpc.h" #include "core/client.h" @@ -25,6 +26,9 @@ struct lgtd_lifx_gateway_list lgtd_lifx_gateways = LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); +struct lgtd_lifx_tag_list lgtd_lifx_tags = + LIST_HEAD_INITIALIZER(&lgtd_lifx_tags); + struct lgtd_lifx_gateway * lgtd_tests_insert_mock_gateway(int id) { @@ -75,3 +79,13 @@ lgtd_tests_build_target_list(const char *target, ...) return targets; } + +struct lgtd_lifx_tag * +lgtd_tests_insert_mock_tag(const char *tag_label) +{ + assert(strlen(tag_label) < LGTD_LIFX_LABEL_SIZE); + struct lgtd_lifx_tag *tag = calloc(1, sizeof(*tag)); + strcpy(tag->label, tag_label); + LIST_INSERT_HEAD(&lgtd_lifx_tags, tag, link); + return tag; +} diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index 88aff54..5bcb2d0 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -3,3 +3,4 @@ struct lgtd_lifx_gateway *lgtd_tests_insert_mock_gateway(int); struct lgtd_lifx_bulb *lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *, uint64_t); struct lgtd_proto_target_list *lgtd_tests_build_target_list(const char *, ...); +struct lgtd_lifx_tag *lgtd_tests_insert_mock_tag(const char *); diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c new file mode 100644 index 0000000..56a0d1a --- /dev/null +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c @@ -0,0 +1,122 @@ +#include + +#include "gateway.c" + +#define MOCKED_LIFX_TAGGING_INCREF +#include "test_gateway_utils.h" + +static bool tagging_incref_called = false; + +struct lgtd_lifx_tag * +lgtd_lifx_tagging_incref(const char *label, const struct lgtd_lifx_gateway *gw) +{ + if (!label) { + errx(1, "missing tag label"); + } + if (!gw) { + errx(1, "missing gateway"); + } + + static struct lgtd_lifx_tag *tag = NULL; + + if (!tag) { + tag = calloc(1, sizeof(*tag)); + strcpy(tag->label, label); + struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); + site->gw = gw; + LIST_INSERT_HEAD(&tag->sites, site, link); + } + + tagging_incref_called = true; + + return tag; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + + lgtd_lifx_gateway_allocate_tag_id(&gw, 4, "test"); + if (!gw.tags[4]) { + errx(1, "gw.tag_ids[4] shouldn't be NULL"); + } + if (strcmp(gw.tags[4]->label, "test")) { + errx( + 1, "unexpected tag %.*s (expected test)", + (int)sizeof(gw.tags[0]->label), gw.tags[4]->label + ); + } + if (gw.tag_ids != TAG_ID_TO_VALUE(4)) { + errx( + 1, "tag_ids = %jx (expected %jx)", + (uintmax_t)gw.tag_ids, (uintmax_t)TAG_ID_TO_VALUE(4) + ); + } + + lgtd_lifx_gateway_allocate_tag_id(&gw, 63, "test"); + if (!gw.tags[4]) { + errx(1, "gw.tag_ids[4] shouldn't be NULL"); + } + if (strcmp(gw.tags[4]->label, "test")) { + errx( + 1, "unexpected tag %.*s (expected test)", + (int)sizeof(gw.tags[0]->label), gw.tags[4]->label + ); + } + if (!gw.tags[63]) { + errx(1, "gw.tag_ids[63] shouldn't be NULL"); + } + if (strcmp(gw.tags[63]->label, "test")) { + errx( + 1, "unexpected tag %.*s (expected test)", + (int)sizeof(gw.tags[0]->label), gw.tags[4]->label + ); + } + if (gw.tag_ids != (TAG_ID_TO_VALUE(4) | TAG_ID_TO_VALUE(63))) { + errx( + 1, "tag_ids = %jx (expected %jx)", + (uintmax_t)gw.tag_ids, + (uintmax_t)(TAG_ID_TO_VALUE(4) | TAG_ID_TO_VALUE(63)) + ); + } + + tagging_incref_called = false; + lgtd_lifx_gateway_allocate_tag_id(&gw, 4, "test"); + if (!gw.tags[4]) { + errx(1, "gw.tag_ids[4] shouldn't be NULL"); + } + if (strcmp(gw.tags[4]->label, "test")) { + errx( + 1, "unexpected tag %.*s (expected test)", + (int)sizeof(gw.tags[0]->label), gw.tags[4]->label + ); + } + if (!gw.tags[63]) { + errx(1, "gw.tag_ids[63] shouldn't be NULL"); + } + if (strcmp(gw.tags[63]->label, "test")) { + errx( + 1, "unexpected tag %.*s (expected test)", + (int)sizeof(gw.tags[0]->label), gw.tags[4]->label + ); + } + if (gw.tag_ids != (TAG_ID_TO_VALUE(4) | TAG_ID_TO_VALUE(63))) { + errx( + 1, "tag_ids = %jx (expected %jx)", + (uintmax_t)gw.tag_ids, + (uintmax_t)(TAG_ID_TO_VALUE(4) | TAG_ID_TO_VALUE(63)) + ); + } + if (tagging_incref_called) { + errx(1, "lgtd_lifx_tagging_incref shouldn't have been called"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c new file mode 100644 index 0000000..9e24fe8 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c @@ -0,0 +1,72 @@ +#include + +#include "gateway.c" + +#define MOCKED_LIFX_TAGGING_DECREF +#include "test_gateway_utils.h" + +static bool tagging_decref_called = false; + +void +lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, const struct lgtd_lifx_gateway *gw) +{ + if (!tag) { + errx(1, "missing tag"); + } + if (!gw) { + errx(1, "missing gateway"); + } + + tagging_decref_called = true; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + + struct lgtd_lifx_tag tag = { + .label = "test", + .sites = LIST_HEAD_INITIALIZER(&tag.sites) + }; + + gw.tags[0] = &tag; + gw.tag_ids = TAG_ID_TO_VALUE(0) | TAG_ID_TO_VALUE(42); + + lgtd_lifx_gateway_deallocate_tag_id(&gw, 0); + if (gw.tags[0]) { + errx(1, "gw.tags[0] should have been set to NULL"); + } + if (gw.tag_ids != TAG_ID_TO_VALUE(42)) { + errx( + 1, "unexpected gw.tag_ids value = %jx (expected %jx)", + gw.tag_ids, TAG_ID_TO_VALUE(42) + ); + } + if (!tagging_decref_called) { + errx(1, "lgtd_lifx_tagging_decref should have been called"); + } + + tagging_decref_called = false; + lgtd_lifx_gateway_deallocate_tag_id(&gw, 0); + if (gw.tags[0]) { + errx(1, "gw.tags[0] should be NULL"); + } + if (gw.tag_ids != TAG_ID_TO_VALUE(42)) { + errx( + 1, "unexpected gw.tag_ids value = %jx (expected %jx)", + gw.tag_ids, TAG_ID_TO_VALUE(42) + ); + } + if (tagging_decref_called) { + errx(1, "lgtd_lifx_tagging_decref shouldn't have been called"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_handle_tag_labels.c b/tests/lifx/gateway/test_gateway_handle_tag_labels.c new file mode 100644 index 0000000..721b381 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_handle_tag_labels.c @@ -0,0 +1,78 @@ +#include + +#include "gateway.c" + +#include "test_gateway_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + + struct lgtd_lifx_packet_tag_labels pkt = { + .label = "test", .tags = 0 + }; + + lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); + if (gw.tag_ids != 0) { + errx(1, "expected gw.tags == 0 but got %jx", (uintmax_t)gw.tags); + } + + pkt.tags = TAG_ID_TO_VALUE(42); + lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); + if (gw.tag_ids != TAG_ID_TO_VALUE(42)) { + errx( + 1, "expected gw.tags == %jx but got %jx", + TAG_ID_TO_VALUE(42), (uintmax_t)gw.tags + ); + } + if (!gw.tags[42]) { + errx(1, "tag_id 42 should have been set"); + } + if (strcmp(gw.tags[42]->label, pkt.label)) { + errx( + 1, "unexpected label %.*s (expected %s)", + (int)sizeof(gw.tags[0]->label), gw.tags[42]->label, pkt.label + ); + } + + strcpy(pkt.label, "toto"); + pkt.tags = TAG_ID_TO_VALUE(2) | TAG_ID_TO_VALUE(4); + lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); + memset(&pkt, 0, sizeof(pkt)); + lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); + uint64_t expected; + expected = TAG_ID_TO_VALUE(42) | TAG_ID_TO_VALUE(2) | TAG_ID_TO_VALUE(4); + if (gw.tag_ids != expected) { + errx( + 1, "expected gw.tags == %jx but got %jx", + TAG_ID_TO_VALUE(42), (uintmax_t)gw.tags + ); + } + if (strcmp(gw.tags[2]->label, "toto")) { + errx( + 1, "unexpected label %.*s (expected %s)", + (int)sizeof(gw.tags[0]->label), gw.tags[2]->label, "toto" + ); + } + if (strcmp(gw.tags[4]->label, "toto")) { + errx( + 1, "unexpected label %.*s (expected %s)", + (int)sizeof(gw.tags[0]->label), gw.tags[4]->label, "toto" + ); + } + if (strcmp(gw.tags[42]->label, "test")) { + errx( + 1, "unexpected label %.*s (expected %s)", + (int)sizeof(gw.tags[0]->label), gw.tags[42]->label, "test" + ); + } + + return 0; +} diff --git a/tests/lifx/tagging/CMakeLists.txt b/tests/lifx/tagging/CMakeLists.txt new file mode 100644 index 0000000..a7b0e3c --- /dev/null +++ b/tests/lifx/tagging/CMakeLists.txt @@ -0,0 +1,19 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_LIBRARY( + test_lifx_tagging STATIC + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c +) + +FUNCTION(ADD_TAGGING_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_tagging) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_TAGGING_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/lifx/tagging/test_tagging_decref.c b/tests/lifx/tagging/test_tagging_decref.c new file mode 100644 index 0000000..e7a41d6 --- /dev/null +++ b/tests/lifx/tagging/test_tagging_decref.c @@ -0,0 +1,67 @@ +#include "tagging.c" + +static int +count_tag(const char *tag_label) +{ + int count = 0; + struct lgtd_lifx_tag *tag; + LIST_FOREACH(tag, &lgtd_lifx_tags, link) { + if (!strcmp(tag_label, tag->label)) { + count++; + } + } + return count; +} + +static int +count_site(struct lgtd_lifx_site_list *list, const struct lgtd_lifx_gateway *gw) +{ + int count = 0; + struct lgtd_lifx_site *site; + LIST_FOREACH(site, list, link) { + if (site->gw == gw) { + count++; + } + } + return count; +} + +int +main(void) +{ + struct lgtd_lifx_gateway gw1, gw2; + memset(&gw1, 0, sizeof(gw1)); + memset(&gw2, 0, sizeof(gw2)); + + struct lgtd_lifx_site *site_gw1 = calloc(1, sizeof(*site_gw1)); + site_gw1->gw = &gw1; + struct lgtd_lifx_site *site_gw2 = calloc(1, sizeof(*site_gw2)); + site_gw2->gw = &gw2; + + const char *rawr = "rawr"; + struct lgtd_lifx_tag *tag = calloc(1, sizeof(*tag)); + strcpy(tag->label, rawr); + LIST_INSERT_HEAD(&tag->sites, site_gw1, link); + LIST_INSERT_HEAD(&tag->sites, site_gw2, link); + LIST_INSERT_HEAD(&lgtd_lifx_tags, tag, link); + + for (int i = 0; i != 2; i++) { + lgtd_lifx_tagging_decref(tag, &gw2); + if (count_site(&tag->sites, &gw2) != 0) { + errx(1, "gw2 shouldn't be in the sites list"); + } + if (count_site(&tag->sites, &gw1) != 1) { + errx(1, "gw1 wasn't found once in the sites list"); + } + if (count_tag(rawr) != 1) { + errx(1, "%s wasn't found once in the tags list", rawr); + } + } + + lgtd_lifx_tagging_decref(tag, &gw1); + if (count_tag(rawr)) { + errx(1, "the tags list should be empty"); + } + + return 0; +} diff --git a/tests/lifx/tagging/test_tagging_incref.c b/tests/lifx/tagging/test_tagging_incref.c new file mode 100644 index 0000000..d237c3c --- /dev/null +++ b/tests/lifx/tagging/test_tagging_incref.c @@ -0,0 +1,65 @@ +#include "tagging.c" + +static int +count_tag(const char *tag_label) +{ + int count = 0; + struct lgtd_lifx_tag *tag; + LIST_FOREACH(tag, &lgtd_lifx_tags, link) { + if (!strcmp(tag_label, tag->label)) { + count++; + } + } + return count; +} + +static int +count_site(struct lgtd_lifx_site_list *list, const struct lgtd_lifx_gateway *gw) +{ + int count = 0; + struct lgtd_lifx_site *site; + LIST_FOREACH(site, list, link) { + if (site->gw == gw) { + count++; + } + } + return count; +} + +int +main(void) +{ + struct lgtd_lifx_gateway gw1, gw2; + memset(&gw1, 0, sizeof(gw1)); + memset(&gw2, 0, sizeof(gw2)); + + const char *rawr = "rawr"; + const char *awww = "awww"; + + for (int i = 0; i != 2; i++) { + lgtd_lifx_tagging_incref(rawr, &gw1); + if (count_tag(rawr) != 1) { + errx(1, "%s wasn't found once the list of tags", rawr); + } + if (count_site(&LIST_FIRST(&lgtd_lifx_tags)->sites, &gw1) != 1) { + errx(1, "site %p wasn't found once in the list of sites", &gw1); + } + } + + lgtd_lifx_tagging_incref(rawr, &gw2); + struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(rawr); + if (count_site(&tag->sites, &gw2) != 1) { + errx(1, "gw2 wasn't found once in the sites of tag %s", tag->label); + } + + lgtd_lifx_tagging_incref(awww, &gw1); + if (count_tag(awww) != 1) { + errx(1, "%s wasn't found once in the list of tags", awww); + } + tag = lgtd_lifx_tagging_find_tag(awww); + if (count_site(&tag->sites, &gw1) != 1) { + errx(1, "gw1 wasn't found once in the sites of tag %s", awww); + } + + return 0; +} diff --git a/tests/lifx/wire_proto/test_wire_proto_utils.h b/tests/lifx/wire_proto/test_wire_proto_utils.h index b143e4a..2ca1f5f 100644 --- a/tests/lifx/wire_proto/test_wire_proto_utils.h +++ b/tests/lifx/wire_proto/test_wire_proto_utils.h @@ -26,3 +26,12 @@ void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, (void)hdr; (void)pkt; } + +void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_tag_labels *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} From 437d79b21100b4fe156ec2eeed8f83658796a03a Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 9 May 2015 01:23:17 -0700 Subject: [PATCH 033/181] Add the list_tags command --- core/client.c | 12 ++++++ core/client.h | 4 +- core/jsonrpc.c | 19 ++++++++- core/jsonrpc.h | 2 + core/proto.c | 25 +++++++++-- core/proto.h | 1 + core/router.h | 6 +++ docs/protocol.rst | 5 ++- lifx/CMakeLists.txt | 1 + lifx/wire_proto.h | 4 +- .../jsonrpc/test_jsonrpc_build_target_list.c | 1 + .../test_jsonrpc_check_and_call_power_off.c | 2 + ..._check_and_call_power_off_missing_target.c | 2 + .../test_jsonrpc_check_and_call_power_on.c | 2 + ...c_check_and_call_power_on_missing_target.c | 2 + ...onrpc_check_and_call_set_light_from_hsbk.c | 2 + ..._and_call_set_light_from_hsbk_from_array.c | 2 + ..._call_set_light_from_hsbk_invalid_params.c | 2 + ...test_jsonrpc_check_and_call_set_waveform.c | 2 + ...eck_and_call_set_waveform_invalid_params.c | 2 + .../test_jsonrpc_extract_request_no_params.c | 1 + ...c_extract_request_notification_no_params.c | 1 + ...est_jsonrpc_extract_request_params_array.c | 1 + .../test_jsonrpc_extract_request_params_obj.c | 1 + ...onrpc_extract_request_valid_notification.c | 1 + tests/core/jsonrpc/test_jsonrpc_send_error.c | 1 + .../core/jsonrpc/test_jsonrpc_send_response.c | 1 + ...onrpc_type_float_between_0_and_1_invalid.c | 1 + ...jsonrpc_type_float_between_0_and_1_valid.c | 1 + ...rpc_type_float_between_0_and_360_invalid.c | 1 + ...onrpc_type_float_between_0_and_360_valid.c | 1 + .../core/jsonrpc/test_jsonrpc_type_integer.c | 1 + ..._jsonrpc_type_integer_invalid_characters.c | 1 + .../test_jsonrpc_type_integer_too_big.c | 1 + .../test_jsonrpc_type_integer_too_small.c | 1 + tests/core/jsonrpc/test_jsonrpc_utils.h | 24 +++-------- tests/core/mock_client_buf.h | 23 +++++++++++ tests/core/proto/CMakeLists.txt | 2 - tests/core/proto/test_proto_list_tags.c | 41 +++++++++++++++++++ tests/core/proto/test_proto_power_off.c | 7 +++- .../test_proto_power_off_routing_error.c | 7 +++- tests/core/proto/test_proto_power_on.c | 7 +++- .../proto/test_proto_power_on_routing_error.c | 7 +++- .../proto/test_proto_set_light_from_hsbk.c | 7 +++- ...oto_set_light_from_hsbk_on_routing_error.c | 7 +++- tests/core/proto/test_proto_set_waveform.c | 7 +++- ...test_proto_set_waveform_on_routing_error.c | 7 +++- tests/core/proto/tests_proto_utils.h | 34 +++++++++++++++ tests/lightsc | 4 ++ 49 files changed, 259 insertions(+), 38 deletions(-) create mode 100644 tests/core/mock_client_buf.h create mode 100644 tests/core/proto/test_proto_list_tags.c create mode 100644 tests/core/proto/tests_proto_utils.h diff --git a/core/client.c b/core/client.c index 5a2c588..83583c6 100644 --- a/core/client.c +++ b/core/client.c @@ -142,6 +142,18 @@ lgtd_client_send_response(struct lgtd_client *client, const char *msg) lgtd_jsonrpc_send_response(client, msg); } +void +lgtd_client_start_send_response(struct lgtd_client *client) +{ + lgtd_jsonrpc_start_send_response(client); +} + +void +lgtd_client_end_send_response(struct lgtd_client *client) +{ + lgtd_jsonrpc_end_send_response(client); +} + void lgtd_client_send_error(struct lgtd_client *client, enum lgtd_client_error_code error, diff --git a/core/client.h b/core/client.h index bf1307b..57a1efb 100644 --- a/core/client.h +++ b/core/client.h @@ -31,7 +31,7 @@ enum lgtd_client_error_code { }; struct lgtd_client { - LIST_ENTRY(lgtd_client) link; + LIST_ENTRY(lgtd_client) link; struct bufferevent *io; char ip_addr[INET6_ADDRSTRLEN]; uint16_t port; @@ -50,4 +50,6 @@ struct lgtd_client *lgtd_client_open(evutil_socket_t, const struct sockaddr_stor void lgtd_client_close_all(void); void lgtd_client_send_response(struct lgtd_client *, const char *); +void lgtd_client_start_send_response(struct lgtd_client *); +void lgtd_client_end_send_response(struct lgtd_client *); void lgtd_client_send_error(struct lgtd_client *, enum lgtd_client_error_code, const char *); diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 6183c7d..7906a3b 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -459,6 +459,22 @@ lgtd_jsonrpc_send_response(struct lgtd_client *client, LGTD_CLIENT_WRITE_STRING(client, "}"); } +void +lgtd_jsonrpc_start_send_response(struct lgtd_client *client) +{ + assert(client); + + LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); + lgtd_jsonrpc_write_id(client); + LGTD_CLIENT_WRITE_STRING(client, ", \"result\": "); +} + +void +lgtd_jsonrpc_end_send_response(struct lgtd_client *client) +{ + LGTD_CLIENT_WRITE_STRING(client, "}"); +} + static bool lgtd_jsonrpc_check_and_extract_request(struct lgtd_jsonrpc_request *request, const jsmntok_t *tokens, @@ -856,7 +872,6 @@ lgtd_jsonrpc_extract_target_list(struct lgtd_proto_target_list *targets, static void lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client) { - struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); if (!ok) { @@ -870,7 +885,6 @@ lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client) static void lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client) { - struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); if (!ok) { @@ -902,6 +916,7 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) "set_waveform", 10, lgtd_jsonrpc_check_and_call_set_waveform ), + LGTD_JSONRPC_METHOD("list_tags", 0, lgtd_proto_list_tags), }; assert(client); diff --git a/core/jsonrpc.h b/core/jsonrpc.h index 709ce70..23007d6 100644 --- a/core/jsonrpc.h +++ b/core/jsonrpc.h @@ -88,3 +88,5 @@ void lgtd_jsonrpc_send_error(struct lgtd_client *, const char *); void lgtd_jsonrpc_send_response(struct lgtd_client *, const char *); +void lgtd_jsonrpc_start_send_response(struct lgtd_client *); +void lgtd_jsonrpc_end_send_response(struct lgtd_client *); diff --git a/core/proto.c b/core/proto.c index 7df6bd1..a3b7878 100644 --- a/core/proto.c +++ b/core/proto.c @@ -26,11 +26,13 @@ #include #include +#include #include #include "lifx/wire_proto.h" #include "time_monotonic.h" #include "lifx/bulb.h" +#include "lifx/tagging.h" #include "jsmn.h" #include "jsonrpc.h" #include "client.h" @@ -39,7 +41,7 @@ #include "lightsd.h" #define SEND_RESULT(client, ok) do { \ - lgtd_jsonrpc_send_response((client), (ok) ? "true" : "false"); \ + lgtd_client_send_response((client), (ok) ? "true" : "false"); \ } while(0) void @@ -105,8 +107,8 @@ lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, lgtd_lifx_wire_encode_light_color(&pkt); SEND_RESULT( - client, lgtd_router_send(targets, LGTD_LIFX_SET_LIGHT_COLOR, &pkt)) - ; + client, lgtd_router_send(targets, LGTD_LIFX_SET_LIGHT_COLOR, &pkt) + ); } void @@ -146,3 +148,20 @@ lgtd_proto_set_waveform(struct lgtd_client *client, client, lgtd_router_send(targets, LGTD_LIFX_SET_WAVEFORM, &pkt) ); } + +void +lgtd_proto_list_tags(struct lgtd_client *client) +{ + lgtd_client_start_send_response(client); + + LGTD_CLIENT_WRITE_STRING(client, "["); + struct lgtd_lifx_tag *tag; + LIST_FOREACH(tag, &lgtd_lifx_tags, link) { + LGTD_CLIENT_WRITE_STRING(client, "\""); + LGTD_CLIENT_WRITE_STRING(client, tag->label); + LGTD_CLIENT_WRITE_STRING(client, LIST_NEXT(tag, link) ? "\", " : "\""); + } + LGTD_CLIENT_WRITE_STRING(client, "]"); + + lgtd_client_end_send_response(client); +} diff --git a/core/proto.h b/core/proto.h index 677f780..ebaf423 100644 --- a/core/proto.h +++ b/core/proto.h @@ -38,3 +38,4 @@ void lgtd_proto_set_waveform(struct lgtd_client *, int, float, int, bool); void lgtd_proto_power_on(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_power_off(struct lgtd_client *, const struct lgtd_proto_target_list *); +void lgtd_proto_list_tags(struct lgtd_client *); diff --git a/core/router.h b/core/router.h index 643551f..9dc3703 100644 --- a/core/router.h +++ b/core/router.h @@ -17,6 +17,12 @@ #pragma once +// TODO: return that from the functions in there and handle it: +enum lgtd_router_error { + LGTD_ROUTER_INVALID_TARGET_ERROR, + LGTD_ROUTER_CANNOT_ENQUEUE_PACKET_ERROR +}; + bool lgtd_router_send(const struct lgtd_proto_target_list *, enum lgtd_lifx_packet_type, void *); void lgtd_router_send_to_device(struct lgtd_lifx_bulb *, enum lgtd_lifx_packet_type, void *); void lgtd_router_broadcast(enum lgtd_lifx_packet_type, void *); diff --git a/docs/protocol.rst b/docs/protocol.rst index 8ef08e3..62684e9 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -60,8 +60,9 @@ Available methods end of the waveform, otherwise it will revert back to its original state. -.. function:: tag_list(tag) +.. function:: list_tags() - Return an array of labels or adresses of the devices having the given tag. + Return a dictionnary with tags as keys and arrays of devices addresses or + labels as values. .. vim: set tw=80 spelllang=en spell: diff --git a/lifx/CMakeLists.txt b/lifx/CMakeLists.txt index 46962e3..119314f 100644 --- a/lifx/CMakeLists.txt +++ b/lifx/CMakeLists.txt @@ -10,6 +10,7 @@ ADD_LIBRARY( broadcast.c bulb.c gateway.c + tagging.c timer.c wire_proto.c ) diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 7e2158a..dd9216d 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -267,4 +267,6 @@ void lgtd_lifx_wire_encode_light_status(struct lgtd_lifx_packet_light_status *); void lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *); void lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *); -void lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt); +void lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *); + +void lgtd_lifx_wire_decode_tag_labels(struct lgtd_lifx_packet_tag_labels *); diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c index efdeca1..3a3d8ab 100644 --- a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c +++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c index 782dea0..80a5baf 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_POWER_OFF #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c index 7ed7a85..e30174a 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_POWER_OFF #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c index 302dbe3..36fb6e3 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_POWER_ON #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c index 7bb6c3d..e1e54ab 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_POWER_ON #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c index 639a22b..e52fbeb 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_SET_LIGHT_FROM_HSBK #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c index ebedd88..588024e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_SET_LIGHT_FROM_HSBK #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c index 520f17e..d77d691 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_SET_LIGHT_FROM_HSBK #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c index 5f32d46..d9d4245 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_SET_WAVEFORM #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c index 64d3650..4d38d2b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c @@ -1,5 +1,7 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" + #define LGTD_TESTING_SET_WAVEFORM #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c index 9027f15..b496592 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c index 72eae61..ad89a97 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c index 0d1d7f3..9529f98 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c index fdd6178..1823c09 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c index d7250b1..4f37324 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c index cbd3184..bea6a50 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_error.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_send_response.c b/tests/core/jsonrpc/test_jsonrpc_send_response.c index d03a5af..5f0d895 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_response.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_response.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c index 2e84a21..2e6346a 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c index 7a7b2f6..a6820d1 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c index d056fba..3651308 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c index 322cb1c..03ee592 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer.c b/tests/core/jsonrpc/test_jsonrpc_type_integer.c index 8b04da8..b90cc13 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c index 0abeea0..b6ef418 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c index c780464..80893a1 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c index d154c9a..b96b76b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c @@ -1,5 +1,6 @@ #include "jsonrpc.c" +#include "mock_client_buf.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index 06a5e0a..9899a2f 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -10,30 +10,16 @@ parse_json(jsmntok_t *tokens, size_t capacity, const char *json , size_t len) return jsmn_parse(&ctx, json, len, tokens, capacity); } -static char client_write_buf[4096] = { 0 }; -static int client_write_buf_idx = 0; - -static inline void -reset_client_write_buf(void) -{ - memset(client_write_buf, 0, sizeof(client_write_buf)); - client_write_buf_idx = 0; -} - -int -bufferevent_write(struct bufferevent *bev, const void *data, size_t nbytes) +void +lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) { - (void)bev; - int to_write = LGTD_MIN(nbytes, sizeof(client_write_buf)); - memcpy(&client_write_buf[client_write_buf_idx], data, to_write); - client_write_buf_idx += to_write; - return 0; + assert(targets); } void -lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) +lgtd_proto_list_tags(struct lgtd_client *client) { - assert(targets); + assert(client); } #ifndef LGTD_TESTING_SET_LIGHT_FROM_HSBK diff --git a/tests/core/mock_client_buf.h b/tests/core/mock_client_buf.h new file mode 100644 index 0000000..f9e9426 --- /dev/null +++ b/tests/core/mock_client_buf.h @@ -0,0 +1,23 @@ +#pragma once + +static char client_write_buf[4096] = { 0 }; +static int client_write_buf_idx = 0; + +static inline void +reset_client_write_buf(void) +{ + memset(client_write_buf, 0, sizeof(client_write_buf)); + client_write_buf_idx = 0; +} + +int +bufferevent_write(struct bufferevent *bev, const void *data, size_t nbytes) +{ + (void)bev; + int to_write = LGTD_MIN( + nbytes, sizeof(client_write_buf) - client_write_buf_idx + ); + memcpy(&client_write_buf[client_write_buf_idx], data, to_write); + client_write_buf_idx += to_write; + return 0; +} diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt index a7e0463..db4514b 100644 --- a/tests/core/proto/CMakeLists.txt +++ b/tests/core/proto/CMakeLists.txt @@ -14,8 +14,6 @@ ADD_LIBRARY( ${TIME_MONOTONIC_IMPL} ) -TARGET_LINK_LIBRARIES(test_core_proto ${EVENT2_CORE_LIBRARY}) - FUNCTION(ADD_ROUTER_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_proto) ENDFUNCTION() diff --git a/tests/core/proto/test_proto_list_tags.c b/tests/core/proto/test_proto_list_tags.c new file mode 100644 index 0000000..ee0338a --- /dev/null +++ b/tests/core/proto/test_proto_list_tags.c @@ -0,0 +1,41 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "tests_utils.h" +#include "tests_proto_utils.h" + +int +main(void) +{ + struct lgtd_client client; + + lgtd_proto_list_tags(&client); + if (strcmp(client_write_buf, "[]")) { + errx( + 1, "expected empty list of tags but got %s instead", + client_write_buf + ); + } + + reset_client_write_buf(); + lgtd_tests_insert_mock_tag("test"); + lgtd_proto_list_tags(&client); + if (strcmp(client_write_buf, "[\"test\"]")) { + errx( + 1, "expected [\"test\"] but got %s instead", + client_write_buf + ); + } + + reset_client_write_buf(); + lgtd_tests_insert_mock_tag("@_@"); + lgtd_proto_list_tags(&client); + if (strcmp(client_write_buf, "[\"@_@\", \"test\"]")) { + errx( + 1, "expected [\"@_@\", \"test\"] but got %s instead", + client_write_buf + ); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_power_off.c b/tests/core/proto/test_proto_power_off.c index 86ca09e..5b57dfa 100644 --- a/tests/core/proto/test_proto_power_off.c +++ b/tests/core/proto/test_proto_power_off.c @@ -1,7 +1,12 @@ #include "proto.c" +#include "mock_client_buf.h" #include "tests_utils.h" +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -30,7 +35,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } void -lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { errx(1, "client shouldn't ne NULL"); diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c index 63324e1..a2f270a 100644 --- a/tests/core/proto/test_proto_power_off_routing_error.c +++ b/tests/core/proto/test_proto_power_off_routing_error.c @@ -1,7 +1,12 @@ #include "proto.c" +#include "mock_client_buf.h" #include "tests_utils.h" +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -30,7 +35,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } void -lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { errx(1, "client shouldn't ne NULL"); diff --git a/tests/core/proto/test_proto_power_on.c b/tests/core/proto/test_proto_power_on.c index beb28d8..5eef1b6 100644 --- a/tests/core/proto/test_proto_power_on.c +++ b/tests/core/proto/test_proto_power_on.c @@ -1,7 +1,12 @@ #include "proto.c" +#include "mock_client_buf.h" #include "tests_utils.h" +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -30,7 +35,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } void -lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { errx(1, "client shouldn't ne NULL"); diff --git a/tests/core/proto/test_proto_power_on_routing_error.c b/tests/core/proto/test_proto_power_on_routing_error.c index df54d02..81649e1 100644 --- a/tests/core/proto/test_proto_power_on_routing_error.c +++ b/tests/core/proto/test_proto_power_on_routing_error.c @@ -1,7 +1,12 @@ #include "proto.c" +#include "mock_client_buf.h" #include "tests_utils.h" +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -30,7 +35,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } void -lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { errx(1, "client shouldn't ne NULL"); diff --git a/tests/core/proto/test_proto_set_light_from_hsbk.c b/tests/core/proto/test_proto_set_light_from_hsbk.c index e560851..1696db1 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk.c @@ -2,8 +2,13 @@ #include "proto.c" +#include "mock_client_buf.h" #include "tests_utils.h" +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -50,7 +55,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } void -lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { errx(1, "client shouldn't ne NULL"); diff --git a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c index 2728623..eb8d026 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c @@ -2,8 +2,13 @@ #include "proto.c" +#include "mock_client_buf.h" #include "tests_utils.h" +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -50,7 +55,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } void -lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { errx(1, "client shouldn't ne NULL"); diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index 6a669a0..9e675c5 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -2,8 +2,13 @@ #include "proto.c" +#include "mock_client_buf.h" #include "tests_utils.h" +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -65,7 +70,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } void -lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { errx(1, "client shouldn't ne NULL"); diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index 46788df..d0d9c5f 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -2,8 +2,13 @@ #include "proto.c" +#include "mock_client_buf.h" #include "tests_utils.h" +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -65,7 +70,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } void -lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { errx(1, "client shouldn't ne NULL"); diff --git a/tests/core/proto/tests_proto_utils.h b/tests/core/proto/tests_proto_utils.h new file mode 100644 index 0000000..0b901fb --- /dev/null +++ b/tests/core/proto/tests_proto_utils.h @@ -0,0 +1,34 @@ +#pragma once + +void +lgtd_client_start_send_response(struct lgtd_client *client) +{ + (void)client; +} + +void +lgtd_client_end_send_response(struct lgtd_client *client) +{ + (void)client; +} + +#ifndef MOCKED_CLIENT_SEND_RESPONSE +void +lgtd_client_send_response(struct lgtd_client *client, const char *msg) +{ + LGTD_CLIENT_WRITE_STRING(client, msg); +} +#endif + +#ifndef MOCKED_ROUTER_SEND +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)targets; + (void)pkt_type; + (void)pkt; + return true; +} +#endif diff --git a/tests/lightsc b/tests/lightsc index 635377e..7778f72 100755 --- a/tests/lightsc +++ b/tests/lightsc @@ -62,6 +62,10 @@ def power_on(socket, id, target): def power_off(socket, id, target): jsonrpc_call(socket, id, "power_off", {"target": target}) + +def list_tags(socket, id): + jsonrpc_call(socket, id, "list_tags", []) + if __name__ == "__main__": s = socket.create_connection(("localhost", 1234)) h = 0 From 5619726200e07003e0037252bc1c85356c7e8ea6 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 13 May 2015 18:08:08 -0700 Subject: [PATCH 034/181] Update README with currently supported features --- README.rst | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index fb8223b..06d2e78 100644 --- a/README.rst +++ b/README.rst @@ -21,8 +21,26 @@ but has some advantages: Current features ---------------- -lightsd doesn't do much yet, it just discovers your LIFX bulbs and stay in sync -with them. +lightsd discovers your LIFX bulbs, stays in sync with them and support the +following commands through a JSON-RPC_ interface: + +- power_off; +- power_on; +- set_light_from_hsbk; +- set_waveform (change the light according to a function like SAW or SINE); +- get_light_status (coming up). + +lightsd can target single or multiple bulbs at once: + +- by device address; +- by device label (coming up, labels are already discovered); +- by tag (coming up, tags are already discovered); +- broadcast; +- composite (list of targets); + +lightsd works and is developed against LIFX firmwares 1.1, 1.5 and 2.0. + +.. _JSON-RPC: http://www.jsonrpc.org/specification Developpers ----------- @@ -41,4 +59,6 @@ dependencies: - CMake ≥ 2.8; - libevent ≥ 2.0.19. +lightsd is actively developed and tested from Arch Linux and Mac OS X. + .. vim: set tw=80 spelllang=en spell: From d72595c8ea3cd99f9dad45b6314e2a6b38d30064 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 18 May 2015 01:14:25 -0700 Subject: [PATCH 035/181] Update README and add some build instructions --- README.rst | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 06d2e78..b4a88a0 100644 --- a/README.rst +++ b/README.rst @@ -33,8 +33,8 @@ following commands through a JSON-RPC_ interface: lightsd can target single or multiple bulbs at once: - by device address; -- by device label (coming up, labels are already discovered); -- by tag (coming up, tags are already discovered); +- by device label; +- by tag; - broadcast; - composite (list of targets); @@ -61,4 +61,22 @@ dependencies: lightsd is actively developed and tested from Arch Linux and Mac OS X. +Build instructions +------------------ + +Clone this repository and then: + +:: + + .../lightsd$ mkdir build && cd build + .../lightsd/build$ cmake .. + .../lightsd/build$ make -j5 + +Finally, to start lightsd with the jsonrpc interface listening on localhost +port 1234: + +:: + + .../lightsd/build$ core/lightsd -v info -l ::1:1234 + .. vim: set tw=80 spelllang=en spell: From 67ccb15db04f6125d5a3785b89286357d459ef0b Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 18 May 2015 01:14:25 -0700 Subject: [PATCH 036/181] Labels and tags can now be used as targets Labels and device addresses are ambiguous (the fact that JSON doesn't support hexadecimal numbers doesn't really help): if it looks like an address let's try it otherwise interpret the target as a label. --- core/router.c | 119 ++++++++++++--- core/router.h | 2 + lifx/gateway.c | 16 +-- lifx/tagging.c | 31 ++-- lifx/tagging.h | 10 +- lifx/wire_proto.h | 6 + tests/core/jsonrpc/CMakeLists.txt | 2 + ...cast.c => test_router_send_to_broadcast.c} | 0 ..._device.c => test_router_send_to_device.c} | 0 ... => test_router_send_to_invalid_targets.c} | 9 +- tests/core/router/test_router_send_to_label.c | 64 +++++++++ tests/core/router/test_router_send_to_tag.c | 135 ++++++++++++++++++ tests/core/router/tests_router_utils.h | 7 + tests/core/tests_shims.c | 13 ++ tests/core/tests_utils.c | 12 ++ tests/core/tests_utils.h | 3 + ...ateway_allocate_tag_id_from_lifx_network.c | 18 +-- ...eway_deallocate_tag_id_from_lifx_network.c | 13 +- .../gateway/test_gateway_handle_tag_labels.c | 16 ++- tests/lifx/gateway/test_gateway_utils.h | 15 +- 20 files changed, 419 insertions(+), 72 deletions(-) rename tests/core/router/{test_router_broadcast.c => test_router_send_to_broadcast.c} (100%) rename tests/core/router/{test_router_device.c => test_router_send_to_device.c} (100%) rename tests/core/router/{test_router_invalid_targets.c => test_router_send_to_invalid_targets.c} (83%) create mode 100644 tests/core/router/test_router_send_to_label.c create mode 100644 tests/core/router/test_router_send_to_tag.c diff --git a/core/router.c b/core/router.c index 2cd9b3e..4adcdcb 100644 --- a/core/router.c +++ b/core/router.c @@ -38,8 +38,9 @@ #include "jsonrpc.h" #include "client.h" #include "proto.h" -#include "router.h" #include "lifx/gateway.h" +#include "lifx/tagging.h" +#include "router.h" #include "lightsd.h" void @@ -111,6 +112,74 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, lgtd_info("sending %s to %s", pkt_infos->name, lgtd_addrtoa(bulb->addr)); } +void +lgtd_router_send_to_tag(const struct lgtd_lifx_tag *tag, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + const struct lgtd_lifx_packet_infos *pkt_infos = NULL; + + struct lgtd_lifx_site *site = NULL; + LIST_FOREACH(site, &tag->sites, link) { + struct lgtd_lifx_gateway *gw = site->gw; + for (int tag_id = 0; tag_id != LGTD_ARRAY_SIZE(gw->tags); tag_id++) { + if (tag == gw->tags[tag_id]) { + struct lgtd_lifx_packet_header hdr; + union lgtd_lifx_target target; + target.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); + pkt_infos = lgtd_lifx_wire_setup_header( + &hdr, + LGTD_LIFX_TARGET_TAGS, + target, + gw->site.as_array, + pkt_type + ); + assert(pkt_infos); + + lgtd_lifx_gateway_enqueue_packet( + gw, &hdr, pkt_type, pkt, pkt_infos->size + ); + } + } + } + + if (pkt_infos) { + lgtd_info("sending %s to #%s", pkt_infos->name, tag->label); + } +} + +void +lgtd_router_send_to_label(const char *label, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + const struct lgtd_lifx_packet_infos *pkt_infos = NULL; + + struct lgtd_lifx_bulb *bulb; + RB_FOREACH(bulb, lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table) { + if (!strcmp(bulb->state.label, label)) { + struct lgtd_lifx_packet_header hdr; + union lgtd_lifx_target target = { .addr = bulb->addr }; + pkt_infos = lgtd_lifx_wire_setup_header( + &hdr, + LGTD_LIFX_TARGET_DEVICE, + target, + bulb->gw->site.as_array, + pkt_type + ); + assert(pkt_infos); + + lgtd_lifx_gateway_enqueue_packet( + bulb->gw, &hdr, pkt_type, pkt, pkt_infos->size + ); + } + } + + if (pkt_infos) { + lgtd_info("sending %s to #%s", pkt_infos->name, label); + } +} + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -125,23 +194,41 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, if (!strcmp(target->target, "*")) { lgtd_router_broadcast(pkt_type, pkt); continue; - } else if (isxdigit(target->target[0])) { - const char *endptr = NULL; - errno = 0; - long long device = strtoll(target->target, (char **)&endptr, 16); - if (*endptr || errno == ERANGE) { - lgtd_debug("invalid target device %s", target->target); - rv = false; - } - device = htobe64(device); - struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get( - (uint8_t *)&device + sizeof(device) - LGTD_LIFX_ADDR_LENGTH - ); - if (bulb) { - lgtd_router_send_to_device(bulb, pkt_type, pkt); + } else if (target->target[0] == '#') { + const struct lgtd_lifx_tag *tag; + tag = lgtd_lifx_tagging_find_tag(&target->target[1]); + if (tag) { + lgtd_router_send_to_tag(tag, pkt_type, pkt); continue; } - lgtd_debug("target device %#llx not found", device); + lgtd_debug("invalid target tag %s", target->target); + } else if (target->target[0]) { + // NOTE: labels and hardware addresses are ambiguous target types, + // we can't really solve this since json doesn't have hexadecimal. + if (isxdigit(target->target[0])) { + errno = 0; + uint64_t device; + const char *endptr = NULL; + device = strtoull(target->target, (char **)&endptr, 16); + if (!*endptr && errno != ERANGE) { + device = htobe64(device); + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get( + (uint8_t *)&device + + sizeof(device) - LGTD_LIFX_ADDR_LENGTH + ); + if (bulb) { + lgtd_router_send_to_device(bulb, pkt_type, pkt); + continue; + } + } + lgtd_debug( + "%s looked like a device address but didn't " + "yield any device, trying as a label", target->target + ); + } + // Fallback as label: + lgtd_router_send_to_label(target->target, pkt_type, pkt); + continue; } rv = false; } diff --git a/core/router.h b/core/router.h index 9dc3703..517ad0b 100644 --- a/core/router.h +++ b/core/router.h @@ -25,4 +25,6 @@ enum lgtd_router_error { bool lgtd_router_send(const struct lgtd_proto_target_list *, enum lgtd_lifx_packet_type, void *); void lgtd_router_send_to_device(struct lgtd_lifx_bulb *, enum lgtd_lifx_packet_type, void *); +void lgtd_router_send_to_tag(const struct lgtd_lifx_tag *, enum lgtd_lifx_packet_type, void *); +void lgtd_router_send_to_label(const char *, enum lgtd_lifx_packet_type, void *); void lgtd_router_broadcast(enum lgtd_lifx_packet_type, void *); diff --git a/lifx/gateway.c b/lifx/gateway.c index c833753..5ad787b 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -497,12 +497,6 @@ lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, lgtd_lifx_bulb_set_power_state(b, pkt->power); } -#if LGTD_SIZEOF_VOID_P == 8 -# define TAG_ID_TO_VALUE(x) (1UL << (x)) -#elif LGTD_SIZEOF_VOID_P == 4 -# define TAG_ID_TO_VALUE(x) (1ULL << (x)) -#endif - int lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id, @@ -514,7 +508,7 @@ lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, assert(tag_id >= 0); assert(tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); - if (!(gw->tag_ids & TAG_ID_TO_VALUE(tag_id))) { + if (!(gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id))) { struct lgtd_lifx_tag *tag; tag = lgtd_lifx_tagging_incref(tag_label, gw); if (!tag) { @@ -529,7 +523,7 @@ lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, tag_id, tag_label, gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) ); - gw->tag_ids |= TAG_ID_TO_VALUE(tag_id); + gw->tag_ids |= LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); gw->tags[tag_id] = tag; } @@ -543,7 +537,7 @@ lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id) assert(tag_id >= 0); assert(tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); - if (gw->tag_ids & TAG_ID_TO_VALUE(tag_id)) { + if (gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id)) { lgtd_debug( "tag_id %d deallocated for tag [%s] on gw [%s]:%hu (site %s)", tag_id, gw->tags[tag_id]->label, @@ -551,7 +545,7 @@ lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id) lgtd_addrtoa(gw->site.as_array) ); lgtd_lifx_tagging_decref(gw->tags[tag_id], gw); - gw->tag_ids &= ~TAG_ID_TO_VALUE(tag_id); + gw->tag_ids &= ~LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); gw->tags[tag_id] = NULL; } } @@ -580,6 +574,6 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, } else if (gw->tags[tag_id]) { lgtd_lifx_gateway_deallocate_tag_id(gw, tag_id); } - tags &= ~TAG_ID_TO_VALUE(tag_id); + tags &= ~LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); } } diff --git a/lifx/tagging.c b/lifx/tagging.c index 34101cb..9715ff6 100644 --- a/lifx/tagging.c +++ b/lifx/tagging.c @@ -40,21 +40,9 @@ struct lgtd_lifx_tag_list lgtd_lifx_tags = LIST_HEAD_INITIALIZER(&lgtd_lifx_tags); -static struct lgtd_lifx_tag * -lgtd_lifx_tagging_find_tag(const char *tag_label) -{ - struct lgtd_lifx_tag *tag = NULL; - LIST_FOREACH(tag, &lgtd_lifx_tags, link) { - if (!strcmp(tag->label, tag_label)) { - break; - } - } - return tag; -} - static struct lgtd_lifx_site * lgtd_lifx_tagging_find_site(struct lgtd_lifx_site_list *sites, - const struct lgtd_lifx_gateway *gw) + struct lgtd_lifx_gateway *gw) { struct lgtd_lifx_site *site = NULL; LIST_FOREACH(site, sites, link) { @@ -66,8 +54,19 @@ lgtd_lifx_tagging_find_site(struct lgtd_lifx_site_list *sites, } struct lgtd_lifx_tag * -lgtd_lifx_tagging_incref(const char *tag_label, - const struct lgtd_lifx_gateway *gw) +lgtd_lifx_tagging_find_tag(const char *tag_label) +{ + struct lgtd_lifx_tag *tag = NULL; + LIST_FOREACH(tag, &lgtd_lifx_tags, link) { + if (!strcmp(tag->label, tag_label)) { + break; + } + } + return tag; +} + +struct lgtd_lifx_tag * +lgtd_lifx_tagging_incref(const char *tag_label, struct lgtd_lifx_gateway *gw) { assert(strlen(tag_label) < LGTD_LIFX_LABEL_SIZE); assert(gw); @@ -111,7 +110,7 @@ lgtd_lifx_tagging_incref(const char *tag_label, void lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, - const struct lgtd_lifx_gateway *gw) + struct lgtd_lifx_gateway *gw) { assert(tag); assert(gw); diff --git a/lifx/tagging.h b/lifx/tagging.h index 11aaadf..e06cbf7 100644 --- a/lifx/tagging.h +++ b/lifx/tagging.h @@ -20,8 +20,8 @@ extern struct lgtd_lifx_tag_list lgtd_lifx_tags; struct lgtd_lifx_site { - LIST_ENTRY(lgtd_lifx_site) link; - const struct lgtd_lifx_gateway *gw; + LIST_ENTRY(lgtd_lifx_site) link; + struct lgtd_lifx_gateway *gw; }; LIST_HEAD(lgtd_lifx_site_list, lgtd_lifx_site); @@ -33,5 +33,7 @@ struct lgtd_lifx_tag { LIST_HEAD(lgtd_lifx_tag_list, lgtd_lifx_tag); struct lgtd_lifx_tag *lgtd_lifx_tagging_incref(const char *, - const struct lgtd_lifx_gateway *); -void lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *, const struct lgtd_lifx_gateway *); + struct lgtd_lifx_gateway *); +void lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *, struct lgtd_lifx_gateway *); + +struct lgtd_lifx_tag *lgtd_lifx_tagging_find_tag(const char *); diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index dd9216d..04b92ff 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -247,6 +247,12 @@ union lgtd_lifx_target { extern union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET; +#if LGTD_SIZEOF_VOID_P == 8 +# define LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(x) (1UL << (x)) +#elif LGTD_SIZEOF_VOID_P == 4 +# define LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(x) (1ULL << (x)) +#endif + enum lgtd_lifx_waveform_type lgtd_lifx_wire_waveform_string_id_to_type(const char *, int); const struct lgtd_lifx_packet_infos *lgtd_lifx_wire_get_packet_infos(enum lgtd_lifx_packet_type); diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt index df78e50..9a71d26 100644 --- a/tests/core/jsonrpc/CMakeLists.txt +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -8,8 +8,10 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/jsmn.c ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c + ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c + ${TIME_MONOTONIC_IMPL} ) FUNCTION(ADD_JSONRPC_TEST TEST_SOURCE) diff --git a/tests/core/router/test_router_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c similarity index 100% rename from tests/core/router/test_router_broadcast.c rename to tests/core/router/test_router_send_to_broadcast.c diff --git a/tests/core/router/test_router_device.c b/tests/core/router/test_router_send_to_device.c similarity index 100% rename from tests/core/router/test_router_device.c rename to tests/core/router/test_router_send_to_device.c diff --git a/tests/core/router/test_router_invalid_targets.c b/tests/core/router/test_router_send_to_invalid_targets.c similarity index 83% rename from tests/core/router/test_router_invalid_targets.c rename to tests/core/router/test_router_send_to_invalid_targets.c index 7d912e8..f4b68e6 100644 --- a/tests/core/router/test_router_invalid_targets.c +++ b/tests/core/router/test_router_send_to_invalid_targets.c @@ -12,9 +12,16 @@ test_target(const char *target) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list(target, NULL); bool ok = lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &payload); +// XXX: Return proper errors from lgtd_router. +#if 0 if (ok) { - lgtd_errx(1, "router_send didn't return false for an unknown device"); + lgtd_errx( + 1, "router_send didn't return false for unknown device %s", target + ); } +#else + (void)ok; +#endif if (lgtd_tests_gw_pkt_queue_size) { lgtd_errx(1, "no packets should have been sent"); } diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c new file mode 100644 index 0000000..5e3129a --- /dev/null +++ b/tests/core/router/test_router_send_to_label.c @@ -0,0 +1,64 @@ +#include "router.c" + +#include "tests_utils.h" +#include "tests_router_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); + struct lgtd_lifx_bulb *bulb_1 = lgtd_tests_insert_mock_bulb(gw_1, 1); + struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); + struct lgtd_lifx_bulb *bulb_2 = lgtd_tests_insert_mock_bulb(gw_2, 2); + + const char *label = "feed"; + strcpy(bulb_1->state.label, label); + strcpy(bulb_2->state.label, "trololo"); + + struct lgtd_lifx_packet_power_state payload = { + .power = LGTD_LIFX_POWER_ON + }; + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list(label, NULL); + lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &payload); + + if (lgtd_tests_gw_pkt_queue_size != 1) { + lgtd_errx(1, "1 packet should have been sent"); + } + + struct lgtd_lifx_gateway *recpt_gw = lgtd_tests_gw_pkt_queue[0].gw; + struct lgtd_lifx_packet_header *hdr_queued = lgtd_tests_gw_pkt_queue[0].hdr; + const void *pkt_queued = lgtd_tests_gw_pkt_queue[0].pkt; + int pkt_size = lgtd_tests_gw_pkt_queue[0].pkt_size; + + if (recpt_gw != gw_1) { + lgtd_errx(1, "the packet has been sent to the wrong gateway"); + } + + if (!hdr_queued->protocol.addressable || hdr_queued->protocol.tagged) { + lgtd_errx(1, "the packet header doesn't have the right protocol flags"); + } + + if (memcmp(hdr_queued->target.device_addr, bulb_1->addr, sizeof(bulb_1->addr))) { + lgtd_errx(1, "the packet header doesn't have the right target address"); + } + + if (memcmp(gw_1->site.as_array, hdr_queued->site, sizeof(hdr_queued->site))) { + lgtd_errx(1, "incorrect site in the headers"); + } + + if (pkt_queued != &payload) { + lgtd_errx(1, "invalid payload"); + } + + if (pkt_size != sizeof(payload)) { + lgtd_errx( + 1, "unexpected pkt size %d (expected %ld)", + pkt_size, sizeof(payload) + ); + } + + return 0; +} diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c new file mode 100644 index 0000000..e1f342c --- /dev/null +++ b/tests/core/router/test_router_send_to_tag.c @@ -0,0 +1,135 @@ +#include "router.c" + +#include "tests_utils.h" +#include "tests_router_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); + struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); + + struct lgtd_lifx_tag *tag_foo = lgtd_tests_insert_mock_tag("foo"); + lgtd_tests_add_tag_to_gw(tag_foo, gw_1, 42); + + struct lgtd_lifx_packet_power_state payload = { + .power = LGTD_LIFX_POWER_ON + }; + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("#foo", NULL); + lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &payload); + + if (lgtd_tests_gw_pkt_queue_size != 1) { + lgtd_errx(1, "1 packet should have been sent"); + } + + struct lgtd_lifx_gateway *recpt_gw = lgtd_tests_gw_pkt_queue[0].gw; + struct lgtd_lifx_packet_header *hdr_queued = lgtd_tests_gw_pkt_queue[0].hdr; + const void *pkt_queued = lgtd_tests_gw_pkt_queue[0].pkt; + int pkt_size = lgtd_tests_gw_pkt_queue[0].pkt_size; + + if (recpt_gw != gw_1) { + lgtd_errx(1, "the packet has been sent to the wrong gateway"); + } + + if (!hdr_queued->protocol.addressable || !hdr_queued->protocol.tagged) { + lgtd_errx(1, "the packet header doesn't have the right protocol flags"); + } + + if (hdr_queued->target.tags != LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42)) { + lgtd_errx(1, "the packet header doesn't have the right tags set"); + } + + if (memcmp(gw_1->site.as_array, hdr_queued->site, sizeof(hdr_queued->site))) { + lgtd_errx(1, "incorrect site in the headers"); + } + + if (pkt_queued != &payload) { + lgtd_errx(1, "invalid payload"); + } + + if (pkt_size != sizeof(payload)) { + lgtd_errx( + 1, "unexpected pkt size %d (expected %ld)", + pkt_size, sizeof(payload) + ); + } + + lgtd_tests_router_reset_pkt_queue(); + + struct lgtd_lifx_tag *tag_bar = lgtd_tests_insert_mock_tag("bar"); + lgtd_tests_add_tag_to_gw(tag_bar, gw_1, 26); + lgtd_tests_add_tag_to_gw(tag_foo, gw_2, 21); + + targets = lgtd_tests_build_target_list("#foo", "#bar", NULL); + lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &payload); + + if (lgtd_tests_gw_pkt_queue_size != 3) { + lgtd_errx(1, "3 packet should have been sent"); + } + + int count_tag_foo_gw_1 = 0; + int count_tag_bar_gw_1 = 0; + int count_tag_foo_gw_2 = 0; + int count_other = 0; + for (int i = 0; lgtd_tests_gw_pkt_queue[i].gw; i++) { + recpt_gw = lgtd_tests_gw_pkt_queue[i].gw; + hdr_queued = lgtd_tests_gw_pkt_queue[i].hdr; + pkt_queued = lgtd_tests_gw_pkt_queue[i].pkt; + pkt_size = lgtd_tests_gw_pkt_queue[i].pkt_size; + + if (!hdr_queued->protocol.addressable || !hdr_queued->protocol.tagged) { + lgtd_errx(1, "the packet header doesn't have the right protocol flags"); + } + + if (memcmp(recpt_gw->site.as_array, hdr_queued->site, sizeof(hdr_queued->site))) { + lgtd_errx(1, "incorrect site in the headers"); + } + + if (pkt_queued != &payload) { + lgtd_errx(1, "invalid payload"); + } + + if (pkt_size != sizeof(payload)) { + lgtd_errx( + 1, "unexpected pkt size %d (expected %ld)", + pkt_size, sizeof(payload) + ); + } + + if (recpt_gw == gw_1) { + if (hdr_queued->target.tags == LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42)) { + count_tag_foo_gw_1++; + } else if (hdr_queued->target.tags == LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(26)) { + count_tag_bar_gw_1++; + } else { + count_other++; + } + } else if (recpt_gw == gw_2) { + if (hdr_queued->target.tags == LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(21)) { + count_tag_foo_gw_2++; + } else { + count_other++; + } + } else { + lgtd_errx(1, "unexpected gateway %p", recpt_gw); + } + } + + if (count_tag_foo_gw_1 != 1) { + lgtd_errx(1, "The packet for #foo should have been enqueued on gw_1"); + } + if (count_tag_bar_gw_1 != 1) { + lgtd_errx(1, "The packet for #bar should have been enqueued on gw_1"); + } + if (count_tag_foo_gw_2 != 1) { + lgtd_errx(1, "The packet for #foo should have been enqueued on gw_2"); + } + if (count_other) { + lgtd_errx(1, "Unexpected packets have been enqueued"); + } + + return 0; +} diff --git a/tests/core/router/tests_router_utils.h b/tests/core/router/tests_router_utils.h index 6901dc5..4ab234d 100644 --- a/tests/core/router/tests_router_utils.h +++ b/tests/core/router/tests_router_utils.h @@ -31,3 +31,10 @@ lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].pkt_size = pkt_size; lgtd_tests_gw_pkt_queue_size++; } + +void +lgtd_tests_router_reset_pkt_queue(void) +{ + memset(lgtd_tests_gw_pkt_queue, 0, sizeof(lgtd_tests_gw_pkt_queue)); + lgtd_tests_gw_pkt_queue_size = 0; +} diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c index 4203ef4..23ef18c 100644 --- a/tests/core/tests_shims.c +++ b/tests/core/tests_shims.c @@ -14,6 +14,7 @@ #include "core/time_monotonic.h" #include "lifx/bulb.h" #include "lifx/gateway.h" +#include "lifx/tagging.h" #include "lightsd.h" struct lgtd_opts lgtd_opts = { @@ -65,3 +66,15 @@ void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, (void)hdr; (void)pkt; } + +struct lgtd_lifx_tag * +lgtd_lifx_tagging_find_tag(const char *tag_label) +{ + struct lgtd_lifx_tag *tag = NULL; + LIST_FOREACH(tag, &lgtd_lifx_tags, link) { + if (!strcmp(tag->label, tag_label)) { + break; + } + } + return tag; +} diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index d713e64..9bd1441 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -89,3 +89,15 @@ lgtd_tests_insert_mock_tag(const char *tag_label) LIST_INSERT_HEAD(&lgtd_lifx_tags, tag, link); return tag; } + +struct lgtd_lifx_site * +lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *tag, + struct lgtd_lifx_gateway *gw, + int tag_id) +{ + struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); + site->gw = gw; + gw->tags[tag_id] = tag; + LIST_INSERT_HEAD(&tag->sites, site, link); + return site; +} diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index 5bcb2d0..8150ff5 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -4,3 +4,6 @@ struct lgtd_lifx_gateway *lgtd_tests_insert_mock_gateway(int); struct lgtd_lifx_bulb *lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *, uint64_t); struct lgtd_proto_target_list *lgtd_tests_build_target_list(const char *, ...); struct lgtd_lifx_tag *lgtd_tests_insert_mock_tag(const char *); +struct lgtd_lifx_site *lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *, + struct lgtd_lifx_gateway *, + int); diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c index 56a0d1a..7b6ccd9 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c @@ -8,7 +8,7 @@ static bool tagging_incref_called = false; struct lgtd_lifx_tag * -lgtd_lifx_tagging_incref(const char *label, const struct lgtd_lifx_gateway *gw) +lgtd_lifx_tagging_incref(const char *label, struct lgtd_lifx_gateway *gw) { if (!label) { errx(1, "missing tag label"); @@ -53,10 +53,10 @@ main(void) (int)sizeof(gw.tags[0]->label), gw.tags[4]->label ); } - if (gw.tag_ids != TAG_ID_TO_VALUE(4)) { + if (gw.tag_ids != LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(4)) { errx( 1, "tag_ids = %jx (expected %jx)", - (uintmax_t)gw.tag_ids, (uintmax_t)TAG_ID_TO_VALUE(4) + (uintmax_t)gw.tag_ids, (uintmax_t)LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(4) ); } @@ -79,11 +79,12 @@ main(void) (int)sizeof(gw.tags[0]->label), gw.tags[4]->label ); } - if (gw.tag_ids != (TAG_ID_TO_VALUE(4) | TAG_ID_TO_VALUE(63))) { + uint64_t expected_tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(4) + | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(63); + if (gw.tag_ids != expected_tags) { errx( 1, "tag_ids = %jx (expected %jx)", - (uintmax_t)gw.tag_ids, - (uintmax_t)(TAG_ID_TO_VALUE(4) | TAG_ID_TO_VALUE(63)) + (uintmax_t)gw.tag_ids, (uintmax_t)expected_tags ); } @@ -107,11 +108,10 @@ main(void) (int)sizeof(gw.tags[0]->label), gw.tags[4]->label ); } - if (gw.tag_ids != (TAG_ID_TO_VALUE(4) | TAG_ID_TO_VALUE(63))) { + if (gw.tag_ids != expected_tags) { errx( 1, "tag_ids = %jx (expected %jx)", - (uintmax_t)gw.tag_ids, - (uintmax_t)(TAG_ID_TO_VALUE(4) | TAG_ID_TO_VALUE(63)) + (uintmax_t)gw.tag_ids, (uintmax_t)expected_tags ); } if (tagging_incref_called) { diff --git a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c index 9e24fe8..990822a 100644 --- a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c @@ -8,7 +8,7 @@ static bool tagging_decref_called = false; void -lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, const struct lgtd_lifx_gateway *gw) +lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, struct lgtd_lifx_gateway *gw) { if (!tag) { errx(1, "missing tag"); @@ -37,16 +37,17 @@ main(void) }; gw.tags[0] = &tag; - gw.tag_ids = TAG_ID_TO_VALUE(0) | TAG_ID_TO_VALUE(42); + gw.tag_ids = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(0) + | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42); lgtd_lifx_gateway_deallocate_tag_id(&gw, 0); if (gw.tags[0]) { errx(1, "gw.tags[0] should have been set to NULL"); } - if (gw.tag_ids != TAG_ID_TO_VALUE(42)) { + if (gw.tag_ids != LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42)) { errx( 1, "unexpected gw.tag_ids value = %jx (expected %jx)", - gw.tag_ids, TAG_ID_TO_VALUE(42) + (uintmax_t)gw.tag_ids, (uintmax_t)LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42) ); } if (!tagging_decref_called) { @@ -58,10 +59,10 @@ main(void) if (gw.tags[0]) { errx(1, "gw.tags[0] should be NULL"); } - if (gw.tag_ids != TAG_ID_TO_VALUE(42)) { + if (gw.tag_ids != LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42)) { errx( 1, "unexpected gw.tag_ids value = %jx (expected %jx)", - gw.tag_ids, TAG_ID_TO_VALUE(42) + (uintmax_t)gw.tag_ids, (uintmax_t)LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42) ); } if (tagging_decref_called) { diff --git a/tests/lifx/gateway/test_gateway_handle_tag_labels.c b/tests/lifx/gateway/test_gateway_handle_tag_labels.c index 721b381..52013b6 100644 --- a/tests/lifx/gateway/test_gateway_handle_tag_labels.c +++ b/tests/lifx/gateway/test_gateway_handle_tag_labels.c @@ -24,12 +24,12 @@ main(void) errx(1, "expected gw.tags == 0 but got %jx", (uintmax_t)gw.tags); } - pkt.tags = TAG_ID_TO_VALUE(42); + pkt.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42); lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); - if (gw.tag_ids != TAG_ID_TO_VALUE(42)) { + if (gw.tag_ids != LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42)) { errx( 1, "expected gw.tags == %jx but got %jx", - TAG_ID_TO_VALUE(42), (uintmax_t)gw.tags + LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tags ); } if (!gw.tags[42]) { @@ -43,16 +43,18 @@ main(void) } strcpy(pkt.label, "toto"); - pkt.tags = TAG_ID_TO_VALUE(2) | TAG_ID_TO_VALUE(4); + pkt.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(2) + | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(4); lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); memset(&pkt, 0, sizeof(pkt)); lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); - uint64_t expected; - expected = TAG_ID_TO_VALUE(42) | TAG_ID_TO_VALUE(2) | TAG_ID_TO_VALUE(4); + uint64_t expected = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42) + | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(2) + | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(4); if (gw.tag_ids != expected) { errx( 1, "expected gw.tags == %jx but got %jx", - TAG_ID_TO_VALUE(42), (uintmax_t)gw.tags + LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tags ); } if (strcmp(gw.tags[2]->label, "toto")) { diff --git a/tests/lifx/gateway/test_gateway_utils.h b/tests/lifx/gateway/test_gateway_utils.h index cefd2fb..aeb382f 100644 --- a/tests/lifx/gateway/test_gateway_utils.h +++ b/tests/lifx/gateway/test_gateway_utils.h @@ -48,7 +48,7 @@ evbuffer_write_atmost(struct evbuffer *buf, #ifndef MOCKED_LIFX_TAGGING_INCREF struct lgtd_lifx_tag * -lgtd_lifx_tagging_incref(const char *label, const struct lgtd_lifx_gateway *gw) +lgtd_lifx_tagging_incref(const char *label, struct lgtd_lifx_gateway *gw) { struct lgtd_lifx_tag *tag = calloc(1, sizeof(*tag)); strcpy(tag->label, label); @@ -63,7 +63,7 @@ lgtd_lifx_tagging_incref(const char *label, const struct lgtd_lifx_gateway *gw) #ifndef MOCKED_LIFX_TAGGING_DECREF void lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, - const struct lgtd_lifx_gateway *gw) + struct lgtd_lifx_gateway *gw) { (void)tag; (void)gw; @@ -82,6 +82,17 @@ evbuffer_free(struct evbuffer *buf) (void)buf; } +struct lgtd_lifx_tag * +lgtd_lifx_tagging_find_tag(const char *tag_label) +{ + struct lgtd_lifx_tag *tag = NULL; + LIST_FOREACH(tag, &lgtd_lifx_tags, link) { + if (!strcmp(tag->label, tag_label)) { + break; + } + } + return tag; +} static struct event *last_event_passed_to_event_add = NULL; From 7dbecc7183b94d97515df7e08f96774ac3be4c2b Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 18 May 2015 01:14:25 -0700 Subject: [PATCH 037/181] Store the tag_id alongside each (tag) site So we don't have to iterate over gw->tags when routing to a tag. Note: We should probably rename the struct lgtd_lifx_site to lgtd_lifx_tag_site. --- core/router.c | 36 +++++++++---------- lifx/gateway.c | 2 +- lifx/tagging.c | 7 +++- lifx/tagging.h | 4 ++- tests/core/tests_utils.c | 1 + ...ateway_allocate_tag_id_from_lifx_network.c | 8 ++++- tests/lifx/gateway/test_gateway_utils.h | 5 ++- tests/lifx/tagging/test_tagging_incref.c | 15 ++++++-- 8 files changed, 51 insertions(+), 27 deletions(-) diff --git a/core/router.c b/core/router.c index 4adcdcb..9b639ce 100644 --- a/core/router.c +++ b/core/router.c @@ -119,28 +119,26 @@ lgtd_router_send_to_tag(const struct lgtd_lifx_tag *tag, { const struct lgtd_lifx_packet_infos *pkt_infos = NULL; - struct lgtd_lifx_site *site = NULL; + struct lgtd_lifx_site *site; LIST_FOREACH(site, &tag->sites, link) { struct lgtd_lifx_gateway *gw = site->gw; - for (int tag_id = 0; tag_id != LGTD_ARRAY_SIZE(gw->tags); tag_id++) { - if (tag == gw->tags[tag_id]) { - struct lgtd_lifx_packet_header hdr; - union lgtd_lifx_target target; - target.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); - pkt_infos = lgtd_lifx_wire_setup_header( - &hdr, - LGTD_LIFX_TARGET_TAGS, - target, - gw->site.as_array, - pkt_type - ); - assert(pkt_infos); + int tag_id = site->tag_id; + struct lgtd_lifx_packet_header hdr; + union lgtd_lifx_target target; + assert(tag == gw->tags[tag_id]); + target.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); + pkt_infos = lgtd_lifx_wire_setup_header( + &hdr, + LGTD_LIFX_TARGET_TAGS, + target, + gw->site.as_array, + pkt_type + ); + assert(pkt_infos); - lgtd_lifx_gateway_enqueue_packet( - gw, &hdr, pkt_type, pkt, pkt_infos->size - ); - } - } + lgtd_lifx_gateway_enqueue_packet( + gw, &hdr, pkt_type, pkt, pkt_infos->size + ); } if (pkt_infos) { diff --git a/lifx/gateway.c b/lifx/gateway.c index 5ad787b..b118523 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -510,7 +510,7 @@ lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, if (!(gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id))) { struct lgtd_lifx_tag *tag; - tag = lgtd_lifx_tagging_incref(tag_label, gw); + tag = lgtd_lifx_tagging_incref(tag_label, gw, tag_id); if (!tag) { lgtd_warn( "couldn't allocate a new reference to tag [%s] (site %s)", diff --git a/lifx/tagging.c b/lifx/tagging.c index 9715ff6..9be008c 100644 --- a/lifx/tagging.c +++ b/lifx/tagging.c @@ -66,9 +66,12 @@ lgtd_lifx_tagging_find_tag(const char *tag_label) } struct lgtd_lifx_tag * -lgtd_lifx_tagging_incref(const char *tag_label, struct lgtd_lifx_gateway *gw) +lgtd_lifx_tagging_incref(const char *tag_label, + struct lgtd_lifx_gateway *gw, + int tag_id) { assert(strlen(tag_label) < LGTD_LIFX_LABEL_SIZE); + assert(tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); assert(gw); bool dealloc_tag = false; @@ -102,8 +105,10 @@ lgtd_lifx_tagging_incref(const char *tag_label, struct lgtd_lifx_gateway *gw) tag_label, gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) ); site->gw = gw; + site->tag_id = tag_id; LIST_INSERT_HEAD(&tag->sites, site, link); } + assert(site->tag_id == tag_id); return tag; } diff --git a/lifx/tagging.h b/lifx/tagging.h index e06cbf7..680e54a 100644 --- a/lifx/tagging.h +++ b/lifx/tagging.h @@ -21,6 +21,7 @@ extern struct lgtd_lifx_tag_list lgtd_lifx_tags; struct lgtd_lifx_site { LIST_ENTRY(lgtd_lifx_site) link; + int tag_id; struct lgtd_lifx_gateway *gw; }; LIST_HEAD(lgtd_lifx_site_list, lgtd_lifx_site); @@ -33,7 +34,8 @@ struct lgtd_lifx_tag { LIST_HEAD(lgtd_lifx_tag_list, lgtd_lifx_tag); struct lgtd_lifx_tag *lgtd_lifx_tagging_incref(const char *, - struct lgtd_lifx_gateway *); + struct lgtd_lifx_gateway *, + int); void lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *, struct lgtd_lifx_gateway *); struct lgtd_lifx_tag *lgtd_lifx_tagging_find_tag(const char *); diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index 9bd1441..d16a0d6 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -97,6 +97,7 @@ lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *tag, { struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); site->gw = gw; + site->tag_id = tag_id; gw->tags[tag_id] = tag; LIST_INSERT_HEAD(&tag->sites, site, link); return site; diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c index 7b6ccd9..2d152fe 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c @@ -8,7 +8,9 @@ static bool tagging_incref_called = false; struct lgtd_lifx_tag * -lgtd_lifx_tagging_incref(const char *label, struct lgtd_lifx_gateway *gw) +lgtd_lifx_tagging_incref(const char *label, + struct lgtd_lifx_gateway *gw, + int tag_id) { if (!label) { errx(1, "missing tag label"); @@ -16,6 +18,9 @@ lgtd_lifx_tagging_incref(const char *label, struct lgtd_lifx_gateway *gw) if (!gw) { errx(1, "missing gateway"); } + if (tag_id != 4 && tag_id != 63) { + errx(1, "got tag_id %d but expected 4 or 63", tag_id); + } static struct lgtd_lifx_tag *tag = NULL; @@ -24,6 +29,7 @@ lgtd_lifx_tagging_incref(const char *label, struct lgtd_lifx_gateway *gw) strcpy(tag->label, label); struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); site->gw = gw; + site->tag_id = tag_id; LIST_INSERT_HEAD(&tag->sites, site, link); } diff --git a/tests/lifx/gateway/test_gateway_utils.h b/tests/lifx/gateway/test_gateway_utils.h index aeb382f..127f74b 100644 --- a/tests/lifx/gateway/test_gateway_utils.h +++ b/tests/lifx/gateway/test_gateway_utils.h @@ -48,13 +48,16 @@ evbuffer_write_atmost(struct evbuffer *buf, #ifndef MOCKED_LIFX_TAGGING_INCREF struct lgtd_lifx_tag * -lgtd_lifx_tagging_incref(const char *label, struct lgtd_lifx_gateway *gw) +lgtd_lifx_tagging_incref(const char *label, + struct lgtd_lifx_gateway *gw, + int tag_id) { struct lgtd_lifx_tag *tag = calloc(1, sizeof(*tag)); strcpy(tag->label, label); LIST_INSERT_HEAD(&lgtd_lifx_tags, tag, link); struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); site->gw = gw; + site->tag_id = tag_id; LIST_INSERT_HEAD(&tag->sites, site, link); return tag; } diff --git a/tests/lifx/tagging/test_tagging_incref.c b/tests/lifx/tagging/test_tagging_incref.c index d237c3c..0e15bb0 100644 --- a/tests/lifx/tagging/test_tagging_incref.c +++ b/tests/lifx/tagging/test_tagging_incref.c @@ -37,7 +37,7 @@ main(void) const char *awww = "awww"; for (int i = 0; i != 2; i++) { - lgtd_lifx_tagging_incref(rawr, &gw1); + lgtd_lifx_tagging_incref(rawr, &gw1, 1); if (count_tag(rawr) != 1) { errx(1, "%s wasn't found once the list of tags", rawr); } @@ -46,13 +46,13 @@ main(void) } } - lgtd_lifx_tagging_incref(rawr, &gw2); + lgtd_lifx_tagging_incref(rawr, &gw2, 1); struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(rawr); if (count_site(&tag->sites, &gw2) != 1) { errx(1, "gw2 wasn't found once in the sites of tag %s", tag->label); } - lgtd_lifx_tagging_incref(awww, &gw1); + lgtd_lifx_tagging_incref(awww, &gw1, 1); if (count_tag(awww) != 1) { errx(1, "%s wasn't found once in the list of tags", awww); } @@ -61,5 +61,14 @@ main(void) errx(1, "gw1 wasn't found once in the sites of tag %s", awww); } + LIST_FOREACH(tag, &lgtd_lifx_tags, link) { + struct lgtd_lifx_site *site; + LIST_FOREACH(site, &tag->sites, link) { + if (site->tag_id != 1) { + lgtd_errx(1, "site->tag_id = %d (expected 1)", site->tag_id); + } + } + } + return 0; } From ced943709416e1b2b7f0948395cdfd33302af766 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 18 May 2015 01:14:25 -0700 Subject: [PATCH 038/181] Fix a bunch of compilation warnings in RELEASE mode --- lifx/broadcast.c | 6 ++++-- lifx/gateway.c | 2 ++ lifx/wire_proto.c | 2 ++ tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c | 4 +++- tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c | 4 +++- .../test_jsonrpc_check_and_call_set_light_from_hsbk.c | 4 +++- ..._jsonrpc_check_and_call_set_light_from_hsbk_from_array.c | 4 +++- .../core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c | 4 +++- tests/core/jsonrpc/test_jsonrpc_utils.h | 4 ++-- 9 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lifx/broadcast.c b/lifx/broadcast.c index 3b38325..1c0e79a 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -161,7 +161,8 @@ lgtd_lifx_broadcast_handle_write(void) struct sockaddr_in lifx_addr = { .sin_family = AF_INET, .sin_addr = { INADDR_BROADCAST }, - .sin_port = htons(LGTD_LIFX_PROTOCOL_PORT) + .sin_port = htons(LGTD_LIFX_PROTOCOL_PORT), + .sin_zero = { 0 } }; struct lgtd_lifx_packet_header get_pan_gateway; lgtd_lifx_wire_setup_header( @@ -289,7 +290,8 @@ lgtd_lifx_broadcast_setup(void) struct sockaddr_in lifx_addr = { .sin_family = AF_INET, .sin_addr = { INADDR_ANY }, - .sin_port = htons(LGTD_LIFX_PROTOCOL_PORT) + .sin_port = htons(LGTD_LIFX_PROTOCOL_PORT), + .sin_zero = { 0 } }; err = bind( diff --git a/lifx/gateway.c b/lifx/gateway.c index b118523..ff8842b 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -390,6 +390,8 @@ lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_pan_gateway *pkt) { + (void)pkt; + assert(gw && hdr && pkt); lgtd_debug( diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index c426343..72193f1 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -308,6 +308,8 @@ lgtd_lifx_wire_decode_light_status(struct lgtd_lifx_packet_light_status *pkt) void lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *pkt) { + (void)pkt; + assert(pkt); } diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c index 80a5baf..b56f625 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c @@ -11,7 +11,9 @@ void lgtd_proto_power_off(struct lgtd_client *client, const struct lgtd_proto_target_list *targets) { - assert(client); + if (!client) { + errx(1, "missing client!"); + } if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c index 36fb6e3..33e0262 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c @@ -11,7 +11,9 @@ void lgtd_proto_power_on(struct lgtd_client *client, const struct lgtd_proto_target_list *targets) { - assert(client); + if (!client) { + errx(1, "missing client!"); + } if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c index e52fbeb..c5bb0ce 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -16,7 +16,9 @@ lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, int kelvin, int transition_msecs) { - assert(client); + if (!client) { + errx(1, "missing client!"); + } if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c index 588024e..221ff90 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -16,7 +16,9 @@ lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, int kelvin, int transition_msecs) { - assert(client); + if (!client) { + errx(1, "missing client!"); + } if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c index d9d4245..e1c71d8 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c @@ -16,7 +16,9 @@ lgtd_proto_set_waveform(struct lgtd_client *client, int period, float cycles, int skew_ratio, bool transient) { - assert(client); + if (!client) { + errx(1, "missing client"); + } if (strcmp(SLIST_FIRST(targets)->target, "*")) { errx( diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index 9899a2f..a389989 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -13,13 +13,13 @@ parse_json(jsmntok_t *tokens, size_t capacity, const char *json , size_t len) void lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) { - assert(targets); + (void)targets; } void lgtd_proto_list_tags(struct lgtd_client *client) { - assert(client); + (void)client; } #ifndef LGTD_TESTING_SET_LIGHT_FROM_HSBK From 17843d1e3f94b0e0de536684e6f1ad5cb85ca505 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Tue, 19 May 2015 00:27:23 -0700 Subject: [PATCH 039/181] Don't log device labels as tags when routing packets --- core/router.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router.c b/core/router.c index 9b639ce..07375f4 100644 --- a/core/router.c +++ b/core/router.c @@ -174,7 +174,7 @@ lgtd_router_send_to_label(const char *label, } if (pkt_infos) { - lgtd_info("sending %s to #%s", pkt_infos->name, label); + lgtd_info("sending %s to %s", pkt_infos->name, label); } } From b66c72080da9c0fe63175acaf85ce5918a9d8dd3 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Tue, 19 May 2015 00:27:23 -0700 Subject: [PATCH 040/181] Set bulb->dirty_at when targeting labels and tags Still not sure how this retry mechanism is gonna look like. I guess I could write unit tests for the dirty_at bit since I don't see how that would change. Also optimize this a bit in lgtd_router_broadcast. --- core/router.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/core/router.c b/core/router.c index 07375f4..8358b2e 100644 --- a/core/router.c +++ b/core/router.c @@ -60,13 +60,15 @@ lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) pkt_type ); assert(pkt_infos); + lgtd_lifx_gateway_enqueue_packet( gw, &hdr, pkt_type, pkt, pkt_infos->size ); - struct lgtd_lifx_bulb *bulb; - lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); - SLIST_FOREACH(bulb, &gw->bulbs, link_by_gw) { - if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { + + if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { + struct lgtd_lifx_bulb *bulb; + lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); + SLIST_FOREACH(bulb, &gw->bulbs, link_by_gw) { bulb->dirty_at = now; struct lgtd_lifx_packet_power_state *payload = pkt; bulb->expected_power_on = payload->power; @@ -123,6 +125,7 @@ lgtd_router_send_to_tag(const struct lgtd_lifx_tag *tag, LIST_FOREACH(site, &tag->sites, link) { struct lgtd_lifx_gateway *gw = site->gw; int tag_id = site->tag_id; + struct lgtd_lifx_packet_header hdr; union lgtd_lifx_target target; assert(tag == gw->tags[tag_id]); @@ -139,6 +142,18 @@ lgtd_router_send_to_tag(const struct lgtd_lifx_tag *tag, lgtd_lifx_gateway_enqueue_packet( gw, &hdr, pkt_type, pkt, pkt_infos->size ); + + if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { + struct lgtd_lifx_bulb *bulb; + lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); + SLIST_FOREACH(bulb, &gw->bulbs, link_by_gw) { + if (bulb->state.tags & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id)) { + bulb->dirty_at = now; + struct lgtd_lifx_packet_power_state *payload = pkt; + bulb->expected_power_on = payload->power; + } + } + } } if (pkt_infos) { @@ -170,6 +185,12 @@ lgtd_router_send_to_label(const char *label, lgtd_lifx_gateway_enqueue_packet( bulb->gw, &hdr, pkt_type, pkt, pkt_infos->size ); + + if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { + bulb->dirty_at = lgtd_time_monotonic_msecs(); + struct lgtd_lifx_packet_power_state *payload = pkt; + bulb->expected_power_on = payload->power; + } } } From 8288393cf16fe698e15ea4f399cd7b637256f48e Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 4 Jul 2015 00:03:13 +0800 Subject: [PATCH 041/181] Cope with older versions of CMake and the glibc - Early 2.8 versions of CMake don't have FOUND_VAR for FPHS; - clock_gettime is in librt for glibc < 2.17. --- CMakeLists.txt | 5 ++++- CMakeScripts/CompatTimeMonotonic.cmake | 27 ++++++++++++++++++++++---- CMakeScripts/FindEndian.cmake | 2 +- CMakeScripts/FindEvent2.cmake | 1 - core/CMakeLists.txt | 8 ++++++-- tests/core/jsonrpc/CMakeLists.txt | 2 +- tests/core/proto/CMakeLists.txt | 2 +- tests/core/router/CMakeLists.txt | 7 +++++-- tests/lifx/gateway/CMakeLists.txt | 2 +- 9 files changed, 42 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b5399a..740d033 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,10 @@ IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") ENDIF () ENDIF () -INCLUDE_DIRECTORIES(${LIGHTSD_SOURCE_DIR}/compat/generic ${LIGHTSD_BINARY_DIR}/compat) +INCLUDE_DIRECTORIES( + ${LIGHTSD_SOURCE_DIR}/compat/generic + ${LIGHTSD_BINARY_DIR}/compat +) ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(lifx) diff --git a/CMakeScripts/CompatTimeMonotonic.cmake b/CMakeScripts/CompatTimeMonotonic.cmake index de794e8..4cb72b5 100644 --- a/CMakeScripts/CompatTimeMonotonic.cmake +++ b/CMakeScripts/CompatTimeMonotonic.cmake @@ -1,31 +1,50 @@ INCLUDE(CheckFunctionExists) -IF (NOT TIME_MONOTONIC_IMPL) +IF (NOT TIME_MONOTONIC_LIBRARY) SET(COMPAT_TIME_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.c") SET(COMPAT_TIME_MONOTONIC_H "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.h") SET(GENERIC_TIME_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/generic/time_monotonic.c") SET(GENERIC_TIME_MONOTONIC_H "${LIGHTSD_SOURCE_DIR}/compat/generic/time_monotonic.h") + SET(TIME_MONOTONIC_LIBRARY time_monotonic CACHE INTERNAL "lgtd_time_monotonic implementation") SET(CMAKE_REQUIRED_QUIET TRUE) MESSAGE(STATUS "Looking for clock_gettime") CHECK_FUNCTION_EXISTS("clock_gettime" HAVE_CLOCK_GETTIME) + IF (NOT HAVE_CLOCK_GETTIME) + # glibc < 2.17: + MESSAGE(STATUS "Looking for clock_gettime again in librt") + UNSET(HAVE_CLOCK_GETTIME CACHE) + SET(TIME_MONOTONIC_LIBRARY_DEP rt CACHE INTERNAL "dependency for lgtd_time_monotonic") + SET(CMAKE_REQUIRED_LIBRARIES ${TIME_MONOTONIC_LIBRARY_DEP}) + CHECK_FUNCTION_EXISTS("clock_gettime" HAVE_CLOCK_GETTIME) + UNSET(CMAKE_REQUIRED_LIBRARIES) + IF (NOT HAVE_CLOCK_GETTIME) + UNSET(TIME_MONOTONIC_LIBRARY_DEP CACHE) + ENDIF () + ENDIF () UNSET(CMAKE_REQUIRED_QUIET) IF (HAVE_CLOCK_GETTIME) MESSAGE(STATUS "Looking for clock_gettime - found") FILE(COPY "${GENERIC_TIME_MONOTONIC_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/core/") SET( - TIME_MONOTONIC_IMPL "${GENERIC_TIME_MONOTONIC_IMPL}" - CACHE INTERNAL "lgtd_time_monotonic (POSIX generic implementation)" + TIME_MONOTONIC_IMPL ${GENERIC_TIME_MONOTONIC_IMPL} + CACHE INTERNAL "lgtd_time_monotonic (POSIX generic source)" ) ELSEIF (EXISTS "${COMPAT_TIME_MONOTONIC_IMPL}") MESSAGE(STATUS "Looking for clock_gettime - not found, using built-in compatibilty file") FILE(COPY "${COMPAT_TIME_MONOTONIC_H}" DESTINATION "${LIGHTSD_BINARY_DIR}/core/") SET( TIME_MONOTONIC_IMPL "${COMPAT_TIME_MONOTONIC_IMPL}" - CACHE INTERNAL "lgtd_time_monotonic (${CMAKE_SYSTEM_NAME} specific implementation)" + CACHE INTERNAL "lgtd_time_monotonic (${CMAKE_SYSTEM_NAME} specific source)" ) ELSE () MESSAGE(SEND_ERROR "Looking for clock_gettime - not found") ENDIF () ENDIF () + +ADD_LIBRARY(${TIME_MONOTONIC_LIBRARY} STATIC "${TIME_MONOTONIC_IMPL}") + +IF (TIME_MONOTONIC_LIBRARY_DEP) + TARGET_LINK_LIBRARIES(${TIME_MONOTONIC_LIBRARY} ${TIME_MONOTONIC_LIBRARY_DEP}) +ENDIF () diff --git a/CMakeScripts/FindEndian.cmake b/CMakeScripts/FindEndian.cmake index b964c65..19998d0 100644 --- a/CMakeScripts/FindEndian.cmake +++ b/CMakeScripts/FindEndian.cmake @@ -10,7 +10,7 @@ IF (NOT ENDIAN_H_PATH) UNSET(CMAKE_REQUIRED_QUIET) IF (HAVE_ENDIAN_H) - MESSAGE(STATUS "Looking for endan.h - found") + MESSAGE(STATUS "Looking for endian.h - found") SET(ENDIAN_H_PATH "using native headers" CACHE INTERNAL "endian.h path") ELSEIF (EXISTS "${COMPAT_ENDIAN_H}") MESSAGE(STATUS "Looking for endian.h - not found, using built-in compatibility file") diff --git a/CMakeScripts/FindEvent2.cmake b/CMakeScripts/FindEvent2.cmake index bdccc30..116c147 100644 --- a/CMakeScripts/FindEvent2.cmake +++ b/CMakeScripts/FindEvent2.cmake @@ -12,7 +12,6 @@ INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( Event2 - FOUND_VAR Event2_FOUND HANDLE_COMPONENTS REQUIRED_VARS EVENT2_CORE_LIBRARY EVENT2_INCLUDE_DIR ) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a5e982f..6d8de3b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -22,7 +22,11 @@ ADD_EXECUTABLE( log.c proto.c router.c - ${TIME_MONOTONIC_IMPL} ) -TARGET_LINK_LIBRARIES(lightsd lifx ${EVENT2_CORE_LIBRARY}) +TARGET_LINK_LIBRARIES( + lightsd + lifx + ${EVENT2_CORE_LIBRARY} + ${TIME_MONOTONIC_LIBRARY} +) diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt index 9a71d26..79b8486 100644 --- a/tests/core/jsonrpc/CMakeLists.txt +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -11,8 +11,8 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c - ${TIME_MONOTONIC_IMPL} ) +TARGET_LINK_LIBRARIES(test_core_jsonrpc ${TIME_MONOTONIC_LIBRARY}) FUNCTION(ADD_JSONRPC_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_jsonrpc) diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt index db4514b..fae9f43 100644 --- a/tests/core/proto/CMakeLists.txt +++ b/tests/core/proto/CMakeLists.txt @@ -11,8 +11,8 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c - ${TIME_MONOTONIC_IMPL} ) +TARGET_LINK_LIBRARIES(test_core_proto ${TIME_MONOTONIC_LIBRARY}) FUNCTION(ADD_ROUTER_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_proto) diff --git a/tests/core/router/CMakeLists.txt b/tests/core/router/CMakeLists.txt index 04c851b..b48b79b 100644 --- a/tests/core/router/CMakeLists.txt +++ b/tests/core/router/CMakeLists.txt @@ -12,10 +12,13 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c - ${TIME_MONOTONIC_IMPL} ) -TARGET_LINK_LIBRARIES(test_core_router ${EVENT2_CORE_LIBRARY}) +TARGET_LINK_LIBRARIES( + test_core_router + ${EVENT2_CORE_LIBRARY} + ${TIME_MONOTONIC_LIBRARY} +) FUNCTION(ADD_ROUTER_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_router) diff --git a/tests/lifx/gateway/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt index 2a85fd2..b16f869 100644 --- a/tests/lifx/gateway/CMakeLists.txt +++ b/tests/lifx/gateway/CMakeLists.txt @@ -13,8 +13,8 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/lifx/timer.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c - ${TIME_MONOTONIC_IMPL} ) +TARGET_LINK_LIBRARIES(test_lifx_gateway ${TIME_MONOTONIC_LIBRARY}) FUNCTION(ADD_GATEWAY_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_gateway) From b2e24a002fc51160d87d9c3cdec1ee22a0a762be Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 4 Jul 2015 00:03:13 +0800 Subject: [PATCH 042/181] Fix build warnings on mips32 --- tests/core/router/test_router_send_to_broadcast.c | 4 ++-- tests/core/router/test_router_send_to_device.c | 4 ++-- tests/core/router/test_router_send_to_label.c | 4 ++-- tests/core/router/test_router_send_to_tag.c | 8 ++++---- tests/lifx/gateway/test_gateway_handle_tag_labels.c | 10 +++++----- tests/lifx/gateway/test_gateway_write_callback.c | 4 ++-- ...test_gateway_write_callback_clears_ring_full_flag.c | 4 ++-- .../test_gateway_write_callback_last_packet_on_ring.c | 4 ++-- .../test_gateway_write_callback_partial_write.c | 4 ++-- .../test_gateway_write_callback_ring_wraparound.c | 4 ++-- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/core/router/test_router_send_to_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c index 5eb67b3..6012732 100644 --- a/tests/core/router/test_router_send_to_broadcast.c +++ b/tests/core/router/test_router_send_to_broadcast.c @@ -46,8 +46,8 @@ main(void) } if (lgtd_tests_gw_pkt_queue[i].pkt_size != sizeof(payload)) { lgtd_errx( - 1, "unexpected pkt size %d (expected %ld)", - lgtd_tests_gw_pkt_queue[i].pkt_size, sizeof(payload) + 1, "unexpected pkt size %d (expected %ju)", + lgtd_tests_gw_pkt_queue[i].pkt_size, (uintmax_t)sizeof(payload) ); } } diff --git a/tests/core/router/test_router_send_to_device.c b/tests/core/router/test_router_send_to_device.c index c620756..313fc40 100644 --- a/tests/core/router/test_router_send_to_device.c +++ b/tests/core/router/test_router_send_to_device.c @@ -51,8 +51,8 @@ main(void) if (pkt_size != sizeof(payload)) { lgtd_errx( - 1, "unexpected pkt size %d (expected %ld)", - pkt_size, sizeof(payload) + 1, "unexpected pkt size %d (expected %ju)", + pkt_size, (uintmax_t)sizeof(payload) ); } diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index 5e3129a..85026fc 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -55,8 +55,8 @@ main(void) if (pkt_size != sizeof(payload)) { lgtd_errx( - 1, "unexpected pkt size %d (expected %ld)", - pkt_size, sizeof(payload) + 1, "unexpected pkt size %d (expected %ju)", + pkt_size, (uintmax_t)sizeof(payload) ); } diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index e1f342c..0d16eab 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -52,8 +52,8 @@ main(void) if (pkt_size != sizeof(payload)) { lgtd_errx( - 1, "unexpected pkt size %d (expected %ld)", - pkt_size, sizeof(payload) + 1, "unexpected pkt size %d (expected %ju)", + pkt_size, (uintmax_t)sizeof(payload) ); } @@ -94,8 +94,8 @@ main(void) if (pkt_size != sizeof(payload)) { lgtd_errx( - 1, "unexpected pkt size %d (expected %ld)", - pkt_size, sizeof(payload) + 1, "unexpected pkt size %d (expected %ju)", + pkt_size, (uintmax_t)sizeof(payload) ); } diff --git a/tests/lifx/gateway/test_gateway_handle_tag_labels.c b/tests/lifx/gateway/test_gateway_handle_tag_labels.c index 52013b6..89ef2d2 100644 --- a/tests/lifx/gateway/test_gateway_handle_tag_labels.c +++ b/tests/lifx/gateway/test_gateway_handle_tag_labels.c @@ -21,15 +21,15 @@ main(void) lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); if (gw.tag_ids != 0) { - errx(1, "expected gw.tags == 0 but got %jx", (uintmax_t)gw.tags); + errx(1, "expected gw.tag_ids == 0 but got %jx", (uintmax_t)gw.tag_ids); } pkt.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42); lgtd_lifx_gateway_handle_tag_labels(&gw, &hdr, &pkt); if (gw.tag_ids != LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42)) { errx( - 1, "expected gw.tags == %jx but got %jx", - LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tags + 1, "expected gw.tag_ids == %jx but got %jx", + LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tag_ids ); } if (!gw.tags[42]) { @@ -53,8 +53,8 @@ main(void) | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(4); if (gw.tag_ids != expected) { errx( - 1, "expected gw.tags == %jx but got %jx", - LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tags + 1, "expected gw.tag_ids == %jx but got %jx", + LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tag_ids ); } if (strcmp(gw.tags[2]->label, "toto")) { diff --git a/tests/lifx/gateway/test_gateway_write_callback.c b/tests/lifx/gateway/test_gateway_write_callback.c index adc749e..d39620e 100644 --- a/tests/lifx/gateway/test_gateway_write_callback.c +++ b/tests/lifx/gateway/test_gateway_write_callback.c @@ -32,8 +32,8 @@ evbuffer_write_atmost(struct evbuffer *buf, expected += sizeof(struct lgtd_lifx_packet_power_state); if (howmuch != expected) { errx( - 1, "evbuffer_write_atmost expected %d but got %ld", - expected, howmuch + 1, "evbuffer_write_atmost expected %d but got %jd", + expected, (intmax_t)howmuch ); } diff --git a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c index 02fdd7b..00a9130 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c +++ b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c @@ -32,8 +32,8 @@ evbuffer_write_atmost(struct evbuffer *buf, expected += sizeof(struct lgtd_lifx_packet_power_state); if (howmuch != expected) { errx( - 1, "evbuffer_write_atmost expected %d but got %ld", - expected, howmuch + 1, "evbuffer_write_atmost expected %d but got %jd", + expected, (intmax_t)howmuch ); } diff --git a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c index 4ec0713..f85329f 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c +++ b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c @@ -31,8 +31,8 @@ evbuffer_write_atmost(struct evbuffer *buf, expected += sizeof(struct lgtd_lifx_packet_power_state); if (howmuch != expected) { errx( - 1, "evbuffer_write_atmost expected %d but got %ld", - expected, howmuch + 1, "evbuffer_write_atmost expected %d but got %jd", + expected, (intmax_t)howmuch ); } diff --git a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c index 39da792..eaec56e 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c +++ b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c @@ -34,8 +34,8 @@ evbuffer_write_atmost(struct evbuffer *buf, ); if (howmuch != expected) { errx( - 1, "evbuffer_write_atmost expected %d but got %ld", - expected, howmuch + 1, "evbuffer_write_atmost expected %d but got %jd", + expected, (intmax_t)howmuch ); } diff --git a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c index f4ddf11..4e4ddd6 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c @@ -32,8 +32,8 @@ evbuffer_write_atmost(struct evbuffer *buf, expected += sizeof(struct lgtd_lifx_packet_power_state); if (howmuch != expected) { errx( - 1, "evbuffer_write_atmost expected %d but got %ld", - expected, howmuch + 1, "evbuffer_write_atmost expected %d but got %jd", + expected, (intmax_t)howmuch ); } From 6b22a59ffd08da42f4e30739c762c66edb3c01b0 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 4 Jul 2015 00:03:13 +0800 Subject: [PATCH 043/181] Add the get_light_state command --- CMakeLists.txt | 5 +- README.rst | 2 +- core/jsonrpc.c | 63 ++++++- core/jsonrpc.h | 3 + core/proto.c | 77 +++++++- core/proto.h | 1 + core/router.c | 151 ++++++++++++++-- core/router.h | 8 + docs/protocol.rst | 12 +- lifx/gateway.c | 29 +-- lifx/wire_proto.c | 13 +- lifx/wire_proto.h | 33 +++- ...est_jsonrpc_uint16_range_to_float_string.c | 56 ++++++ tests/core/jsonrpc/test_jsonrpc_utils.h | 10 + tests/core/proto/CMakeLists.txt | 1 + tests/core/proto/test_proto_get_light_state.c | 123 +++++++++++++ ..._proto_get_light_state_empty_device_list.c | 60 ++++++ ...t_proto_get_light_state_null_device_list.c | 58 ++++++ tests/core/proto/test_proto_list_tags.c | 4 +- tests/core/proto/tests_proto_utils.h | 29 +++ .../router/test_router_targets_to_devices.c | 171 ++++++++++++++++++ tests/core/tests_utils.c | 37 ++-- tests/lightsc | 56 +++--- 23 files changed, 906 insertions(+), 96 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c create mode 100644 tests/core/proto/test_proto_get_light_state.c create mode 100644 tests/core/proto/test_proto_get_light_state_empty_device_list.c create mode 100644 tests/core/proto/test_proto_get_light_state_null_device_list.c create mode 100644 tests/core/router/test_router_targets_to_devices.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 740d033..80d078b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,8 @@ SET(CPACK_PACKAGE_VERSION_PATCH "1") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") -MESSAGE(STATUS "lgtd version: ${LIGHTSD_VERSION}") -MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -MESSAGE(STATUS "System: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}") +MESSAGE(STATUS "lightsd version: ${LIGHTSD_VERSION}") +MESSAGE(STATUS "Target system: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} ${CMAKE_SYSTEM_PROCESSOR}") MESSAGE(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") MESSAGE(STATUS "Source directory: ${LIGHTSD_SOURCE_DIR}") diff --git a/README.rst b/README.rst index b4a88a0..7765e0a 100644 --- a/README.rst +++ b/README.rst @@ -28,7 +28,7 @@ following commands through a JSON-RPC_ interface: - power_on; - set_light_from_hsbk; - set_waveform (change the light according to a function like SAW or SINE); -- get_light_status (coming up). +- get_light_status. lightsd can target single or multiple bulbs at once: diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 7906a3b..116c7b3 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -206,12 +206,13 @@ lgtd_jsonrpc_float_range_to_uint16(const char *s, int len, int start, int stop) assert(len > 0); assert(start < stop); - int range = stop * 10E5 - start * 10E5; + int range; + range = stop * LGTD_JSONRPC_FLOAT_PREC - start * LGTD_JSONRPC_FLOAT_PREC; const char *dot = NULL; long fracpart = 0; - long intpart = strtol(s, (char **)&dot, 10) * 10E5; + long intpart = strtol(s, (char **)&dot, 10) * LGTD_JSONRPC_FLOAT_PREC; if (dot - s != len && *dot == '.') { - for (int i = dot - s + 1, multiplier = 10E4; + for (int i = dot - s + 1, multiplier = LGTD_JSONRPC_FLOAT_PREC / 10; i != len && multiplier != 0; i++, multiplier /= 10) { fracpart += (s[i] - '0') * multiplier; @@ -220,6 +221,45 @@ lgtd_jsonrpc_float_range_to_uint16(const char *s, int len, int start, int stop) return ((intpart + fracpart) * UINT16_MAX) / range; } +void +lgtd_jsonrpc_uint16_range_to_float_string(uint16_t encoded, int start, int stop, + char *out, int size) +{ + assert(out); + assert(size > 1); + assert(start < stop); + + int range; + range = stop * LGTD_JSONRPC_FLOAT_PREC - start * LGTD_JSONRPC_FLOAT_PREC; + int value = (uint64_t)encoded * (uint64_t)range / UINT16_MAX; + + int multiplier = 1; + while (value / (multiplier * 10)) { + multiplier *= 10; + } + + int i = 0; + do { + if (multiplier == LGTD_JSONRPC_FLOAT_PREC / 10) { + if (i == 0) { + out[i++] = '0'; + } + if (i != size) { + out[i++] = '.'; + } + } + if (i != size) { + out[i++] = '0' + value / multiplier; + } + value -= value / multiplier * multiplier; + multiplier /= 10; + } while ((value || multiplier >= LGTD_JSONRPC_FLOAT_PREC) + && multiplier && i != size); + out[LGTD_MIN(i, size - 1)] = '\0'; + + assert(i <= size); +} + static int lgtd_jsonrpc_consume_object_or_array(const jsmntok_t *tokens, int ti, @@ -895,6 +935,19 @@ lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client) lgtd_proto_target_list_clear(&targets); } +static void +lgtd_jsonrpc_check_and_call_get_light_state(struct lgtd_client *client) +{ + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); + bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); + if (!ok) { + return; + } + + lgtd_proto_get_light_state(client, &targets); + lgtd_proto_target_list_clear(&targets); +} + void lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) { @@ -916,6 +969,10 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) "set_waveform", 10, lgtd_jsonrpc_check_and_call_set_waveform ), + LGTD_JSONRPC_METHOD( + "get_light_state", 1, // t + lgtd_jsonrpc_check_and_call_get_light_state + ), LGTD_JSONRPC_METHOD("list_tags", 0, lgtd_proto_list_tags), }; diff --git a/core/jsonrpc.h b/core/jsonrpc.h index 23007d6..da7c5bb 100644 --- a/core/jsonrpc.h +++ b/core/jsonrpc.h @@ -19,6 +19,8 @@ struct lgtd_client; +enum { LGTD_JSONRPC_FLOAT_PREC = (int)10E5 }; + struct lgtd_jsonrpc_request { const jsmntok_t *method; const jsmntok_t *params; @@ -90,3 +92,4 @@ void lgtd_jsonrpc_send_response(struct lgtd_client *, const char *); void lgtd_jsonrpc_start_send_response(struct lgtd_client *); void lgtd_jsonrpc_end_send_response(struct lgtd_client *); +void lgtd_jsonrpc_uint16_range_to_float_string(uint16_t, int, int, char *, int); diff --git a/core/proto.c b/core/proto.c index a3b7878..4b988ea 100644 --- a/core/proto.c +++ b/core/proto.c @@ -36,6 +36,7 @@ #include "jsmn.h" #include "jsonrpc.h" #include "client.h" +#include "lifx/gateway.h" #include "proto.h" #include "router.h" #include "lightsd.h" @@ -159,9 +160,83 @@ lgtd_proto_list_tags(struct lgtd_client *client) LIST_FOREACH(tag, &lgtd_lifx_tags, link) { LGTD_CLIENT_WRITE_STRING(client, "\""); LGTD_CLIENT_WRITE_STRING(client, tag->label); - LGTD_CLIENT_WRITE_STRING(client, LIST_NEXT(tag, link) ? "\", " : "\""); + LGTD_CLIENT_WRITE_STRING(client, LIST_NEXT(tag, link) ? "\"," : "\""); } LGTD_CLIENT_WRITE_STRING(client, "]"); lgtd_client_end_send_response(client); } + +void +lgtd_proto_get_light_state(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + assert(targets); + + struct lgtd_router_device_list *devices; + devices = lgtd_router_targets_to_devices(targets); + if (!devices) { + lgtd_client_send_error( + client, LGTD_CLIENT_INTERNAL_ERROR, "couldn't allocate device list" + ); + return; + } + + static const char *state_fmt = ("{" + "\"hsbk\":[%s,%s,%s,%hu]," + "\"power\":%s," + "\"label\":\"%s\"," + "\"tags\":["); + +#define PRINT_COMPONENT(src, dst, start, stop) \ + lgtd_jsonrpc_uint16_range_to_float_string( \ + (src), (start), (stop), (dst), sizeof((dst)) \ + ) + + lgtd_client_start_send_response(client); + LGTD_CLIENT_WRITE_STRING(client, "["); + struct lgtd_router_device *device; + SLIST_FOREACH(device, devices, link) { + struct lgtd_lifx_bulb *bulb = device->device; + + char h[16], s[16], b[16]; + PRINT_COMPONENT(bulb->state.hue, h, 0, 360); + PRINT_COMPONENT(bulb->state.saturation, s, 0, 1); + PRINT_COMPONENT(bulb->state.brightness, b, 0, 1); + + char buf[3072]; + int written = snprintf( + buf, sizeof(buf), state_fmt, + h, s, b, bulb->state.kelvin, + bulb->state.power == LGTD_LIFX_POWER_ON ? "true" : "false", + bulb->state.label + ); + if (written == sizeof(buf)) { + lgtd_warnx( + "can't send state of bulb %s (%s) to client " + "[%s]:%hu: output buffer to small", + bulb->state.label, lgtd_addrtoa(bulb->addr), + client->ip_addr, client->port + ); + continue; + } + LGTD_CLIENT_WRITE_STRING(client, buf); + + bool comma = false; + int tag_id; + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, bulb->state.tags) { + LGTD_CLIENT_WRITE_STRING(client, comma ? ",\"" : "\""); + LGTD_CLIENT_WRITE_STRING(client, bulb->gw->tags[tag_id]->label); + LGTD_CLIENT_WRITE_STRING(client, "\""); + comma = true; + } + + LGTD_CLIENT_WRITE_STRING( + client, SLIST_NEXT(device, link) ? "]}," : "]}" + ); + } + LGTD_CLIENT_WRITE_STRING(client, "]"); + lgtd_client_end_send_response(client); + + lgtd_router_device_list_free(devices); +} diff --git a/core/proto.h b/core/proto.h index ebaf423..9c164d0 100644 --- a/core/proto.h +++ b/core/proto.h @@ -38,4 +38,5 @@ void lgtd_proto_set_waveform(struct lgtd_client *, int, float, int, bool); void lgtd_proto_power_on(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_power_off(struct lgtd_client *, const struct lgtd_proto_target_list *); +void lgtd_proto_get_light_state(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_list_tags(struct lgtd_client *); diff --git a/core/router.c b/core/router.c index 8358b2e..1bff9ea 100644 --- a/core/router.c +++ b/core/router.c @@ -199,6 +199,22 @@ lgtd_router_send_to_label(const char *label, } } +static struct lgtd_lifx_bulb * +lgtd_router_device_addr_to_device(const char *device_addr) +{ + errno = 0; + uint64_t device; + const char *endptr = NULL; + device = strtoull(device_addr, (char **)&endptr, 16); + if (!*endptr && errno != ERANGE) { + device = htobe64(device); + return lgtd_lifx_bulb_get( + (uint8_t *)&device + sizeof(device) - LGTD_LIFX_ADDR_LENGTH + ); + } + return NULL; +} + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -225,20 +241,11 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, // NOTE: labels and hardware addresses are ambiguous target types, // we can't really solve this since json doesn't have hexadecimal. if (isxdigit(target->target[0])) { - errno = 0; - uint64_t device; - const char *endptr = NULL; - device = strtoull(target->target, (char **)&endptr, 16); - if (!*endptr && errno != ERANGE) { - device = htobe64(device); - struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get( - (uint8_t *)&device - + sizeof(device) - LGTD_LIFX_ADDR_LENGTH - ); - if (bulb) { - lgtd_router_send_to_device(bulb, pkt_type, pkt); - continue; - } + struct lgtd_lifx_bulb *bulb = + lgtd_router_device_addr_to_device(target->target); + if (bulb) { + lgtd_router_send_to_device(bulb, pkt_type, pkt); + continue; } lgtd_debug( "%s looked like a device address but didn't " @@ -254,3 +261,119 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, return rv; } + +static void +lgtd_router_clear_device_list(struct lgtd_router_device_list *devices) +{ + assert(devices); + + while (!SLIST_EMPTY(devices)) { + struct lgtd_router_device *device = SLIST_FIRST(devices); + SLIST_REMOVE_HEAD(devices, link); + free(device); + } +} + +static struct lgtd_router_device * +lgtd_router_insert_device_if_not_in_list(struct lgtd_router_device_list *devices, + struct lgtd_lifx_bulb *device) +{ + struct lgtd_router_device *it; + SLIST_FOREACH(it, devices, link) { + if (it->device == device) { + return it; + } + } + + struct lgtd_router_device *new = calloc(1, sizeof(*new)); + if (new) { + new->device = device; + SLIST_INSERT_HEAD(devices, new, link); + } + + return new; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + assert(targets); + + struct lgtd_router_device_list *devices = calloc(1, sizeof(*devices)); + if (!devices) { + return NULL; + } + + SLIST_INIT(devices); + + struct lgtd_proto_target *target; + SLIST_FOREACH(target, targets, link) { + if (!strcmp(target->target, "*")) { + lgtd_router_clear_device_list(devices); + struct lgtd_lifx_bulb *bulb; + RB_FOREACH(bulb, lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table) { + struct lgtd_router_device *device = calloc(1, sizeof(*device)); + if (!device) { + goto device_alloc_error; + } + device->device = bulb; + SLIST_INSERT_HEAD(devices, device, link); + } + return devices; + } else if (target->target[0] == '#') { + const struct lgtd_lifx_tag *tag; + tag = lgtd_lifx_tagging_find_tag(&target->target[1]); + if (tag) { + struct lgtd_lifx_site *site; + LIST_FOREACH(site, &tag->sites, link) { + struct lgtd_lifx_bulb *bulb; + uint64_t tag = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(site->tag_id); + SLIST_FOREACH(bulb, &site->gw->bulbs, link_by_gw) { + if (bulb->state.tags & tag) { + struct lgtd_router_device *device; + device = lgtd_router_insert_device_if_not_in_list( + devices, bulb + ); + if (!device) { + goto device_alloc_error; + } + } + } + } + } + } else if (target->target[0]) { + struct lgtd_lifx_bulb *bulb = NULL; + if (isxdigit(target->target[0])) { + bulb = lgtd_router_device_addr_to_device(target->target); + } + if (!bulb) { + RB_FOREACH(bulb, lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table) { + if (!strcmp(bulb->state.label, target->target)) { + break; + } + } + } + if (!bulb) { + continue; + } + if (!lgtd_router_insert_device_if_not_in_list(devices, bulb)) { + goto device_alloc_error; + } + } + } + + return devices; + +device_alloc_error: + lgtd_router_device_list_free(devices); + return NULL; +} + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + assert(devices); + + lgtd_router_clear_device_list(devices); + free(devices); +} diff --git a/core/router.h b/core/router.h index 517ad0b..3991aec 100644 --- a/core/router.h +++ b/core/router.h @@ -23,8 +23,16 @@ enum lgtd_router_error { LGTD_ROUTER_CANNOT_ENQUEUE_PACKET_ERROR }; +struct lgtd_router_device { + SLIST_ENTRY(lgtd_router_device) link; + struct lgtd_lifx_bulb *device; +}; +SLIST_HEAD(lgtd_router_device_list, lgtd_router_device); + bool lgtd_router_send(const struct lgtd_proto_target_list *, enum lgtd_lifx_packet_type, void *); void lgtd_router_send_to_device(struct lgtd_lifx_bulb *, enum lgtd_lifx_packet_type, void *); void lgtd_router_send_to_tag(const struct lgtd_lifx_tag *, enum lgtd_lifx_packet_type, void *); void lgtd_router_send_to_label(const char *, enum lgtd_lifx_packet_type, void *); void lgtd_router_broadcast(enum lgtd_lifx_packet_type, void *); +struct lgtd_router_device_list *lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *); +void lgtd_router_device_list_free(struct lgtd_router_device_list *); diff --git a/docs/protocol.rst b/docs/protocol.rst index 62684e9..d30225f 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -60,9 +60,15 @@ Available methods end of the waveform, otherwise it will revert back to its original state. -.. function:: list_tags() +.. function:: get_light_state(target) - Return a dictionnary with tags as keys and arrays of devices addresses or - labels as values. + Return a list of dictionnaries, each dict representing the state of one + targeted bulb, the list is not in any specific order. Each dict has the + following fields: + + - hsbk: tuple (h, s, b, k) see function:`set_light_from_hsbk`; + - label: bulb label (utf-8 encoded string); + - power: boolean, true when the bulb is powered on false otherwise; + - tags: list of tags applied to the bulb (utf-8 encoded strings). .. vim: set tw=80 spelllang=en spell: diff --git a/lifx/gateway.c b/lifx/gateway.c index ff8842b..8e37630 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -49,26 +49,6 @@ struct lgtd_lifx_gateway_list lgtd_lifx_gateways = LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); -// Kim Walisch (2012) -// http://chessprogramming.wikispaces.com/BitScan#DeBruijnMultiplation -static inline int -lgtd_lifx_bitscan64_forward(uint64_t n) -{ - enum { DEBRUIJN_NUMBER = 0x03f79d71b4cb0a89 }; - static const int DEBRUIJN_SEQUENCE[64] = { - 0, 47, 1, 56, 48, 27, 2, 60, - 57, 49, 41, 37, 28, 16, 3, 61, - 54, 58, 35, 52, 50, 42, 21, 44, - 38, 32, 29, 23, 17, 11, 4, 62, - 46, 55, 26, 59, 40, 36, 15, 53, - 34, 51, 20, 43, 31, 22, 10, 45, - 25, 39, 14, 33, 19, 30, 9, 24, - 13, 18, 8, 12, 7, 6, 5, 63 - }; - - return n ? DEBRUIJN_SEQUENCE[((n ^ (n - 1)) * DEBRUIJN_NUMBER) >> 58] : -1; -} - void lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) { @@ -565,17 +545,12 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, pkt->label, (uintmax_t)pkt->tags ); - uint64_t tags = pkt->tags; - while (true) { - int tag_id = lgtd_lifx_bitscan64_forward(tags); - if (tag_id == -1) { - break; - } + int tag_id; + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, pkt->tags) { if (pkt->label[0]) { lgtd_lifx_gateway_allocate_tag_id(gw, tag_id, pkt->label); } else if (gw->tags[tag_id]) { lgtd_lifx_gateway_deallocate_tag_id(gw, tag_id); } - tags &= ~LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); } } diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 72193f1..b28b1b5 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -35,7 +35,18 @@ #include "gateway.h" #include "core/lightsd.h" -union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET = { .tags = 0 }; +const union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET = { .tags = 0 }; + +const int LGTD_LIFX_DEBRUIJN_SEQUENCE[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, + 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, + 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, + 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, + 13, 18, 8, 12, 7, 6, 5, 63 +}; static struct lgtd_lifx_packet_infos_map lgtd_lifx_packet_infos = RB_INITIALIZER(&lgtd_lifx_packets_infos); diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 04b92ff..946d08c 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -245,7 +245,7 @@ union lgtd_lifx_target { const uint8_t *addr; //! site or device address }; -extern union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET; +extern const union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET; #if LGTD_SIZEOF_VOID_P == 8 # define LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(x) (1UL << (x)) @@ -253,6 +253,37 @@ extern union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET; # define LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(x) (1ULL << (x)) #endif +// Kim Walisch (2012) +// http://chessprogramming.wikispaces.com/BitScan#DeBruijnMultiplation + +enum { LGTD_LIFX_DEBRUIJN_NUMBER = 0x03f79d71b4cb0a89 }; +extern const int LGTD_LIFX_DEBRUIJN_SEQUENCE[64]; + +static inline int +lgtd_lifx_wire_bitscan64_forward(uint64_t n) +{ + return n ? LGTD_LIFX_DEBRUIJN_SEQUENCE[ + ((n ^ (n - 1)) * LGTD_LIFX_DEBRUIJN_NUMBER) >> 58 + ] : -1; +} + +static inline int +lgtd_lifx_wire_next_tag_id(int current_tag_id, uint64_t tags) +{ + // A bitshift >= than the width of the type is undefined behavior in C: + if (current_tag_id < 63) { + tags &= ~0ULL << (current_tag_id + 1); + return lgtd_lifx_wire_bitscan64_forward(tags); + } + return -1; +} + +#define LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id_varname, tags) \ + for ((tag_id_varname) = lgtd_lifx_wire_next_tag_id(-1, (tags)); \ + (tag_id_varname) != -1; \ + (tag_id_varname) = lgtd_lifx_wire_next_tag_id((tag_id_varname), (tags))) + + enum lgtd_lifx_waveform_type lgtd_lifx_wire_waveform_string_id_to_type(const char *, int); const struct lgtd_lifx_packet_infos *lgtd_lifx_wire_get_packet_infos(enum lgtd_lifx_packet_type); diff --git a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c new file mode 100644 index 0000000..d3b5f14 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c @@ -0,0 +1,56 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + char buf[32]; + int bufsz = sizeof(buf); + + lgtd_jsonrpc_uint16_range_to_float_string(UINT16_MAX, 0, 1, buf, bufsz); + if (strcmp(buf, "1")) { + lgtd_errx( + 1, "UINT16_MAX converted to %.*s (expected %s)", + bufsz, buf, "1" + ); + } + + lgtd_jsonrpc_uint16_range_to_float_string(UINT16_MAX / 2, 0, 1, buf, bufsz); + if (strcmp(buf, "0.499992")) { + lgtd_errx( + 1, "UINT16_MAX / 2 converted to %.*s (expected %s)", + bufsz, buf, "0.499992" + ); + } + + lgtd_jsonrpc_uint16_range_to_float_string(0, 0, 1, buf, bufsz); + if (strcmp(buf, "0")) { + lgtd_errx( + 1, "UINT16_MAX / 2 converted to %.*s (expected %s)", + bufsz, buf, "0" + ); + } + + lgtd_jsonrpc_uint16_range_to_float_string(0xaaaa, 0, 360, buf, bufsz); + if (strcmp(buf, "240")) { + lgtd_errx( + 1, "0xaaaa converted to %.*s (expected %s)", + bufsz, buf, "240" + ); + } + + bufsz = 2; + lgtd_jsonrpc_uint16_range_to_float_string(UINT16_MAX / 2, 0, 1, buf, bufsz); + if (strcmp(buf, "0")) { + lgtd_errx( + 1, + "UINT16_MAX / 2 converted to %.*s " + "(expected %s in case of overflow)", + bufsz, buf, "0" + ); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index a389989..f43b3fa 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -85,3 +85,13 @@ lgtd_proto_set_waveform(struct lgtd_client *client, (void)transient; } #endif + +#ifndef MOCKED_LGTD_GET_LIGHT_STATE +void +lgtd_proto_get_light_state(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + (void)client; + (void)targets; +} +#endif diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt index fae9f43..0f440f1 100644 --- a/tests/core/proto/CMakeLists.txt +++ b/tests/core/proto/CMakeLists.txt @@ -6,6 +6,7 @@ INCLUDE_DIRECTORIES( ADD_LIBRARY( test_core_proto STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/jsonrpc.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/timer.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c new file mode 100644 index 0000000..f4be4a2 --- /dev/null +++ b/tests/core/proto/test_proto_get_light_state.c @@ -0,0 +1,123 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != (void *)0x2a) { + lgtd_errx(1, "unexpected targets list"); + } + + static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); + + static struct lgtd_lifx_bulb bulb_1 = { + .addr = { 1, 2, 3, 4, 5 }, + .state = { + .hue = 0xaaaa, + .saturation = 0xffff, + .brightness = 0xbbbb, + .kelvin = 3600, + .label = "wave", + .power = LGTD_LIFX_POWER_ON, + .tags = 0 + } + }; + static struct lgtd_router_device device_1 = { .device = &bulb_1 }; + SLIST_INSERT_HEAD(&devices, &device_1, link); + + struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor"); + struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b"); + struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~"); + static struct lgtd_lifx_gateway gw_bulb_2 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), + .tag_ids = 0x7 + }; + lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0); + lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); + lgtd_tests_add_tag_to_gw(gw_2_tag_3, &gw_bulb_2, 2); + static struct lgtd_lifx_bulb bulb_2 = { + .addr = { 5, 4, 3, 2, 1 }, + .state = { + .hue = 0x0000, + .saturation = 0x0000, + .brightness = 0xffff, + .kelvin = 4000, + .label = "", + .power = LGTD_LIFX_POWER_OFF, + .tags = 0x3 + }, + .gw = &gw_bulb_2 + }; + static struct lgtd_router_device device_2 = { .device = &bulb_2 }; + SLIST_INSERT_HEAD(&devices, &device_2, link); + + return &devices; +} + +int +main(void) +{ + struct lgtd_client client; + struct lgtd_proto_target_list *targets = (void *)0x2a; + + lgtd_proto_get_light_state(&client, targets); + + const char expected[] = ( + "[" + "{" + "\"hsbk\":[0,0,1,4000]," + "\"power\":false," + "\"label\":\"\"," + "\"tags\":[\"vapor\",\"d^-^b\"]" + "}," + "{" + "\"hsbk\":[240,1,0.733333,3600]," + "\"power\":true," + "\"label\":\"wave\"," + "\"tags\":[]" + "}" + "]" + ); + + if (client_write_buf_idx != sizeof(expected) - 1) { + lgtd_errx( + 1, + "%d bytes written, expected %lu " + "(got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected) - 1, + client_write_buf_idx, client_write_buf, expected + ); + } + + if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected + ); + } + + if (!device_list_free_called) { + lgtd_errx(1, "the list of devices hasn't been freed"); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_get_light_state_empty_device_list.c b/tests/core/proto/test_proto_get_light_state_empty_device_list.c new file mode 100644 index 0000000..4b15a25 --- /dev/null +++ b/tests/core/proto/test_proto_get_light_state_empty_device_list.c @@ -0,0 +1,60 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != (void *)0x2a) { + lgtd_errx(1, "unexpected targets list"); + } + + static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); + + return &devices; +} + +int +main(void) +{ + struct lgtd_client client; + struct lgtd_proto_target_list *targets = (void *)0x2a; + + lgtd_proto_get_light_state(&client, targets); + + const char expected[] = "[]"; + + if (client_write_buf_idx != sizeof(expected) - 1) { + lgtd_errx( + 1, "%d bytes written, expected %lu", + client_write_buf_idx, sizeof(expected) - 1 + ); + } + + if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected + ); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_get_light_state_null_device_list.c b/tests/core/proto/test_proto_get_light_state_null_device_list.c new file mode 100644 index 0000000..0ff2d71 --- /dev/null +++ b/tests/core/proto/test_proto_get_light_state_null_device_list.c @@ -0,0 +1,58 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_CLIENT_SEND_ERROR +#include "tests_proto_utils.h" + +static bool send_error_called = false; + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != (void *)0x2a) { + lgtd_errx(1, "unexpected targets list"); + } + + return NULL; +} + +void +lgtd_client_send_error(struct lgtd_client *client, + enum lgtd_client_error_code error, + const char *msg) +{ + if (!client) { + lgtd_errx(1, "Expected client"); + } + + if (error != LGTD_CLIENT_INTERNAL_ERROR) { + lgtd_errx( + 1, "Got error code %d (expected %d)", + error, LGTD_CLIENT_INTERNAL_ERROR + ); + } + + if (!msg) { + lgtd_errx(1, "Expected error message"); + } + + send_error_called = true; +} + +int +main(void) +{ + struct lgtd_client client; + struct lgtd_proto_target_list *targets = (void *)0x2a; + + lgtd_proto_get_light_state(&client, targets); + + if (!send_error_called) { + lgtd_errx(1, "lgtd_client_send_error hasn't been called"); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_list_tags.c b/tests/core/proto/test_proto_list_tags.c index ee0338a..eb5c5f0 100644 --- a/tests/core/proto/test_proto_list_tags.c +++ b/tests/core/proto/test_proto_list_tags.c @@ -30,9 +30,9 @@ main(void) reset_client_write_buf(); lgtd_tests_insert_mock_tag("@_@"); lgtd_proto_list_tags(&client); - if (strcmp(client_write_buf, "[\"@_@\", \"test\"]")) { + if (strcmp(client_write_buf, "[\"@_@\",\"test\"]")) { errx( - 1, "expected [\"@_@\", \"test\"] but got %s instead", + 1, "expected [\"@_@\",\"test\"] but got %s instead", client_write_buf ); } diff --git a/tests/core/proto/tests_proto_utils.h b/tests/core/proto/tests_proto_utils.h index 0b901fb..70218be 100644 --- a/tests/core/proto/tests_proto_utils.h +++ b/tests/core/proto/tests_proto_utils.h @@ -20,6 +20,18 @@ lgtd_client_send_response(struct lgtd_client *client, const char *msg) } #endif +#ifndef MOCKED_CLIENT_SEND_ERROR +void +lgtd_client_send_error(struct lgtd_client *client, + enum lgtd_client_error_code error, + const char *msg) +{ + (void)client; + (void)error; + (void)msg; +} +#endif + #ifndef MOCKED_ROUTER_SEND bool lgtd_router_send(const struct lgtd_proto_target_list *targets, @@ -32,3 +44,20 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, return true; } #endif + +#ifndef MOCKED_ROUTER_TARGETS_TO_DEVICES +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + (void)targets; + return NULL; +} +#endif + +#ifndef MOCKED_ROUTER_DEVICE_LIST_FREE +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + (void)devices; +} +#endif diff --git a/tests/core/router/test_router_targets_to_devices.c b/tests/core/router/test_router_targets_to_devices.c new file mode 100644 index 0000000..24c41ed --- /dev/null +++ b/tests/core/router/test_router_targets_to_devices.c @@ -0,0 +1,171 @@ +#include "router.c" + +#include "tests_utils.h" +#include "tests_router_utils.h" + +static int +count_device(const struct lgtd_router_device_list *devices, const void *device) +{ + if (!devices) { + lgtd_errx(1, "unexpected NULL devices list"); + } + + int count = 0; + struct lgtd_router_device *it; + SLIST_FOREACH(it, devices, link) { + if (it->device == device) { + count++; + } + } + + return count; +} + +static int +len(const struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "unexpected NULL devices list"); + } + + int count = 0; + struct lgtd_router_device *it; + SLIST_FOREACH(it, devices, link) { + count++; + } + + return count; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); + struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); + + struct lgtd_lifx_tag *tag_foo = lgtd_tests_insert_mock_tag("foo"); + lgtd_tests_add_tag_to_gw(tag_foo, gw_1, 42); + lgtd_tests_add_tag_to_gw(tag_foo, gw_2, 63); + + struct lgtd_lifx_tag *tag_bar = lgtd_tests_insert_mock_tag("bar"); + lgtd_tests_add_tag_to_gw(tag_bar, gw_2, 42); + + struct lgtd_lifx_bulb *bulb_1_gw_1 = lgtd_tests_insert_mock_bulb(gw_1, 3); + bulb_1_gw_1->state.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42); + + struct lgtd_lifx_bulb *bulb_2_gw_1 = lgtd_tests_insert_mock_bulb(gw_1, 4); + + struct lgtd_lifx_bulb *bulb_1_gw_2 = lgtd_tests_insert_mock_bulb(gw_2, 5); + strcpy(bulb_1_gw_2->state.label, "desk"); + + struct lgtd_lifx_bulb *bulb_2_gw_2 = lgtd_tests_insert_mock_bulb(gw_2, 6); + bulb_2_gw_2->state.tags = + LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(63) | LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42); + + struct lgtd_proto_target_list *targets; + struct lgtd_router_device_list *devices; + int count; + + targets = lgtd_tests_build_target_list(NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = len(devices)) != 0) { + lgtd_errx(1, "expected 0 devices but got %d", count); + } + + targets = lgtd_tests_build_target_list("#pouet", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = len(devices))) { + lgtd_errx(1, "expected 0 device but got %d", count); + } + + targets = lgtd_tests_build_target_list("#pouet", "label", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = len(devices))) { + lgtd_errx(1, "expected 0 device but got %d", count); + } + + targets = lgtd_tests_build_target_list("#foo", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = count_device(devices, bulb_1_gw_1)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_1 found %d times, expected 1", count); + } + if ((count = count_device(devices, bulb_2_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_2_gw_2 found %d times, expected 1", count); + } + if ((count = len(devices)) != 2) { + lgtd_errx(1, "expected 2 devices but got %d", count); + } + + targets = lgtd_tests_build_target_list("#bar", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = count_device(devices, bulb_2_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_2_gw_2 found %d times, expected 1", count); + } + if ((count = len(devices)) != 1) { + lgtd_errx(1, "expected 1 devices but got %d", count); + } + + targets = lgtd_tests_build_target_list("desk", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = count_device(devices, bulb_1_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_2 found %d times, expected 1", count); + } + if ((count = len(devices)) != 1) { + lgtd_errx(1, "expected 1 device but got %d", count); + } + + targets = lgtd_tests_build_target_list("4", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = count_device(devices, bulb_2_gw_1)) != 1) { + lgtd_errx(1, "bulb bulb_2_gw_1 found %d times, expected 1", count); + } + if ((count = len(devices)) != 1) { + lgtd_errx(1, "expected 1 device but got %d", count); + } + + targets = lgtd_tests_build_target_list("desk", "5", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = count_device(devices, bulb_1_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_2 found %d times, expected 1", count); + } + if ((count = len(devices)) != 1) { + lgtd_errx(1, "expected 1 device but got %d", count); + } + + targets = lgtd_tests_build_target_list("desk", "5", "#foo", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = count_device(devices, bulb_1_gw_1)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_1 found %d times, expected 1", count); + } + if ((count = count_device(devices, bulb_1_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_2 found %d times, expected 1", count); + } + if ((count = count_device(devices, bulb_2_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_2_gw_2 found %d times, expected 1", count); + } + if ((count = len(devices)) != 3) { + lgtd_errx(1, "expected 3 device but got %d", count); + } + + targets = lgtd_tests_build_target_list("*", "#foo", "*", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = count_device(devices, bulb_1_gw_1)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_1 found %d times, expected 1", count); + } + if ((count = count_device(devices, bulb_1_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_2 found %d times, expected 1", count); + } + if ((count = count_device(devices, bulb_1_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_2 found %d times, expected 1", count); + } + if ((count = count_device(devices, bulb_2_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_2_gw_2 found %d times, expected 1", count); + } + if ((count = len(devices)) != 4) { + lgtd_errx(1, "expected 4 device but got %d", count); + } + + return 0; +} diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index d16a0d6..f1500d7 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -51,8 +51,11 @@ lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *gw, uint64_t addr) uint8_t as_array[LGTD_LIFX_ADDR_LENGTH]; uint64_t as_scalar; } bulb_addr = { .as_scalar = htobe64(addr) >> 16 }; + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_open(gw, bulb_addr.as_array); - return lgtd_lifx_bulb_open(gw, bulb_addr.as_array); + SLIST_INSERT_HEAD(&gw->bulbs, bulb, link_by_gw); + + return bulb; } struct lgtd_proto_target_list * @@ -61,21 +64,25 @@ lgtd_tests_build_target_list(const char *target, ...) struct lgtd_proto_target_list *targets = malloc(sizeof(*targets)); SLIST_INIT(targets); - struct lgtd_proto_target *tail = malloc( - sizeof(*tail) + strlen(target) + 1 - ); - strcpy(tail->target, target); - SLIST_INSERT_HEAD(targets, tail, link); - - va_list ap; - va_start(ap, target); - while ((target = va_arg(ap, const char *))) { - struct lgtd_proto_target *t = malloc(sizeof(*t) + strlen(target) + 1); - strcpy(t->target, target); - SLIST_INSERT_AFTER(tail, t, link); - tail = t; + if (target) { + struct lgtd_proto_target *tail = malloc( + sizeof(*tail) + strlen(target) + 1 + ); + strcpy(tail->target, target); + SLIST_INSERT_HEAD(targets, tail, link); + + va_list ap; + va_start(ap, target); + while ((target = va_arg(ap, const char *))) { + struct lgtd_proto_target *t = malloc( + sizeof(*t) + strlen(target) + 1 + ); + strcpy(t->target, target); + SLIST_INSERT_AFTER(tail, t, link); + tail = t; + } + va_end(ap); } - va_end(ap); return targets; } diff --git a/tests/lightsc b/tests/lightsc index 7778f72..634a5be 100755 --- a/tests/lightsc +++ b/tests/lightsc @@ -1,43 +1,49 @@ #!/usr/bin/env python import json +import pprint import socket import sys import time +import uuid from IPython.terminal.embed import InteractiveShellEmbed -def jsonrpc_call(socket, id, method, params): +def jsonrpc_call(socket, method, params): payload = { "method": method, "params": params, "jsonrpc": "2.0", - "id": id, + "id": str(uuid.uuid4()), } - socket.send(json.dumps(payload).encode("ascii")) - response = socket.recv(2048) - response = json.loads(response.decode("ascii")) - print(response) + socket.send(json.dumps(payload).encode("utf-8")) + response = socket.recv(2048).decode("utf-8") + try: + response = json.loads(response) + except ValueError: + print("received invalid json: {}".format(response)) + return None + return response -def set_light_from_hsbk(socket, id, target, h, s, b, k, t): - jsonrpc_call(socket, id, "set_light_from_hsbk", [ +def set_light_from_hsbk(socket, target, h, s, b, k, t): + return jsonrpc_call(socket, "set_light_from_hsbk", [ target, h, s, b, k, t ]) -def set_waveform(socket, id, target, waveform, +def set_waveform(socket, target, waveform, h, s, b, k, period, cycles, skew_ratio, transient): - jsonrpc_call(socket, id, "set_waveform", [ + return jsonrpc_call(socket, "set_waveform", [ target, waveform, h, s, b, k, period, cycles, skew_ratio, transient ]) -def saw(socket, id, target, h, s, b, k, period, cycles, transient=True): - set_waveform( - socket, id, target, "SAW", h, s, b, k, +def saw(socket, target, h, s, b, k, period, cycles, transient=True): + return set_waveform( + socket, target, "SAW", h, s, b, k, cycles=cycles, period=period, skew_ratio=0.5, @@ -45,9 +51,9 @@ def saw(socket, id, target, h, s, b, k, period, cycles, transient=True): ) -def sine(socket, id, target, h, s, b, k, period, cycles, peak=0.5, transient=True): - set_waveform( - socket, id, target, "SINE", h, s, b, k, +def sine(socket, target, h, s, b, k, period, cycles, peak=0.5, transient=True): + return set_waveform( + socket, target, "SINE", h, s, b, k, cycles=cycles, period=period, skew_ratio=peak, @@ -55,16 +61,16 @@ def sine(socket, id, target, h, s, b, k, period, cycles, peak=0.5, transient=Tru ) -def power_on(socket, id, target): - jsonrpc_call(socket, id, "power_on", {"target": target}) +def power_on(socket, target): + return jsonrpc_call(socket, "power_on", {"target": target}) -def power_off(socket, id, target): - jsonrpc_call(socket, id, "power_off", {"target": target}) +def power_off(socket, target): + return jsonrpc_call(socket, "power_off", {"target": target}) -def list_tags(socket, id): - jsonrpc_call(socket, id, "list_tags", []) +def get_light_state(socket, target): + return jsonrpc_call(socket, "get_light_state", [target]) if __name__ == "__main__": s = socket.create_connection(("localhost", 1234)) @@ -80,12 +86,12 @@ if __name__ == "__main__": ipshell = InteractiveShellEmbed() ipshell() sys.exit(0) - power_on(s, id, target) + power_on(s, target) while True: h = (h + 5) % 360 id += 1 - set_light_from_hsbk(s, id, target, h, 0.8, 0.1, 2500, 450) + set_light_from_hsbk(s, target, h, 0.8, 0.1, 2500, 450) time.sleep(0.5) - power_off(s, id, target) + power_off(s, target) finally: s.close() From efbdba05a718dd618d5f630c6109a17cf9dea6b2 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 4 Jul 2015 00:03:13 +0800 Subject: [PATCH 044/181] Fix 32 bit integer overflow in jsonrpc_float_range_to_uint16 --- core/jsonrpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 116c7b3..118f259 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -218,7 +218,7 @@ lgtd_jsonrpc_float_range_to_uint16(const char *s, int len, int start, int stop) fracpart += (s[i] - '0') * multiplier; } } - return ((intpart + fracpart) * UINT16_MAX) / range; + return ((int64_t)intpart + (int64_t)fracpart) * UINT16_MAX / (int64_t)range; } void From 85a344e8aa0f559b8a65e16d240c1808c6c677ee Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 4 Jul 2015 00:03:13 +0800 Subject: [PATCH 045/181] Fix bitshift in test_utils_insert_mock_bulb for big endian systems --- CMakeLists.txt | 4 ++++ tests/core/tests_utils.c | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 80d078b..b42f3a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,8 +22,11 @@ ENABLE_TESTING() # TODO: we need at least 2.0.19-stable because of the logging defines FIND_PACKAGE(Event2 REQUIRED COMPONENTS core) FIND_PACKAGE(Endian REQUIRED) +INCLUDE(TestBigEndian) INCLUDE(CompatTimeMonotonic) +TEST_BIG_ENDIAN(LGTD_BIG_ENDIAN_SYSTEM) + ### Global definitions ######################################################### INCLUDE(AddAllSubdirectories) @@ -32,6 +35,7 @@ INCLUDE(AddTestFromSources) SET(CMAKE_C_FLAGS "-pipe -Wextra -Wall -Wstrict-prototypes -std=c99") ADD_DEFINITIONS("-DLGTD_SIZEOF_VOID_P=${CMAKE_SIZEOF_VOID_P}") +ADD_DEFINITIONS("-DLGTD_BIG_ENDIAN_SYSTEM=${LGTD_BIG_ENDIAN_SYSTEM}") # Only relevant for the GNU libc: ADD_DEFINITIONS( diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index f1500d7..77a6ea5 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -50,7 +50,10 @@ lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *gw, uint64_t addr) union { uint8_t as_array[LGTD_LIFX_ADDR_LENGTH]; uint64_t as_scalar; - } bulb_addr = { .as_scalar = htobe64(addr) >> 16 }; + } bulb_addr = { + .as_scalar = LGTD_BIG_ENDIAN_SYSTEM ? + htobe64(addr) << 16 : htobe64(addr) >> 16 + }; struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_open(gw, bulb_addr.as_array); SLIST_INSERT_HEAD(&gw->bulbs, bulb, link_by_gw); From 9d7a6c5705cf20ba4d0643632e741f637cdd3760 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 9 Jul 2015 00:35:39 -0700 Subject: [PATCH 046/181] Fix things for big endian architectures Unfortunately, you can't use bitfields because they aren't standardized across compilers and architectures. Also probably fixes how ack_required and res_required are handled even though I'm leaving that untesteed for now. --- README.rst | 5 +- core/jsonrpc.c | 1 + lifx/broadcast.c | 9 +- lifx/wire_proto.c | 55 ++++---- lifx/wire_proto.h | 67 +++++++--- .../router/test_router_send_to_broadcast.c | 3 +- .../core/router/test_router_send_to_device.c | 2 +- tests/core/router/test_router_send_to_label.c | 2 +- tests/core/router/test_router_send_to_tag.c | 5 +- tests/core/tests_utils.h | 30 +++++ tests/lifx/wire_proto/CMakeLists.txt | 8 +- .../test_wire_proto_encode_decode_header.c | 126 ++++++++++++++++++ 12 files changed, 257 insertions(+), 56 deletions(-) create mode 100644 tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c diff --git a/README.rst b/README.rst index 7765e0a..ed1ca97 100644 --- a/README.rst +++ b/README.rst @@ -52,14 +52,15 @@ Requirements ------------ lightsd aims to be highly portable on any slightly POSIX system (win32 support -should be quite easy, but isn't really the goal) and on any kind of hardware +should be quite easy, but isn't really the focus) and on any kind of hardware including embedded devices. Hence why lightsd is written in C with reasonable dependencies: - CMake ≥ 2.8; - libevent ≥ 2.0.19. -lightsd is actively developed and tested from Arch Linux and Mac OS X. +lightsd is actively developed and tested from Arch Linux, Debian and Mac OS X; +both for 32/64 bits and little/big endian architectures. Build instructions ------------------ diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 118f259..281b168 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -1,3 +1,4 @@ +// Copyright (c) 2014, 2015, Louis Opter // // This file is part of lighstd. // diff --git a/lifx/broadcast.c b/lifx/broadcast.c index 1c0e79a..9eeb926 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -105,10 +105,11 @@ lgtd_lifx_broadcast_handle_read(void) ); return false; } - if (read.hdr.protocol.version != LGTD_LIFX_PROTOCOL_V1) { + int proto_version = read.hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; + if (proto_version != LGTD_LIFX_PROTOCOL_V1) { lgtd_warnx( "unsupported protocol %d from [%s]:%hu", - read.hdr.protocol.version, peer_addr, peer_port + read.hdr.protocol & 0x0fff, peer_addr, peer_port ); } if (read.hdr.packet_type == LGTD_LIFX_GET_PAN_GATEWAY) { @@ -121,10 +122,10 @@ lgtd_lifx_broadcast_handle_read(void) lgtd_warnx( "received unknown packet %#x from [%s]:%hu", read.hdr.packet_type, peer_addr, peer_port - ) + ); continue; } - if (read.hdr.protocol.tagged || !read.hdr.protocol.addressable) { + if (!(read.hdr.protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE)) { lgtd_warnx( "received non-addressable packet %s from [%s]:%hu", pkt_infos->name, peer_addr, peer_port diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index b28b1b5..2877252 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -209,6 +209,30 @@ lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) return LGTD_LIFX_WAVEFORM_INVALID; } +static void +lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr, int flags) +{ + assert(hdr); + + hdr->size = htole16(hdr->size); + hdr->protocol = htole16(LGTD_LIFX_PROTOCOL_V1); + if (flags & LGTD_LIFX_ADDRESSABLE) { + hdr->protocol |= LGTD_LIFX_PROTOCOL_ADDRESSABLE; + } + if (flags & LGTD_LIFX_TAGGED) { + hdr->protocol |= LGTD_LIFX_PROTOCOL_TAGGED; + htole64(hdr->target.tags); + } + if (flags & LGTD_LIFX_ACK_REQUIRED) { + hdr->flags |= LGTD_LIFX_FLAG_ACK_REQUIRED; + } + if (flags & LGTD_LIFX_RES_REQUIRED) { + hdr->flags |= LGTD_LIFX_FLAG_RES_REQUIRED; + } + hdr->at_time = htole64(hdr->at_time); + hdr->packet_type = htole16(hdr->packet_type); +} + // Convert all the fields in the header to the host endianness. // // \return The payload size or -1 if the header is invalid. @@ -218,10 +242,8 @@ lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *hdr) assert(hdr); hdr->size = le16toh(hdr->size); - hdr->protocol.version = le16toh(hdr->protocol.version); - if (hdr->protocol.tagged) { - le64toh(hdr->target.tags); - } + hdr->protocol = le16toh(hdr->protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK) + | (hdr->protocol & LGTD_LIFX_PROTOCOL_FLAGS_MASK); hdr->at_time = le64toh(hdr->at_time); hdr->packet_type = le16toh(hdr->packet_type); } @@ -242,7 +264,6 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, memset(hdr, 0, sizeof(*hdr)); hdr->size = pkt_infos->size + sizeof(*hdr); - hdr->protocol.version = LGTD_LIFX_PROTOCOL_V1; hdr->packet_type = packet_type; if (site) { memcpy(hdr->site, site, sizeof(hdr->site)); @@ -250,42 +271,26 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, assert(target_type == LGTD_LIFX_TARGET_ALL_DEVICES); } + int flags = LGTD_LIFX_ADDRESSABLE; switch (target_type) { case LGTD_LIFX_TARGET_SITE: case LGTD_LIFX_TARGET_ALL_DEVICES: - hdr->protocol.tagged = true; - hdr->protocol.addressable = true; + flags |= LGTD_LIFX_TAGGED; break; case LGTD_LIFX_TARGET_TAGS: - hdr->protocol.tagged = true; - hdr->protocol.addressable = true; + flags |= LGTD_LIFX_TAGGED; hdr->target.tags = target.tags; break; case LGTD_LIFX_TARGET_DEVICE: - hdr->protocol.addressable = true; memcpy(hdr->target.device_addr, target.addr, LGTD_LIFX_ADDR_LENGTH); break; } - lgtd_lifx_wire_encode_header(hdr); + lgtd_lifx_wire_encode_header(hdr, flags); return pkt_infos; } -void -lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr) -{ - assert(hdr); - - hdr->size = htole16(hdr->size); - hdr->protocol.version = htole16(hdr->protocol.version); - if (hdr->protocol.tagged) { - le64toh(hdr->target.tags); - } - hdr->at_time = htole64(hdr->at_time); - hdr->packet_type = htole16(hdr->packet_type); -} - void lgtd_lifx_wire_decode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *pkt) { diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 946d08c..6896a4f 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -33,16 +33,16 @@ enum { LGTD_LIFX_ADDR_LENGTH = 6 }; struct lgtd_lifx_packet_header { //! Packet size including the headers (i.e: this structure). uint16le_t size; - struct { - //! Protocol version should be LGTD_LIFX_LIFX_PROTOCOL_V1. - uint16le_t version:12; - //! True when the target field holds a device address. - uint16le_t addressable:1; - //! True when the target field holds tags. - uint16le_t tagged:1; - //! LIFX internal use should be 0. - uint16le_t origin:2; - } protocol; + //! 15 (MSB) 13 12 11 0 + //! +-----------------+------------+-----------------+--------------+ + //! | origin (2 bits) | tagged (1) | addressable (1) | version (12) | + //! +-----------------+------------+-----------------+--------------+ + //! + //! - version: protocol version should be LGTD_LIFX_PROTOCOL_V1; + //! - addressable: true when the target field holds a device address; + //! - tagged: true when the target field holds tags; + //! - origin: LIFX internal use, should be 0. + uint16le_t protocol; //! Here is what LIFXKit says about it, maybe it's related to zigbee: //! Message source identifier from NAT table (Internal LIFX use) uint32le_t source; @@ -53,13 +53,15 @@ struct lgtd_lifx_packet_header { uint8_t device_addr[LGTD_LIFX_ADDR_LENGTH]; } target; uint8_t site[LGTD_LIFX_ADDR_LENGTH]; - struct { - //! True when a response is required, called acknowledge in lifx-gem... - uint8_t response_required:1; - //! True when an acknowledgement is required, no idea what it means. - uint8_t ack_required:1; - uint8_t reserved:6; - } flags; + //! 7 2 1 0 + //! +-------------------+------------------+------------------+ + //! | reserved (6 bits) | ack required (1) | res required (1) | + //! +-------------------+------------------+------------------+ + //! + //! - ack required: true when an acknowledge packet is required; + //! - res required: true when a response is required (the response type + //! depends on the request type). + uint8_t flags; //! Wrap-around sequence number, LIFX internal use. uint8_t seqn; //! Apparently this is a unix epoch timestamp in milliseconds at which the @@ -71,7 +73,25 @@ struct lgtd_lifx_packet_header { enum { LGTD_LIFX_PACKET_HEADER_SIZE = sizeof(struct lgtd_lifx_packet_header) }; -enum { LGTD_LIFX_PROTOCOL_V1 = 1024 }; +enum lgtd_lifx_protocol { + LGTD_LIFX_PROTOCOL_V1 = 0x400, +#if LGTD_BIG_ENDIAN_SYSTEM + LGTD_LIFX_PROTOCOL_VERSION_MASK = 0xff0f, + LGTD_LIFX_PROTOCOL_FLAGS_MASK = 0x00f0, + LGTD_LIFX_PROTOCOL_ADDRESSABLE = 0x0010, + LGTD_LIFX_PROTOCOL_TAGGED = 0x0020 +#else + LGTD_LIFX_PROTOCOL_VERSION_MASK = 0x0fff, + LGTD_LIFX_PROTOCOL_FLAGS_MASK = 0xf000, + LGTD_LIFX_PROTOCOL_ADDRESSABLE = 0x1000, + LGTD_LIFX_PROTOCOL_TAGGED = 0x2000 +#endif +}; + +enum lgtd_lifx_flags { + LGTD_LIFX_FLAG_ACK_REQUIRED = 1 << 1, + LGTD_LIFX_FLAG_RES_REQUIRED = 1 +}; // Let's define a maximum packet size just in case somebody sends us weird // headers: @@ -114,6 +134,9 @@ enum lgtd_lifx_packet_type { LGTD_LIFX_REBOOT = 0x26, LGTD_LIFX_SET_FACTORY_TEST_MODE = 0x27, LGTD_LIFX_DISABLE_FACTORY_TEST_MODE = 0x28, + LGTD_LIFX_ACK = 0x2d, + LGTD_LIFX_ECHO_REQUEST = 0x3a, + LGTD_LIFX_ECHO_RESPONSE = 0x3b, LGTD_LIFX_GET_LIGHT_STATE = 0x65, LGTD_LIFX_SET_LIGHT_COLOR = 0x66, LGTD_LIFX_SET_WAVEFORM = 0x67, @@ -211,6 +234,13 @@ struct lgtd_lifx_packet_tag_labels { #pragma pack(pop) +enum lgtd_lifx_header_flags { + LGTD_LIFX_ADDRESSABLE = 1, + LGTD_LIFX_TAGGED = 1 << 1, + LGTD_LIFX_ACK_REQUIRED = 1 << 2, + LGTD_LIFX_RES_REQUIRED = 1 << 3 +}; + struct lgtd_lifx_waveform_string_id { const char *str; int len; @@ -295,7 +325,6 @@ const struct lgtd_lifx_packet_infos *lgtd_lifx_wire_setup_header(struct lgtd_lif const uint8_t *, enum lgtd_lifx_packet_type); void lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *); -void lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *); void lgtd_lifx_wire_decode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *); void lgtd_lifx_wire_encode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *); diff --git a/tests/core/router/test_router_send_to_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c index 6012732..2ab0f6a 100644 --- a/tests/core/router/test_router_send_to_broadcast.c +++ b/tests/core/router/test_router_send_to_broadcast.c @@ -32,7 +32,8 @@ main(void) } const struct lgtd_lifx_packet_header *hdr; hdr = lgtd_tests_gw_pkt_queue[i].hdr; - if (!hdr->protocol.tagged || !hdr->protocol.addressable) { + int expected_flags = LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_TAGGED; + if (!lgtd_tests_lifx_header_has_flags(hdr, expected_flags)) { lgtd_errx(1, "packet header doesn't have the right bits set"); } if (hdr->target.tags != 0) { diff --git a/tests/core/router/test_router_send_to_device.c b/tests/core/router/test_router_send_to_device.c index 313fc40..b817bbc 100644 --- a/tests/core/router/test_router_send_to_device.c +++ b/tests/core/router/test_router_send_to_device.c @@ -33,7 +33,7 @@ main(void) lgtd_errx(1, "the packet has been sent to the wrong gateway"); } - if (!hdr_queued->protocol.addressable || hdr_queued->protocol.tagged) { + if (!lgtd_tests_lifx_header_has_flags(hdr_queued, LGTD_LIFX_ADDRESSABLE)) { lgtd_errx(1, "the packet header doesn't have the right protocol flags"); } diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index 85026fc..aa581d4 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -37,7 +37,7 @@ main(void) lgtd_errx(1, "the packet has been sent to the wrong gateway"); } - if (!hdr_queued->protocol.addressable || hdr_queued->protocol.tagged) { + if (!lgtd_tests_lifx_header_has_flags(hdr_queued, LGTD_LIFX_ADDRESSABLE)) { lgtd_errx(1, "the packet header doesn't have the right protocol flags"); } diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index 0d16eab..10d4d77 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -34,7 +34,8 @@ main(void) lgtd_errx(1, "the packet has been sent to the wrong gateway"); } - if (!hdr_queued->protocol.addressable || !hdr_queued->protocol.tagged) { + int expected_flags = LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_TAGGED; + if (!lgtd_tests_lifx_header_has_flags(hdr_queued, expected_flags)) { lgtd_errx(1, "the packet header doesn't have the right protocol flags"); } @@ -80,7 +81,7 @@ main(void) pkt_queued = lgtd_tests_gw_pkt_queue[i].pkt; pkt_size = lgtd_tests_gw_pkt_queue[i].pkt_size; - if (!hdr_queued->protocol.addressable || !hdr_queued->protocol.tagged) { + if (!lgtd_tests_lifx_header_has_flags(hdr_queued, expected_flags)) { lgtd_errx(1, "the packet header doesn't have the right protocol flags"); } diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index 8150ff5..41150d8 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -1,5 +1,35 @@ #pragma once +static inline bool +lgtd_tests_lifx_header_has_flags(const struct lgtd_lifx_packet_header *hdr, + int flags) +{ + int expected_protocol_flags = 0; + if (flags & LGTD_LIFX_ADDRESSABLE) { + expected_protocol_flags |= LGTD_LIFX_PROTOCOL_ADDRESSABLE; + } + if (flags & LGTD_LIFX_TAGGED) { + expected_protocol_flags |= LGTD_LIFX_PROTOCOL_TAGGED; + } + int protocol_flags = hdr->protocol & LGTD_LIFX_PROTOCOL_FLAGS_MASK; + if (protocol_flags != expected_protocol_flags) { + return false; + } + + int expected_flags = 0; + if (flags & LGTD_LIFX_ACK_REQUIRED) { + expected_flags |= LGTD_LIFX_FLAG_ACK_REQUIRED; + } + if (flags & LGTD_LIFX_RES_REQUIRED) { + expected_flags |= LGTD_LIFX_FLAG_ACK_REQUIRED; + } + if (hdr->flags != expected_flags) { + return false; + } + + return true; +} + struct lgtd_lifx_gateway *lgtd_tests_insert_mock_gateway(int); struct lgtd_lifx_bulb *lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *, uint64_t); struct lgtd_proto_target_list *lgtd_tests_build_target_list(const char *, ...); diff --git a/tests/lifx/wire_proto/CMakeLists.txt b/tests/lifx/wire_proto/CMakeLists.txt index a3d3b8f..da226ef 100644 --- a/tests/lifx/wire_proto/CMakeLists.txt +++ b/tests/lifx/wire_proto/CMakeLists.txt @@ -3,8 +3,14 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) +ADD_LIBRARY( + test_lifx_wire_proto STATIC + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c +) + FUNCTION(ADD_WIRE_PROTO_TEST TEST_SOURCE) - ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} FALSE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_wire_proto) ENDFUNCTION() FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c new file mode 100644 index 0000000..c9bb146 --- /dev/null +++ b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c @@ -0,0 +1,126 @@ +#include + +#include "wire_proto.c" + +#include "test_wire_proto_utils.h" + +int +main(void) +{ + struct lgtd_lifx_packet_header hdr = { + .size = 42, + .target = { .tags = 0xbad }, + .packet_type = LGTD_LIFX_ECHO_REQUEST + }; + lgtd_lifx_wire_encode_header(&hdr, LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_TAGGED); + + if (htobe16(hdr.protocol) != 0x34) { + lgtd_errx(1, "protocol = %#hx (expected = 0x34)", hdr.protocol); + } + if (le16toh(hdr.size) != 42) { + lgtd_errx(1, "size = %hu (expected = 42)", le16toh(hdr.size)); + } + if (le64toh(hdr.target.tags != 0xbad)) { + lgtd_errx( + 1, "tags = %#jx (expected = 0xbad)", + (uintmax_t)le16toh(hdr.target.tags) + ); + } + if (le16toh(hdr.packet_type) != LGTD_LIFX_ECHO_REQUEST) { + lgtd_errx( + 1, "packet_type = %hx (expected = %#x)", + le16toh(hdr.packet_type), LGTD_LIFX_ECHO_REQUEST + ); + } + + lgtd_lifx_wire_decode_header(&hdr); + + int proto_version = hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; + if (proto_version != LGTD_LIFX_PROTOCOL_V1) { + lgtd_errx( + 1, "protocol version = %d (expected %d)", + proto_version, LGTD_LIFX_PROTOCOL_V1 + ); + } + if (!(hdr.protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE)) { + lgtd_errx(1, "the protocol addressable bit should be set"); + } + if (!(hdr.protocol & LGTD_LIFX_PROTOCOL_TAGGED)) { + lgtd_errx(1, "the protocol tagged bit should be set"); + } + if (hdr.size != 42) { + lgtd_errx(1, "size = %hu (expected = 42)", hdr.size); + } + if (hdr.target.tags != 0xbad) { + lgtd_errx( + 1, "tags = %#jx (expected = 0xbad)", (uintmax_t)hdr.target.tags + ); + } + if (hdr.packet_type != LGTD_LIFX_ECHO_REQUEST) { + lgtd_errx( + 1, "packet_type = %hx (expected = %#x)", + hdr.packet_type, LGTD_LIFX_ECHO_REQUEST + ); + } + + memset(&hdr, 0, sizeof(hdr)); + hdr.size = 42; + hdr.target.device_addr[2] = 44; + hdr.packet_type = LGTD_LIFX_ECHO_REQUEST; + lgtd_lifx_wire_encode_header(&hdr, LGTD_LIFX_ADDRESSABLE); + + if (htobe16(hdr.protocol) != 0x14) { + lgtd_errx(1, "protocol = %#hx (expected = 0x14)", hdr.protocol); + } + if (le16toh(hdr.size) != 42) { + lgtd_errx(1, "size = %hu (expected = 42)", le16toh(hdr.size)); + } + uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { + 0, 0, 44, 0, 0, 0 + }; + if (memcmp(hdr.target.device_addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { + lgtd_errx( + 1, "device addr = %s (expected = %s)", + lgtd_addrtoa(hdr.target.device_addr), lgtd_addrtoa(expected_addr) + ); + } + if (le16toh(hdr.packet_type) != LGTD_LIFX_ECHO_REQUEST) { + lgtd_errx( + 1, "packet_type = %#hx (expected = %#x)", + le16toh(hdr.packet_type), LGTD_LIFX_ECHO_REQUEST + ); + } + + lgtd_lifx_wire_decode_header(&hdr); + + proto_version = hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; + if (proto_version != LGTD_LIFX_PROTOCOL_V1) { + lgtd_errx( + 1, "protocol version = %d (expected %d)", + proto_version, LGTD_LIFX_PROTOCOL_V1 + ); + } + if (!(hdr.protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE)) { + lgtd_errx(1, "the protocol addressable bit should be set"); + } + if (hdr.protocol & LGTD_LIFX_PROTOCOL_TAGGED) { + lgtd_errx(1, "the protocol tagged bit should not be set"); + } + if (memcmp(hdr.target.device_addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { + lgtd_errx( + 1, "device addr = %s (expected = %s)", + lgtd_addrtoa(hdr.target.device_addr), lgtd_addrtoa(expected_addr) + ); + } + if (hdr.size != 42) { + lgtd_errx(1, "size = %hu (expected = 42)", hdr.size); + } + if (hdr.packet_type != LGTD_LIFX_ECHO_REQUEST) { + lgtd_errx( + 1, "packet_type = %#hx (expected = %#x)", + hdr.packet_type, LGTD_LIFX_ECHO_REQUEST + ); + } + + return 0; +} From 4467da5f217b72a6f0f601d7b69d18a5138314e1 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 9 Jul 2015 00:35:39 -0700 Subject: [PATCH 047/181] Remove the list_tags command You can build the list of tags using get_light_state("*"). --- core/jsonrpc.c | 3 +- core/proto.c | 17 ---------- core/proto.h | 1 - tests/core/proto/test_proto_list_tags.c | 41 ------------------------- 4 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 tests/core/proto/test_proto_list_tags.c diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 281b168..922d709 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -973,8 +973,7 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) LGTD_JSONRPC_METHOD( "get_light_state", 1, // t lgtd_jsonrpc_check_and_call_get_light_state - ), - LGTD_JSONRPC_METHOD("list_tags", 0, lgtd_proto_list_tags), + ) }; assert(client); diff --git a/core/proto.c b/core/proto.c index 4b988ea..6ac9d18 100644 --- a/core/proto.c +++ b/core/proto.c @@ -150,23 +150,6 @@ lgtd_proto_set_waveform(struct lgtd_client *client, ); } -void -lgtd_proto_list_tags(struct lgtd_client *client) -{ - lgtd_client_start_send_response(client); - - LGTD_CLIENT_WRITE_STRING(client, "["); - struct lgtd_lifx_tag *tag; - LIST_FOREACH(tag, &lgtd_lifx_tags, link) { - LGTD_CLIENT_WRITE_STRING(client, "\""); - LGTD_CLIENT_WRITE_STRING(client, tag->label); - LGTD_CLIENT_WRITE_STRING(client, LIST_NEXT(tag, link) ? "\"," : "\""); - } - LGTD_CLIENT_WRITE_STRING(client, "]"); - - lgtd_client_end_send_response(client); -} - void lgtd_proto_get_light_state(struct lgtd_client *client, const struct lgtd_proto_target_list *targets) diff --git a/core/proto.h b/core/proto.h index 9c164d0..aaaf2ca 100644 --- a/core/proto.h +++ b/core/proto.h @@ -39,4 +39,3 @@ void lgtd_proto_set_waveform(struct lgtd_client *, void lgtd_proto_power_on(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_power_off(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_get_light_state(struct lgtd_client *, const struct lgtd_proto_target_list *); -void lgtd_proto_list_tags(struct lgtd_client *); diff --git a/tests/core/proto/test_proto_list_tags.c b/tests/core/proto/test_proto_list_tags.c deleted file mode 100644 index eb5c5f0..0000000 --- a/tests/core/proto/test_proto_list_tags.c +++ /dev/null @@ -1,41 +0,0 @@ -#include "proto.c" - -#include "mock_client_buf.h" -#include "tests_utils.h" -#include "tests_proto_utils.h" - -int -main(void) -{ - struct lgtd_client client; - - lgtd_proto_list_tags(&client); - if (strcmp(client_write_buf, "[]")) { - errx( - 1, "expected empty list of tags but got %s instead", - client_write_buf - ); - } - - reset_client_write_buf(); - lgtd_tests_insert_mock_tag("test"); - lgtd_proto_list_tags(&client); - if (strcmp(client_write_buf, "[\"test\"]")) { - errx( - 1, "expected [\"test\"] but got %s instead", - client_write_buf - ); - } - - reset_client_write_buf(); - lgtd_tests_insert_mock_tag("@_@"); - lgtd_proto_list_tags(&client); - if (strcmp(client_write_buf, "[\"@_@\",\"test\"]")) { - errx( - 1, "expected [\"@_@\",\"test\"] but got %s instead", - client_write_buf - ); - } - - return 0; -} From 64520f0b96d0471836f75a5f083ba109f0a61e4b Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 9 Jul 2015 00:35:39 -0700 Subject: [PATCH 048/181] Fix endian conversion in lifx_wire_encode_waveform --- lifx/wire_proto.c | 2 +- tests/core/proto/test_proto_set_waveform.c | 2 +- tests/core/proto/test_proto_set_waveform_on_routing_error.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 2877252..81c443d 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -350,7 +350,7 @@ lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) pkt->saturation = htole16(pkt->saturation); pkt->brightness = htole16(pkt->brightness); pkt->kelvin = htole16(pkt->kelvin); - pkt->period = htole16(pkt->period); + pkt->period = htole32(pkt->period); pkt->skew_ratio = htole16(pkt->skew_ratio); } diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index 9e675c5..3d397b2 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -34,7 +34,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, int saturation = le16toh(waveform->saturation); int brightness = le16toh(waveform->brightness); int kelvin = le16toh(waveform->kelvin); - int period = le16toh(waveform->period); + int period = le32toh(waveform->period); float cycles = waveform->cycles; int skew_ratio = le16toh(waveform->skew_ratio); diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index d0d9c5f..032b861 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -34,7 +34,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, int saturation = le16toh(waveform->saturation); int brightness = le16toh(waveform->brightness); int kelvin = le16toh(waveform->kelvin); - int period = le16toh(waveform->period); + int period = le32toh(waveform->period); float cycles = waveform->cycles; int skew_ratio = le16toh(waveform->skew_ratio); From 5fdf0544baab80d9561c769a14e1cced8158c6f7 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 9 Jul 2015 22:41:06 -0700 Subject: [PATCH 049/181] Fix set_waveform on big endian systems floats need to be converted to little endian too... --- lifx/wire_proto.c | 1 + lifx/wire_proto.h | 17 ++++++++++++++++- tests/core/proto/test_proto_set_waveform.c | 2 +- .../test_proto_set_waveform_on_routing_error.c | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 81c443d..1862ce9 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -351,6 +351,7 @@ lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) pkt->brightness = htole16(pkt->brightness); pkt->kelvin = htole16(pkt->kelvin); pkt->period = htole32(pkt->period); + pkt->cycles = lifx_wire_htolefloat(pkt->cycles); pkt->skew_ratio = htole16(pkt->skew_ratio); } diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 6896a4f..85babf6 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -23,6 +23,21 @@ typedef uint32_t uint32le_t; typedef uint32_t uint32be_t; typedef uint64_t uint64le_t; typedef uint64_t uint64be_t; +typedef float floatle_t; + +static inline floatle_t +lifx_wire_htolefloat(float f) +{ + *(uint32_t *)&f = htole32(*(uint32_t *)&f); + return f; +} + +static inline floatle_t +lifx_wire_lefloattoh(float f) +{ + *(uint32_t *)&f = le32toh(*(uint32_t *)&f); + return f; +} enum { LGTD_LIFX_PROTOCOL_PORT = 56700 }; @@ -217,7 +232,7 @@ struct lgtd_lifx_packet_waveform { uint16le_t brightness; uint16le_t kelvin; uint32le_t period; // milliseconds - float cycles; // yes, this value is really encoded as a float. + floatle_t cycles; // yes, this value is really encoded as a float. uint16le_t skew_ratio; uint8_t waveform; // see enum lgtd_lifx_waveform_type }; diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index 3d397b2..be863ab 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -35,7 +35,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, int brightness = le16toh(waveform->brightness); int kelvin = le16toh(waveform->kelvin); int period = le32toh(waveform->period); - float cycles = waveform->cycles; + float cycles = lifx_wire_lefloattoh(waveform->cycles); int skew_ratio = le16toh(waveform->skew_ratio); if (waveform_type != LGTD_LIFX_WAVEFORM_SAW) { diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index 032b861..13c9b93 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -35,7 +35,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, int brightness = le16toh(waveform->brightness); int kelvin = le16toh(waveform->kelvin); int period = le32toh(waveform->period); - float cycles = waveform->cycles; + float cycles = lifx_wire_lefloattoh(waveform->cycles); int skew_ratio = le16toh(waveform->skew_ratio); if (waveform_type != LGTD_LIFX_WAVEFORM_SAW) { From 0ceb8d840996acc8fa82feeccda731e25c0d1a9f Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 15 Jul 2015 23:49:24 -0700 Subject: [PATCH 050/181] Fix incorrect values returned in get_light_state Derp, I still can't do basic maths. --- core/jsonrpc.c | 27 ++++++++++++ ...est_jsonrpc_uint16_range_to_float_string.c | 41 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 922d709..d7d1582 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -230,16 +230,42 @@ lgtd_jsonrpc_uint16_range_to_float_string(uint16_t encoded, int start, int stop, assert(size > 1); assert(start < stop); + if (size < 2) { + if (size) { + *out = '\0'; + } + return; + } + int range; range = stop * LGTD_JSONRPC_FLOAT_PREC - start * LGTD_JSONRPC_FLOAT_PREC; int value = (uint64_t)encoded * (uint64_t)range / UINT16_MAX; + if (!value) { + out[0] = '0'; + out[1] = '\0'; + return; + } + int multiplier = 1; while (value / (multiplier * 10)) { multiplier *= 10; } int i = 0; + + if (LGTD_JSONRPC_FLOAT_PREC / 10 > multiplier) { + out[i++] = '0'; + if (i != size) { + out[i++] = '.'; + } + for (int divider = 10; + LGTD_JSONRPC_FLOAT_PREC / divider > multiplier && i != size; + divider *= 10) { + out[i++] = '0'; + } + } + do { if (multiplier == LGTD_JSONRPC_FLOAT_PREC / 10) { if (i == 0) { @@ -256,6 +282,7 @@ lgtd_jsonrpc_uint16_range_to_float_string(uint16_t encoded, int start, int stop, multiplier /= 10; } while ((value || multiplier >= LGTD_JSONRPC_FLOAT_PREC) && multiplier && i != size); + out[LGTD_MIN(i, size - 1)] = '\0'; assert(i <= size); diff --git a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c index d3b5f14..7cad76d 100644 --- a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c +++ b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c @@ -1,3 +1,5 @@ +#define NDEBUG 1 + #include "jsonrpc.c" #include "mock_client_buf.h" @@ -17,6 +19,17 @@ main(void) ); } + lgtd_jsonrpc_uint16_range_to_float_string( + UINT16_MAX / 2 + UINT16_MAX / 3, 0, 1, buf, bufsz + ); + if (strcmp(buf, "0.833325")) { + lgtd_errx( + 1, + "UINT16_MAX / 2 + UINT16_MAX / 3 converted to %.*s (expected %s)", + bufsz, buf, "0.499992" + ); + } + lgtd_jsonrpc_uint16_range_to_float_string(UINT16_MAX / 2, 0, 1, buf, bufsz); if (strcmp(buf, "0.499992")) { lgtd_errx( @@ -25,6 +38,22 @@ main(void) ); } + lgtd_jsonrpc_uint16_range_to_float_string(UINT16_MAX / 10, 0, 1, buf, bufsz); + if (strcmp(buf, "0.099992")) { + lgtd_errx( + 1, "UINT16_MAX / 10 converted to %.*s (expected %s)", + bufsz, buf, "0.499992" + ); + } + + lgtd_jsonrpc_uint16_range_to_float_string(UINT16_MAX / 100, 0, 1, buf, bufsz); + if (strcmp(buf, "0.009994")) { + lgtd_errx( + 1, "UINT16_MAX / 100 converted to %.*s (expected %s)", + bufsz, buf, "0.499992" + ); + } + lgtd_jsonrpc_uint16_range_to_float_string(0, 0, 1, buf, bufsz); if (strcmp(buf, "0")) { lgtd_errx( @@ -52,5 +81,17 @@ main(void) ); } + bufsz = 1; + lgtd_jsonrpc_uint16_range_to_float_string(UINT16_MAX / 2, 0, 1, buf, bufsz); + if (buf[0]) { + lgtd_errx(1, "buffer of one should be '\\0'"); + } + + buf[0] = 'A'; + lgtd_jsonrpc_uint16_range_to_float_string(UINT16_MAX / 2, 0, 1, buf, 0); + if (buf[0] != 'A') { + lgtd_errx(1, "buffer of zero shouldn't be written to"); + } + return 0; } From 9e44070cd88ff987098630e09972f02319fc042a Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 15 Jul 2015 23:49:25 -0700 Subject: [PATCH 051/181] Update README with upcoming features and spell functions correctly --- README.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ed1ca97..82b2736 100644 --- a/README.rst +++ b/README.rst @@ -28,7 +28,13 @@ following commands through a JSON-RPC_ interface: - power_on; - set_light_from_hsbk; - set_waveform (change the light according to a function like SAW or SINE); -- get_light_status. +- get_light_state; +- tag/untag (group/ungroup bulbs together, coming up: need unit & regression + tests); +- toggle (power on if off and vice-versa, coming up). + +The JSON-RPC interface works on top on IPv4/v6, over a command (named) pipe +(coming up) and Unix sockets (coming up). lightsd can target single or multiple bulbs at once: From 954cf35ddeccba99f23f50a02ccc64a22044213e Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 15 Jul 2015 23:49:25 -0700 Subject: [PATCH 052/181] Fix build warnings on Mac OS X --- core/client.c | 1 + core/jsonrpc.c | 1 + core/lightsd.c | 1 + core/log.c | 1 + core/proto.c | 1 + lifx/broadcast.c | 1 + lifx/bulb.c | 1 + lifx/timer.c | 1 + tests/core/tests_shims.c | 1 + 9 files changed, 9 insertions(+) diff --git a/core/client.c b/core/client.c index 83583c6..973f70e 100644 --- a/core/client.c +++ b/core/client.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/core/jsonrpc.c b/core/jsonrpc.c index d7d1582..a1d2244 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/core/lightsd.c b/core/lightsd.c index 2397932..00351cc 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/core/log.c b/core/log.c index 8c9fefa..f36b788 100644 --- a/core/log.c +++ b/core/log.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/core/proto.c b/core/proto.c index 6ac9d18..aacf82a 100644 --- a/core/proto.c +++ b/core/proto.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/lifx/broadcast.c b/lifx/broadcast.c index 9eeb926..0abff22 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/lifx/bulb.c b/lifx/bulb.c index 1492bdc..141ef66 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/lifx/timer.c b/lifx/timer.c index 2aec8b3..c5c9376 100644 --- a/lifx/timer.c +++ b/lifx/timer.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c index 23ef18c..6f40467 100644 --- a/tests/core/tests_shims.c +++ b/tests/core/tests_shims.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include From 20760d998d83c83104f5ee3993ebd2e1c5a6b834 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 01:42:23 -0700 Subject: [PATCH 053/181] Allow duplicate listening address in the command line --- core/listen.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/listen.c b/core/listen.c index 773c2fe..9f1ee10 100644 --- a/core/listen.c +++ b/core/listen.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -76,6 +77,13 @@ lgtd_listen_open(const char *addr, const char *port) assert(addr); assert(port); + struct lgtd_listen *listener; + SLIST_FOREACH(listener, &lgtd_listeners, link) { + if (!strcmp(listener->addr, addr) && listener->port == port) { + return true; + } + } + struct evutil_addrinfo *res = NULL, hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, @@ -91,7 +99,6 @@ lgtd_listen_open(const char *addr, const char *port) return false; } - struct lgtd_listen *listener; struct evconnlistener *evlistener; for (struct evutil_addrinfo *it = res; it; it = it->ai_next) { evlistener = NULL; From 05df13ade57dbfc928da4a87030c2607b3a235d5 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 01:43:38 -0700 Subject: [PATCH 054/181] Add support for a write-only command pipe for easy scripting You can just dump simple JSON-RPC calls in it, it will pair nicely with the toggle command that I'm gonna implement. --- CMakeLists.txt | 2 + README.rst | 4 +- core/CMakeLists.txt | 1 + core/client.c | 34 +++ core/client.h | 7 +- core/jsonrpc.c | 30 +- core/lightsd.c | 21 +- core/pipe.c | 248 +++++++++++++++++ core/pipe.h | 33 +++ core/proto.c | 14 +- tests/lightsc => examples/lightsc.py | 0 examples/lightsc.sh | 72 +++++ tests/core/CMakeLists.txt | 2 + tests/core/jsonrpc/test_jsonrpc_utils.h | 2 + tests/core/mock_client_buf.h | 12 + tests/core/mock_event2.h | 109 ++++++++ tests/core/pipe/CMakeLists.txt | 26 ++ tests/core/pipe/test_pipe_close.c | 116 ++++++++ tests/core/pipe/test_pipe_open.c | 170 ++++++++++++ .../pipe/test_pipe_open_fifo_already_exists.c | 175 ++++++++++++ tests/core/pipe/test_pipe_read_callback.c | 191 +++++++++++++ .../pipe/test_pipe_read_callback_extra_data.c | 218 +++++++++++++++ ...est_pipe_read_callback_multiple_requests.c | 258 ++++++++++++++++++ tests/core/pipe/tests_pipe_utils.h | 19 ++ tests/core/proto/CMakeLists.txt | 5 +- tests/core/proto/test_proto_get_light_state.c | 10 +- ..._proto_get_light_state_empty_device_list.c | 4 +- ...t_proto_get_light_state_null_device_list.c | 2 +- tests/core/proto/test_proto_power_off.c | 2 +- .../test_proto_power_off_routing_error.c | 2 +- tests/core/proto/test_proto_power_on.c | 2 +- .../proto/test_proto_power_on_routing_error.c | 2 +- tests/core/proto/tests_proto_utils.h | 6 +- tests/core/router/CMakeLists.txt | 1 + tests/core/router/tests_router_utils.h | 2 + tests/core/tests_shims.c | 49 ---- tests/core/tests_utils.c | 47 +++- tests/core/tests_utils.h | 3 + tests/lifx/mock_gateway.h | 119 ++++++++ 39 files changed, 1924 insertions(+), 96 deletions(-) create mode 100644 core/pipe.c create mode 100644 core/pipe.h rename tests/lightsc => examples/lightsc.py (100%) create mode 100644 examples/lightsc.sh create mode 100644 tests/core/mock_event2.h create mode 100644 tests/core/pipe/CMakeLists.txt create mode 100644 tests/core/pipe/test_pipe_close.c create mode 100644 tests/core/pipe/test_pipe_open.c create mode 100644 tests/core/pipe/test_pipe_open_fifo_already_exists.c create mode 100644 tests/core/pipe/test_pipe_read_callback.c create mode 100644 tests/core/pipe/test_pipe_read_callback_extra_data.c create mode 100644 tests/core/pipe/test_pipe_read_callback_multiple_requests.c create mode 100644 tests/core/pipe/tests_pipe_utils.h create mode 100644 tests/lifx/mock_gateway.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b42f3a4..ce5f320 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,8 @@ ADD_DEFINITIONS( "-D_POSIX_C_SOURCE=200809L" "-D_BSD_SOURCE=1" "-D_DEFAULT_SOURCE=1" + + "-D_DARWIN_C_SOURCE=1" ) IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") diff --git a/README.rst b/README.rst index 82b2736..66828ba 100644 --- a/README.rst +++ b/README.rst @@ -33,8 +33,8 @@ following commands through a JSON-RPC_ interface: tests); - toggle (power on if off and vice-versa, coming up). -The JSON-RPC interface works on top on IPv4/v6, over a command (named) pipe -(coming up) and Unix sockets (coming up). +The JSON-RPC interface works on top of TCP/IPv4/v6, Unix sockets (coming up) or +over a command pipe (named pipe, see mkfifo(1)). lightsd can target single or multiple bulbs at once: diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 6d8de3b..0d9f39b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -20,6 +20,7 @@ ADD_EXECUTABLE( listen.c lightsd.c log.c + pipe.c proto.c router.c ) diff --git a/core/client.c b/core/client.c index 973f70e..cc369e4 100644 --- a/core/client.c +++ b/core/client.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -137,6 +138,29 @@ lgtd_client_event_callback(struct bufferevent *bev, short events, void *ctx) } } +void +lgtd_client_write_string(struct lgtd_client *client, const char *msg) +{ + assert(client); + assert(msg); + + if (client->io) { + bufferevent_write(client->io, msg, strlen(msg)); + } +} + +void +lgtd_client_write_buf(struct lgtd_client *client, const char *buf, int bufsz) +{ + assert(client); + assert(buf); + assert(bufsz >= 0); + + if (bufsz > 0 && client->io) { + bufferevent_write(client->io, buf, bufsz); + } +} + void lgtd_client_send_response(struct lgtd_client *client, const char *msg) { @@ -195,3 +219,13 @@ lgtd_client_open(evutil_socket_t peer, const struct sockaddr_storage *peer_addr) return client; } + +void +lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) +{ + assert(pipe_client); + + memset(pipe_client, 0, sizeof(*pipe_client)); + + jsmn_init(&pipe_client->jsmn_ctx); +} diff --git a/core/client.h b/core/client.h index 57a1efb..2fad890 100644 --- a/core/client.h +++ b/core/client.h @@ -42,13 +42,12 @@ struct lgtd_client { }; LIST_HEAD(lgtd_client_list, lgtd_client); -#define LGTD_CLIENT_WRITE_STRING(client, s) do { \ - bufferevent_write((client)->io, s, strlen((s))); \ -} while(0) - struct lgtd_client *lgtd_client_open(evutil_socket_t, const struct sockaddr_storage *); +void lgtd_client_open_from_pipe(struct lgtd_client *); void lgtd_client_close_all(void); +void lgtd_client_write_string(struct lgtd_client *, const char *); +void lgtd_client_write_buf(struct lgtd_client *, const char *, int); void lgtd_client_send_response(struct lgtd_client *, const char *); void lgtd_client_start_send_response(struct lgtd_client *); void lgtd_client_end_send_response(struct lgtd_client *); diff --git a/core/jsonrpc.c b/core/jsonrpc.c index a1d2244..6ef8ddc 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -480,7 +480,7 @@ static void lgtd_jsonrpc_write_id(struct lgtd_client *client) { if (!client->current_request->id) { - LGTD_CLIENT_WRITE_STRING(client, "null"); + lgtd_client_write_string(client, "null"); return; } @@ -492,7 +492,7 @@ lgtd_jsonrpc_write_id(struct lgtd_client *client) start = client->current_request->id->start; stop = client->current_request->id->end; } - bufferevent_write(client->io, &client->json[start], stop - start); + lgtd_client_write_buf(client, &client->json[start], stop - start); } void @@ -503,15 +503,15 @@ lgtd_jsonrpc_send_error(struct lgtd_client *client, assert(client); assert(message); - LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); + lgtd_client_write_string(client, "{\"jsonrpc\": \"2.0\", \"id\": "); lgtd_jsonrpc_write_id(client); - LGTD_CLIENT_WRITE_STRING(client, ", \"error\": {\"code\": "); + lgtd_client_write_string(client, ", \"error\": {\"code\": "); char str_code[8] = { 0 }; snprintf(str_code, sizeof(str_code), "%d", code); - LGTD_CLIENT_WRITE_STRING(client, str_code); - LGTD_CLIENT_WRITE_STRING(client, ", \"message\": \""); - LGTD_CLIENT_WRITE_STRING(client, message); - LGTD_CLIENT_WRITE_STRING(client, "\"}}"); + lgtd_client_write_string(client, str_code); + lgtd_client_write_string(client, ", \"message\": \""); + lgtd_client_write_string(client, message); + lgtd_client_write_string(client, "\"}}"); } void @@ -521,11 +521,11 @@ lgtd_jsonrpc_send_response(struct lgtd_client *client, assert(client); assert(result); - LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); + lgtd_client_write_string(client, "{\"jsonrpc\": \"2.0\", \"id\": "); lgtd_jsonrpc_write_id(client); - LGTD_CLIENT_WRITE_STRING(client, ", \"result\": "); - LGTD_CLIENT_WRITE_STRING(client, result); - LGTD_CLIENT_WRITE_STRING(client, "}"); + lgtd_client_write_string(client, ", \"result\": "); + lgtd_client_write_string(client, result); + lgtd_client_write_string(client, "}"); } void @@ -533,15 +533,15 @@ lgtd_jsonrpc_start_send_response(struct lgtd_client *client) { assert(client); - LGTD_CLIENT_WRITE_STRING(client, "{\"jsonrpc\": \"2.0\", \"id\": "); + lgtd_client_write_string(client, "{\"jsonrpc\": \"2.0\", \"id\": "); lgtd_jsonrpc_write_id(client); - LGTD_CLIENT_WRITE_STRING(client, ", \"result\": "); + lgtd_client_write_string(client, ", \"result\": "); } void lgtd_jsonrpc_end_send_response(struct lgtd_client *client) { - LGTD_CLIENT_WRITE_STRING(client, "}"); + lgtd_client_write_string(client, "}"); } static bool diff --git a/core/lightsd.c b/core/lightsd.c index 00351cc..81b54fa 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -45,6 +45,7 @@ #include "jsmn.h" #include "jsonrpc.h" #include "client.h" +#include "pipe.h" #include "listen.h" #include "lightsd.h" @@ -60,6 +61,7 @@ void lgtd_cleanup(void) { lgtd_listen_close_all(); + lgtd_command_pipe_close_all(); lgtd_client_close_all(); lgtd_lifx_timer_close(); lgtd_lifx_broadcast_close(); @@ -126,8 +128,14 @@ static void lgtd_usage(const char *progname) { printf( - "Usage: %s -l addr:port [-l ...] [-f] [-t] [-h] [-V] " - "[-v debug|info|warning|error]\n", + "Usage: %s ...\n\n" + " [-l,--listen addr:port [+]]\n" + " [-c,--comand-pipe /command/fifo [+]]\n" + " [-f,--foreground]\n" + " [-t,--no-timestamps]\n" + " [-h,--help]\n" + " [-V,--version]\n" + " [-v,--verbosity debug|info|warning|error]\n", progname ); exit(0); @@ -141,6 +149,7 @@ main(int argc, char *argv[]) static const struct option long_opts[] = { {"listen", required_argument, NULL, 'l'}, + {"command-pipe", required_argument, NULL, 'c'}, {"foreground", no_argument, NULL, 'f'}, {"no-timestamps", no_argument, NULL, 't'}, {"help", no_argument, NULL, 'h'}, @@ -148,7 +157,7 @@ main(int argc, char *argv[]) {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} }; - const char short_opts[] = "l:fthv:V"; + const char short_opts[] = "l:c:fthv:V"; for (int rv = getopt_long(argc, argv, short_opts, long_opts, NULL); rv != -1; @@ -164,6 +173,12 @@ main(int argc, char *argv[]) if (!lgtd_listen_open(optarg, sep + 1)) { exit(1); } + break; + case 'c': + if (!lgtd_command_pipe_open(optarg)) { + exit(1); + } + break; case 'f': lgtd_opts.foreground = true; break; diff --git a/core/pipe.c b/core/pipe.c new file mode 100644 index 0000000..edcf93d --- /dev/null +++ b/core/pipe.c @@ -0,0 +1,248 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "jsmn.h" +#include "jsonrpc.h" +#include "client.h" +#include "pipe.h" +#include "lightsd.h" + +struct lgtd_command_pipe_list lgtd_command_pipes = + SLIST_HEAD_INITIALIZER(&lgtd_command_pipes); + +static void +lgtd_command_pipe_close(struct lgtd_command_pipe *pipe) +{ + assert(pipe); + + event_del(pipe->read_ev); + if (pipe->fd != -1) { + close(pipe->fd); + } + unlink(pipe->path); + SLIST_REMOVE(&lgtd_command_pipes, pipe, lgtd_command_pipe, link); + evbuffer_free(pipe->read_buf); + event_free(pipe->read_ev); + + lgtd_info("closed command pipe %s", pipe->path); + free(pipe); +} + +static void +lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) +{ + assert(ctx); + assert(socket != -1); + + (void)socket; + (void)events; + + struct lgtd_command_pipe *pipe = ctx; + + bool drain = false; + int read = 0; + for (int nbytes = evbuffer_read(pipe->read_buf, pipe->fd, -1); + nbytes; + nbytes = evbuffer_read(pipe->read_buf, pipe->fd, -1)) { + if (nbytes == -1) { + if (errno == EINTR) { + continue; + } + if (errno != EAGAIN) { + lgtd_warn("read error on command pipe %s", pipe->path); + const char *path = pipe->path; + lgtd_command_pipe_close(pipe); + lgtd_command_pipe_open(path); + return; + } + continue; + } + + if (!drain) { + read += nbytes; + next_request: + (void)0; + const char *buf = (char *)evbuffer_pullup(pipe->read_buf, -1); + ssize_t bufsz = evbuffer_get_length(pipe->read_buf); + jsmnerr_t rv = jsmn_parse( + &pipe->client.jsmn_ctx, + buf, + bufsz, + pipe->client.jsmn_tokens, + LGTD_ARRAY_SIZE(pipe->client.jsmn_tokens) + ); + switch (rv) { + case JSMN_ERROR_NOMEM: + case JSMN_ERROR_INVAL: + lgtd_warnx("pipe %s: request too big or invalid", pipe->path); + // ignore what's left + drain = true; + break; + case JSMN_ERROR_PART: +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + case 0: +#pragma GCC diagnostic pop + if (bufsz >= LGTD_CLIENT_MAX_REQUEST_BUF_SIZE) { + lgtd_warnx("pipe %s: request too big", pipe->path); + drain = true; + } + break; + default: + pipe->client.json = buf; + int ntokens = rv; + lgtd_jsonrpc_dispatch_request(&pipe->client, ntokens); + + pipe->client.json = NULL; + jsmn_init(&pipe->client.jsmn_ctx); + int request_size = pipe->client.jsmn_tokens[0].end; + evbuffer_drain(pipe->read_buf, request_size); + read -= request_size; + if (read) { + goto next_request; + } + break; + } + } else { + evbuffer_drain(pipe->read_buf, read + nbytes); + read = 0; + } + } + + if (read) { + lgtd_debug( + "pipe %s: discarding %d bytes of unusable data", pipe->path, read + ); + evbuffer_drain(pipe->read_buf, read); + } + jsmn_init(&pipe->client.jsmn_ctx); +} + +static mode_t +lgtd_command_pipe_get_umask(void) +{ + mode_t mask = umask(0); + umask(mask); + return mask; +} + +bool +lgtd_command_pipe_open(const char *path) +{ + assert(path); + + struct lgtd_command_pipe *pipe; + SLIST_FOREACH(pipe, &lgtd_command_pipes, link) { + if (!strcmp(pipe->path, path)) { + return true; + } + } + + pipe = calloc(1, sizeof(*pipe)); + if (!pipe) { + lgtd_warn("can't open command pipe %s", path); + return false; + } + + lgtd_client_open_from_pipe(&pipe->client); + pipe->path = path; + pipe->fd = -1; + + mode_t mode = S_IWUSR|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IWGRP; + if (mkfifo(path, mode)) { + if (errno != EEXIST) { + goto error; + } + mode &= ~lgtd_command_pipe_get_umask(); + if (chmod(path, mode)) { + goto error; + } + } + + pipe->fd = open(path, O_RDONLY|O_NONBLOCK); + if (pipe->fd == -1) { + goto error; + } + + if (evutil_make_socket_nonblocking(pipe->fd) == -1) { + goto error; + } + + pipe->read_ev = event_new( + lgtd_ev_base, + pipe->fd, + EV_READ|EV_PERSIST, + lgtd_command_pipe_read_callback, + pipe + ); + if (!pipe->read_ev) { + goto error; + } + + pipe->read_buf = evbuffer_new(); + if (!pipe->read_buf) { + goto error; + } + + if (event_add(pipe->read_ev, NULL)) { + goto error; + } + + lgtd_info("command pipe ready at %s", pipe->path); + + SLIST_INSERT_HEAD(&lgtd_command_pipes, pipe, link); + + return true; + +error: + lgtd_warn("can't open command pipe %s", path); + if (pipe->read_buf) { + evbuffer_free(pipe->read_buf); + } + if (pipe->read_ev) { + event_free(pipe->read_ev); + } + if (pipe->fd != -1) { + close(pipe->fd); + } + free(pipe); + return false; +} + +void +lgtd_command_pipe_close_all(void) +{ + while (!SLIST_EMPTY(&lgtd_command_pipes)) { + lgtd_command_pipe_close(SLIST_FIRST(&lgtd_command_pipes)); + } +} diff --git a/core/pipe.h b/core/pipe.h new file mode 100644 index 0000000..4e5ca57 --- /dev/null +++ b/core/pipe.h @@ -0,0 +1,33 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#pragma once + +struct lgtd_command_pipe { + SLIST_ENTRY(lgtd_command_pipe) link; + const char *path; + int fd; + struct event *read_ev; + struct evbuffer *read_buf; + struct lgtd_client client; +}; +SLIST_HEAD(lgtd_command_pipe_list, lgtd_command_pipe); + +extern struct lgtd_command_pipe_list lgtd_command_pipes; + +bool lgtd_command_pipe_open(const char *); +void lgtd_command_pipe_close_all(void); diff --git a/core/proto.c b/core/proto.c index aacf82a..98fa494 100644 --- a/core/proto.c +++ b/core/proto.c @@ -178,7 +178,7 @@ lgtd_proto_get_light_state(struct lgtd_client *client, ) lgtd_client_start_send_response(client); - LGTD_CLIENT_WRITE_STRING(client, "["); + lgtd_client_write_string(client, "["); struct lgtd_router_device *device; SLIST_FOREACH(device, devices, link) { struct lgtd_lifx_bulb *bulb = device->device; @@ -204,22 +204,22 @@ lgtd_proto_get_light_state(struct lgtd_client *client, ); continue; } - LGTD_CLIENT_WRITE_STRING(client, buf); + lgtd_client_write_string(client, buf); bool comma = false; int tag_id; LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, bulb->state.tags) { - LGTD_CLIENT_WRITE_STRING(client, comma ? ",\"" : "\""); - LGTD_CLIENT_WRITE_STRING(client, bulb->gw->tags[tag_id]->label); - LGTD_CLIENT_WRITE_STRING(client, "\""); + lgtd_client_write_string(client, comma ? ",\"" : "\""); + lgtd_client_write_string(client, bulb->gw->tags[tag_id]->label); + lgtd_client_write_string(client, "\""); comma = true; } - LGTD_CLIENT_WRITE_STRING( + lgtd_client_write_string( client, SLIST_NEXT(device, link) ? "]}," : "]}" ); } - LGTD_CLIENT_WRITE_STRING(client, "]"); + lgtd_client_write_string(client, "]"); lgtd_client_end_send_response(client); lgtd_router_device_list_free(devices); diff --git a/tests/lightsc b/examples/lightsc.py similarity index 100% rename from tests/lightsc rename to examples/lightsc.py diff --git a/examples/lightsc.sh b/examples/lightsc.sh new file mode 100644 index 0000000..315e7a5 --- /dev/null +++ b/examples/lightsc.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +# Here is an example script that dims bulbs to a warm orange: + +# #!/bin/sh +# +# # Optional (default value: /run/lightsd.cmd): +# COMMAND_PIPE=/foo/bar/lightsd.cmd +# +# . /usr/lib/lightsd/lightsc.sh +# +# lightsc set_light_from_hsbk ${*:-'"*"'} 37.469443 1.0 0.05 3500 600 + +# Here is how you could use it: +# +# - dim all the bulbs: orange +# - dim the bulb named kitchen: orange '"kitchen"' +# - dim the bulb named kitchen and the bulbs tagged bedroom: +# orange '["kitchen", "#bedroom"]' +# +# You can also load this file directly in your shell rc configuration file. +# +# NOTE: Keep in mind that arguments must be JSON, you will have to enclose +# tags and labels into double quotes '"likethis"'. Also keep in mind +# that the pipe is write-only you cannot read any result back. + +_b64e() { + if type base64 >/dev/null 2>&1 ; then + base64 + elif type b64encode >/dev/null 2>&1 ; then + b64encode + else + cat >/dev/null + echo null + fi +} + +_gen_request_id() { + if type dd >/dev/null 2>&1 ; then + printf '"%s"' `dd if=/dev/urandom bs=8 count=1 2>&- | _b64e` + else + echo null + fi +} + +lightsc() { + if [ $# -lt 2 ] ; then + echo >&2 "Usage: $0 METHOD PARAMS ..." + return 1 + fi + + local pipe=${COMMAND_PIPE:-/run/lightsd.cmd} + if [ ! -p $pipe ] ; then + echo >&2 "$pipe cannot be found, is lightsd running?" + return 1 + fi + + local method=$1 ; shift + local params=$1 ; shift + for target in $* ; do + params=$params,$target + done + + tee $pipe <io, msg, strlen(msg)); +} + +void +lgtd_client_write_buf(struct lgtd_client *client, const char *buf, int bufsz) +{ + bufferevent_write(client->io, buf, bufsz); +} diff --git a/tests/core/mock_event2.h b/tests/core/mock_event2.h new file mode 100644 index 0000000..42b2c00 --- /dev/null +++ b/tests/core/mock_event2.h @@ -0,0 +1,109 @@ +#pragma once + +#ifndef MOCKED_EVBUFFER_DRAIN +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + (void)buf; + (void)len; + return 0; +} +#endif + +#ifndef MOCKED_EVBUFFER_NEW +struct evbuffer * +evbuffer_new(void) +{ + return NULL; +} +#endif + +#ifndef MOCKED_EVENT_FREE +void +evbuffer_free(struct evbuffer *buf) +{ + (void)buf; +} +#endif + +#ifndef MOCKED_EVBUFFER_GET_LENGTH +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + (void)buf; + return 0; +} +#endif + +#ifndef MOCKED_EVBUFFER_PULLUP +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + (void)buf; + (void)size; + return NULL; +} +#endif + +#ifndef MOCKED_EVBUFFER_READ +int +evbuffer_read(struct evbuffer *buffer, evutil_socket_t fd, int howmuch) +{ + (void)buffer; + (void)fd; + return howmuch; +} +#endif + +#ifndef MOCKED_EVENT_ADD +int +event_add(struct event *ev, const struct timeval *timeout) +{ + (void)ev; + (void)timeout; + return 0; +} +#endif + +#ifndef MOCKED_EVENT_DEL +int +event_del(struct event *ev) +{ + (void)ev; + return 0; +} +#endif + +#ifndef MOCKED_EVENT_FREE +void +event_free(struct event *ev) +{ + (void)ev; +} +#endif + +#ifndef MOCKED_EVENT_NEW +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + (void)base; + (void)fd; + (void)events; + (void)cb; + (void)ctx; + return NULL; +} +#endif + +#ifndef MOCKED_EVUTIL_MAKE_SOCKET_NONBLOCKING +int +evutil_make_socket_nonblocking(evutil_socket_t fd) +{ + (void)fd; + return 0; +} +#endif diff --git a/tests/core/pipe/CMakeLists.txt b/tests/core/pipe/CMakeLists.txt new file mode 100644 index 0000000..7d310cb --- /dev/null +++ b/tests/core/pipe/CMakeLists.txt @@ -0,0 +1,26 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_LIBRARY( + test_core_pipe STATIC + ${LIGHTSD_SOURCE_DIR}/core/jsmn.c + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c + ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c +) + +TARGET_LINK_LIBRARIES(test_core_pipe ${TIME_MONOTONIC_LIBRARY}) + +FUNCTION(ADD_PIPE_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_pipe) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_PIPE_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/core/pipe/test_pipe_close.c b/tests/core/pipe/test_pipe_close.c new file mode 100644 index 0000000..2dcdca9 --- /dev/null +++ b/tests/core/pipe/test_pipe_close.c @@ -0,0 +1,116 @@ +#include "pipe.c" + +#include +#include +#include + +#include "lifx/wire_proto.h" + +#define MOCKED_EVENT_NEW +#define MOCKED_EVBUFFER_NEW +#define MOCKED_EVENT_DEL +#define MOCKED_EVBUFFER_FREE +#define MOCKED_EVENT_FREE +#include "mock_event2.h" +#include "mock_gateway.h" + +#include "tests_utils.h" +#include "tests_pipe_utils.h" + +char *tmpdir = NULL; + +void +cleanup_tmpdir(void) +{ + lgtd_tests_remove_temp_dir(tmpdir); +} + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + (void)base; + (void)fd; + (void)events; + (void)cb; + (void)ctx; + + return (void *)1; +} + +struct evbuffer * +evbuffer_new(void) +{ + return (void *)2; +} + +static int event_del_call_count = 0; + +int +event_del(struct event *ev) +{ + (void)ev; + + event_del_call_count++; + + return 0; +} + +static int evbuffer_free_call_count = 0; + +void +evbuffer_free(struct evbuffer *buf) +{ + (void)buf; + + evbuffer_free_call_count++; +} + +static int event_free_call_count = 0; + +void +event_free(struct event *ev) +{ + (void)ev; + + event_free_call_count++; +} + +int +main(void) +{ + tmpdir = lgtd_tests_make_temp_dir(); + atexit(cleanup_tmpdir); + + char path[PATH_MAX] = { 0 }; + snprintf(path, sizeof(path), "%s/lightsd.pipe", tmpdir); + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + + int pipe_fd = SLIST_FIRST(&lgtd_command_pipes)->fd; + + lgtd_command_pipe_close_all(); + + if (event_del_call_count != 1) { + errx(1, "event_del_call_count = %d", event_del_call_count); + } + if (evbuffer_free_call_count != 1) { + errx(1, "evbuffer_free_call_count = %d", evbuffer_free_call_count); + } + if (event_free_call_count != 1) { + errx(1, "event_free_call_count = %d", event_free_call_count); + } + struct stat sb; + if (fstat(pipe_fd, &sb) != -1 && errno != EBADF) { + errx(1, "the pipe file descriptor wasn't closed correctly"); + } + if (stat(path, &sb) != -1 && errno != ENOENT) { + errx(1, "the pipe wasn't removed correctly"); + } + + return 0; +} diff --git a/tests/core/pipe/test_pipe_open.c b/tests/core/pipe/test_pipe_open.c new file mode 100644 index 0000000..7b1de43 --- /dev/null +++ b/tests/core/pipe/test_pipe_open.c @@ -0,0 +1,170 @@ +#include "pipe.c" + +#include +#include +#include + +#include "lifx/wire_proto.h" + +#define MOCKED_EVUTIL_MAKE_SOCKET_NONBLOCKING +#define MOCKED_EVENT_NEW +#define MOCKED_EVBUFFER_NEW +#define MOCKED_EVENT_ADD +#include "mock_event2.h" +#include "mock_gateway.h" + +#include "tests_utils.h" +#define MOCKED_CLIENT_OPEN_FROM_PIPE +#include "tests_pipe_utils.h" + +char *tmpdir = NULL; + +void +cleanup_tmpdir(void) +{ + lgtd_tests_remove_temp_dir(tmpdir); +} + +static bool make_socket_nonblocking_call_count = 0; + +int +evutil_make_socket_nonblocking(evutil_socket_t fd) +{ + if (fd <= 0) { + errx(1, "got invalid fd %d in make_socket_nonblocking", fd); + } + + make_socket_nonblocking_call_count++; + + return 0; +} + +static int event_new_call_count = 0; + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + if (base != lgtd_ev_base) { + errx( + 1, "unexpected lgtd_ev_base = %p (expected %p)", + base, lgtd_ev_base + ); + } + if (fd <= 0) { + errx(1, "got invalid fd %d in event_new", fd); + } + if (events != (EV_READ|EV_PERSIST)) { + errx(1, "got events %#x (expected %#x)", events, EV_READ|EV_PERSIST); + } + if (cb != lgtd_command_pipe_read_callback) { + errx(1, "the read callback wasn't set correctly"); + } + if (!ctx) { + errx(1, "the callback context wasn't set correctly"); + } + + event_new_call_count++; + + return (void *)1; +} + +static int evbuffer_new_call_count = 0; + +struct evbuffer * +evbuffer_new(void) +{ + evbuffer_new_call_count++; + + return (void *)2; +} + +static int event_add_call_count = 0; + +int +event_add(struct event *ev, const struct timeval *timeout) +{ + if (ev != (void *)1) { + errx(1, "got unexpected event %p (expected %p)", ev, (void*)1); + } + + if (timeout) { + errx(1, "a timeout shouldn't have been passed"); + } + + event_add_call_count++; + + return 0; +} + +static int client_open_from_pipe_call_count = 0; + +void +lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) +{ + if (!pipe_client) { + errx(1, "missing pipe_client"); + } + + client_open_from_pipe_call_count++; +} + +int +main(void) +{ + tmpdir = lgtd_tests_make_temp_dir(); + atexit(cleanup_tmpdir); + + char path[PATH_MAX] = { 0 }; + snprintf(path, sizeof(path), "%s/lightsd.pipe", tmpdir); + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + + if (make_socket_nonblocking_call_count != 1) { + errx( + 1, "make_socket_nonblocking_call_count = %d", + make_socket_nonblocking_call_count + ); + } + if (event_new_call_count != 1) { + errx(1, "event_new_call_count = %d", event_new_call_count); + } + if (evbuffer_new_call_count != 1) { + errx(1, "evbuffer_new_call_count = %d", evbuffer_new_call_count); + } + if (event_add_call_count != 1) { + errx(1, "event_add_call_count = %d", event_add_call_count); + } + if (SLIST_EMPTY(&lgtd_command_pipes)) { + errx(1, "the list of command pipes shouldn't be empty"); + } + + struct stat sb; + if (stat(path, &sb)) { + errx(1, "can't stat pipe %s", path); + } + + mode_t expected_mode; + expected_mode = S_IFIFO|S_IWUSR|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IWGRP; + expected_mode &= ~lgtd_command_pipe_get_umask(); + if (sb.st_mode != expected_mode) { + errx( + 1, "unexpected mode %o (expected %o)", + sb.st_mode, expected_mode + ); + } + + // make sure it's idempotent: + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + if (event_new_call_count != 1) { + errx(1, "event_new_call_count = %d", event_new_call_count); + } + + return 0; +} diff --git a/tests/core/pipe/test_pipe_open_fifo_already_exists.c b/tests/core/pipe/test_pipe_open_fifo_already_exists.c new file mode 100644 index 0000000..79c5e7f --- /dev/null +++ b/tests/core/pipe/test_pipe_open_fifo_already_exists.c @@ -0,0 +1,175 @@ +#include "pipe.c" + +#include +#include +#include + +#include "lifx/wire_proto.h" + +#define MOCKED_EVUTIL_MAKE_SOCKET_NONBLOCKING +#define MOCKED_EVENT_NEW +#define MOCKED_EVBUFFER_NEW +#define MOCKED_EVENT_ADD +#include "mock_event2.h" +#include "mock_gateway.h" + +#include "tests_utils.h" +#define MOCKED_CLIENT_OPEN_FROM_PIPE +#include "tests_pipe_utils.h" + +char *tmpdir = NULL; + +void +cleanup_tmpdir(void) +{ + lgtd_tests_remove_temp_dir(tmpdir); +} + +static bool make_socket_nonblocking_call_count = 0; + +int +evutil_make_socket_nonblocking(evutil_socket_t fd) +{ + if (fd <= 0) { + errx(1, "got invalid fd %d in make_socket_nonblocking", fd); + } + + make_socket_nonblocking_call_count++; + + return 0; +} + +static int event_new_call_count = 0; + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + if (base != lgtd_ev_base) { + errx( + 1, "unexpected lgtd_ev_base = %p (expected %p)", + base, lgtd_ev_base + ); + } + if (fd <= 0) { + errx(1, "got invalid fd %d in event_new", fd); + } + if (events != (EV_READ|EV_PERSIST)) { + errx(1, "got events %#x (expected %#x)", events, EV_READ|EV_PERSIST); + } + if (cb != lgtd_command_pipe_read_callback) { + errx(1, "the read callback wasn't set correctly"); + } + if (!ctx) { + errx(1, "the callback context wasn't set correctly"); + } + + event_new_call_count++; + + return (void *)1; +} + +static int evbuffer_new_call_count = 0; + +struct evbuffer * +evbuffer_new(void) +{ + evbuffer_new_call_count++; + + return (void *)2; +} + +static int event_add_call_count = 0; + +int +event_add(struct event *ev, const struct timeval *timeout) +{ + if (ev != (void *)1) { + errx(1, "got unexpected event %p (expected %p)", ev, (void*)1); + } + + if (timeout) { + errx(1, "a timeout shouldn't have been passed"); + } + + event_add_call_count++; + + return 0; +} + +static int client_open_from_pipe_call_count = 0; + +void +lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) +{ + if (!pipe_client) { + errx(1, "missing pipe_client"); + } + + client_open_from_pipe_call_count++; +} + +int +main(void) +{ + tmpdir = lgtd_tests_make_temp_dir(); + atexit(cleanup_tmpdir); + + char path[PATH_MAX] = { 0 }; + snprintf(path, sizeof(path), "%s/lightsd.pipe", tmpdir); + + if (mkfifo(path, 0)) { + errx(1, "can't open fifo"); + } + + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + + if (make_socket_nonblocking_call_count != 1) { + errx( + 1, "make_socket_nonblocking_call_count = %d", + make_socket_nonblocking_call_count + ); + } + if (event_new_call_count != 1) { + errx(1, "event_new_call_count = %d", event_new_call_count); + } + if (evbuffer_new_call_count != 1) { + errx(1, "evbuffer_new_call_count = %d", evbuffer_new_call_count); + } + if (event_add_call_count != 1) { + errx(1, "event_add_call_count = %d", event_add_call_count); + } + if (SLIST_EMPTY(&lgtd_command_pipes)) { + errx(1, "the list of command pipes shouldn't be empty"); + } + + struct stat sb; + if (stat(path, &sb)) { + errx(1, "can't stat pipe %s", path); + } + + mode_t expected_mode; + expected_mode = S_IFIFO|S_IWUSR|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IWGRP; + expected_mode &= ~lgtd_command_pipe_get_umask(); + if (sb.st_mode != expected_mode) { + errx( + 1, "unexpected mode %o (expected %o)", + sb.st_mode, expected_mode + ); + } + + // make sure it's idempotent: + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + if (event_new_call_count != 1) { + errx(1, "event_new_call_count = %d", event_new_call_count); + } + + return 0; +} diff --git a/tests/core/pipe/test_pipe_read_callback.c b/tests/core/pipe/test_pipe_read_callback.c new file mode 100644 index 0000000..07bfbff --- /dev/null +++ b/tests/core/pipe/test_pipe_read_callback.c @@ -0,0 +1,191 @@ +#include "pipe.c" + +#include +#include +#include + +#include "lifx/wire_proto.h" + +#define MOCKED_EVENT_NEW +#define MOCKED_EVBUFFER_NEW +#define MOCKED_EVBUFFER_READ +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_GET_LENGTH +#define MOCKED_EVBUFFER_DRAIN +#include "mock_event2.h" +#include "mock_gateway.h" + +#include "tests_utils.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "tests_pipe_utils.h" + +static unsigned char request[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"get_light_state\"," + "\"params\": [\"*\"]," + "\"id\": 42" +"}"); + +static char *tmpdir = NULL; + +void +cleanup_tmpdir(void) +{ + lgtd_tests_remove_temp_dir(tmpdir); +} + +static int jsonrpc_dispatch_request_call_count = 0; + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + if (!parsed) { + errx(1, "number of parsed json tokens not passed in"); + } + + if (memcmp(client->json, request, sizeof(request))) { + errx(1, "got unexpected json"); + } + + jsonrpc_dispatch_request_call_count++; +} + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + (void)base; + (void)fd; + (void)events; + (void)cb; + (void)ctx; + + return (void *)1; +} + +static int +get_nbytes_read(int call_count) +{ + switch (call_count) { + case 0: + return sizeof(request) - 1; // we don't return the '\0' + default: + return 0; + } +} + +struct evbuffer * +evbuffer_new(void) +{ + return (void *)2; +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + jsmn_parser jsmn_ctx; + jsmn_init(&jsmn_ctx); + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { + errx(1, "the client json parser context wasn't re-initialized"); + } + + + switch (evbuffer_drain_call_count) { + case 0: + if (len != sizeof(request) - 1) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)sizeof(request) - 1 + ); + } + break; + default: + break; + } + evbuffer_drain_call_count++; + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + if (size != -1) { + errx(1, "got unexpected size %ld in pullup (expected -1)", size); + } + + return &request[evbuffer_pullup_call_count++ ? sizeof(request) - 1 : 0]; +} + +static int evbuffer_get_length_call_count = 0; + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + return get_nbytes_read(evbuffer_get_length_call_count++); +} + +static int evbuffer_read_call_count = 0; + +int +evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (fd != pipe->fd) { + errx(1, "got unexpected fd %d (expected %d)", fd, pipe->fd); + } + + if (howmuch != -1) { + errx( + 1, "got unexpected howmuch bytes to read %d (expected -1)", howmuch + ); + } + + return get_nbytes_read(evbuffer_read_call_count++); +} + +int +main(void) +{ + tmpdir = lgtd_tests_make_temp_dir(); + atexit(cleanup_tmpdir); + + char path[PATH_MAX] = { 0 }; + snprintf(path, sizeof(path), "%s/lightsd.pipe", tmpdir); + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + + lgtd_command_pipe_read_callback(pipe->fd, EV_READ, pipe); + + return 0; +} diff --git a/tests/core/pipe/test_pipe_read_callback_extra_data.c b/tests/core/pipe/test_pipe_read_callback_extra_data.c new file mode 100644 index 0000000..e8847ce --- /dev/null +++ b/tests/core/pipe/test_pipe_read_callback_extra_data.c @@ -0,0 +1,218 @@ +#include "pipe.c" + +#include +#include +#include + +#include "lifx/wire_proto.h" + +#define MOCKED_EVENT_NEW +#define MOCKED_EVBUFFER_NEW +#define MOCKED_EVBUFFER_READ +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_GET_LENGTH +#define MOCKED_EVBUFFER_DRAIN +#include "mock_event2.h" +#include "mock_gateway.h" + +#include "tests_utils.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "tests_pipe_utils.h" + +#define REQUEST_1 "{" \ + "\"jsonrpc\": \"2.0\"," \ + "\"method\": \"get_light_state\"," \ + "\"params\": [\"*\"]," \ + "\"id\": 42" \ +"}" +#define EXTRA_DATA "BLUBLBULBUBUHIFESHFUSsoundsaboutright" + +static unsigned char request[] = ( + REQUEST_1 + EXTRA_DATA +); + +static char *tmpdir = NULL; + +void +cleanup_tmpdir(void) +{ + lgtd_tests_remove_temp_dir(tmpdir); +} + +static int jsonrpc_dispatch_request_call_count = 0; + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + if (!parsed) { + errx(1, "number of parsed json tokens not passed in"); + } + + if (memcmp(client->json, request, sizeof(request))) { + errx(1, "got unexpected json"); + } + + jsonrpc_dispatch_request_call_count++; +} + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + (void)base; + (void)fd; + (void)events; + (void)cb; + (void)ctx; + + return (void *)1; +} + +static int +get_nbytes_read(int call_count) +{ + switch (call_count) { + case 0: + return sizeof(request) - 1; // we don't return the '\0' + default: + return 0; + } +} + +struct evbuffer * +evbuffer_new(void) +{ + return (void *)2; +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + jsmn_parser jsmn_ctx; + jsmn_init(&jsmn_ctx); + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { + errx(1, "the client json parser context wasn't re-initialized"); + } + + switch (evbuffer_drain_call_count) { + case 0: + if (len != sizeof(REQUEST_1) - 1) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)sizeof(request) - 1 + ); + } + break; + case 1: + if (len != sizeof(request) - sizeof(REQUEST_1)) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, sizeof(request) - sizeof(REQUEST_1) + ); + } + break; + default: + break; + } + evbuffer_drain_call_count++; + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + if (size != -1) { + errx(1, "got unexpected size %ld in pullup (expected -1)", size); + } + + return &request[evbuffer_pullup_call_count++ ? sizeof(request) - 1 : 0]; +} + +static int evbuffer_get_length_call_count = 0; + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + size_t len; + switch (evbuffer_get_length_call_count) { + case 0: + len = sizeof(request) - 1; + break; + case 1: + len = sizeof(request) - sizeof(REQUEST_1); + break; + default: + len = 0; + break; + } + evbuffer_get_length_call_count++; + + return len; +} + +static int evbuffer_read_call_count = 0; + +int +evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (fd != pipe->fd) { + errx(1, "got unexpected fd %d (expected %d)", fd, pipe->fd); + } + + if (howmuch != -1) { + errx( + 1, "got unexpected howmuch bytes to read %d (expected -1)", howmuch + ); + } + + return get_nbytes_read(evbuffer_read_call_count++); +} + +int +main(void) +{ + tmpdir = lgtd_tests_make_temp_dir(); + atexit(cleanup_tmpdir); + + char path[PATH_MAX] = { 0 }; + snprintf(path, sizeof(path), "%s/lightsd.pipe", tmpdir); + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + + lgtd_command_pipe_read_callback(pipe->fd, EV_READ, pipe); + + return 0; +} diff --git a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c new file mode 100644 index 0000000..f6bd0d4 --- /dev/null +++ b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c @@ -0,0 +1,258 @@ +#include "pipe.c" + +#include +#include +#include + +#include "lifx/wire_proto.h" + +#define MOCKED_EVENT_NEW +#define MOCKED_EVBUFFER_NEW +#define MOCKED_EVBUFFER_READ +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_GET_LENGTH +#define MOCKED_EVBUFFER_DRAIN +#include "mock_event2.h" +#include "mock_gateway.h" + +#include "tests_utils.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "tests_pipe_utils.h" + +#define REQUEST_1 "{" \ + "\"jsonrpc\": \"2.0\"," \ + "\"method\": \"get_light_state\"," \ + "\"params\": [\"*\"]," \ + "\"id\": 42" \ +"}" + +#define REQUEST_2 "{" \ + "\"jsonrpc\": \"2.0\"," \ + "\"method\": \"power_on\"," \ + "\"params\": [\"*\"]," \ + "\"id\": 43" \ +"}" + +static unsigned char request[] = ( + REQUEST_1 + REQUEST_2 +); + +static char *tmpdir = NULL; + +void +cleanup_tmpdir(void) +{ + lgtd_tests_remove_temp_dir(tmpdir); +} + +static int jsonrpc_dispatch_request_call_count = 0; + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + if (!parsed) { + errx(1, "number of parsed json tokens not passed in"); + } + + switch (jsonrpc_dispatch_request_call_count) { + case 0: + if (memcmp(client->json, request, sizeof(request) - 1)) { + errx( + 1, "got unexpected json %s (expected %s)", + client->json, request + ); + } + break; + case 1: + if (memcmp(client->json, REQUEST_2, sizeof(REQUEST_2) - 1)) { + errx( + 1, "got unexpected json %s (expected %s)", + client->json, REQUEST_2 + ); + } + break; + default: + errx( + 1, "jsonrpc_dispatch_request_call_count = %d", + jsonrpc_dispatch_request_call_count + ); + break; + } + + jsonrpc_dispatch_request_call_count++; +} + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + (void)base; + (void)fd; + (void)events; + (void)cb; + (void)ctx; + + return (void *)1; +} + +struct evbuffer * +evbuffer_new(void) +{ + return (void *)2; +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + jsmn_parser jsmn_ctx; + jsmn_init(&jsmn_ctx); + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { + errx(1, "the client json parser context wasn't re-initialized"); + } + + switch (evbuffer_drain_call_count) { + case 0: + if (len != sizeof(REQUEST_1) - 1) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)sizeof(REQUEST_1) - 1 + ); + } + break; + case 1: + if (len != sizeof(REQUEST_2) - 1) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)sizeof(REQUEST_2) - 1 + ); + } + break; + default: + errx(1, "evbuffer_drain_call_count = %d", evbuffer_drain_call_count); + break; + } + evbuffer_drain_call_count++; + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + if (size != -1) { + errx(1, "got unexpected size %ld in pullup (expected -1)", size); + } + + int offset; + switch (evbuffer_pullup_call_count) { + case 0: + offset = 0; + break; + case 1: + offset = sizeof(REQUEST_1) - 1; + break; + default: + offset = sizeof(request); + break; + } + evbuffer_pullup_call_count++; + + return &request[offset]; +} + +static int evbuffer_get_length_call_count = 0; + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + size_t len; + switch (evbuffer_get_length_call_count) { + case 0: + len = sizeof(request) - 1; + break; + case 1: + len = sizeof(request) - sizeof(REQUEST_1); + break; + default: + len = 0; + break; + } + evbuffer_get_length_call_count++; + + return len; +} + +static int evbuffer_read_call_count = 0; + +int +evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (fd != pipe->fd) { + errx(1, "got unexpected fd %d (expected %d)", fd, pipe->fd); + } + + if (howmuch != -1) { + errx( + 1, "got unexpected howmuch bytes to read %d (expected -1)", howmuch + ); + } + + int rv = 0; + switch (evbuffer_read_call_count) { + case 0: + rv = sizeof(request) - 1; + default: + break; + } + evbuffer_read_call_count++; + + return rv; +} + +int +main(void) +{ + tmpdir = lgtd_tests_make_temp_dir(); + atexit(cleanup_tmpdir); + + char path[PATH_MAX] = { 0 }; + snprintf(path, sizeof(path), "%s/lightsd.pipe", tmpdir); + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + + lgtd_command_pipe_read_callback(pipe->fd, EV_READ, pipe); + + return 0; +} diff --git a/tests/core/pipe/tests_pipe_utils.h b/tests/core/pipe/tests_pipe_utils.h new file mode 100644 index 0000000..7297afd --- /dev/null +++ b/tests/core/pipe/tests_pipe_utils.h @@ -0,0 +1,19 @@ +#pragma once + +#ifndef MOCKED_CLIENT_OPEN_FROM_PIPE +void +lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) +{ + memset(pipe_client, 0, sizeof(*pipe_client)); + jsmn_init(&pipe_client->jsmn_ctx); +} +#endif + +#ifndef MOCKED_JSONRPC_DISPATCH_REQUEST +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; +} +#endif diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt index 0f440f1..60c4476 100644 --- a/tests/core/proto/CMakeLists.txt +++ b/tests/core/proto/CMakeLists.txt @@ -8,6 +8,7 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/jsonrpc.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/timer.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c @@ -15,11 +16,11 @@ ADD_LIBRARY( ) TARGET_LINK_LIBRARIES(test_core_proto ${TIME_MONOTONIC_LIBRARY}) -FUNCTION(ADD_ROUTER_TEST TEST_SOURCE) +FUNCTION(ADD_PROTO_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_proto) ENDFUNCTION() FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") FOREACH(TEST ${TESTS}) - ADD_ROUTER_TEST(${TEST}) + ADD_PROTO_TEST(${TEST}) ENDFOREACH() diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c index f4be4a2..4b00862 100644 --- a/tests/core/proto/test_proto_get_light_state.c +++ b/tests/core/proto/test_proto_get_light_state.c @@ -29,6 +29,9 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) static struct lgtd_router_device_list devices = SLIST_HEAD_INITIALIZER(&devices); + static struct lgtd_lifx_gateway gw_bulb_1 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + }; static struct lgtd_lifx_bulb bulb_1 = { .addr = { 1, 2, 3, 4, 5 }, .state = { @@ -39,7 +42,8 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) .label = "wave", .power = LGTD_LIFX_POWER_ON, .tags = 0 - } + }, + .gw = &gw_bulb_1 }; static struct lgtd_router_device device_1 = { .device = &bulb_1 }; SLIST_INSERT_HEAD(&devices, &device_1, link); @@ -76,7 +80,7 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) int main(void) { - struct lgtd_client client; + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; struct lgtd_proto_target_list *targets = (void *)0x2a; lgtd_proto_get_light_state(&client, targets); @@ -103,7 +107,7 @@ main(void) 1, "%d bytes written, expected %lu " "(got %.*s instead of %s)", - client_write_buf_idx, sizeof(expected) - 1, + client_write_buf_idx, sizeof(expected) - 1UL, client_write_buf_idx, client_write_buf, expected ); } diff --git a/tests/core/proto/test_proto_get_light_state_empty_device_list.c b/tests/core/proto/test_proto_get_light_state_empty_device_list.c index 4b15a25..7db816e 100644 --- a/tests/core/proto/test_proto_get_light_state_empty_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_empty_device_list.c @@ -35,7 +35,7 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) int main(void) { - struct lgtd_client client; + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; struct lgtd_proto_target_list *targets = (void *)0x2a; lgtd_proto_get_light_state(&client, targets); @@ -45,7 +45,7 @@ main(void) if (client_write_buf_idx != sizeof(expected) - 1) { lgtd_errx( 1, "%d bytes written, expected %lu", - client_write_buf_idx, sizeof(expected) - 1 + client_write_buf_idx, sizeof(expected) - 1UL ); } diff --git a/tests/core/proto/test_proto_get_light_state_null_device_list.c b/tests/core/proto/test_proto_get_light_state_null_device_list.c index 0ff2d71..8014f98 100644 --- a/tests/core/proto/test_proto_get_light_state_null_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_null_device_list.c @@ -45,7 +45,7 @@ lgtd_client_send_error(struct lgtd_client *client, int main(void) { - struct lgtd_client client; + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; struct lgtd_proto_target_list *targets = (void *)0x2a; lgtd_proto_get_light_state(&client, targets); diff --git a/tests/core/proto/test_proto_power_off.c b/tests/core/proto/test_proto_power_off.c index 5b57dfa..7387339 100644 --- a/tests/core/proto/test_proto_power_off.c +++ b/tests/core/proto/test_proto_power_off.c @@ -52,7 +52,7 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client; + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; lgtd_proto_power_off(&client, targets); diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c index a2f270a..c12a3ce 100644 --- a/tests/core/proto/test_proto_power_off_routing_error.c +++ b/tests/core/proto/test_proto_power_off_routing_error.c @@ -52,7 +52,7 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client; + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; lgtd_proto_power_off(&client, targets); diff --git a/tests/core/proto/test_proto_power_on.c b/tests/core/proto/test_proto_power_on.c index 5eef1b6..ddb60ce 100644 --- a/tests/core/proto/test_proto_power_on.c +++ b/tests/core/proto/test_proto_power_on.c @@ -52,7 +52,7 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client; + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; lgtd_proto_power_on(&client, targets); diff --git a/tests/core/proto/test_proto_power_on_routing_error.c b/tests/core/proto/test_proto_power_on_routing_error.c index 81649e1..de83dff 100644 --- a/tests/core/proto/test_proto_power_on_routing_error.c +++ b/tests/core/proto/test_proto_power_on_routing_error.c @@ -52,7 +52,7 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client; + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; lgtd_proto_power_on(&client, targets); diff --git a/tests/core/proto/tests_proto_utils.h b/tests/core/proto/tests_proto_utils.h index 70218be..c332c63 100644 --- a/tests/core/proto/tests_proto_utils.h +++ b/tests/core/proto/tests_proto_utils.h @@ -1,5 +1,9 @@ #pragma once +#include "mock_gateway.h" + +#define FAKE_BUFFEREVENT (void *)0xfeed + void lgtd_client_start_send_response(struct lgtd_client *client) { @@ -16,7 +20,7 @@ lgtd_client_end_send_response(struct lgtd_client *client) void lgtd_client_send_response(struct lgtd_client *client, const char *msg) { - LGTD_CLIENT_WRITE_STRING(client, msg); + lgtd_client_write_string(client, msg); } #endif diff --git a/tests/core/router/CMakeLists.txt b/tests/core/router/CMakeLists.txt index b48b79b..65e06aa 100644 --- a/tests/core/router/CMakeLists.txt +++ b/tests/core/router/CMakeLists.txt @@ -8,6 +8,7 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/proto.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/timer.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c diff --git a/tests/core/router/tests_router_utils.h b/tests/core/router/tests_router_utils.h index 4ab234d..72beb1b 100644 --- a/tests/core/router/tests_router_utils.h +++ b/tests/core/router/tests_router_utils.h @@ -1,5 +1,7 @@ #pragma once +#include "mock_gateway.h" + int lgtd_tests_gw_pkt_queue_size = 0; struct { struct lgtd_lifx_gateway *gw; diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c index 6f40467..4c2ef68 100644 --- a/tests/core/tests_shims.c +++ b/tests/core/tests_shims.c @@ -30,52 +30,3 @@ void lgtd_cleanup(void) { } - - -void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_pan_gateway *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} - -void lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_light_status *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} - -void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_power_state *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} - -void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_tag_labels *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} - -struct lgtd_lifx_tag * -lgtd_lifx_tagging_find_tag(const char *tag_label) -{ - struct lgtd_lifx_tag *tag = NULL; - LIST_FOREACH(tag, &lgtd_lifx_tags, link) { - if (!strcmp(tag->label, tag_label)) { - break; - } - } - return tag; -} diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index 77a6ea5..a8bc826 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -2,13 +2,18 @@ #include #include #include +#include #include +#include +#include #include #include #include #include #include +#include #include +#include #include @@ -26,9 +31,6 @@ struct lgtd_lifx_gateway_list lgtd_lifx_gateways = LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); -struct lgtd_lifx_tag_list lgtd_lifx_tags = - LIST_HEAD_INITIALIZER(&lgtd_lifx_tags); - struct lgtd_lifx_gateway * lgtd_tests_insert_mock_gateway(int id) { @@ -112,3 +114,42 @@ lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *tag, LIST_INSERT_HEAD(&tag->sites, site, link); return site; } + +char * +lgtd_tests_make_temp_dir(void) +{ + char buf[PATH_MAX] = { 0 }; + int n = snprintf(buf, sizeof(buf), "%s/lightsd.test.XXXXXXXX", P_tmpdir); + if (n >= (int)sizeof(buf)) { + errx(1, "cannot allocate temporary directory"); + } + return strdup(mkdtemp(buf)); +} + +void +lgtd_tests_remove_temp_dir(char *path) +{ + DIR *tmpdir = opendir(path); + if (!tmpdir) { + return; + } + + struct dirent db; + struct dirent *entry = &db; + while (!readdir_r(tmpdir, entry, &entry) && entry) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { + continue; + } + char buf[PATH_MAX] = { 0 }; + snprintf(buf, sizeof(buf), "%s/%s", path, entry->d_name); + unlink(buf); + } + + closedir(tmpdir); + + if (rmdir(path)) { + warn("couldn't remove tempdir %s", path); + } + + free(path); +} diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index 41150d8..9f16d60 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -30,6 +30,9 @@ lgtd_tests_lifx_header_has_flags(const struct lgtd_lifx_packet_header *hdr, return true; } +char *lgtd_tests_make_temp_dir(void); +void lgtd_tests_remove_temp_dir(char *); + struct lgtd_lifx_gateway *lgtd_tests_insert_mock_gateway(int); struct lgtd_lifx_bulb *lgtd_tests_insert_mock_bulb(struct lgtd_lifx_gateway *, uint64_t); struct lgtd_proto_target_list *lgtd_tests_build_target_list(const char *, ...); diff --git a/tests/lifx/mock_gateway.h b/tests/lifx/mock_gateway.h new file mode 100644 index 0000000..4c091ad --- /dev/null +++ b/tests/lifx/mock_gateway.h @@ -0,0 +1,119 @@ +#pragma once + +#include "core/time_monotonic.h" +#include "lifx/bulb.h" +#include "lifx/gateway.h" + +struct lgtd_lifx_tag; +struct lgtd_lifx_gateway; + +#ifndef MOCKED_LIFX_GATEWAY_SEND_TO_SITE +bool +lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt) +{ + (void)gw; + (void)pkt_type; + (void)pkt; + return false; +} +#endif + +#ifndef MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID +int +lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, + int tag_id, + const char *tag_label) +{ + (void)gw; + (void)tag_id; + (void)tag_label; + return -1; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_PAN_GATEWAY +void +lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_pan_gateway *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_LIGHT_STATUS +void +lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_light_status *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_POWER_STATE +void +lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_power_state *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_TAG_LABELS +void +lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_tag_labels *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_DEALLOCATE_TAG_ID +void +lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id) +{ + (void)gw; + (void)tag_id; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_GET_TAG_ID +int +lgtd_lifx_gateway_get_tag_id(const struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_tag *tag) +{ + int tag_id; + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, gw->tag_ids) { + if (gw->tags[tag_id] == tag) { + return tag_id; + } + } + + return -1; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_UPDATE_TAG_REFCOUNTS +void +lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, + uint64_t bulb_tags, + uint64_t pkt_tags) +{ + (void)gw; + (void)bulb_tags; + (void)pkt_tags; +} +#endif From 925adaa07d41d5687b88f802e8c0b4b49b7181e9 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 01:43:49 -0700 Subject: [PATCH 055/181] Actual support for daemonization with a nice process name --- CMakeLists.txt | 23 ++- CMakeScripts/AddTestFromSources.cmake | 8 +- CMakeScripts/CompatSetProctitle.cmake | 20 +++ CMakeScripts/CompatTimeMonotonic.cmake | 2 - CMakeScripts/FindLibBSD.cmake | 10 ++ README.rst | 18 ++ compat/CMakeLists.txt | 1 + compat/generic/CMakeLists.txt | 1 + core/CMakeLists.txt | 6 + core/client.c | 6 + core/daemon.c | 155 ++++++++++++++++++ core/daemon.h | 24 +++ core/lightsd.c | 9 +- core/listen.c | 5 + core/listen.h | 4 + core/log.c | 5 + core/stats.c | 47 ++++++ core/stats.h | 35 ++++ lifx/bulb.c | 21 +++ lifx/gateway.c | 5 + tests/CMakeLists.txt | 13 ++ tests/core/daemon/CMakeLists.txt | 24 +++ tests/core/daemon/mock_pipe.h | 4 + .../daemon/test_daemon_update_proctitle.c | 109 ++++++++++++ tests/core/jsonrpc/CMakeLists.txt | 4 +- tests/core/mock_daemon.h | 8 + tests/core/pipe/CMakeLists.txt | 1 + tests/core/pipe/tests_pipe_utils.h | 2 + tests/core/proto/CMakeLists.txt | 4 +- tests/core/proto/test_proto_get_light_state.c | 2 + ..._proto_get_light_state_empty_device_list.c | 1 + ...t_proto_get_light_state_null_device_list.c | 1 + tests/core/proto/test_proto_power_off.c | 3 +- .../test_proto_power_off_routing_error.c | 1 + tests/core/proto/test_proto_power_on.c | 1 + .../proto/test_proto_power_on_routing_error.c | 1 + .../proto/test_proto_set_light_from_hsbk.c | 1 + ...oto_set_light_from_hsbk_on_routing_error.c | 1 + tests/core/proto/test_proto_set_waveform.c | 1 + ...test_proto_set_waveform_on_routing_error.c | 1 + tests/core/router/CMakeLists.txt | 9 +- .../router/test_router_send_to_broadcast.c | 2 + .../core/router/test_router_send_to_device.c | 1 + .../test_router_send_to_invalid_targets.c | 1 + tests/core/router/test_router_send_to_label.c | 1 + tests/core/router/test_router_send_to_tag.c | 1 + .../router/test_router_targets_to_devices.c | 1 + tests/core/tests_utils.c | 19 +++ tests/core/tests_utils.h | 1 + tests/lifx/gateway/CMakeLists.txt | 16 +- tests/lifx/tagging/CMakeLists.txt | 4 + tests/lifx/tests_shims.c | 5 + tests/lifx/wire_proto/CMakeLists.txt | 7 +- 53 files changed, 622 insertions(+), 34 deletions(-) create mode 100644 CMakeScripts/CompatSetProctitle.cmake create mode 100644 CMakeScripts/FindLibBSD.cmake create mode 100644 compat/CMakeLists.txt create mode 100644 compat/generic/CMakeLists.txt create mode 100644 core/daemon.c create mode 100644 core/daemon.h create mode 100644 core/stats.c create mode 100644 core/stats.h create mode 100644 tests/core/daemon/CMakeLists.txt create mode 100644 tests/core/daemon/mock_pipe.h create mode 100644 tests/core/daemon/test_daemon_update_proctitle.c create mode 100644 tests/core/mock_daemon.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ce5f320..b7b5388 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) # first version TARGET_INCLUDE_DIRECTORIES PROJECT(LIGHTSD C) @@ -22,28 +22,34 @@ ENABLE_TESTING() # TODO: we need at least 2.0.19-stable because of the logging defines FIND_PACKAGE(Event2 REQUIRED COMPONENTS core) FIND_PACKAGE(Endian REQUIRED) + +INCLUDE(CheckFunctionExists) INCLUDE(TestBigEndian) +INCLUDE(CompatSetProctitle) INCLUDE(CompatTimeMonotonic) -TEST_BIG_ENDIAN(LGTD_BIG_ENDIAN_SYSTEM) +TEST_BIG_ENDIAN(BIG_ENDIAN_SYSTEM) ### Global definitions ######################################################### INCLUDE(AddAllSubdirectories) INCLUDE(AddTestFromSources) -SET(CMAKE_C_FLAGS "-pipe -Wextra -Wall -Wstrict-prototypes -std=c99") - -ADD_DEFINITIONS("-DLGTD_SIZEOF_VOID_P=${CMAKE_SIZEOF_VOID_P}") -ADD_DEFINITIONS("-DLGTD_BIG_ENDIAN_SYSTEM=${LGTD_BIG_ENDIAN_SYSTEM}") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -Wextra -Wall -Wstrict-prototypes -std=c99") -# Only relevant for the GNU libc: ADD_DEFINITIONS( + # Only relevant for the GNU libc: "-D_POSIX_C_SOURCE=200809L" "-D_BSD_SOURCE=1" "-D_DEFAULT_SOURCE=1" "-D_DARWIN_C_SOURCE=1" + + "-DLGTD_BIG_ENDIAN_SYSTEM=${BIG_ENDIAN_SYSTEM}" + "-DLGTD_SIZEOF_VOID_P=${CMAKE_SIZEOF_VOID_P}" + + "-DLGTD_HAVE_LIBBSD=${HAVE_LIBBSD}" + "-DLGTD_HAVE_PROCTITLE=${HAVE_PROCTITLE}" ) IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") @@ -54,10 +60,11 @@ IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") ENDIF () INCLUDE_DIRECTORIES( - ${LIGHTSD_SOURCE_DIR}/compat/generic ${LIGHTSD_BINARY_DIR}/compat + ${LIGHTSD_BINARY_DIR}/compat/generic ) +ADD_SUBDIRECTORY(compat) ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(lifx) ADD_SUBDIRECTORY(tests) diff --git a/CMakeScripts/AddTestFromSources.cmake b/CMakeScripts/AddTestFromSources.cmake index c13c588..ba2bee3 100644 --- a/CMakeScripts/AddTestFromSources.cmake +++ b/CMakeScripts/AddTestFromSources.cmake @@ -1,11 +1,11 @@ -FUNCTION(ADD_TEST_FROM_C_SOURCES TEST_SOURCE TEST_LIB) +FUNCTION(ADD_TEST_FROM_C_SOURCES TEST_SOURCE) STRING(LENGTH ${TEST_SOURCE} TEST_NAME_LEN) STRING(LENGTH "test_" PREFIX_LEN) MATH(EXPR TEST_NAME_LEN "${TEST_NAME_LEN} - 2 - ${PREFIX_LEN}") STRING(SUBSTRING ${ARGV0} ${PREFIX_LEN} ${TEST_NAME_LEN} TEST_NAME) - ADD_EXECUTABLE(${TEST_NAME} ${TEST_SOURCE} ${ARGN}) - IF (TEST_LIB) - TARGET_LINK_LIBRARIES(${TEST_NAME} ${TEST_LIB}) + ADD_EXECUTABLE(${TEST_NAME} ${TEST_SOURCE}) + IF (ARGN) + TARGET_LINK_LIBRARIES(${TEST_NAME} ${ARGN}) ENDIF () ADD_TEST(test_${TEST_NAME} ${TEST_NAME}) ENDFUNCTION() diff --git a/CMakeScripts/CompatSetProctitle.cmake b/CMakeScripts/CompatSetProctitle.cmake new file mode 100644 index 0000000..1b9c46e --- /dev/null +++ b/CMakeScripts/CompatSetProctitle.cmake @@ -0,0 +1,20 @@ +IF (NOT HAVE_PROCTITLE) + SET(CMAKE_REQUIRED_QUIET TRUE) + SET(HAVE_PROCTITLE 0 CACHE INTERNAL "setproctitle found in libbsd") + SET(HAVE_LIBBSD 0 CACHE INTERNAL "libbsd found") + MESSAGE(STATUS "Looking for setproctitle") + CHECK_FUNCTION_EXISTS("setproctitle" HAVE_PROCTITLE) + IF (NOT HAVE_PROCTITLE) + MESSAGE(STATUS "Looking for setproctitle - not found, falling back on libbsd") + FIND_PACKAGE(LibBSD) + IF (NOT LibBSD_FOUND) + MESSAGE(STATUS "Couldn't find setproctitle, no fancy report in the process list") + ELSE () + SET(HAVE_PROCTITLE 1 CACHE INTERNAL "setproctitle found in libbsd") + SET(HAVE_LIBBSD 1 CACHE INTERNAL "libbsd found") + ENDIF () + ELSE () + SET(HAVE_PROCTITLE 1 CACHE INTERNAL "setproctitle found on the system") + ENDIF () + UNSET(CMAKE_REQUIRED_QUIET) +ENDIF () diff --git a/CMakeScripts/CompatTimeMonotonic.cmake b/CMakeScripts/CompatTimeMonotonic.cmake index 4cb72b5..0c099c0 100644 --- a/CMakeScripts/CompatTimeMonotonic.cmake +++ b/CMakeScripts/CompatTimeMonotonic.cmake @@ -1,5 +1,3 @@ -INCLUDE(CheckFunctionExists) - IF (NOT TIME_MONOTONIC_LIBRARY) SET(COMPAT_TIME_MONOTONIC_IMPL "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.c") SET(COMPAT_TIME_MONOTONIC_H "${LIGHTSD_SOURCE_DIR}/compat/${CMAKE_SYSTEM_NAME}/time_monotonic.h") diff --git a/CMakeScripts/FindLibBSD.cmake b/CMakeScripts/FindLibBSD.cmake new file mode 100644 index 0000000..1afeec4 --- /dev/null +++ b/CMakeScripts/FindLibBSD.cmake @@ -0,0 +1,10 @@ +FIND_PATH(LIBBSD_INCLUDE_DIR bsd.h PATH_SUFFIXES bsd) + +FIND_LIBRARY(LIBBSD_LIBRARY bsd) +IF(LIBBSD_LIBRARY) + SET(LibBSD_FOUND TRUE) +ENDIF() + +INCLUDE(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibBSD DEFAULT_MSG LIBBSD_LIBRARY LIBBSD_INCLUDE_DIR) diff --git a/README.rst b/README.rst index 66828ba..47d83c6 100644 --- a/README.rst +++ b/README.rst @@ -65,6 +65,9 @@ dependencies: - CMake ≥ 2.8; - libevent ≥ 2.0.19. +lightsd optionally depends on libbsd ≥ 0.5.0 on platforms missing +``setproctitle`` (pretty much any non-BSD system, including Mac OS X). + lightsd is actively developed and tested from Arch Linux, Debian and Mac OS X; both for 32/64 bits and little/big endian architectures. @@ -86,4 +89,19 @@ port 1234: .../lightsd/build$ core/lightsd -v info -l ::1:1234 +lightsd forks in the background by default, display running processes and check +how we are doing: + +:: + + ps aux | grep lightsd + +You can stop lightsd with: + +:: + + pkill lightsd + +Use the ``-f`` option to run lightsd in the foreground. + .. vim: set tw=80 spelllang=en spell: diff --git a/compat/CMakeLists.txt b/compat/CMakeLists.txt new file mode 100644 index 0000000..7171054 --- /dev/null +++ b/compat/CMakeLists.txt @@ -0,0 +1 @@ +ADD_SUBDIRECTORY(generic) diff --git a/compat/generic/CMakeLists.txt b/compat/generic/CMakeLists.txt new file mode 100644 index 0000000..76877cb --- /dev/null +++ b/compat/generic/CMakeLists.txt @@ -0,0 +1 @@ +FILE(COPY sys DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 0d9f39b..6e23a5f 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -15,6 +15,7 @@ ADD_DEFINITIONS("-DJSMN_STRICT=1") ADD_EXECUTABLE( lightsd client.c + daemon.c jsmn.c jsonrpc.c listen.c @@ -23,6 +24,7 @@ ADD_EXECUTABLE( pipe.c proto.c router.c + stats.c ) TARGET_LINK_LIBRARIES( @@ -31,3 +33,7 @@ TARGET_LINK_LIBRARIES( ${EVENT2_CORE_LIBRARY} ${TIME_MONOTONIC_LIBRARY} ) + +IF (HAVE_LIBBSD) + TARGET_LINK_LIBRARIES(lightsd ${LIBBSD_LIBRARY}) +ENDIF (HAVE_LIBBSD) diff --git a/core/client.c b/core/client.c index cc369e4..9fd8e2a 100644 --- a/core/client.c +++ b/core/client.c @@ -34,6 +34,8 @@ #include "jsonrpc.h" #include "client.h" #include "proto.h" +#include "stats.h" +#include "daemon.h" #include "lightsd.h" struct lgtd_client_list lgtd_clients = LIST_HEAD_INITIALIZER(&lgtd_clients); @@ -44,6 +46,8 @@ lgtd_client_close(struct lgtd_client *client) assert(client); assert(client->io); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(clients, -1); + LIST_REMOVE(client, link); bufferevent_free(client->io); free(client); @@ -217,6 +221,8 @@ lgtd_client_open(evutil_socket_t peer, const struct sockaddr_storage *peer_addr) LIST_INSERT_HEAD(&lgtd_clients, client, link); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(clients, 1); + return client; } diff --git a/core/daemon.c b/core/daemon.c new file mode 100644 index 0000000..7bf2f0a --- /dev/null +++ b/core/daemon.c @@ -0,0 +1,155 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if LGTD_HAVE_LIBBSD +#include +#endif + +#include + +#include "time_monotonic.h" +#include "lifx/wire_proto.h" +#include "lifx/bulb.h" +#include "lifx/gateway.h" +#include "jsmn.h" +#include "jsonrpc.h" +#include "client.h" +#include "listen.h" +#include "daemon.h" +#include "pipe.h" +#include "stats.h" +#include "lightsd.h" + +bool +lgtd_daemon_unleash(void) +{ + if (chdir("/")) { + return false; + } + + int null = open("/dev/null", O_RDWR); + if (null == -1) { + return false; + } + + const int fds[] = { STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO }; + for (int i = 0; i != LGTD_ARRAY_SIZE(fds); ++i) { + if (dup2(null, fds[i]) == -1) { + close(null); + return false; + } + } + close(null); + +#define SUMMON() do { \ + switch (fork()) { \ + case 0: \ + break; \ + case -1: \ + return false; \ + default: \ + exit(0); \ + } \ +} while (0) + + SUMMON(); // \_o< ! + setsid(); + + SUMMON(); // \_o< !! + + return true; // welcome to UNIX! +} + +void +lgtd_daemon_setup_proctitle(int argc, char *argv[], char *envp[]) +{ +#if LGTD_HAVE_LIBBSD + setproctitle_init(argc, argv, envp); + lgtd_daemon_update_proctitle(); +#else + (void)argc; + (void)argv; + (void)envp; +#endif +} + +void +lgtd_daemon_update_proctitle(void) +{ +#if LGTD_HAVE_PROCTITLE + char title[LGTD_DAEMON_TITLE_SIZE] = { 0 }; + int i = 0; + +#define TITLE_APPEND(fmt, ...) do { \ + int n = snprintf((&title[i]), (sizeof(title) - i), (fmt), __VA_ARGS__); \ + i = LGTD_MIN(i + n, (int)sizeof(title)); \ +} while (0) + +#define PREFIX(fmt, ...) TITLE_APPEND( \ + "%s" fmt, (i && title[i - 1] == ')' ? "; " : ""), __VA_ARGS__ \ +) + +#define ADD_ITEM(fmt, ...) TITLE_APPEND( \ + "%s" fmt, (i && title[i - 1] != '(' ? ", " : ""), __VA_ARGS__ \ +) +#define LOOP(list_type, list, elem_type, prefix, ...) do { \ + if (!list_type ## _EMPTY(list)) { \ + PREFIX("%s(", prefix); \ + elem_type *it; \ + list_type ## _FOREACH(it, list, link) { \ + ADD_ITEM(__VA_ARGS__); \ + } \ + TITLE_APPEND("%s", ")"); \ + } \ +} while (0) + + LOOP( + SLIST, &lgtd_listeners, struct lgtd_listen, + "listening_on", "%s:[%s]", it->addr, it->port + ); + + LOOP( + SLIST, &lgtd_command_pipes, struct lgtd_command_pipe, + "command_pipes", "%s", it->path + ); + + if (!LIST_EMPTY(&lgtd_lifx_gateways)) { + PREFIX("lifx_gateways(found=%d)", LGTD_STATS_GET(gateways)); + } + + PREFIX( + "bulbs(found=%d, on=%d)", + LGTD_STATS_GET(bulbs), LGTD_STATS_GET(bulbs_powered_on) + ); + + PREFIX("clients(connected=%d)", LGTD_STATS_GET(clients)); + + setproctitle("%s", title); +#endif +} diff --git a/core/daemon.h b/core/daemon.h new file mode 100644 index 0000000..9a4c772 --- /dev/null +++ b/core/daemon.h @@ -0,0 +1,24 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#pragma once + +enum { LGTD_DAEMON_TITLE_SIZE = 2048 }; + +bool lgtd_daemon_unleash(void); // \_o< +void lgtd_daemon_setup_proctitle(int, char *[], char *[]); +void lgtd_daemon_update_proctitle(void); diff --git a/core/lightsd.c b/core/lightsd.c index 81b54fa..63ac3ad 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -47,6 +47,7 @@ #include "client.h" #include "pipe.h" #include "listen.h" +#include "daemon.h" #include "lightsd.h" struct lgtd_opts lgtd_opts = { @@ -142,8 +143,10 @@ lgtd_usage(const char *progname) } int -main(int argc, char *argv[]) +main(int argc, char *argv[], char *envp[]) { + lgtd_daemon_setup_proctitle(argc, argv, envp); + lgtd_configure_libevent(); lgtd_configure_signal_handling(); @@ -217,6 +220,10 @@ main(int argc, char *argv[]) lgtd_err(1, "can't setup lightsd"); } + if (!lgtd_opts.foreground && !lgtd_daemon_unleash()) { + lgtd_err(1, "can't fork to the background"); + } + lgtd_lifx_timer_start_discovery(); event_base_dispatch(lgtd_ev_base); diff --git a/core/listen.c b/core/listen.c index 9f1ee10..0e9a9ba 100644 --- a/core/listen.c +++ b/core/listen.c @@ -30,6 +30,7 @@ #include "jsonrpc.h" #include "client.h" #include "listen.h" +#include "daemon.h" #include "lightsd.h" struct lgtd_listen_list lgtd_listeners = @@ -69,6 +70,8 @@ lgtd_listen_close_all(void) evconnlistener_free(listener->evlistener); free(listener); } + + lgtd_daemon_update_proctitle(); } bool @@ -130,6 +133,8 @@ lgtd_listen_open(const char *addr, const char *port) evutil_freeaddrinfo(res); + lgtd_daemon_update_proctitle(); + return true; error: diff --git a/core/listen.h b/core/listen.h index fe61951..adc0961 100644 --- a/core/listen.h +++ b/core/listen.h @@ -17,6 +17,8 @@ #pragma once +struct evconnlistener; + struct lgtd_listen { SLIST_ENTRY(lgtd_listen) link; const char *addr; @@ -25,5 +27,7 @@ struct lgtd_listen { }; SLIST_HEAD(lgtd_listen_list, lgtd_listen); +extern struct lgtd_listen_list lgtd_listeners; + bool lgtd_listen_open(const char *, const char *); void lgtd_listen_close_all(void); diff --git a/core/log.c b/core/log.c index f36b788..f9d53bf 100644 --- a/core/log.c +++ b/core/log.c @@ -28,9 +28,14 @@ #include #include +#if LGTD_HAVE_LIBBSD +#include +#endif + #include #include "lifx/wire_proto.h" +#include "stats.h" #include "lightsd.h" static void diff --git a/core/stats.c b/core/stats.c new file mode 100644 index 0000000..2463dc1 --- /dev/null +++ b/core/stats.c @@ -0,0 +1,47 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#include +#include + +#include "stats.h" + +struct lgtd_stats lgtd_counters = { .gateways = 0 }; + +void +lgtd_stats_add(int offset, int value) +{ + assert(offset >= 0); + assert(offset < (int)sizeof(lgtd_counters)); + assert(offset % sizeof(int) == 0); + + int *counter = (int *)((uint8_t *)&lgtd_counters + offset); + + assert(*counter + value >= 0); + + *counter += value; +} + +int +lgtd_stats_get(int offset) +{ + assert(offset >= 0); + assert(offset < (int)sizeof(lgtd_counters)); + assert(offset % sizeof(int) == 0); + + return *(int *)((uint8_t *)&lgtd_counters + offset); +} diff --git a/core/stats.h b/core/stats.h new file mode 100644 index 0000000..766c259 --- /dev/null +++ b/core/stats.h @@ -0,0 +1,35 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#pragma once + +struct lgtd_stats { + int gateways; + int bulbs; + int bulbs_powered_on; + int clients; +}; + +void lgtd_stats_add(int, int); +int lgtd_stats_get(int); + +#define LGTD_STATS_GET(name) lgtd_stats_get(offsetof(struct lgtd_stats, name)) + +#define LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(name, value) do { \ + lgtd_stats_add(offsetof(struct lgtd_stats, name), (value)); \ + lgtd_daemon_update_proctitle(); \ +} while (0) diff --git a/lifx/bulb.c b/lifx/bulb.c index 141ef66..7be33ea 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -32,6 +32,8 @@ #include "core/time_monotonic.h" #include "bulb.h" #include "gateway.h" +#include "core/daemon.h" +#include "core/stats.h" #include "core/lightsd.h" struct lgtd_lifx_bulb_map lgtd_lifx_bulbs_table = @@ -62,6 +64,7 @@ lgtd_lifx_bulb_open(struct lgtd_lifx_gateway *gw, const uint8_t *addr) bulb->gw = gw; memcpy(bulb->addr, addr, sizeof(bulb->addr)); RB_INSERT(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, bulb); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(bulbs, 1); bulb->last_light_state_at = lgtd_time_monotonic_msecs(); @@ -74,6 +77,10 @@ lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *bulb) assert(bulb); assert(bulb->gw); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(bulbs, -1); + if (bulb->state.power == LGTD_LIFX_POWER_ON) { + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(bulbs_powered_on, -1); + } RB_REMOVE(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, bulb); SLIST_REMOVE(&bulb->gw->bulbs, bulb, lgtd_lifx_bulb, link_by_gw); lgtd_info( @@ -94,6 +101,13 @@ lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *bulb, { assert(bulb); assert(state); + + if (state->power != bulb->state.power) { + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE( + bulbs_powered_on, state->power == LGTD_LIFX_POWER_ON ? 1 : -1 + ); + } + bulb->last_light_state_at = received_at; memcpy(&bulb->state, state, sizeof(bulb->state)); } @@ -102,5 +116,12 @@ void lgtd_lifx_bulb_set_power_state(struct lgtd_lifx_bulb *bulb, uint16_t power) { assert(bulb); + + if (power != bulb->state.power) { + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE( + bulbs_powered_on, power == LGTD_LIFX_POWER_ON ? 1 : -1 + ); + } + bulb->state.power = power; } diff --git a/lifx/gateway.c b/lifx/gateway.c index 8e37630..be48424 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -44,6 +44,8 @@ #include "core/client.h" #include "core/proto.h" #include "core/router.h" +#include "core/stats.h" +#include "core/daemon.h" #include "core/lightsd.h" struct lgtd_lifx_gateway_list lgtd_lifx_gateways = @@ -54,6 +56,7 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) { assert(gw); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, -1); event_del(gw->refresh_ev); event_del(gw->write_ev); if (gw->socket != -1) { @@ -284,6 +287,8 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, // will stop by itself: lgtd_lifx_timer_start_watchdog(); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, 1); + return gw; error_allocate: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5e02551..4f4ce80 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1 +1,14 @@ +FUNCTION(ADD_CORE_LIBRARY LIBNAME) + ADD_LIBRARY(${LIBNAME} ${ARGN}) + TARGET_LINK_LIBRARIES(${LIBNAME} ${TIME_MONOTONIC_LIBRARY}) + TARGET_INCLUDE_DIRECTORIES( + ${LIBNAME} PUBLIC + ${LIGHTSD_SOURCE_DIR}/core/ + ${LIGHTSD_BINARY_DIR}/core/ + ) + IF (HAVE_LIBBSD) + TARGET_LINK_LIBRARIES(${LIBNAME} ${LIBBSD_LIBRARY}) + ENDIF (HAVE_LIBBSD) +ENDFUNCTION() + ADD_ALL_SUBDIRECTORIES() diff --git a/tests/core/daemon/CMakeLists.txt b/tests/core/daemon/CMakeLists.txt new file mode 100644 index 0000000..b9e0ff7 --- /dev/null +++ b/tests/core/daemon/CMakeLists.txt @@ -0,0 +1,24 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_CORE_LIBRARY( + test_core_daemon STATIC + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c + ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c +) + +FUNCTION(ADD_DAEMON_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_daemon) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_DAEMON_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/core/daemon/mock_pipe.h b/tests/core/daemon/mock_pipe.h new file mode 100644 index 0000000..52f14ea --- /dev/null +++ b/tests/core/daemon/mock_pipe.h @@ -0,0 +1,4 @@ +#pragma once + +struct lgtd_command_pipe_list lgtd_command_pipes = + SLIST_HEAD_INITIALIZER(&lgtd_command_pipes); diff --git a/tests/core/daemon/test_daemon_update_proctitle.c b/tests/core/daemon/test_daemon_update_proctitle.c new file mode 100644 index 0000000..10d6ff3 --- /dev/null +++ b/tests/core/daemon/test_daemon_update_proctitle.c @@ -0,0 +1,109 @@ +void mock_setproctitle(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); + +#undef LGTD_HAVE_LIBBSD +#undef LGTD_HAVE_PROCTITLE +#define LGTD_HAVE_PROCTITLE 1 +#define setproctitle mock_setproctitle +#include "daemon.c" + +#include + +#include "mock_gateway.h" +#include "mock_pipe.h" + +#include "tests_utils.h" + +const char *expected = ""; +int setproctitle_call_count = 0; + +void +mock_setproctitle(const char *fmt, ...) +{ + if (strcmp(fmt, "%s")) { + errx(1, "unexepected format %s (expected %%s)", fmt); + } + + va_list ap; + va_start(ap, fmt); + const char *title = va_arg(ap, const char *); + va_end(ap); + + if (strcmp(title, expected)) { + errx(1, "unexpected title: %s (expected %s)", title, expected); + } + + setproctitle_call_count++; +} + +int +main(void) +{ + expected = "bulbs(found=0, on=0); clients(connected=0)"; + lgtd_daemon_update_proctitle(); + if (setproctitle_call_count != 1) { + errx(1, "setproctitle should have been called"); + } + + expected = ( + "lifx_gateways(found=1); " + "bulbs(found=0, on=0); " + "clients(connected=0)" + ); + struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); + if (setproctitle_call_count != 2) { + errx(1, "setproctitle should have been called"); + } + + expected = ( + "lifx_gateways(found=1); " + "bulbs(found=1, on=0); " + "clients(connected=0)" + ); + lgtd_tests_insert_mock_bulb(gw_1, 2); + expected = ( + "lifx_gateways(found=1); " + "bulbs(found=2, on=0); " + "clients(connected=0)" + ); + lgtd_tests_insert_mock_bulb(gw_1, 3); + if (setproctitle_call_count != 4) { + errx(1, "setproctitle should have been called"); + } + + expected = ( + "listening_on(foobar.com:[1234]); " + "lifx_gateways(found=1); " + "bulbs(found=2, on=0); " + "clients(connected=0)" + ); + lgtd_tests_insert_mock_listener("foobar.com", "1234"); + lgtd_daemon_update_proctitle(); + if (setproctitle_call_count != 5) { + errx(1, "setproctitle should have been called"); + } + + expected = ( + "listening_on(foobar.com:[1234]); " + "lifx_gateways(found=1); " + "bulbs(found=2, on=1); " + "clients(connected=0)" + ); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(bulbs_powered_on, 1); + if (setproctitle_call_count != 6) { + errx(1, "setproctitle should have been called"); + } + + expected = ( + "listening_on(foobar.com:[1234]); " + "lifx_gateways(found=1); " + "bulbs(found=2, on=1); " + "clients(connected=1)" + ); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(clients, 1); + if (setproctitle_call_count != 7) { + errx(1, "setproctitle should have been called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt index 79b8486..ee70592 100644 --- a/tests/core/jsonrpc/CMakeLists.txt +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -3,16 +3,16 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) -ADD_LIBRARY( +ADD_CORE_LIBRARY( test_core_jsonrpc STATIC ${LIGHTSD_SOURCE_DIR}/core/jsmn.c ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) -TARGET_LINK_LIBRARIES(test_core_jsonrpc ${TIME_MONOTONIC_LIBRARY}) FUNCTION(ADD_JSONRPC_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_jsonrpc) diff --git a/tests/core/mock_daemon.h b/tests/core/mock_daemon.h new file mode 100644 index 0000000..0f2632e --- /dev/null +++ b/tests/core/mock_daemon.h @@ -0,0 +1,8 @@ +#pragma once + +#ifndef MOCKED_DAEMON_UPDATE_PROCTITLE +void +lgtd_daemon_update_proctitle(void) +{ +} +#endif diff --git a/tests/core/pipe/CMakeLists.txt b/tests/core/pipe/CMakeLists.txt index 7d310cb..0ea7a09 100644 --- a/tests/core/pipe/CMakeLists.txt +++ b/tests/core/pipe/CMakeLists.txt @@ -7,6 +7,7 @@ ADD_LIBRARY( test_core_pipe STATIC ${LIGHTSD_SOURCE_DIR}/core/jsmn.c ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c diff --git a/tests/core/pipe/tests_pipe_utils.h b/tests/core/pipe/tests_pipe_utils.h index 7297afd..2ff0e39 100644 --- a/tests/core/pipe/tests_pipe_utils.h +++ b/tests/core/pipe/tests_pipe_utils.h @@ -1,5 +1,7 @@ #pragma once +#include "mock_daemon.h" + #ifndef MOCKED_CLIENT_OPEN_FROM_PIPE void lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt index 60c4476..5708142 100644 --- a/tests/core/proto/CMakeLists.txt +++ b/tests/core/proto/CMakeLists.txt @@ -3,10 +3,11 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) -ADD_LIBRARY( +ADD_CORE_LIBRARY( test_core_proto STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/jsonrpc.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/timer.c @@ -14,7 +15,6 @@ ADD_LIBRARY( ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) -TARGET_LINK_LIBRARIES(test_core_proto ${TIME_MONOTONIC_LIBRARY}) FUNCTION(ADD_PROTO_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_proto) diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c index 4b00862..b5fcefa 100644 --- a/tests/core/proto/test_proto_get_light_state.c +++ b/tests/core/proto/test_proto_get_light_state.c @@ -1,6 +1,8 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_gateway.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_empty_device_list.c b/tests/core/proto/test_proto_get_light_state_empty_device_list.c index 7db816e..8c09869 100644 --- a/tests/core/proto/test_proto_get_light_state_empty_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_empty_device_list.c @@ -1,6 +1,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_null_device_list.c b/tests/core/proto/test_proto_get_light_state_null_device_list.c index 8014f98..868d3db 100644 --- a/tests/core/proto/test_proto_get_light_state_null_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_null_device_list.c @@ -1,6 +1,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_power_off.c b/tests/core/proto/test_proto_power_off.c index 7387339..720673e 100644 --- a/tests/core/proto/test_proto_power_off.c +++ b/tests/core/proto/test_proto_power_off.c @@ -1,6 +1,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE @@ -38,7 +39,7 @@ void lgtd_client_send_response(struct lgtd_client *client, const char *msg) { if (!client) { - errx(1, "client shouldn't ne NULL"); + errx(1, "client shouldn't be NULL"); } if (strcmp(msg, "true")) { diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c index c12a3ce..0c375e2 100644 --- a/tests/core/proto/test_proto_power_off_routing_error.c +++ b/tests/core/proto/test_proto_power_off_routing_error.c @@ -1,6 +1,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_on.c b/tests/core/proto/test_proto_power_on.c index ddb60ce..07ad5d1 100644 --- a/tests/core/proto/test_proto_power_on.c +++ b/tests/core/proto/test_proto_power_on.c @@ -1,6 +1,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_on_routing_error.c b/tests/core/proto/test_proto_power_on_routing_error.c index de83dff..55a5a45 100644 --- a/tests/core/proto/test_proto_power_on_routing_error.c +++ b/tests/core/proto/test_proto_power_on_routing_error.c @@ -1,6 +1,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_light_from_hsbk.c b/tests/core/proto/test_proto_set_light_from_hsbk.c index 1696db1..cfd4ced 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk.c @@ -3,6 +3,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c index eb8d026..5726d30 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c @@ -3,6 +3,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index be863ab..ccbd4f7 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -3,6 +3,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index 13c9b93..725384f 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -3,6 +3,7 @@ #include "proto.c" #include "mock_client_buf.h" +#include "mock_daemon.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/router/CMakeLists.txt b/tests/core/router/CMakeLists.txt index 65e06aa..cff412f 100644 --- a/tests/core/router/CMakeLists.txt +++ b/tests/core/router/CMakeLists.txt @@ -3,10 +3,11 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) -ADD_LIBRARY( +ADD_CORE_LIBRARY( test_core_router STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/proto.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/timer.c @@ -15,11 +16,7 @@ ADD_LIBRARY( ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) -TARGET_LINK_LIBRARIES( - test_core_router - ${EVENT2_CORE_LIBRARY} - ${TIME_MONOTONIC_LIBRARY} -) +TARGET_LINK_LIBRARIES(test_core_router ${EVENT2_CORE_LIBRARY}) FUNCTION(ADD_ROUTER_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_router) diff --git a/tests/core/router/test_router_send_to_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c index 2ab0f6a..f91f09a 100644 --- a/tests/core/router/test_router_send_to_broadcast.c +++ b/tests/core/router/test_router_send_to_broadcast.c @@ -1,6 +1,8 @@ #include "router.c" +#include "mock_daemon.h" #include "tests_utils.h" + #include "tests_router_utils.h" int diff --git a/tests/core/router/test_router_send_to_device.c b/tests/core/router/test_router_send_to_device.c index b817bbc..a825cc6 100644 --- a/tests/core/router/test_router_send_to_device.c +++ b/tests/core/router/test_router_send_to_device.c @@ -1,5 +1,6 @@ #include "router.c" +#include "mock_daemon.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_invalid_targets.c b/tests/core/router/test_router_send_to_invalid_targets.c index f4b68e6..27716cf 100644 --- a/tests/core/router/test_router_send_to_invalid_targets.c +++ b/tests/core/router/test_router_send_to_invalid_targets.c @@ -1,5 +1,6 @@ #include "router.c" +#include "mock_daemon.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index aa581d4..3d64b8d 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -1,5 +1,6 @@ #include "router.c" +#include "mock_daemon.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index 10d4d77..399f03d 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -1,5 +1,6 @@ #include "router.c" +#include "mock_daemon.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_targets_to_devices.c b/tests/core/router/test_router_targets_to_devices.c index 24c41ed..a73e178 100644 --- a/tests/core/router/test_router_targets_to_devices.c +++ b/tests/core/router/test_router_targets_to_devices.c @@ -1,5 +1,6 @@ #include "router.c" +#include "mock_daemon.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index a8bc826..0fa9010 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -24,6 +24,9 @@ #include "core/jsonrpc.h" #include "core/client.h" #include "core/proto.h" +#include "core/listen.h" +#include "core/daemon.h" +#include "core/stats.h" #include "lifx/bulb.h" #include "lifx/gateway.h" #include "tests_utils.h" @@ -31,6 +34,9 @@ struct lgtd_lifx_gateway_list lgtd_lifx_gateways = LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); +struct lgtd_listen_list lgtd_listeners = + SLIST_HEAD_INITIALIZER(&lgtd_listeners); + struct lgtd_lifx_gateway * lgtd_tests_insert_mock_gateway(int id) { @@ -41,6 +47,8 @@ lgtd_tests_insert_mock_gateway(int id) LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, 1); + return gw; } @@ -115,6 +123,17 @@ lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *tag, return site; } +struct lgtd_listen * +lgtd_tests_insert_mock_listener(const char *addr, const char *port) +{ + struct lgtd_listen *listener = calloc(1, sizeof(*listener)); + listener->addr = addr; + listener->port = port; + SLIST_INSERT_HEAD(&lgtd_listeners, listener, link); + + return listener; +} + char * lgtd_tests_make_temp_dir(void) { diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index 9f16d60..e01a8aa 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -40,3 +40,4 @@ struct lgtd_lifx_tag *lgtd_tests_insert_mock_tag(const char *); struct lgtd_lifx_site *lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *, struct lgtd_lifx_gateway *, int); +struct lgtd_listen *lgtd_tests_insert_mock_listener(const char *addr, const char *port); diff --git a/tests/lifx/gateway/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt index b16f869..1d606db 100644 --- a/tests/lifx/gateway/CMakeLists.txt +++ b/tests/lifx/gateway/CMakeLists.txt @@ -3,21 +3,27 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) -ADD_LIBRARY( - test_lifx_gateway STATIC +ADD_CORE_LIBRARY( + test_lifx_gateway_core STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/proto.c ${LIGHTSD_SOURCE_DIR}/core/router.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c +) + +ADD_LIBRARY( + test_lifx_gateway STATIC ${LIGHTSD_SOURCE_DIR}/lifx/broadcast.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/timer.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c - ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ) -TARGET_LINK_LIBRARIES(test_lifx_gateway ${TIME_MONOTONIC_LIBRARY}) FUNCTION(ADD_GATEWAY_TEST TEST_SOURCE) - ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_gateway) + ADD_TEST_FROM_C_SOURCES( + ${TEST_SOURCE} test_lifx_gateway_core test_lifx_gateway + ) ENDFUNCTION() FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") diff --git a/tests/lifx/tagging/CMakeLists.txt b/tests/lifx/tagging/CMakeLists.txt index a7b0e3c..9ba35b8 100644 --- a/tests/lifx/tagging/CMakeLists.txt +++ b/tests/lifx/tagging/CMakeLists.txt @@ -6,8 +6,12 @@ INCLUDE_DIRECTORIES( ADD_LIBRARY( test_lifx_tagging STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ) +IF (HAVE_LIBBSD) + TARGET_LINK_LIBRARIES(test_lifx_tagging ${LIBBSD_LIBRARY}) +ENDIF (HAVE_LIBBSD) FUNCTION(ADD_TAGGING_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_tagging) diff --git a/tests/lifx/tests_shims.c b/tests/lifx/tests_shims.c index 4d6a313..610d615 100644 --- a/tests/lifx/tests_shims.c +++ b/tests/lifx/tests_shims.c @@ -35,3 +35,8 @@ lgtd_sockaddrport(const struct sockaddr_storage *peer) return ntohs(in6_peer->sin6_port); } } + +void +lgtd_daemon_update_proctitle(void) +{ +} diff --git a/tests/lifx/wire_proto/CMakeLists.txt b/tests/lifx/wire_proto/CMakeLists.txt index da226ef..ba09548 100644 --- a/tests/lifx/wire_proto/CMakeLists.txt +++ b/tests/lifx/wire_proto/CMakeLists.txt @@ -3,14 +3,15 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) -ADD_LIBRARY( - test_lifx_wire_proto STATIC +ADD_CORE_LIBRARY( + test_lifx_wire_proto_core STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ) FUNCTION(ADD_WIRE_PROTO_TEST TEST_SOURCE) - ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_wire_proto) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_wire_proto_core) ENDFUNCTION() FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") From 5a674c2519d68cb63b004afb1b401a768dcb278b Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 01:45:08 -0700 Subject: [PATCH 056/181] UI/Cosmetic fixes --- CMakeLists.txt | 6 ++++-- core/lightsd.c | 7 ++++++- core/version.h.in | 7 ++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b7b5388..1b1f1a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,9 @@ TEST_BIG_ENDIAN(BIG_ENDIAN_SYSTEM) INCLUDE(AddAllSubdirectories) INCLUDE(AddTestFromSources) -SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -Wextra -Wall -Wstrict-prototypes -std=c99") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}} -pipe") +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Wstrict-prototypes -std=c99") +SET(CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE} "") ADD_DEFINITIONS( # Only relevant for the GNU libc: @@ -55,7 +57,7 @@ ADD_DEFINITIONS( IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") ADD_DEFINITIONS("-DQUEUE_MACRO_DEBUG=1") IF (CMAKE_COMPILER_IS_GNUCC) - ADD_DEFINITIONS("-g3" "-ggdb") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -ggdb") ENDIF () ENDIF () diff --git a/core/lightsd.c b/core/lightsd.c index 63ac3ad..b4ec74f 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -139,6 +139,7 @@ lgtd_usage(const char *progname) " [-v,--verbosity debug|info|warning|error]\n", progname ); + lgtd_cleanup(); exit(0); } @@ -162,6 +163,10 @@ main(int argc, char *argv[], char *envp[]) }; const char short_opts[] = "l:c:fthv:V"; + if (argc == 1) { + lgtd_usage(argv[0]); + } + for (int rv = getopt_long(argc, argv, short_opts, long_opts, NULL); rv != -1; rv = getopt_long(argc, argv, short_opts, long_opts, NULL)) { @@ -205,7 +210,7 @@ main(int argc, char *argv[], char *envp[]) } break; case 'V': - printf("%s v%s\n", argv[0], LGTD_VERSION); + printf("lightsd v%s\n", LGTD_VERSION); return 0; default: lgtd_usage(argv[0]); diff --git a/core/version.h.in b/core/version.h.in index 85843e5..02d056b 100644 --- a/core/version.h.in +++ b/core/version.h.in @@ -29,4 +29,9 @@ #pragma once -const char LGTD_VERSION[] = "@LGTD_VERSION@"; +const char LGTD_VERSION[] = ( + "@LIGHTSD_VERSION@\n\n" + "cflags: @CMAKE_C_FLAGS@\n" + "proctitle_support: @HAVE_PROCTITLE@\n\n" + "Copyright (c) 2014, 2015, Louis Opter " +); From c2a3605c7e81fa836a303de88cc9b711b6c94de2 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 01:45:13 -0700 Subject: [PATCH 057/181] Add initial support for tag (grouping) and untag (ungrouping) It's a bit rough without an auto-retry mechanism and it doesn't seem to work well with the newer bulbs like the LIFX White 800. --- README.rst | 3 +- core/jsonrpc.c | 92 ++++++ core/proto.c | 142 +++++++++ core/proto.h | 2 + core/router.c | 8 +- examples/lightsc.py | 8 + lifx/bulb.c | 31 +- lifx/bulb.h | 1 + lifx/gateway.c | 222 +++++++++++--- lifx/gateway.h | 12 + lifx/tagging.c | 43 ++- lifx/tagging.h | 3 + lifx/timer.c | 2 +- lifx/wire_proto.c | 95 ++++-- lifx/wire_proto.h | 5 +- .../jsonrpc/test_jsonrpc_check_and_call_tag.c | 65 ++++ ...sonrpc_check_and_call_tag_missing_params.c | 53 ++++ .../test_jsonrpc_check_and_call_untag.c | 65 ++++ ...nrpc_check_and_call_untag_invalid_params.c | 53 ++++ tests/core/jsonrpc/test_jsonrpc_utils.h | 24 ++ tests/core/proto/test_proto_tag_create.c | 253 ++++++++++++++++ ...st_proto_tag_create_lifx_gw_tag_ids_full.c | 209 +++++++++++++ tests/core/proto/test_proto_tag_update.c | 283 ++++++++++++++++++ tests/core/proto/test_proto_untag.c | 170 +++++++++++ .../test_proto_untag_tag_does_not_exist.c | 90 ++++++ tests/core/proto/tests_proto_utils.h | 12 + tests/core/tests_shims.h | 23 ++ tests/core/tests_utils.c | 5 +- tests/lifx/bulb/CMakeLists.txt | 29 ++ tests/lifx/bulb/test_bulb_close.c | 33 ++ tests/lifx/bulb/test_bulb_open.c | 44 +++ tests/lifx/bulb/test_bulb_set_light_state.c | 92 ++++++ tests/lifx/bulb/test_bulb_set_power_state.c | 39 +++ tests/lifx/bulb/test_bulb_set_tags.c | 50 ++++ .../gateway/test_gateway_allocate_tag_id.c | 102 +++++++ ...t_gateway_allocate_tag_id_no_tag_id_left.c | 89 ++++++ .../test_gateway_update_tag_refcounts.c | 106 +++++++ tests/lifx/mock_gateway.h | 12 + tests/lifx/wire_proto/test_wire_proto_utils.h | 9 + 39 files changed, 2500 insertions(+), 79 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c create mode 100644 tests/core/proto/test_proto_tag_create.c create mode 100644 tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c create mode 100644 tests/core/proto/test_proto_tag_update.c create mode 100644 tests/core/proto/test_proto_untag.c create mode 100644 tests/core/proto/test_proto_untag_tag_does_not_exist.c create mode 100644 tests/core/tests_shims.h create mode 100644 tests/lifx/bulb/CMakeLists.txt create mode 100644 tests/lifx/bulb/test_bulb_close.c create mode 100644 tests/lifx/bulb/test_bulb_open.c create mode 100644 tests/lifx/bulb/test_bulb_set_light_state.c create mode 100644 tests/lifx/bulb/test_bulb_set_power_state.c create mode 100644 tests/lifx/bulb/test_bulb_set_tags.c create mode 100644 tests/lifx/gateway/test_gateway_allocate_tag_id.c create mode 100644 tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c create mode 100644 tests/lifx/gateway/test_gateway_update_tag_refcounts.c diff --git a/README.rst b/README.rst index 47d83c6..36d0225 100644 --- a/README.rst +++ b/README.rst @@ -29,8 +29,7 @@ following commands through a JSON-RPC_ interface: - set_light_from_hsbk; - set_waveform (change the light according to a function like SAW or SINE); - get_light_state; -- tag/untag (group/ungroup bulbs together, coming up: need unit & regression - tests); +- tag/untag (group/ungroup bulbs together); - toggle (power on if off and vice-versa, coming up). The JSON-RPC interface works on top of TCP/IPv4/v6, Unix sockets (coming up) or diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 6ef8ddc..cebb313 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -977,6 +977,90 @@ lgtd_jsonrpc_check_and_call_get_light_state(struct lgtd_client *client) lgtd_proto_target_list_clear(&targets); } +static void +lgtd_jsonrpc_check_and_call_proto_tag_or_untag(struct lgtd_client *client, + void (*lgtd_proto_fn)(struct lgtd_client *, + const struct lgtd_proto_target_list *, + const char *)) + +{ + struct lgtd_jsonrpc_target_args { + const jsmntok_t *target; + int target_ntokens; + const jsmntok_t *tag; + } params = { NULL, 0, NULL }; + static const struct lgtd_jsonrpc_node schema[] = { + LGTD_JSONRPC_NODE( + "target", + offsetof(struct lgtd_jsonrpc_target_args, target), + offsetof(struct lgtd_jsonrpc_target_args, target_ntokens), + lgtd_jsonrpc_type_string_number_or_array, + false + ), + LGTD_JSONRPC_NODE( + "tag", + offsetof(struct lgtd_jsonrpc_target_args, tag), + -1, + lgtd_jsonrpc_type_string, + false + ) + }; + + struct lgtd_jsonrpc_request *req = client->current_request; + bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( + ¶ms, + schema, + LGTD_ARRAY_SIZE(schema), + req->params, + req->params_ntokens, + client->json + ); + if (!ok) { + lgtd_jsonrpc_send_error( + client, LGTD_JSONRPC_INVALID_PARAMS, "Invalid parameters" + ); + return; + } + + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); + ok = lgtd_jsonrpc_build_target_list( + &targets, client, params.target, params.target_ntokens + ); + if (!ok) { + return; + } + + char *tag = strndup( + &client->json[params.tag->start], LGTD_JSONRPC_TOKEN_LEN(params.tag) + ); + if (!tag) { + lgtd_warn("can't allocate a tag"); + lgtd_jsonrpc_send_error( + client, LGTD_JSONRPC_INTERNAL_ERROR, "Can't allocate memory" + ); + goto error_strdup; + } + + lgtd_proto_fn(client, &targets, tag); + + free(tag); + +error_strdup: + lgtd_proto_target_list_clear(&targets); +} + +static void +lgtd_jsonrpc_check_and_call_tag(struct lgtd_client *client) +{ + return lgtd_jsonrpc_check_and_call_proto_tag_or_untag(client, lgtd_proto_tag); +} + +static void +lgtd_jsonrpc_check_and_call_untag(struct lgtd_client *client) +{ + return lgtd_jsonrpc_check_and_call_proto_tag_or_untag(client, lgtd_proto_untag); +} + void lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) { @@ -1001,6 +1085,14 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) LGTD_JSONRPC_METHOD( "get_light_state", 1, // t lgtd_jsonrpc_check_and_call_get_light_state + ), + LGTD_JSONRPC_METHOD( + "tag", 2, // t, tag + lgtd_jsonrpc_check_and_call_tag + ), + LGTD_JSONRPC_METHOD( + "untag", 2, // t, tag + lgtd_jsonrpc_check_and_call_untag ) }; diff --git a/core/proto.c b/core/proto.c index 98fa494..1d5c276 100644 --- a/core/proto.c +++ b/core/proto.c @@ -224,3 +224,145 @@ lgtd_proto_get_light_state(struct lgtd_client *client, lgtd_router_device_list_free(devices); } + +void +lgtd_proto_tag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag_label) +{ + assert(client); + assert(targets); + assert(tag_label); + + struct lgtd_router_device_list *devices; + devices = lgtd_router_targets_to_devices(targets); + if (!devices) { + goto error_tag_alloc; + } + + struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(tag_label); + if (!tag) { + tag = lgtd_lifx_tagging_allocate_tag(tag_label); + if (!tag) { + goto error_tag_alloc; + } + lgtd_info("created tag [%s]", tag_label); + } + + struct lgtd_router_device *device; + struct lgtd_lifx_site *site; + + // Loop over the devices and do allocations first, this makes error + // handling easier (since you can't rollback enqueued packets) and build + // the list of affected gateways so we can do SET_TAG_LABELS: + SLIST_FOREACH(device, devices, link) { + struct lgtd_lifx_gateway *gw = device->device->gw; + int tag_id = lgtd_lifx_gateway_get_tag_id(gw, tag); + if (tag_id == -1) { + tag_id = lgtd_lifx_gateway_allocate_tag_id(gw, -1, tag_label); + if (tag_id == -1) { + goto error_site_alloc; + } + } + } + + // SET_TAG_LABELS, this is idempotent, do it everytime so we can recover + // from any bad state: + LIST_FOREACH(site, &tag->sites, link) { + int tag_id = site->tag_id; + assert(tag_id > -1 && tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); + struct lgtd_lifx_packet_tag_labels pkt = { .tags = 0 }; + pkt.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); + strncpy(pkt.label, tag_label, sizeof(pkt.label) - 1); + lgtd_lifx_wire_encode_tag_labels(&pkt); + bool enqueued = lgtd_lifx_gateway_send_to_site( + site->gw, LGTD_LIFX_SET_TAG_LABELS, &pkt + ); + if (!enqueued) { + goto error_site_alloc; + } + lgtd_info( + "created tag [%s] with id %d on gw [%s]:%hu", + tag_label, tag_id, site->gw->ip_addr, site->gw->port + ); + } + + // Finally SET_TAGS on the devices: + SLIST_FOREACH(device, devices, link) { + struct lgtd_lifx_bulb *bulb = device->device; + int tag_id = lgtd_lifx_gateway_get_tag_id(bulb->gw, tag); + assert(tag_id > -1 && tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); + int tag_value = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); + if (!(bulb->state.tags & tag_value)) { + struct lgtd_lifx_packet_tags pkt; + pkt.tags = bulb->state.tags | tag_value; + lgtd_lifx_wire_encode_tags(&pkt); + lgtd_router_send_to_device(bulb, LGTD_LIFX_SET_TAGS, &pkt); + } + } + + SEND_RESULT(client, true); + goto fini; + +error_site_alloc: + if (LIST_EMPTY(&tag->sites)) { + lgtd_lifx_tagging_deallocate_tag(tag); + } else { // tagging_decref will deallocate the tag for us: + struct lgtd_lifx_site *next_site; + LIST_FOREACH_SAFE(site, &tag->sites, link, next_site) { + lgtd_lifx_gateway_deallocate_tag_id(site->gw, site->tag_id); + } + } +error_tag_alloc: + lgtd_client_send_error( + client, LGTD_CLIENT_INTERNAL_ERROR, "couldn't allocate new tag" + ); +fini: + lgtd_router_device_list_free(devices); + return; +} + +void +lgtd_proto_untag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag_label) +{ + assert(client); + assert(targets); + assert(tag_label); + + struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(tag_label); + if (!tag) { + SEND_RESULT(client, true); + return; + } + + struct lgtd_router_device_list *devices = NULL; + devices = lgtd_router_targets_to_devices(targets); + if (!devices) { + lgtd_client_send_error( + client, LGTD_CLIENT_INTERNAL_ERROR, "couldn't allocate memory" + ); + return; + } + + struct lgtd_router_device *device; + SLIST_FOREACH(device, devices, link) { + struct lgtd_lifx_bulb *bulb = device->device; + struct lgtd_lifx_gateway *gw = bulb->gw; + int tag_id = lgtd_lifx_gateway_get_tag_id(gw, tag); + if (tag_id != -1) { + int tag_value = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); + if (bulb->state.tags & tag_value) { + struct lgtd_lifx_packet_tags pkt; + pkt.tags = bulb->state.tags & ~tag_value; + lgtd_lifx_wire_encode_tags(&pkt); + lgtd_router_send_to_device(bulb, LGTD_LIFX_SET_TAGS, &pkt); + } + } + } + + SEND_RESULT(client, true); + + lgtd_router_device_list_free(devices); +} diff --git a/core/proto.h b/core/proto.h index aaaf2ca..b1dd25e 100644 --- a/core/proto.h +++ b/core/proto.h @@ -39,3 +39,5 @@ void lgtd_proto_set_waveform(struct lgtd_client *, void lgtd_proto_power_on(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_power_off(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_get_light_state(struct lgtd_client *, const struct lgtd_proto_target_list *); +void lgtd_proto_tag(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *); +void lgtd_proto_untag(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *); diff --git a/core/router.c b/core/router.c index 1bff9ea..5908649 100644 --- a/core/router.c +++ b/core/router.c @@ -372,8 +372,8 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) void lgtd_router_device_list_free(struct lgtd_router_device_list *devices) { - assert(devices); - - lgtd_router_clear_device_list(devices); - free(devices); + if (devices) { + lgtd_router_clear_device_list(devices); + free(devices); + } } diff --git a/examples/lightsc.py b/examples/lightsc.py index 634a5be..cba02de 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -72,6 +72,14 @@ def power_off(socket, target): def get_light_state(socket, target): return jsonrpc_call(socket, "get_light_state", [target]) + +def tag(socket, target, tag): + return jsonrpc_call(socket, "tag", [target, tag]) + + +def untag(socket, target, tag): + return jsonrpc_call(socket, "untag", [target, tag]) + if __name__ == "__main__": s = socket.create_connection(("localhost", 1234)) h = 0 diff --git a/lifx/bulb.c b/lifx/bulb.c index 7be33ea..3d35a0b 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -77,12 +77,29 @@ lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *bulb) assert(bulb); assert(bulb->gw); +#ifndef NDEBUG + // FIXME: Yeah, so an unit test lgtd_lifx_gateway_remove_and_close_bulb + // would be better because it can be automated, but this looks so much + // easier to do and this code path is often exercised: + int tag_id; + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, bulb->state.tags) { + int n = 0; + struct lgtd_lifx_bulb *gw_bulb; + SLIST_FOREACH(gw_bulb, &bulb->gw->bulbs, link_by_gw) { + assert(gw_bulb != bulb); + if (LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id) & gw_bulb->state.tags) { + n++; + } + } + assert(bulb->gw->tag_refcounts[tag_id] == n); + } +#endif + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(bulbs, -1); if (bulb->state.power == LGTD_LIFX_POWER_ON) { LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(bulbs_powered_on, -1); } RB_REMOVE(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, bulb); - SLIST_REMOVE(&bulb->gw->bulbs, bulb, lgtd_lifx_bulb, link_by_gw); lgtd_info( "closed bulb \"%.*s\" (%s) on [%s]:%hu", LGTD_LIFX_LABEL_SIZE, @@ -108,6 +125,8 @@ lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *bulb, ); } + lgtd_lifx_gateway_update_tag_refcounts(bulb->gw, bulb->state.tags, state->tags); + bulb->last_light_state_at = received_at; memcpy(&bulb->state, state, sizeof(bulb->state)); } @@ -125,3 +144,13 @@ lgtd_lifx_bulb_set_power_state(struct lgtd_lifx_bulb *bulb, uint16_t power) bulb->state.power = power; } + +void +lgtd_lifx_bulb_set_tags(struct lgtd_lifx_bulb *bulb, uint64_t tags) +{ + assert(bulb); + + lgtd_lifx_gateway_update_tag_refcounts(bulb->gw, bulb->state.tags, tags); + + bulb->state.tags = tags; +} diff --git a/lifx/bulb.h b/lifx/bulb.h index 4b5dba1..866e0c3 100644 --- a/lifx/bulb.h +++ b/lifx/bulb.h @@ -68,3 +68,4 @@ void lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *, const struct lgtd_lifx_light_state *, lgtd_time_mono_t); void lgtd_lifx_bulb_set_power_state(struct lgtd_lifx_bulb *, uint16_t); +void lgtd_lifx_bulb_set_tags(struct lgtd_lifx_bulb *, uint64_t); diff --git a/lifx/gateway.c b/lifx/gateway.c index be48424..1879559 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -71,9 +71,9 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) lgtd_lifx_tagging_decref(gw->tags[i], gw); } } - struct lgtd_lifx_bulb *bulb, *next_bulb; - SLIST_FOREACH_SAFE(bulb, &gw->bulbs, link_by_gw, next_bulb) { - lgtd_lifx_bulb_close(bulb); + while (!SLIST_EMPTY(&gw->bulbs)) { + struct lgtd_lifx_bulb *bulb = SLIST_FIRST(&gw->bulbs); + lgtd_lifx_gateway_remove_and_close_bulb(gw, bulb); } lgtd_info( @@ -83,6 +83,23 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) free(gw); } +void +lgtd_lifx_gateway_remove_and_close_bulb(struct lgtd_lifx_gateway *gw, + struct lgtd_lifx_bulb *bulb) +{ + assert(gw); + assert(bulb); + + int tag_id; + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, bulb->state.tags) { + assert(gw->tag_refcounts[tag_id] > 0); + gw->tag_refcounts[tag_id]--; + } + SLIST_REMOVE(&gw->bulbs, bulb, lgtd_lifx_bulb, link_by_gw); + + lgtd_lifx_bulb_close(bulb); +} + static void lgtd_lifx_gateway_write_callback(evutil_socket_t socket, short events, void *ctx) @@ -133,13 +150,6 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, if (type == LGTD_LIFX_GET_TAG_LABELS) { gw->pending_refresh_req = false; } - if (lgtd_opts.verbosity <= LGTD_DEBUG) { - const struct lgtd_lifx_packet_infos *pkt_infos = - lgtd_lifx_wire_get_packet_infos(type); - lgtd_debug( - "%s --> [%s]:%hu", pkt_infos->name, gw->ip_addr, gw->port - ); - } gw->pkt_ring[gw->pkt_ring_tail].type = 0; LGTD_LIFX_GATEWAY_INC_MESSAGE_RING_INDEX(gw->pkt_ring_tail); gw->pkt_ring_full = false; @@ -151,37 +161,78 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, } } -static void -lgtd_lifx_gateway_send_get_all_light_state(struct lgtd_lifx_gateway *gw) +static bool +lgtd_lifx_gateway_send_to_site_impl(struct lgtd_lifx_gateway *gw, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt, + const struct lgtd_lifx_packet_infos **pkt_infos) { assert(gw); + assert(pkt_infos); struct lgtd_lifx_packet_header hdr; union lgtd_lifx_target target = { .addr = gw->site.as_array }; - - lgtd_lifx_wire_setup_header( + *pkt_infos = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_SITE, target, gw->site.as_array, - LGTD_LIFX_GET_LIGHT_STATE + pkt_type ); - lgtd_lifx_gateway_enqueue_packet( - gw, &hdr, LGTD_LIFX_GET_LIGHT_STATE, NULL, 0 + assert(*pkt_infos); + + lgtd_lifx_gateway_enqueue_packet(gw, &hdr, pkt_type, pkt, (*pkt_infos)->size); + + return true; // FIXME, have real return values on the send paths... +} + +static bool +lgtd_lifx_gateway_send_to_site_quiet(struct lgtd_lifx_gateway *gw, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt) +{ + + const struct lgtd_lifx_packet_infos *pkt_infos; + bool rv = lgtd_lifx_gateway_send_to_site_impl( + gw, pkt_type, pkt, &pkt_infos ); - struct lgtd_lifx_packet_get_tag_labels pkt = { .tags = LGTD_LIFX_ALL_TAGS }; - lgtd_lifx_wire_setup_header( - &hdr, - LGTD_LIFX_TARGET_SITE, - target, - gw->site.as_array, - LGTD_LIFX_GET_TAG_LABELS + lgtd_debug( + "sending %s to site %s", + pkt_infos->name, lgtd_addrtoa(gw->site.as_array) ); - lgtd_lifx_gateway_enqueue_packet( - gw, &hdr, LGTD_LIFX_GET_TAG_LABELS, &pkt, sizeof(pkt) + + return rv; // FIXME, have real return values on the send paths... +} + +bool +lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt) +{ + const struct lgtd_lifx_packet_infos *pkt_infos; + bool rv = lgtd_lifx_gateway_send_to_site_impl( + gw, pkt_type, pkt, &pkt_infos + ); + + lgtd_info( + "sending %s to site %s", + pkt_infos->name, lgtd_addrtoa(gw->site.as_array) ); + return rv; // FIXME, have real return values on the send paths... +} + +static void +lgtd_lifx_gateway_send_get_all_light_state(struct lgtd_lifx_gateway *gw) +{ + assert(gw); + + lgtd_lifx_gateway_send_to_site_quiet(gw, LGTD_LIFX_GET_LIGHT_STATE, NULL); + + struct lgtd_lifx_packet_tags pkt = { .tags = LGTD_LIFX_ALL_TAGS }; + lgtd_lifx_gateway_send_to_site_quiet(gw, LGTD_LIFX_GET_TAG_LABELS, &pkt); + gw->pending_refresh_req = true; } @@ -370,20 +421,56 @@ lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, event_add(gw->write_ev, NULL); } +void +lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, + uint64_t bulb_tags, + uint64_t pkt_tags) +{ + uint64_t changes = bulb_tags ^ pkt_tags; + uint64_t added_tags = changes & pkt_tags; + uint64_t removed_tags = changes & bulb_tags; + int tag_id; + + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, added_tags) { + if (gw->tag_refcounts[tag_id] != UINT8_MAX) { + gw->tag_refcounts[tag_id]++; + } else { + lgtd_warnx( + "reached refcount limit (%u) for tag [%s] (%d) on gw [%s]:%hu", + UINT8_MAX, gw->tags[tag_id] ? gw->tags[tag_id]->label : NULL, + tag_id, gw->ip_addr, gw->port + ); + } + } + + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, removed_tags) { + assert(gw->tag_refcounts[tag_id] > 0); + if (--gw->tag_refcounts[tag_id] == 0) { + lgtd_info( + "deleting unused tag [%s] (%d) from gw [%s]:%hu (site %s)", + gw->tags[tag_id] ? gw->tags[tag_id]->label : NULL, tag_id, + gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) + ); + struct lgtd_lifx_packet_tag_labels pkt = { + .tags = ~(gw->tag_ids & ~LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id)) + }; + lgtd_lifx_wire_encode_tag_labels(&pkt); + lgtd_lifx_gateway_send_to_site(gw, LGTD_LIFX_SET_TAG_LABELS, &pkt); + } + } +} + void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_pan_gateway *pkt) { - (void)pkt; - assert(gw && hdr && pkt); lgtd_debug( - "SET_PAN_GATEWAY <-- [%s]:%hu - %s site=%s", - gw->ip_addr, gw->port, - lgtd_addrtoa(hdr->target.device_addr), - lgtd_addrtoa(hdr->site) + "SET_PAN_GATEWAY <-- [%s]:%hu - %s site=%s, service_type=%d", + gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), + lgtd_addrtoa(hdr->site), pkt->service_type ); } @@ -484,6 +571,23 @@ lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, lgtd_lifx_bulb_set_power_state(b, pkt->power); } +int +lgtd_lifx_gateway_get_tag_id(const struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_tag *tag) +{ + assert(gw); + assert(tag); + + int tag_id; + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, gw->tag_ids) { + if (gw->tags[tag_id] == tag) { + return tag_id; + } + } + + return -1; +} + int lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id, @@ -491,10 +595,21 @@ lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, { assert(gw); assert(tag_label); - // allocating a new tag_id (tag_id == -1) isn't supported yet: - assert(tag_id >= 0); + assert(tag_id >= -1); assert(tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); + if (tag_id == -1) { + tag_id = lgtd_lifx_wire_bitscan64_forward(~gw->tag_ids); + if (tag_id == -1) { + lgtd_warnx( + "no tag_id left for new tag [%s] on gw [%s]:%hu (site %s)", + tag_label, gw->ip_addr, gw->port, + lgtd_addrtoa(gw->site.as_array) + ); + return -1; + } + } + if (!(gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id))) { struct lgtd_lifx_tag *tag; tag = lgtd_lifx_tagging_incref(tag_label, gw, tag_id); @@ -545,9 +660,9 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, assert(gw && hdr && pkt); lgtd_debug( - "SET_TAG_LABELS <-- [%s]:%hu - %s label=%s, tags=%jx", + "SET_TAG_LABELS <-- [%s]:%hu - %s label=%.*s, tags=%jx", gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), - pkt->label, (uintmax_t)pkt->tags + LGTD_LIFX_LABEL_SIZE, pkt->label, (uintmax_t)pkt->tags ); int tag_id; @@ -559,3 +674,38 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, } } } + +void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_tags *pkt) +{ + assert(gw && hdr && pkt); + + lgtd_debug( + "SET_TAGS <-- [%s]:%hu - %s tags=%#jx", + gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), + (uintmax_t)pkt->tags + ); + + struct lgtd_lifx_bulb *b = lgtd_lifx_gateway_get_or_open_bulb( + gw, hdr->target.device_addr + ); + if (!b) { + return; + } + + int tag_id; + LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, pkt->tags) { + if (!(gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id))) { + lgtd_warnx( + "trying to set unknown tag_id %d (%#jx) " + "on bulb %s (%.*s), gw [%s]:%hu (site %s)", + tag_id, LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id), + lgtd_addrtoa(b->addr), LGTD_LIFX_LABEL_SIZE, b->state.label, + gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) + ); + } + } + + lgtd_lifx_bulb_set_tags(b, pkt->tags); +} diff --git a/lifx/gateway.h b/lifx/gateway.h index 9e472ec..69ab318 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -51,6 +51,7 @@ struct lgtd_lifx_gateway { } site; uint64_t tag_ids; struct lgtd_lifx_tag *tags[LGTD_LIFX_GATEWAY_MAX_TAGS]; + uint8_t tag_refcounts[LGTD_LIFX_GATEWAY_MAX_TAGS]; evutil_socket_t socket; // Those three timers let us measure the latency of the gateway. If we // aren't the only client on the network then this won't be accurate since @@ -84,6 +85,7 @@ struct lgtd_lifx_gateway *lgtd_lifx_gateway_open(const struct sockaddr_storage * void lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *); void lgtd_lifx_gateway_close_all(void); +void lgtd_lifx_gateway_remove_and_close_bulb(struct lgtd_lifx_gateway *, struct lgtd_lifx_bulb *); void lgtd_lifx_gateway_force_refresh(struct lgtd_lifx_gateway *); @@ -92,7 +94,14 @@ void lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *, enum lgtd_lifx_packet_type, const void *, int); +// This could be on router but it's LIFX specific so I'd rather keep it here: +bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *, + enum lgtd_lifx_packet_type, + const void *); +void lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *, uint64_t, uint64_t); + +int lgtd_lifx_gateway_get_tag_id(const struct lgtd_lifx_gateway *, const struct lgtd_lifx_tag *); int lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *, int, const char *); void lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *, int); @@ -108,3 +117,6 @@ void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *, void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, const struct lgtd_lifx_packet_tag_labels *); +void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_tags *); diff --git a/lifx/tagging.c b/lifx/tagging.c index 9be008c..864b3ed 100644 --- a/lifx/tagging.c +++ b/lifx/tagging.c @@ -65,6 +65,32 @@ lgtd_lifx_tagging_find_tag(const char *tag_label) return tag; } +struct lgtd_lifx_tag * +lgtd_lifx_tagging_allocate_tag(const char *tag_label) +{ + assert(tag_label); + assert(strlen(tag_label) < LGTD_LIFX_LABEL_SIZE); + + struct lgtd_lifx_tag *tag = calloc(1, sizeof(*tag)); + if (!tag) { + return NULL; + } + + strncpy(tag->label, tag_label, sizeof(tag->label) - 1); + LIST_INSERT_HEAD(&lgtd_lifx_tags, tag, link); + return tag; +} + +void +lgtd_lifx_tagging_deallocate_tag(struct lgtd_lifx_tag *tag) +{ + assert(tag); + assert(LIST_EMPTY(&tag->sites)); + + LIST_REMOVE(tag, link); + free(tag); +} + struct lgtd_lifx_tag * lgtd_lifx_tagging_incref(const char *tag_label, struct lgtd_lifx_gateway *gw, @@ -77,12 +103,10 @@ lgtd_lifx_tagging_incref(const char *tag_label, bool dealloc_tag = false; struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(tag_label); if (!tag) { - tag = calloc(1, sizeof(*tag)); + tag = lgtd_lifx_tagging_allocate_tag(tag_label); if (!tag) { return NULL; } - strncpy(tag->label, tag_label, sizeof(tag->label) - 1); - LIST_INSERT_HEAD(&lgtd_lifx_tags, tag, link); dealloc_tag = true; } @@ -91,8 +115,7 @@ lgtd_lifx_tagging_incref(const char *tag_label, site = calloc(1, sizeof(*site)); if (!site) { if (dealloc_tag) { - LIST_REMOVE(tag, link); - free(tag); + lgtd_lifx_tagging_deallocate_tag(tag); } errno = ENOMEM; return NULL; @@ -100,9 +123,10 @@ lgtd_lifx_tagging_incref(const char *tag_label, if (dealloc_tag) { lgtd_info("discovered tag [%s]", tag_label); } - lgtd_debug( - "tag [%s] added to gw [%s]:%hu (site %s)", - tag_label, gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) + lgtd_info( + "tag [%s] added to gw [%s]:%hu (site %s) with tag_id %d", + tag_label, gw->ip_addr, gw->port, + lgtd_addrtoa(gw->site.as_array), tag_id ); site->gw = gw; site->tag_id = tag_id; @@ -132,8 +156,7 @@ lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, free(site); } if (LIST_EMPTY(&tag->sites)) { - LIST_REMOVE(tag, link); lgtd_info("forgetting unused tag [%s]", tag->label); - free(tag); + lgtd_lifx_tagging_deallocate_tag(tag); } } diff --git a/lifx/tagging.h b/lifx/tagging.h index 680e54a..8fe7a16 100644 --- a/lifx/tagging.h +++ b/lifx/tagging.h @@ -39,3 +39,6 @@ struct lgtd_lifx_tag *lgtd_lifx_tagging_incref(const char *, void lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *, struct lgtd_lifx_gateway *); struct lgtd_lifx_tag *lgtd_lifx_tagging_find_tag(const char *); +struct lgtd_lifx_tag *lgtd_lifx_tagging_allocate_tag(const char *); + +void lgtd_lifx_tagging_deallocate_tag(struct lgtd_lifx_tag *); diff --git a/lifx/timer.c b/lifx/timer.c index c5c9376..72b4df9 100644 --- a/lifx/timer.c +++ b/lifx/timer.c @@ -95,7 +95,7 @@ lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, "closing bulb \"%.*s\" that hasn't been updated for %dms", LGTD_LIFX_LABEL_SIZE, bulb->state.label, light_state_lag ); - lgtd_lifx_bulb_close(bulb); + lgtd_lifx_gateway_remove_and_close_bulb(bulb->gw, bulb); start_discovery = true; continue; } diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 1862ce9..bcfa5be 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -92,6 +92,7 @@ lgtd_lifx_wire_load_packet_infos_map(void) .handle = lgtd_lifx_wire_null_packet_handler static struct lgtd_lifx_packet_infos packet_table[] = { + // Gateway packets: { REQUEST_ONLY, NO_PAYLOAD, @@ -106,6 +107,43 @@ lgtd_lifx_wire_load_packet_infos_map(void) .encode = ENCODER(lgtd_lifx_wire_encode_pan_gateway), .handle = HANDLER(lgtd_lifx_gateway_handle_pan_gateway) }, + { + REQUEST_ONLY, + .name = "SET_TAG_LABELS", + .type = LGTD_LIFX_SET_TAG_LABELS, + .size = sizeof(struct lgtd_lifx_packet_tag_labels), + .encode = ENCODER(lgtd_lifx_wire_encode_tag_labels) + }, + { + REQUEST_ONLY, + .name = "GET_TAG_LABELS", + .type = LGTD_LIFX_GET_TAG_LABELS, + .size = sizeof(struct lgtd_lifx_packet_tags), + .encode = ENCODER(lgtd_lifx_wire_encode_tags) + }, + { + RESPONSE_ONLY, + .name = "TAG_LABELS", + .type = LGTD_LIFX_TAG_LABELS, + .size = sizeof(struct lgtd_lifx_packet_tag_labels), + .decode = DECODER(lgtd_lifx_wire_decode_tag_labels), + .handle = HANDLER(lgtd_lifx_gateway_handle_tag_labels) + }, + // Bulb packets: + { + REQUEST_ONLY, + .name = "SET_LIGHT_COLOR", + .type = LGTD_LIFX_SET_LIGHT_COLOR, + .size = sizeof(struct lgtd_lifx_packet_light_color), + .encode = ENCODER(lgtd_lifx_wire_encode_light_color) + }, + { + REQUEST_ONLY, + .name = "SET_WAVEFORM", + .type = LGTD_LIFX_SET_WAVEFORM, + .size = sizeof(struct lgtd_lifx_packet_waveform), + .encode = ENCODER(lgtd_lifx_wire_encode_waveform) + }, { REQUEST_ONLY, NO_PAYLOAD, @@ -128,6 +166,7 @@ lgtd_lifx_wire_load_packet_infos_map(void) .type = LGTD_LIFX_SET_POWER_STATE, }, { + RESPONSE_ONLY, .name = "POWER_STATE", .type = LGTD_LIFX_POWER_STATE, .size = sizeof(struct lgtd_lifx_packet_power_state), @@ -136,32 +175,18 @@ lgtd_lifx_wire_load_packet_infos_map(void) }, { REQUEST_ONLY, - .name = "SET_LIGHT_COLOR", - .type = LGTD_LIFX_SET_LIGHT_COLOR, - .size = sizeof(struct lgtd_lifx_packet_light_color), - .encode = ENCODER(lgtd_lifx_wire_encode_light_color) - }, - { - REQUEST_ONLY, - .name = "SET_WAVEFORM", - .type = LGTD_LIFX_SET_WAVEFORM, - .size = sizeof(struct lgtd_lifx_packet_waveform), - .encode = ENCODER(lgtd_lifx_wire_encode_waveform) - }, - { - REQUEST_ONLY, - .name = "GET_TAG_LABELS", - .type = LGTD_LIFX_GET_TAG_LABELS, - .size = sizeof(struct lgtd_lifx_packet_get_tag_labels), - .encode = lgtd_lifx_wire_null_packet_encoder_decoder + .name = "SET_TAGS", + .type = LGTD_LIFX_SET_TAGS, + .size = sizeof(struct lgtd_lifx_packet_tags), + .encode = ENCODER(lgtd_lifx_wire_encode_tags) }, { RESPONSE_ONLY, - .name = "TAG_LABELS", - .type = LGTD_LIFX_TAG_LABELS, - .size = sizeof(struct lgtd_lifx_packet_tag_labels), - .decode = DECODER(lgtd_lifx_wire_decode_tag_labels), - .handle = HANDLER(lgtd_lifx_gateway_handle_tag_labels) + .name = "TAGS", + .type = LGTD_LIFX_TAGS, + .size = sizeof(struct lgtd_lifx_packet_tags), + .decode = DECODER(lgtd_lifx_wire_decode_tags), + .handle = HANDLER(lgtd_lifx_gateway_handle_tags) } }; @@ -355,6 +380,14 @@ lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) pkt->skew_ratio = htole16(pkt->skew_ratio); } +void +lgtd_lifx_wire_encode_tag_labels(struct lgtd_lifx_packet_tag_labels *pkt) +{ + assert(pkt); + + pkt->tags = htole64(pkt->tags); +} + void lgtd_lifx_wire_decode_tag_labels(struct lgtd_lifx_packet_tag_labels *pkt) { @@ -363,3 +396,19 @@ lgtd_lifx_wire_decode_tag_labels(struct lgtd_lifx_packet_tag_labels *pkt) pkt->label[sizeof(pkt->label) - 1] = '\0'; pkt->tags = le64toh(pkt->tags); } + +void +lgtd_lifx_wire_encode_tags(struct lgtd_lifx_packet_tags *pkt) +{ + assert(pkt); + + pkt->tags = htole64(pkt->tags); +} + +void +lgtd_lifx_wire_decode_tags(struct lgtd_lifx_packet_tags *pkt) +{ + assert(pkt); + + pkt->tags = le64toh(pkt->tags); +} diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 85babf6..c733e72 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -238,7 +238,7 @@ struct lgtd_lifx_packet_waveform { }; enum { LGTD_LIFX_ALL_TAGS = ~0 }; -struct lgtd_lifx_packet_get_tag_labels { +struct lgtd_lifx_packet_tags { uint64le_t tags; }; @@ -350,4 +350,7 @@ void lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *); void lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *); void lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *); +void lgtd_lifx_wire_encode_tags(struct lgtd_lifx_packet_tags *); +void lgtd_lifx_wire_decode_tags(struct lgtd_lifx_packet_tags *); +void lgtd_lifx_wire_encode_tag_labels(struct lgtd_lifx_packet_tag_labels *); void lgtd_lifx_wire_decode_tag_labels(struct lgtd_lifx_packet_tag_labels *); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c new file mode 100644 index 0000000..10cc9c9 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c @@ -0,0 +1,65 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#include "mock_gateway.h" + +#define MOCKED_LGTD_TAG +#include "test_jsonrpc_utils.h" + +static bool tag_called = false; + +void +lgtd_proto_tag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (strcmp(tag, "suspensions")) { + errx(1, "Invalid tag [%s] (expected=[suspensions])", tag); + } + + tag_called = true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"tag\"," + "\"params\": {\"target\": \"*\", \"tag\": \"suspensions\"}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_proto_tag_or_untag(&client, lgtd_proto_tag); + + if (!tag_called) { + errx(1, "lgtd_proto_tag wasn't called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c new file mode 100644 index 0000000..138a8ad --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c @@ -0,0 +1,53 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#include "mock_gateway.h" + +#define MOCKED_LGTD_TAG +#include "test_jsonrpc_utils.h" + +static bool tag_called = false; + +void +lgtd_proto_tag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag) +{ + (void)client; + (void)targets; + (void)tag; + tag_called = true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"tag\"," + "\"params\": {\"tag\": \"suspensions\"}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_proto_tag_or_untag(&client, lgtd_proto_tag); + + if (tag_called) { + errx(1, "lgtd_proto_tag was called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c new file mode 100644 index 0000000..eb078bf --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c @@ -0,0 +1,65 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#include "mock_gateway.h" + +#define MOCKED_LGTD_UNTAG +#include "test_jsonrpc_utils.h" + +static bool untag_called = false; + +void +lgtd_proto_untag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "#suspensions")) { + errx( + 1, "Invalid target [%s] (expected=[#suspensions])", + SLIST_FIRST(targets)->target + ); + } + + if (strcmp(tag, "suspensions")) { + errx(1, "Invalid tag [%s] (expected=[suspensions])", tag); + } + + untag_called = true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"tag\"," + "\"params\": [[\"#suspensions\"], \"suspensions\"]," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_proto_tag_or_untag(&client, lgtd_proto_untag); + + if (!untag_called) { + errx(1, "lgtd_proto_tag wasn't called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c new file mode 100644 index 0000000..153e876 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c @@ -0,0 +1,53 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#include "mock_gateway.h" + +#define MOCKED_LGTD_UNTAG +#include "test_jsonrpc_utils.h" + +static bool untag_called = false; + +void +lgtd_proto_untag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag) +{ + (void)client; + (void)targets; + (void)tag; + untag_called = true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"tag\"," + "\"params\": [[\"#suspensions\"], [\"suspensions\"]]," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_proto_tag_or_untag(&client, lgtd_proto_untag); + + if (untag_called) { + errx(1, "lgtd_proto_tag was called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index a77335a..5d29c8e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -97,3 +97,27 @@ lgtd_proto_get_light_state(struct lgtd_client *client, (void)targets; } #endif + +#ifndef MOCKED_LGTD_TAG +void +lgtd_proto_tag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag_label) +{ + (void)client; + (void)targets; + (void)tag_label; +} +#endif + +#ifndef MOCKED_LGTD_UNTAG +void +lgtd_proto_untag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag_label) +{ + (void)client; + (void)targets; + (void)tag_label; +} +#endif diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c new file mode 100644 index 0000000..8cd4235 --- /dev/null +++ b/tests/core/proto/test_proto_tag_create.c @@ -0,0 +1,253 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#define MOCKED_LIFX_GATEWAY_SEND_TO_SITE +#define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID +#include "mock_gateway.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_SEND_TO_DEVICE +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +#define FAKE_TARGET_LIST (void *)0x2a + +static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); +static struct lgtd_router_device_list device_1_only = + SLIST_HEAD_INITIALIZER(&device_1_only); + +static bool send_to_device_called = false; + +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (!bulb) { + errx(1, "lgtd_router_send_to_device must be called with a bulb"); + } + + uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { 1, 2, 3, 4, 5 }; + if (memcmp(bulb->addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { + errx( + 1, "got bulb with addr %s (expected %s)", + lgtd_addrtoa(bulb->addr), lgtd_addrtoa(expected_addr) + ); + } + + if (pkt_type != LGTD_LIFX_SET_TAGS) { + errx( + 1, "got packet type %d (expected %d)", pkt_type, LGTD_LIFX_SET_TAGS + ); + } + + if (!pkt) { + errx(1, "missing SET_TAGS payload"); + } + + const struct lgtd_lifx_packet_tags *pkt_tags = pkt; + uint64_t tags = le64toh(pkt_tags->tags); + if (tags != 0x1) { + errx( + 1, "invalid SET_TAGS payload=%#jx (expected %#x)", + (uintmax_t)tags, 0x1 + ); + } + + send_to_device_called = true; +} + +static bool gateway_send_to_site_called = false; + +bool +lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt) +{ + if (!gw) { + errx(1, "missing gateway"); + } + + if (pkt_type != LGTD_LIFX_SET_TAG_LABELS) { + errx( + 1, "got packet type %#x (expected %#x)", + pkt_type, LGTD_LIFX_SET_TAG_LABELS + ); + } + + const struct lgtd_lifx_packet_tag_labels *pkt_tag_labels = pkt; + uint64_t tags = le64toh(pkt_tag_labels->tags); + if (tags != 0x1) { + errx(1, "got tags %#jx (expected %#x)", (uintmax_t)tags, 0x1); + } + + if (strcmp(pkt_tag_labels->label, "dub")) { + errx(1, "got label %s (expected dub)", pkt_tag_labels->label); + } + + gateway_send_to_site_called = true; + + return true; +} + +static bool gateway_allocate_tag_id_called = false; + +int +lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, + int tag_id, + const char *tag_label) +{ + if (gateway_allocate_tag_id_called) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "should have been called once only" + ); + } + + if (tag_id != -1) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "tag_id %d (expected -1)", tag_id + ); + } + + if (!gw) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "must be called with gateway" + ); + } + + if (!tag_label) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "must be called with a tag_label" + ); + } + + tag_id = 0; + + struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(tag_label); + if (!tag) { + errx(1, "tag %s wasn't found", tag_label); + } + lgtd_tests_add_tag_to_gw(tag, gw, tag_id); + + gateway_allocate_tag_id_called = true; + + return tag_id; +} + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != FAKE_TARGET_LIST) { + lgtd_errx(1, "unexpected targets list"); + } + + return &device_1_only; +} + +static void +setup_devices(void) +{ + static struct lgtd_lifx_gateway gw_bulb_1 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + }; + static struct lgtd_lifx_bulb bulb_1 = { + .addr = { 1, 2, 3, 4, 5 }, + .state = { + .hue = 0xaaaa, + .saturation = 0xffff, + .brightness = 0xbbbb, + .kelvin = 3600, + .label = "wave", + .power = LGTD_LIFX_POWER_ON, + .tags = 0 + }, + .gw = &gw_bulb_1 + }; + static struct lgtd_router_device device_1 = { .device = &bulb_1 }; + SLIST_INSERT_HEAD(&devices, &device_1, link); + SLIST_INSERT_HEAD(&device_1_only, &device_1, link); + + struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor"); + struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b"); + struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~"); + static struct lgtd_lifx_gateway gw_bulb_2 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), + .tag_ids = 0x7 + }; + lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0); + lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); + lgtd_tests_add_tag_to_gw(gw_2_tag_3, &gw_bulb_2, 2); + static struct lgtd_lifx_bulb bulb_2 = { + .addr = { 5, 4, 3, 2, 1 }, + .state = { + .hue = 0x0000, + .saturation = 0x0000, + .brightness = 0xffff, + .kelvin = 4000, + .label = "", + .power = LGTD_LIFX_POWER_OFF, + .tags = 0x3 + }, + .gw = &gw_bulb_2 + }; + static struct lgtd_router_device device_2 = { .device = &bulb_2 }; + SLIST_INSERT_HEAD(&devices, &device_2, link); +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + setup_devices(); + + lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub"); + + const char expected[] = "true"; + if (client_write_buf_idx != sizeof(expected) - 1) { + lgtd_errx( + 1, + "%d bytes written, expected %lu " + "(got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected) - 1UL, + client_write_buf_idx, client_write_buf, expected + ); + } + if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected + ); + } + + if (!gateway_send_to_site_called) { + lgtd_errx(1, "SET_TAG_LABELS wasn't sent"); + } + if (!device_list_free_called) { + lgtd_errx(1, "the list of devices hasn't been freed"); + } + if (!send_to_device_called) { + lgtd_errx(1, "SET_TAGS wasn't send to any device"); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c new file mode 100644 index 0000000..aeba5dd --- /dev/null +++ b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c @@ -0,0 +1,209 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#define MOCKED_LIFX_GATEWAY_SEND_TO_SITE +#define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID +#include "mock_gateway.h" +#include "tests_utils.h" + +#define MOCKED_CLIENT_SEND_ERROR +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_SEND_TO_DEVICE +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +#define FAKE_TARGET_LIST (void *)0x2a + +static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); +static struct lgtd_router_device_list device_1_only = + SLIST_HEAD_INITIALIZER(&device_1_only); + +static bool client_send_error_called = false; + +void +lgtd_client_send_error(struct lgtd_client *client, + enum lgtd_client_error_code error, + const char *msg) +{ + if (!client) { + errx(1, "client_send_error called without a client"); + } + + if (!error) { + errx(1, "client_send_error called without an error code"); + } + + if (!msg) { + errx(1, "client_send_error called without an error message"); + } + + client_send_error_called = true; +} + +static bool send_to_device_called = false; + +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)bulb; + (void)pkt_type; + (void)pkt; + + send_to_device_called = true; +} + +static bool gateway_send_to_site_called = false; + +bool +lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt) +{ + (void)gw; + (void)pkt_type; + (void)pkt; + + gateway_send_to_site_called = true; + + return true; +} + +static bool gateway_allocate_tag_id_called = false; + +int +lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, + int tag_id, + const char *tag_label) +{ + if (gateway_allocate_tag_id_called) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "should have been called once only" + ); + } + + if (tag_id != -1) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "tag_id %d (expected -1)", tag_id + ); + } + + if (!gw) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "must be called with gateway" + ); + } + + if (!tag_label) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "must be called with a tag_label" + ); + } + + return -1; // no more tag id available +} + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != FAKE_TARGET_LIST) { + lgtd_errx(1, "unexpected targets list"); + } + + return &device_1_only; +} + +static void +setup_devices(void) +{ + static struct lgtd_lifx_gateway gw_bulb_1 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + }; + static struct lgtd_lifx_bulb bulb_1 = { + .addr = { 1, 2, 3, 4, 5 }, + .state = { + .hue = 0xaaaa, + .saturation = 0xffff, + .brightness = 0xbbbb, + .kelvin = 3600, + .label = "wave", + .power = LGTD_LIFX_POWER_ON, + .tags = 0 + }, + .gw = &gw_bulb_1 + }; + static struct lgtd_router_device device_1 = { .device = &bulb_1 }; + SLIST_INSERT_HEAD(&devices, &device_1, link); + SLIST_INSERT_HEAD(&device_1_only, &device_1, link); + + struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor"); + struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b"); + struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~"); + static struct lgtd_lifx_gateway gw_bulb_2 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), + .tag_ids = 0x7 + }; + lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0); + lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); + lgtd_tests_add_tag_to_gw(gw_2_tag_3, &gw_bulb_2, 2); + static struct lgtd_lifx_bulb bulb_2 = { + .addr = { 5, 4, 3, 2, 1 }, + .state = { + .hue = 0x0000, + .saturation = 0x0000, + .brightness = 0xffff, + .kelvin = 4000, + .label = "", + .power = LGTD_LIFX_POWER_OFF, + .tags = 0x3 + }, + .gw = &gw_bulb_2 + }; + static struct lgtd_router_device device_2 = { .device = &bulb_2 }; + SLIST_INSERT_HEAD(&devices, &device_2, link); +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + setup_devices(); + + lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub"); + + + if (gateway_send_to_site_called) { + lgtd_errx(1, "SET_TAG_LABELS shouldn't have been sent"); + } + if (!device_list_free_called) { + lgtd_errx(1, "the list of devices hasn't been freed"); + } + if (send_to_device_called) { + lgtd_errx(1, "SET_TAGS shouldn't have been to any device"); + } + if (!client_send_error_called) { + lgtd_errx(1, "client_send_error should have been called"); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c new file mode 100644 index 0000000..069c509 --- /dev/null +++ b/tests/core/proto/test_proto_tag_update.c @@ -0,0 +1,283 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#define MOCKED_LIFX_GATEWAY_SEND_TO_SITE +#define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID +#include "mock_gateway.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_SEND_TO_DEVICE +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +#define FAKE_TARGET_LIST (void *)0x2a + +static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); + +static bool send_to_device_called = false; + +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (send_to_device_called) { + errx(1, "lgtd_router_send_to_device should have been called once only"); + } + + if (!bulb) { + errx(1, "lgtd_router_send_to_device must be called with a bulb"); + } + + uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { 5, 4, 3, 2, 1 }; + if (memcmp(bulb->addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { + errx( + 1, "got bulb with addr %s (expected %s)", + lgtd_addrtoa(bulb->addr), lgtd_addrtoa(expected_addr) + ); + } + + if (pkt_type != LGTD_LIFX_SET_TAGS) { + errx( + 1, "got packet type %d (expected %d)", pkt_type, LGTD_LIFX_SET_TAGS + ); + } + + if (!pkt) { + errx(1, "missing SET_TAGS payload"); + } + + const struct lgtd_lifx_packet_tags *pkt_tags = pkt; + uint64_t tags = le64toh(pkt_tags->tags); + + if (tags != 0x7) { + errx( + 1, "invalid SET_TAGS payload=%#jx (expected %#x)", + (uintmax_t)tags, 0x7 + ); + } + + send_to_device_called = true; +} + +static bool gateway_send_to_site_called_for_gw_1 = false; +static bool gateway_send_to_site_called_for_gw_2 = false; + +bool +lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, + enum lgtd_lifx_packet_type pkt_type, + const void *pkt) +{ + if (!gw) { + errx(1, "missing gateway"); + } + + if (pkt_type != LGTD_LIFX_SET_TAG_LABELS) { + errx( + 1, "got packet type %#x (expected %#x)", + pkt_type, LGTD_LIFX_SET_TAG_LABELS + ); + } + + const struct lgtd_lifx_packet_tag_labels *pkt_tag_labels = pkt; + uint64_t tags = le64toh(pkt_tag_labels->tags); + + if (strcmp(pkt_tag_labels->label, "dub")) { + errx(1, "got label %s (expected dub)", pkt_tag_labels->label); + } + + if (gw->site.as_integer == 42) { + if (tags != 0x1) { + errx(1, "got tags %#jx (expected %#x)", (uintmax_t)tags, 0x1); + } + if (gateway_send_to_site_called_for_gw_1) { + errx(1, "LGTD_LIFX_SET_TAG_LABELS already called for gw 1"); + } + gateway_send_to_site_called_for_gw_1 = true; + } else if (gw->site.as_integer == 44) { + if (tags != 0x4) { + errx(1, "got tags %#jx (expected %#x)", (uintmax_t)tags, 0x4); + } + if (gateway_send_to_site_called_for_gw_2) { + errx(1, "LGTD_LIFX_SET_TAG_LABELS already called for gw 2"); + } + gateway_send_to_site_called_for_gw_2 = true; + } else { + errx(1, "LGTD_LIFX_SET_TAG_LABELS received an invalid gateway"); + } + + return true; +} + +static bool gateway_allocate_tag_id_called = false; + +int +lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, + int tag_id, + const char *tag_label) +{ + if (gateway_allocate_tag_id_called) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "should have been called once only" + ); + } + + if (tag_id != -1) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "tag_id %d (expected -1)", tag_id + ); + } + + if (!gw) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "must be called with gateway" + ); + } + + if (!tag_label) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id " + "must be called with a tag_label" + ); + } + + if (gw->site.as_integer != 44) { + errx( + 1, "lgtd_lifx_gateway_allocate_tag_id got the wrong gateway " + "%#jx (expected %d)", (uintmax_t)gw->site.as_integer, 44 + ); + } + + tag_id = 2; + + struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(tag_label); + if (!tag) { + errx(1, "tag %s wasn't found", tag_label); + } + lgtd_tests_add_tag_to_gw(tag, gw, tag_id); + + gateway_allocate_tag_id_called = true; + + return tag_id; +} + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != FAKE_TARGET_LIST) { + lgtd_errx(1, "unexpected targets list"); + } + + return &devices; +} + +static void +setup_devices(void) +{ + static struct lgtd_lifx_gateway gw_bulb_1 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs), + .site = { .as_integer = 42 } + }; + static struct lgtd_lifx_bulb bulb_1 = { + .addr = { 1, 2, 3, 4, 5 }, + .state = { + .hue = 0xaaaa, + .saturation = 0xffff, + .brightness = 0xbbbb, + .kelvin = 3600, + .label = "wave", + .power = LGTD_LIFX_POWER_ON, + .tags = 1 + }, + .gw = &gw_bulb_1 + }; + static struct lgtd_router_device device_1 = { .device = &bulb_1 }; + SLIST_INSERT_HEAD(&devices, &device_1, link); + struct lgtd_lifx_tag *gw_1_tag_1 = lgtd_tests_insert_mock_tag("dub"); + lgtd_tests_add_tag_to_gw(gw_1_tag_1, &gw_bulb_1, 0); + + struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor"); + struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b"); + static struct lgtd_lifx_gateway gw_bulb_2 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), + .site = { .as_integer = 44 }, + .tag_ids = 0x3 + }; + lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0); + lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); + static struct lgtd_lifx_bulb bulb_2 = { + .addr = { 5, 4, 3, 2, 1 }, + .state = { + .hue = 0x0000, + .saturation = 0x0000, + .brightness = 0xffff, + .kelvin = 4000, + .label = "", + .power = LGTD_LIFX_POWER_OFF, + .tags = 0x3 + }, + .gw = &gw_bulb_2 + }; + static struct lgtd_router_device device_2 = { .device = &bulb_2 }; + SLIST_INSERT_HEAD(&devices, &device_2, link); +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + setup_devices(); + + lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub"); + + const char expected[] = "true"; + if (client_write_buf_idx != sizeof(expected) - 1) { + lgtd_errx( + 1, + "%d bytes written, expected %lu " + "(got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected) - 1UL, + client_write_buf_idx, client_write_buf, expected + ); + } + if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected + ); + } + + if (!gateway_send_to_site_called_for_gw_1) { + lgtd_errx(1, "SET_TAG_LABELS wasn't sent to gw 1"); + } + if (!gateway_send_to_site_called_for_gw_2) { + lgtd_errx(1, "SET_TAG_LABELS wasn't sent to gw 2"); + } + if (!device_list_free_called) { + lgtd_errx(1, "the list of devices hasn't been freed"); + } + if (!send_to_device_called) { + lgtd_errx(1, "SET_TAGS wasn't send to any device"); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c new file mode 100644 index 0000000..7a73f27 --- /dev/null +++ b/tests/core/proto/test_proto_untag.c @@ -0,0 +1,170 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_SEND_TO_DEVICE +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (device_list_free_called) { + errx(1, "the device list should have been freed once"); + } + + if (!devices) { + errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +static struct lgtd_lifx_tag *tag_vapor = NULL; + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != (void *)0x2a) { + lgtd_errx(1, "unexpected targets list"); + } + + static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); + + static struct lgtd_lifx_gateway gw_bulb_1 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + }; + static struct lgtd_lifx_bulb bulb_1 = { + .addr = { 1, 2, 3, 4, 5 }, + .state = { + .hue = 0xaaaa, + .saturation = 0xffff, + .brightness = 0xbbbb, + .kelvin = 3600, + .label = "wave", + .power = LGTD_LIFX_POWER_ON, + .tags = 0 + }, + .gw = &gw_bulb_1 + }; + static struct lgtd_router_device device_1 = { .device = &bulb_1 }; + SLIST_INSERT_HEAD(&devices, &device_1, link); + + struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b"); + struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~"); + static struct lgtd_lifx_gateway gw_bulb_2 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), + .tag_ids = 0x7 + }; + lgtd_tests_add_tag_to_gw(tag_vapor, &gw_bulb_2, 0); + lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); + lgtd_tests_add_tag_to_gw(gw_2_tag_3, &gw_bulb_2, 2); + static struct lgtd_lifx_bulb bulb_2 = { + .addr = { 5, 4, 3, 2, 1 }, + .state = { + .hue = 0x0000, + .saturation = 0x0000, + .brightness = 0xffff, + .kelvin = 4000, + .label = "", + .power = LGTD_LIFX_POWER_OFF, + .tags = 0x3 + }, + .gw = &gw_bulb_2 + }; + static struct lgtd_router_device device_2 = { .device = &bulb_2 }; + SLIST_INSERT_HEAD(&devices, &device_2, link); + + return &devices; +} + +static bool send_to_device_called = false; + +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (send_to_device_called) { + errx(1, "lgtd_router_send_to_device should have been called once"); + } + + if (!bulb) { + errx(1, "lgtd_router_send_to_device must be called with a bulb"); + } + + uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { 5, 4, 3, 2, 1 }; + if (memcmp(bulb->addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { + errx( + 1, "got bulb with addr %s (expected %s)", + lgtd_addrtoa(bulb->addr), lgtd_addrtoa(expected_addr) + ); + } + + if (pkt_type != LGTD_LIFX_SET_TAGS) { + errx( + 1, "got packet type %d (expected %d)", pkt_type, LGTD_LIFX_SET_TAGS + ); + } + + if (!pkt) { + errx(1, "missing SET_TAGS payload"); + } + + struct lgtd_lifx_packet_tags *pkt_tags = pkt; + if (pkt_tags->tags != 0x2) { + errx( + 1, "invalid SET_TAGS payload=%#jx (expected %#x)", + (uintmax_t)pkt_tags->tags, 0x2 + ); + } + + send_to_device_called = true; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + struct lgtd_proto_target_list *targets = (void *)0x2a; + + tag_vapor = lgtd_tests_insert_mock_tag("vapor"); + + lgtd_proto_untag(&client, targets, "vapor"); + + const char expected[] = "true"; + + if (client_write_buf_idx != sizeof(expected) - 1) { + lgtd_errx( + 1, + "%d bytes written, expected %lu " + "(got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected) - 1UL, + client_write_buf_idx, client_write_buf, expected + ); + } + + if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected + ); + } + + if (!device_list_free_called) { + lgtd_errx(1, "the list of devices hasn't been freed"); + } + if (!send_to_device_called) { + lgtd_errx(1, "nothing was send to any device"); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_untag_tag_does_not_exist.c b/tests/core/proto/test_proto_untag_tag_does_not_exist.c new file mode 100644 index 0000000..485cfe6 --- /dev/null +++ b/tests/core/proto/test_proto_untag_tag_does_not_exist.c @@ -0,0 +1,90 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_SEND_TO_DEVICE +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + (void)devices; + + device_list_free_called = true; +} + +static bool targets_to_devices_called = false; + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + (void)targets; + + targets_to_devices_called = true; + + static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); + + return &devices; +} + +static bool send_to_device_called = false; + +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)bulb; + (void)pkt_type; + (void)pkt; + send_to_device_called = true; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + lgtd_proto_untag(&client, targets, "vapor"); + + const char expected[] = "true"; + + if (client_write_buf_idx != sizeof(expected) - 1) { + lgtd_errx( + 1, + "%d bytes written, expected %lu " + "(got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected) - 1UL, + client_write_buf_idx, client_write_buf, expected + ); + } + if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected + ); + } + + if (targets_to_devices_called) { + lgtd_errx(1, "unexpected call to targets_to_devices"); + } + if (device_list_free_called) { + lgtd_errx(1, "nothing should have been freed"); + } + if (send_to_device_called) { + lgtd_errx(1, "nothing should have been sent to any device"); + } + + return 0; +} diff --git a/tests/core/proto/tests_proto_utils.h b/tests/core/proto/tests_proto_utils.h index c332c63..72913ee 100644 --- a/tests/core/proto/tests_proto_utils.h +++ b/tests/core/proto/tests_proto_utils.h @@ -36,6 +36,18 @@ lgtd_client_send_error(struct lgtd_client *client, } #endif +#ifndef MOCKED_ROUTER_SEND_TO_DEVICE +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)bulb; + (void)pkt_type; + (void)pkt; +} +#endif + #ifndef MOCKED_ROUTER_SEND bool lgtd_router_send(const struct lgtd_proto_target_list *targets, diff --git a/tests/core/tests_shims.h b/tests/core/tests_shims.h new file mode 100644 index 0000000..c3e9da1 --- /dev/null +++ b/tests/core/tests_shims.h @@ -0,0 +1,23 @@ +#pragma once + +struct lgtd_opts lgtd_opts = { + .foreground = false, + .log_timestamps = false, + .verbosity = LGTD_DEBUG +}; + +struct event_base *lgtd_ev_base = NULL; + +const char *lgtd_binds = NULL; + +void +lgtd_cleanup(void) +{ +} + +#ifndef MOCKED_DAEMON_UPDATE_PROCTITLE +void +lgtd_daemon_update_proctitle(void) +{ +} +#endif diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index 0fa9010..9e49bf9 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -118,8 +118,11 @@ lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *tag, struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); site->gw = gw; site->tag_id = tag_id; - gw->tags[tag_id] = tag; LIST_INSERT_HEAD(&tag->sites, site, link); + + gw->tags[tag_id] = tag; + gw->tag_ids |= LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); + return site; } diff --git a/tests/lifx/bulb/CMakeLists.txt b/tests/lifx/bulb/CMakeLists.txt new file mode 100644 index 0000000..530e5d1 --- /dev/null +++ b/tests/lifx/bulb/CMakeLists.txt @@ -0,0 +1,29 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_CORE_LIBRARY( + test_lifx_bulb_core STATIC + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/router.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c +) + +ADD_LIBRARY( + test_lifx_bulb STATIC + ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c +) + +FUNCTION(ADD_BULB_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES( + ${TEST_SOURCE} test_lifx_bulb_core test_lifx_bulb + ) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_BULB_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/lifx/bulb/test_bulb_close.c b/tests/lifx/bulb/test_bulb_close.c new file mode 100644 index 0000000..315f8fd --- /dev/null +++ b/tests/lifx/bulb/test_bulb_close.c @@ -0,0 +1,33 @@ +#include "bulb.c" + +#include "mock_gateway.h" + +int +main(void) +{ + struct lgtd_lifx_gateway gw; + uint8_t bulb_addr[LGTD_LIFX_ADDR_LENGTH] = { 5, 4, 3, 2, 1, 0 }; + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_open(&gw, bulb_addr); + + bulb->state.power = LGTD_LIFX_POWER_ON; + LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(bulbs_powered_on, 1); + + lgtd_lifx_bulb_close(bulb); + + if (!RB_EMPTY(&lgtd_lifx_bulbs_table)) { + errx(1, "The bulbs table should be empty!"); + } + + if (LGTD_STATS_GET(bulbs) != 0) { + errx(1, "The bulbs counter is %d (expected 0)", LGTD_STATS_GET(bulbs)); + } + + if (LGTD_STATS_GET(bulbs_powered_on) != 0) { + errx( + 1, "The powered on bulbs counter is %d (expected 0)", + LGTD_STATS_GET(bulbs_powered_on) + ); + } + + return 0; +} diff --git a/tests/lifx/bulb/test_bulb_open.c b/tests/lifx/bulb/test_bulb_open.c new file mode 100644 index 0000000..3695e3f --- /dev/null +++ b/tests/lifx/bulb/test_bulb_open.c @@ -0,0 +1,44 @@ +#include "bulb.c" + +#include "mock_gateway.h" + +int +main(void) +{ + struct lgtd_lifx_gateway gw; + uint8_t bulb_addr[LGTD_LIFX_ADDR_LENGTH] = { 5, 4, 3, 2, 1, 0 }; + lgtd_time_mono_t now = lgtd_time_monotonic_msecs(); + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_open(&gw, bulb_addr); + + if (!bulb) { + errx(1, "lgtd_lifx_bulb_open didn't return any bulb"); + } + + if (memcmp(bulb->addr, bulb_addr, LGTD_LIFX_ADDR_LENGTH)) { + errx( + 1, "got bulb addr %s (expected %s)", + lgtd_addrtoa(bulb->addr), lgtd_addrtoa(bulb_addr) + ); + } + + if (bulb->gw != &gw) { + errx(1, "got bulb gateway %p (expected %p)", bulb->gw, &gw); + } + + if (lgtd_lifx_bulb_get(bulb_addr) != bulb) { + errx(1, "the new bulb can't be found"); + } + + if (bulb->last_light_state_at < now) { + errx( + 1, "got bulb->last_light_state_at %ju (expected >= %ju)", + (uintmax_t)bulb->last_light_state_at, (uintmax_t)now + ); + } + + if (LGTD_STATS_GET(bulbs) != 1) { + errx(1, "bulbs counter is %d (expected 1)", LGTD_STATS_GET(bulbs)); + } + + return 0; +} diff --git a/tests/lifx/bulb/test_bulb_set_light_state.c b/tests/lifx/bulb/test_bulb_set_light_state.c new file mode 100644 index 0000000..6e080cd --- /dev/null +++ b/tests/lifx/bulb/test_bulb_set_light_state.c @@ -0,0 +1,92 @@ +#include "bulb.c" + +#define MOCKED_LGTD_LIFX_GATEWAY_UPDATE_TAG_REFCOUNTS +#include "mock_gateway.h" + +static int update_tag_refcouts_call_counts = 0; + +void +lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, + uint64_t bulb_tags, + uint64_t pkt_tags) +{ + if (gw != (void *)0xdeaf) { + errx(1, "got wrong gw %p (expected 0xdeaf)", gw); + } + + if (pkt_tags != 0xfeed) { + errx(1, "got pkt_tags %#jx (expected 0xfeed)", (uintmax_t)pkt_tags); + } + + if (!update_tag_refcouts_call_counts) { + if (bulb_tags != 0x2a) { + errx(1, "got bulb_tags %#jx (expected 0x2a)", (uintmax_t)bulb_tags); + } + } else { + if (bulb_tags != 0xfeed) { + errx(1, "got bulb_tags %#jx (expected 0xfeed)", (uintmax_t)bulb_tags); + } + } + + update_tag_refcouts_call_counts++; +} + +int +main(void) +{ + struct lgtd_lifx_bulb bulb = { + .state = { + .hue = 54321, + .brightness = UINT16_MAX, + .kelvin = 12345, + .dim = 808, + .power = LGTD_LIFX_POWER_OFF, + .label = "lair", + .tags = 0x2a + }, + .gw = (void *)0xdeaf + }; + + struct lgtd_lifx_light_state new_state = { + .hue = 22222, + .brightness = UINT16_MAX / 2, + .kelvin = 54321, + .dim = 303, + .power = LGTD_LIFX_POWER_ON, + .label = "caverne", + .tags = 0xfeed + }; + + lgtd_lifx_bulb_set_light_state(&bulb, &new_state, 2015); + if (memcmp(&bulb.state, &new_state, sizeof(new_state))) { + errx(1, "new light state incorrectly set"); + } + if (LGTD_STATS_GET(bulbs_powered_on) != 1) { + errx( + 1, "unexpected bulbs_powered_on counter value %d (expected 1)", + LGTD_STATS_GET(bulbs_powered_on) + ); + } + if (bulb.last_light_state_at != 2015) { + errx( + 1, "got bulb.last_light_state = %jx (expected 2015)", + (uintmax_t)bulb.last_light_state_at + ); + } + if (update_tag_refcouts_call_counts != 1) { + errx(1, "lgtd_lifx_gateway_update_tag_refcounts wasn't called"); + } + + lgtd_lifx_bulb_set_light_state(&bulb, &new_state, 2015); + if (update_tag_refcouts_call_counts != 2) { + errx(1, "lgtd_lifx_gateway_update_tag_refcounts wasn't called"); + } + if (LGTD_STATS_GET(bulbs_powered_on) != 1) { + errx( + 1, "unexpected bulbs_powered_on counter value %d (expected 1)", + LGTD_STATS_GET(bulbs_powered_on) + ); + } + + return 0; +} diff --git a/tests/lifx/bulb/test_bulb_set_power_state.c b/tests/lifx/bulb/test_bulb_set_power_state.c new file mode 100644 index 0000000..09ff816 --- /dev/null +++ b/tests/lifx/bulb/test_bulb_set_power_state.c @@ -0,0 +1,39 @@ +#include "bulb.c" + +#include "mock_gateway.h" + +int +main(void) +{ + struct lgtd_lifx_bulb bulb = { + .state = { + .hue = 54321, + .brightness = UINT16_MAX, + .kelvin = 12345, + .dim = 808, + .power = LGTD_LIFX_POWER_OFF, + .label = "lair", + .tags = 0x2a + }, + .gw = (void *)0xdeaf + }; + struct lgtd_lifx_light_state new_state; + memcpy(&new_state, &bulb.state, sizeof(new_state)); + new_state.power = LGTD_LIFX_POWER_ON; + + + for (int i = 0; i != 2; i++) { + lgtd_lifx_bulb_set_power_state(&bulb, LGTD_LIFX_POWER_ON); + if (memcmp(&bulb.state, &new_state, sizeof(new_state))) { + errx(1, "new light state incorrectly set"); + } + if (LGTD_STATS_GET(bulbs_powered_on) != 1) { + errx( + 1, "unexpected bulbs_powered_on counter value %d (expected 1)", + LGTD_STATS_GET(bulbs_powered_on) + ); + } + } + + return 0; +} diff --git a/tests/lifx/bulb/test_bulb_set_tags.c b/tests/lifx/bulb/test_bulb_set_tags.c new file mode 100644 index 0000000..e6968bd --- /dev/null +++ b/tests/lifx/bulb/test_bulb_set_tags.c @@ -0,0 +1,50 @@ +#include "bulb.c" + +#define MOCKED_LGTD_LIFX_GATEWAY_UPDATE_TAG_REFCOUNTS +#include "mock_gateway.h" + +static bool update_tag_refcouts_called = false; + +void +lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, + uint64_t bulb_tags, + uint64_t pkt_tags) +{ + if (gw != (void *)0xdeaf) { + errx(1, "got wrong gw %p (expected 0xdeaf)", gw); + } + + if (bulb_tags != 0x2a) { + errx(1, "got bulb_tags %#jx (expected 0x2a)", (uintmax_t)bulb_tags); + } + + if (pkt_tags != 0xfeed) { + errx(1, "got pkt_tags %#jx (expected 0xfeed)", (uintmax_t)pkt_tags); + } + + update_tag_refcouts_called = true; +} + +int +main(void) +{ + struct lgtd_lifx_bulb bulb = { + .state = { .tags = 0x2a }, + .gw = (void *)0xdeaf + }; + + lgtd_lifx_bulb_set_tags(&bulb, 0xfeed); + + if (bulb.state.tags != 0xfeed) { + errx( + 1, "got bulb.state.tags = %#jx (expected 0xfeed)", + (uintmax_t)bulb.state.tags + ); + } + + if (!update_tag_refcouts_called) { + errx(1, "lgtd_lifx_gateway_update_tag_refcounts wasn't called"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id.c b/tests/lifx/gateway/test_gateway_allocate_tag_id.c new file mode 100644 index 0000000..b74edc3 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id.c @@ -0,0 +1,102 @@ +#include "gateway.c" + +#include + +#define MOCKED_LIFX_TAGGING_INCREF +#include "test_gateway_utils.h" + +static bool tagging_incref_called = false; + +struct lgtd_lifx_tag * +lgtd_lifx_tagging_incref(const char *label, + struct lgtd_lifx_gateway *gw, + int tag_id) +{ + if (!label) { + errx(1, "missing tag label"); + } + if (!gw) { + errx(1, "missing gateway"); + } + if (tag_id > 2) { + errx(1, "got tag_id %d but expected < 3", tag_id); + } + + struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(label); + if (!tag) { + tag = calloc(1, sizeof(*tag)); + strcpy(tag->label, label); + struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); + site->gw = gw; + site->tag_id = tag_id; + LIST_INSERT_HEAD(&tag->sites, site, link); + } + + tagging_incref_called = true; + + return tag; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + + uint64_t expected_tag_ids = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(0); + + lgtd_lifx_gateway_allocate_tag_id(&gw, 0, "test"); + if (!gw.tags[0]) { + errx(1, "gw.tag_ids[0] shouldn't be NULL"); + } + if (strcmp(gw.tags[0]->label, "test")) { + errx( + 1, "unexpected tag %.*s (expected test)", + (int)sizeof(gw.tags[0]->label), gw.tags[0]->label + ); + } + if (gw.tag_ids != LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(0)) { + errx( + 1, "tag_ids = %jx (expected %jx)", + (uintmax_t)gw.tag_ids, (uintmax_t)expected_tag_ids + ); + } + if (!tagging_incref_called) { + errx(1, "lgtd_lifx_tagging_incref should have been called"); + } + tagging_incref_called = false; + + for (int i = 1; i != 3; i++) { + int tag_id = lgtd_lifx_gateway_allocate_tag_id(&gw, -1, "lounge"); + if (tag_id < 1) { + errx(1, "no tag_id was allocated (received tag_id %d)", tag_id); + } + if (!gw.tags[tag_id]) { + errx(1, "gw.tag_ids[%d] shouldn't be NULL", i); + } + if (strcmp(gw.tags[tag_id]->label, "lounge")) { + errx( + 1, "unexpected tag %.*s (expected lounge)", + (int)sizeof(gw.tags[tag_id]->label), gw.tags[tag_id]->label + ); + } + expected_tag_ids |= LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); + if (gw.tag_ids != expected_tag_ids) { + errx( + 1, "tag_ids = %jx (expected %jx)", + (uintmax_t)gw.tag_ids, (uintmax_t)expected_tag_ids + ); + } + if (!tagging_incref_called) { + errx(1, "lgtd_lifx_tagging_incref should have been called"); + } + tagging_incref_called = false; + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c new file mode 100644 index 0000000..805596b --- /dev/null +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c @@ -0,0 +1,89 @@ + +#include + +#include "gateway.c" + +#define MOCKED_LIFX_TAGGING_INCREF +#include "test_gateway_utils.h" + +static bool tagging_incref_called = false; + +struct lgtd_lifx_tag * +lgtd_lifx_tagging_incref(const char *label, + struct lgtd_lifx_gateway *gw, + int tag_id) +{ + if (!label) { + errx(1, "missing tag label"); + } + if (!gw) { + errx(1, "missing gateway"); + } + if (tag_id < 0) { + errx(1, "got tag_id %d but expected >= 0", tag_id); + } + + struct lgtd_lifx_tag *tag = lgtd_lifx_tagging_find_tag(label); + if (!tag) { + tag = calloc(1, sizeof(*tag)); + strcpy(tag->label, label); + struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); + site->gw = gw; + site->tag_id = tag_id; + LIST_INSERT_HEAD(&tag->sites, site, link); + } + + tagging_incref_called = true; + + return tag; +} + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + + uint64_t expected_tag_ids = 0; + for (int i = 0; i != LGTD_LIFX_GATEWAY_MAX_TAGS; i++) { + int tag_id = lgtd_lifx_gateway_allocate_tag_id(&gw, -1, "lounge"); + if (tag_id < 0) { + errx(1, "no tag_id was allocated (received tag_id %d)", tag_id); + } + if (!gw.tags[tag_id]) { + errx(1, "gw.tag_ids[%d] shouldn't be NULL", i); + } + if (strcmp(gw.tags[tag_id]->label, "lounge")) { + errx( + 1, "unexpected tag %.*s (expected lounge)", + (int)sizeof(gw.tags[tag_id]->label), gw.tags[tag_id]->label + ); + } + expected_tag_ids |= LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); + if (gw.tag_ids != expected_tag_ids) { + errx( + 1, "tag_ids = %jx (expected %jx)", + (uintmax_t)gw.tag_ids, (uintmax_t)expected_tag_ids + ); + } + if (!tagging_incref_called) { + errx(1, "lgtd_lifx_tagging_incref should have been called"); + } + tagging_incref_called = false; + } + + int tag_id = lgtd_lifx_gateway_allocate_tag_id(&gw, -1, "lounge"); + if (tag_id != -1) { + errx(1, "tag_ids full but tag_id %d was allocated", tag_id); + } + if (tagging_incref_called) { + errx(1, "lgtd_lifx_tagging_incref should not have been called"); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c new file mode 100644 index 0000000..ef6e16c --- /dev/null +++ b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c @@ -0,0 +1,106 @@ +#include "gateway.c" + +#include "test_gateway_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_infos_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + lgtd_lifx_gateway_update_tag_refcounts(&gw, 0, 0); + for (int i = 0; i != LGTD_LIFX_GATEWAY_MAX_TAGS; i++) { + if (gw.tag_refcounts[i]) { + errx( + 1, "gw.tag_refcounts[%d] was %d, (expected 0)", + i, gw.tag_refcounts[i] + ); + } + } + + for (int n = 1; n != 3; n++) { + lgtd_lifx_gateway_update_tag_refcounts(&gw, 0, 1); + if (gw.tag_refcounts[0] != n) { + errx( + 1, "gw.tag_refcounts[0] was %d (expected %d)", + gw.tag_refcounts[0], n + ); + } + for (int i = 1; i != LGTD_LIFX_GATEWAY_MAX_TAGS; i++) { + if (gw.tag_refcounts[i]) { + errx( + 1, "gw.tag_refcounts[%d] was %d (expected 0)", + i, gw.tag_refcounts[i] + ); + } + } + } + + lgtd_lifx_gateway_update_tag_refcounts(&gw, 0, 2); + gw.tag_ids = 0x2; + + for (int n = 1; n >= 0; n--) { + lgtd_lifx_gateway_update_tag_refcounts(&gw, 1, 0); + if (gw.tag_refcounts[0] != n) { + errx( + 1, "gw.tag_refcounts[0] was %d (expected %d)", + gw.tag_refcounts[0], n - 1 + ); + } + if (gw.tag_refcounts[1] != 1) { + errx( + 1, "gw.tag_refcounts[1] was %d (expected 1)", + gw.tag_refcounts[1] + ); + } + for (int i = 2; i != LGTD_LIFX_GATEWAY_MAX_TAGS; i++) { + if (gw.tag_refcounts[i]) { + errx( + 1, "gw.tag_refcounts[%d] was %d (expected 0)", + i, gw.tag_refcounts[i] + ); + } + } + } + if (gw.pkt_ring[0].type != LGTD_LIFX_SET_TAG_LABELS) { + errx(1, "SET_TAG_LABELS should have been enqueued on the gateway"); + } + + struct lgtd_lifx_packet_tag_labels *pkt = + (void *)&gw_write_buf[sizeof(struct lgtd_lifx_packet_header)]; + uint64_t tags = le64toh(pkt->tags); + if (tags != ~2ULL) { + errx( + 1, "tags on LGTD_LIFX_SET_TAG_LABELS was %#jx (expected %#jx)", + (uintmax_t)tags, (uintmax_t)~2ULL + ); + } + const char blank_label[LGTD_LIFX_LABEL_SIZE] = { 0 }; + if (memcmp(pkt->label, blank_label, LGTD_LIFX_LABEL_SIZE)) { + errx( + 1, "label on LGTD_LIFX_SET_TAG_LABELS should be " + "all zero but got %.*s", LGTD_LIFX_LABEL_SIZE, pkt->label + ); + } + + for (int n = 0; n != UINT8_MAX; n++) { + lgtd_lifx_gateway_update_tag_refcounts(&gw, 0, 4); + } + if (gw.tag_refcounts[2] != UINT8_MAX) { + errx( + 1, "gw.tag_refcounts[2] was %d (expected %d)", + gw.tag_refcounts[2], UINT8_MAX + ); + } + lgtd_lifx_gateway_update_tag_refcounts(&gw, 0, 4); + if (gw.tag_refcounts[2] != UINT8_MAX) { + errx( + 1, "gw.tag_refcounts[2] was %d (expected %d)", + gw.tag_refcounts[2], UINT8_MAX + ); + } + + return 0; +} diff --git a/tests/lifx/mock_gateway.h b/tests/lifx/mock_gateway.h index 4c091ad..3757503 100644 --- a/tests/lifx/mock_gateway.h +++ b/tests/lifx/mock_gateway.h @@ -81,6 +81,18 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, } #endif +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_TAGS +void +lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_tags *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + #ifndef MOCKED_LGTD_LIFX_GATEWAY_DEALLOCATE_TAG_ID void lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id) diff --git a/tests/lifx/wire_proto/test_wire_proto_utils.h b/tests/lifx/wire_proto/test_wire_proto_utils.h index 2ca1f5f..b79ebd4 100644 --- a/tests/lifx/wire_proto/test_wire_proto_utils.h +++ b/tests/lifx/wire_proto/test_wire_proto_utils.h @@ -35,3 +35,12 @@ void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, (void)hdr; (void)pkt; } + +void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_tags *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} From be1bb5ddd05286b80acc831fab3630c2629905b4 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:07:02 -0700 Subject: [PATCH 058/181] Reword the README a bit --- README.rst | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 36d0225..e71a911 100644 --- a/README.rst +++ b/README.rst @@ -10,8 +10,8 @@ but has some advantages: - no discovery delay ever, you get all the bulbs and their state right away; - lightsd is always in sync with the bulbs and always know their state; -- lightsd act as an abstraction layer and can expose new discovery mechanism and - totally new APIs; +- lightsd act as an abstraction layer and can expose new discovery mechanisms and + an unified API across different kind of smart bulbs; - For those of you with a high paranoia factor, lightsd let you place your bulbs in a totally separate and closed network. @@ -24,8 +24,8 @@ Current features lightsd discovers your LIFX bulbs, stays in sync with them and support the following commands through a JSON-RPC_ interface: -- power_off; -- power_on; +- power_off (with auto-retry); +- power_on (with auto-retry); - set_light_from_hsbk; - set_waveform (change the light according to a function like SAW or SINE); - get_light_state; @@ -103,4 +103,22 @@ You can stop lightsd with: Use the ``-f`` option to run lightsd in the foreground. +Known issues +------------ + +The grouping (tagging) code of the LIFX White 800 is bugged: after a tagging +operation the LIFX White 800 keep saying it has no tags. Reboot the bulb to make +the tags appears. + +Power ON/OFF are the only commands with auto-retry, i.e: lightsd will keep +sending the command to the bulb until its state changes. This is not implemented +(yet) for ``set_light_from_hsbk``, ``set_waveform``, ``tag`` and ``untag``. + +While lighsd appears to be pretty stable, if you want to run lightsd in the +background, I recommend doing so in a processor supervisor (e.g: Supervisor_) +that can restart lightsd if it crashes. Otherwise, please feel free to report +crashes to me. + +.. _Supervisor: http://www.supervisord.org/ + .. vim: set tw=80 spelllang=en spell: From da9c1bb7e38e10c8122338df50a214b289d0a040 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:07:41 -0700 Subject: [PATCH 059/181] Handle intermediate values between POWER_STATE_ON and OFF from the bulbs It's supposed to be 0xffff or 0 but that's actually not true. --- lifx/wire_proto.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index bcfa5be..3391852 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -342,16 +342,22 @@ lgtd_lifx_wire_decode_light_status(struct lgtd_lifx_packet_light_status *pkt) pkt->brightness = le16toh(pkt->brightness); pkt->kelvin = le16toh(pkt->kelvin); pkt->dim = le16toh(pkt->dim); - pkt->power = le16toh(pkt->power); + // The bulbs actually return power values between 0 and 0xffff, not sure + // what the intermediate values mean, let's pull them down to 0: + if (pkt->power != LGTD_LIFX_POWER_ON) { + pkt->power = LGTD_LIFX_POWER_OFF; + } pkt->tags = le64toh(pkt->tags); } void lgtd_lifx_wire_decode_power_state(struct lgtd_lifx_packet_power_state *pkt) { - (void)pkt; - assert(pkt); + + if (pkt->power != LGTD_LIFX_POWER_ON) { + pkt->power = LGTD_LIFX_POWER_OFF; + } } void From 799614cf53f4babfd64bd3c4e99118fec1fa32ab Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:34:35 -0700 Subject: [PATCH 060/181] Handle bulbs with bugged tags bitfields I'm getting that on the LIFX White 800 for tag and untag I'm afraid something is weird with their firmare. --- core/proto.c | 18 ++- ...est_proto_get_light_state_unknown_tag_id.c | 129 ++++++++++++++++++ 2 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 tests/core/proto/test_proto_get_light_state_unknown_tag_id.c diff --git a/core/proto.c b/core/proto.c index 1d5c276..1d504a1 100644 --- a/core/proto.c +++ b/core/proto.c @@ -209,10 +209,20 @@ lgtd_proto_get_light_state(struct lgtd_client *client, bool comma = false; int tag_id; LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, bulb->state.tags) { - lgtd_client_write_string(client, comma ? ",\"" : "\""); - lgtd_client_write_string(client, bulb->gw->tags[tag_id]->label); - lgtd_client_write_string(client, "\""); - comma = true; + if (LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id) & bulb->gw->tag_ids) { + lgtd_client_write_string(client, comma ? ",\"" : "\""); + lgtd_client_write_string(client, bulb->gw->tags[tag_id]->label); + lgtd_client_write_string(client, "\""); + comma = true; + } else { + lgtd_warnx( + "tag_id %d on bulb %.*s (%s) doesn't " + "exist on gw [%s]:%hu (site %s)", + tag_id, (int)sizeof(bulb->state.label), bulb->state.label, + lgtd_addrtoa(bulb->addr), bulb->gw->ip_addr, bulb->gw->port, + lgtd_addrtoa(bulb->gw->site.as_array) + ); + } } lgtd_client_write_string( diff --git a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c new file mode 100644 index 0000000..d9da2f0 --- /dev/null +++ b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c @@ -0,0 +1,129 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != (void *)0x2a) { + lgtd_errx(1, "unexpected targets list"); + } + + static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); + + static struct lgtd_lifx_gateway gw_bulb_1 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + }; + static struct lgtd_lifx_bulb bulb_1 = { + .addr = { 1, 2, 3, 4, 5 }, + .state = { + .hue = 0xaaaa, + .saturation = 0xffff, + .brightness = 0xbbbb, + .kelvin = 3600, + .label = "wave", + .power = LGTD_LIFX_POWER_ON, + .tags = 5 + }, + .gw = &gw_bulb_1 + }; + static struct lgtd_router_device device_1 = { .device = &bulb_1 }; + SLIST_INSERT_HEAD(&devices, &device_1, link); + + struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor"); + struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b"); + struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~"); + static struct lgtd_lifx_gateway gw_bulb_2 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), + .tag_ids = 0x7 + }; + lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0); + lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); + lgtd_tests_add_tag_to_gw(gw_2_tag_3, &gw_bulb_2, 2); + static struct lgtd_lifx_bulb bulb_2 = { + .addr = { 5, 4, 3, 2, 1 }, + .state = { + .hue = 0x0000, + .saturation = 0x0000, + .brightness = 0xffff, + .kelvin = 4000, + .label = "", + .power = LGTD_LIFX_POWER_OFF, + .tags = 0x3 + }, + .gw = &gw_bulb_2 + }; + static struct lgtd_router_device device_2 = { .device = &bulb_2 }; + SLIST_INSERT_HEAD(&devices, &device_2, link); + + return &devices; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_proto_target_list *targets = (void *)0x2a; + + lgtd_proto_get_light_state(&client, targets); + + const char expected[] = ( + "[" + "{" + "\"hsbk\":[0,0,1,4000]," + "\"power\":false," + "\"label\":\"\"," + "\"tags\":[\"vapor\",\"d^-^b\"]" + "}," + "{" + "\"hsbk\":[240,1,0.733333,3600]," + "\"power\":true," + "\"label\":\"wave\"," + "\"tags\":[]" + "}" + "]" + ); + + if (client_write_buf_idx != sizeof(expected) - 1) { + lgtd_errx( + 1, + "%d bytes written, expected %lu " + "(got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected) - 1UL, + client_write_buf_idx, client_write_buf, expected + ); + } + + if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected + ); + } + + if (!device_list_free_called) { + lgtd_errx(1, "the list of devices hasn't been freed"); + } + + return 0; +} From c8683651b10322272352f8321fcea29eeee8e156 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:34:36 -0700 Subject: [PATCH 061/181] Fix a couple function names --- lifx/wire_proto.c | 2 +- lifx/wire_proto.h | 4 ++-- tests/core/proto/test_proto_set_waveform.c | 2 +- tests/core/proto/test_proto_set_waveform_on_routing_error.c | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 3391852..6844698 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -382,7 +382,7 @@ lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) pkt->brightness = htole16(pkt->brightness); pkt->kelvin = htole16(pkt->kelvin); pkt->period = htole32(pkt->period); - pkt->cycles = lifx_wire_htolefloat(pkt->cycles); + pkt->cycles = lgtd_lifx_wire_htolefloat(pkt->cycles); pkt->skew_ratio = htole16(pkt->skew_ratio); } diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index c733e72..f937d20 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -26,14 +26,14 @@ typedef uint64_t uint64be_t; typedef float floatle_t; static inline floatle_t -lifx_wire_htolefloat(float f) +lgtd_lifx_wire_htolefloat(float f) { *(uint32_t *)&f = htole32(*(uint32_t *)&f); return f; } static inline floatle_t -lifx_wire_lefloattoh(float f) +lgtd_lifx_wire_lefloattoh(float f) { *(uint32_t *)&f = le32toh(*(uint32_t *)&f); return f; diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index ccbd4f7..8fbc5fb 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -36,7 +36,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, int brightness = le16toh(waveform->brightness); int kelvin = le16toh(waveform->kelvin); int period = le32toh(waveform->period); - float cycles = lifx_wire_lefloattoh(waveform->cycles); + float cycles = lgtd_lifx_wire_lefloattoh(waveform->cycles); int skew_ratio = le16toh(waveform->skew_ratio); if (waveform_type != LGTD_LIFX_WAVEFORM_SAW) { diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index 725384f..0d4317a 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -36,7 +36,7 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, int brightness = le16toh(waveform->brightness); int kelvin = le16toh(waveform->kelvin); int period = le32toh(waveform->period); - float cycles = lifx_wire_lefloattoh(waveform->cycles); + float cycles = lgtd_lifx_wire_lefloattoh(waveform->cycles); int skew_ratio = le16toh(waveform->skew_ratio); if (waveform_type != LGTD_LIFX_WAVEFORM_SAW) { From 3bc2951a73b835aa5cbe74e3e8bce7244a7e5ef8 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:34:36 -0700 Subject: [PATCH 062/181] Ignore my packet captures directory I never found myself looking for an older pcap, what we really need is a bulb simulator and test scenarios. --- .hgignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgignore b/.hgignore index fd55498..454e24e 100644 --- a/.hgignore +++ b/.hgignore @@ -1,3 +1,4 @@ .*\.sw[a-z]$ .*\.py[co]$ ^build +^pcaps From c8ad1d853a559c5d4f7bb42e13b1627d3f9850be Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:34:36 -0700 Subject: [PATCH 063/181] Remove unused __attribute__((unused)) --- core/jsonrpc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/jsonrpc.c b/core/jsonrpc.c index cebb313..aeb6d33 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -129,7 +129,7 @@ lgtd_jsonrpc_type_number(const jsmntok_t *t, const char *json) return c == '-' || (c >= '0' && c <= '9'); } -static bool __attribute__((unused)) +static bool lgtd_jsonrpc_type_bool(const jsmntok_t *t, const char *json) { if (t->type != JSMN_PRIMITIVE) { From 72ef8266622cb89f0e03dec19e7e06046b9dba01 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:34:36 -0700 Subject: [PATCH 064/181] Pass pkt_info around instead of looking it up twice And do that spelling fix too. --- core/lightsd.c | 2 +- core/router.c | 53 ++++++++----------- lifx/broadcast.c | 12 ++--- lifx/gateway.c | 46 ++++++++-------- lifx/gateway.h | 7 ++- lifx/wire_proto.c | 36 ++++++------- lifx/wire_proto.h | 16 +++--- tests/core/proto/test_proto_tag_create.c | 2 +- ...st_proto_tag_create_lifx_gw_tag_ids_full.c | 2 +- tests/core/proto/test_proto_tag_update.c | 2 +- .../router/test_router_send_to_broadcast.c | 2 +- .../core/router/test_router_send_to_device.c | 2 +- .../test_router_send_to_invalid_targets.c | 2 +- tests/core/router/test_router_send_to_label.c | 2 +- tests/core/router/test_router_send_to_tag.c | 2 +- .../router/test_router_targets_to_devices.c | 2 +- tests/core/router/tests_router_utils.h | 9 ++-- .../gateway/test_gateway_allocate_tag_id.c | 2 +- ...ateway_allocate_tag_id_from_lifx_network.c | 2 +- ...t_gateway_allocate_tag_id_no_tag_id_left.c | 2 +- ...eway_deallocate_tag_id_from_lifx_network.c | 2 +- .../gateway/test_gateway_enqueue_packet.c | 8 ++- .../test_gateway_enqueue_packet_ring_full.c | 12 ++--- ...t_gateway_enqueue_packet_ring_wraparound.c | 8 ++- .../gateway/test_gateway_handle_tag_labels.c | 2 +- .../test_gateway_update_tag_refcounts.c | 2 +- .../gateway/test_gateway_write_callback.c | 2 +- ...way_write_callback_clears_ring_full_flag.c | 2 +- ...teway_write_callback_last_packet_on_ring.c | 2 +- ...est_gateway_write_callback_partial_write.c | 2 +- ...t_gateway_write_callback_ring_wraparound.c | 2 +- tests/lifx/mock_gateway.h | 2 +- 32 files changed, 114 insertions(+), 137 deletions(-) diff --git a/core/lightsd.c b/core/lightsd.c index b4ec74f..d28cabf 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -220,7 +220,7 @@ main(int argc, char *argv[], char *envp[]) argc -= optind; argv += optind; - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); if (!lgtd_lifx_timer_setup() || !lgtd_lifx_broadcast_setup()) { lgtd_err(1, "can't setup lightsd"); } diff --git a/core/router.c b/core/router.c index 5908649..b238de4 100644 --- a/core/router.c +++ b/core/router.c @@ -49,21 +49,19 @@ lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) struct lgtd_lifx_packet_header hdr; union lgtd_lifx_target target = { .tags = 0 }; - const struct lgtd_lifx_packet_infos *pkt_infos = NULL; + const struct lgtd_lifx_packet_info *pkt_info = NULL; struct lgtd_lifx_gateway *gw; LIST_FOREACH(gw, &lgtd_lifx_gateways, link) { - pkt_infos = lgtd_lifx_wire_setup_header( + pkt_info = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_ALL_DEVICES, target, gw->site.as_array, pkt_type ); - assert(pkt_infos); + assert(pkt_info); - lgtd_lifx_gateway_enqueue_packet( - gw, &hdr, pkt_type, pkt, pkt_infos->size - ); + lgtd_lifx_gateway_enqueue_packet(gw, &hdr, pkt_info, pkt); if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { struct lgtd_lifx_bulb *bulb; @@ -76,8 +74,8 @@ lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) } } - if (pkt_infos) { - lgtd_info("broadcasting %s", pkt_infos->name); + if (pkt_info) { + lgtd_info("broadcasting %s", pkt_info->name); } } @@ -91,19 +89,16 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, struct lgtd_lifx_packet_header hdr; union lgtd_lifx_target target = { .addr = bulb->addr }; - const struct lgtd_lifx_packet_infos *pkt_infos; - pkt_infos = lgtd_lifx_wire_setup_header( + const struct lgtd_lifx_packet_info *pkt_info = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_DEVICE, target, bulb->gw->site.as_array, pkt_type ); - assert(pkt_infos); + assert(pkt_info); - lgtd_lifx_gateway_enqueue_packet( - bulb->gw, &hdr, pkt_type, pkt, pkt_infos->size - ); + lgtd_lifx_gateway_enqueue_packet(bulb->gw, &hdr, pkt_info, pkt); if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { bulb->dirty_at = lgtd_time_monotonic_msecs(); @@ -111,7 +106,7 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, bulb->expected_power_on = payload->power; } - lgtd_info("sending %s to %s", pkt_infos->name, lgtd_addrtoa(bulb->addr)); + lgtd_info("sending %s to %s", pkt_info->name, lgtd_addrtoa(bulb->addr)); } void @@ -119,7 +114,7 @@ lgtd_router_send_to_tag(const struct lgtd_lifx_tag *tag, enum lgtd_lifx_packet_type pkt_type, void *pkt) { - const struct lgtd_lifx_packet_infos *pkt_infos = NULL; + const struct lgtd_lifx_packet_info *pkt_info = NULL; struct lgtd_lifx_site *site; LIST_FOREACH(site, &tag->sites, link) { @@ -130,18 +125,16 @@ lgtd_router_send_to_tag(const struct lgtd_lifx_tag *tag, union lgtd_lifx_target target; assert(tag == gw->tags[tag_id]); target.tags = LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); - pkt_infos = lgtd_lifx_wire_setup_header( + pkt_info = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_TAGS, target, gw->site.as_array, pkt_type ); - assert(pkt_infos); + assert(pkt_info); - lgtd_lifx_gateway_enqueue_packet( - gw, &hdr, pkt_type, pkt, pkt_infos->size - ); + lgtd_lifx_gateway_enqueue_packet(gw, &hdr, pkt_info, pkt); if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { struct lgtd_lifx_bulb *bulb; @@ -156,8 +149,8 @@ lgtd_router_send_to_tag(const struct lgtd_lifx_tag *tag, } } - if (pkt_infos) { - lgtd_info("sending %s to #%s", pkt_infos->name, tag->label); + if (pkt_info) { + lgtd_info("sending %s to #%s", pkt_info->name, tag->label); } } @@ -166,25 +159,23 @@ lgtd_router_send_to_label(const char *label, enum lgtd_lifx_packet_type pkt_type, void *pkt) { - const struct lgtd_lifx_packet_infos *pkt_infos = NULL; + const struct lgtd_lifx_packet_info *pkt_info = NULL; struct lgtd_lifx_bulb *bulb; RB_FOREACH(bulb, lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table) { if (!strcmp(bulb->state.label, label)) { struct lgtd_lifx_packet_header hdr; union lgtd_lifx_target target = { .addr = bulb->addr }; - pkt_infos = lgtd_lifx_wire_setup_header( + pkt_info = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_DEVICE, target, bulb->gw->site.as_array, pkt_type ); - assert(pkt_infos); + assert(pkt_info); - lgtd_lifx_gateway_enqueue_packet( - bulb->gw, &hdr, pkt_type, pkt, pkt_infos->size - ); + lgtd_lifx_gateway_enqueue_packet(bulb->gw, &hdr, pkt_info, pkt); if (pkt_type == LGTD_LIFX_SET_POWER_STATE) { bulb->dirty_at = lgtd_time_monotonic_msecs(); @@ -194,8 +185,8 @@ lgtd_router_send_to_label(const char *label, } } - if (pkt_infos) { - lgtd_info("sending %s to %s", pkt_infos->name, label); + if (pkt_info) { + lgtd_info("sending %s to %s", pkt_info->name, label); } } diff --git a/lifx/broadcast.c b/lifx/broadcast.c index 0abff22..34dc22c 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -117,9 +117,9 @@ lgtd_lifx_broadcast_handle_read(void) continue; } - const struct lgtd_lifx_packet_infos *pkt_infos = - lgtd_lifx_wire_get_packet_infos(read.hdr.packet_type); - if (!pkt_infos) { + const struct lgtd_lifx_packet_info *pkt_info = + lgtd_lifx_wire_get_packet_info(read.hdr.packet_type); + if (!pkt_info) { lgtd_warnx( "received unknown packet %#x from [%s]:%hu", read.hdr.packet_type, peer_addr, peer_port @@ -129,7 +129,7 @@ lgtd_lifx_broadcast_handle_read(void) if (!(read.hdr.protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE)) { lgtd_warnx( "received non-addressable packet %s from [%s]:%hu", - pkt_infos->name, peer_addr, peer_port + pkt_info->name, peer_addr, peer_port ); continue; } @@ -145,8 +145,8 @@ lgtd_lifx_broadcast_handle_read(void) if (gw) { void *pkt = &read.buf[LGTD_LIFX_PACKET_HEADER_SIZE]; gw->last_pkt_at = received_at; - pkt_infos->decode(pkt); - pkt_infos->handle(gw, &read.hdr, pkt); + pkt_info->decode(pkt); + pkt_info->handle(gw, &read.hdr, pkt); } else { lgtd_warnx( "got packet from unknown gateway [%s]:%hu", peer_addr, peer_port diff --git a/lifx/gateway.c b/lifx/gateway.c index 1879559..0cb1c57 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -164,24 +164,24 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, static bool lgtd_lifx_gateway_send_to_site_impl(struct lgtd_lifx_gateway *gw, enum lgtd_lifx_packet_type pkt_type, - const void *pkt, - const struct lgtd_lifx_packet_infos **pkt_infos) + void *pkt, + const struct lgtd_lifx_packet_info **pkt_info) { assert(gw); - assert(pkt_infos); + assert(pkt_info); struct lgtd_lifx_packet_header hdr; union lgtd_lifx_target target = { .addr = gw->site.as_array }; - *pkt_infos = lgtd_lifx_wire_setup_header( + *pkt_info = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_SITE, target, gw->site.as_array, pkt_type ); - assert(*pkt_infos); + assert(*pkt_info); - lgtd_lifx_gateway_enqueue_packet(gw, &hdr, pkt_type, pkt, (*pkt_infos)->size); + lgtd_lifx_gateway_enqueue_packet(gw, &hdr, *pkt_info, pkt); return true; // FIXME, have real return values on the send paths... } @@ -189,17 +189,17 @@ lgtd_lifx_gateway_send_to_site_impl(struct lgtd_lifx_gateway *gw, static bool lgtd_lifx_gateway_send_to_site_quiet(struct lgtd_lifx_gateway *gw, enum lgtd_lifx_packet_type pkt_type, - const void *pkt) + void *pkt) { - const struct lgtd_lifx_packet_infos *pkt_infos; + const struct lgtd_lifx_packet_info *pkt_info; bool rv = lgtd_lifx_gateway_send_to_site_impl( - gw, pkt_type, pkt, &pkt_infos + gw, pkt_type, pkt, &pkt_info ); lgtd_debug( "sending %s to site %s", - pkt_infos->name, lgtd_addrtoa(gw->site.as_array) + pkt_info->name, lgtd_addrtoa(gw->site.as_array) ); return rv; // FIXME, have real return values on the send paths... @@ -208,16 +208,16 @@ lgtd_lifx_gateway_send_to_site_quiet(struct lgtd_lifx_gateway *gw, bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, enum lgtd_lifx_packet_type pkt_type, - const void *pkt) + void *pkt) { - const struct lgtd_lifx_packet_infos *pkt_infos; + const struct lgtd_lifx_packet_info *pkt_info; bool rv = lgtd_lifx_gateway_send_to_site_impl( - gw, pkt_type, pkt, &pkt_infos + gw, pkt_type, pkt, &pkt_info ); lgtd_info( "sending %s to site %s", - pkt_infos->name, lgtd_addrtoa(gw->site.as_array) + pkt_info->name, lgtd_addrtoa(gw->site.as_array) ); return rv; // FIXME, have real return values on the send paths... @@ -387,13 +387,12 @@ lgtd_lifx_gateway_close_all(void) void lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, - enum lgtd_lifx_packet_type pkt_type, - const void *pkt, - int pkt_size) + const struct lgtd_lifx_packet_info *pkt_info, + void *pkt) { assert(gw); assert(hdr); - assert(pkt_size >= 0 && pkt_size < LGTD_LIFX_MAX_PACKET_SIZE); + assert(pkt_info); assert(!memcmp(hdr->site, gw->site.as_array, LGTD_LIFX_ADDR_LENGTH)); assert(gw->pkt_ring_head >= 0); assert(gw->pkt_ring_head < (int)LGTD_ARRAY_SIZE(gw->pkt_ring)); @@ -401,19 +400,18 @@ lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, if (gw->pkt_ring_full) { lgtd_warnx( "dropping packet type %s: packet queue on [%s]:%hu is full", - lgtd_lifx_wire_get_packet_infos(pkt_type)->name, - gw->ip_addr, gw->port + pkt_info->name, gw->ip_addr, gw->port ); return; } evbuffer_add(gw->write_buf, hdr, sizeof(*hdr)); if (pkt) { - assert((unsigned)pkt_size == le16toh(hdr->size) - sizeof(*hdr)); - evbuffer_add(gw->write_buf, pkt, pkt_size); + assert(pkt_info->size == le16toh(hdr->size) - sizeof(*hdr)); + evbuffer_add(gw->write_buf, pkt, pkt_info->size); } - gw->pkt_ring[gw->pkt_ring_head].size = sizeof(*hdr) + pkt_size; - gw->pkt_ring[gw->pkt_ring_head].type = pkt_type; + gw->pkt_ring[gw->pkt_ring_head].size = sizeof(*hdr) + pkt_info->size; + gw->pkt_ring[gw->pkt_ring_head].type = pkt_info->type; LGTD_LIFX_GATEWAY_INC_MESSAGE_RING_INDEX(gw->pkt_ring_head); if (gw->pkt_ring_head == gw->pkt_ring_tail) { gw->pkt_ring_full = true; diff --git a/lifx/gateway.h b/lifx/gateway.h index 69ab318..ebcba31 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -91,13 +91,12 @@ void lgtd_lifx_gateway_force_refresh(struct lgtd_lifx_gateway *); void lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, - enum lgtd_lifx_packet_type, - const void *, - int); + const struct lgtd_lifx_packet_info *, + void *); // This could be on router but it's LIFX specific so I'd rather keep it here: bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *, enum lgtd_lifx_packet_type, - const void *); + void *); void lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *, uint64_t, uint64_t); diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 6844698..7d64b14 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -48,14 +48,14 @@ const int LGTD_LIFX_DEBRUIJN_SEQUENCE[64] = { 13, 18, 8, 12, 7, 6, 5, 63 }; -static struct lgtd_lifx_packet_infos_map lgtd_lifx_packet_infos = +static struct lgtd_lifx_packet_info_map lgtd_lifx_packet_info = RB_INITIALIZER(&lgtd_lifx_packets_infos); RB_GENERATE_STATIC( - lgtd_lifx_packet_infos_map, - lgtd_lifx_packet_infos, + lgtd_lifx_packet_info_map, + lgtd_lifx_packet_info, link, - lgtd_lifx_packet_infos_cmp + lgtd_lifx_packet_info_cmp ); static void @@ -75,7 +75,7 @@ lgtd_lifx_wire_null_packet_handler(struct lgtd_lifx_gateway *gw, } void -lgtd_lifx_wire_load_packet_infos_map(void) +lgtd_lifx_wire_load_packet_info_map(void) { #define DECODER(x) ((void (*)(void *))(x)) #define ENCODER(x) ((void (*)(void *))(x)) @@ -91,7 +91,7 @@ lgtd_lifx_wire_load_packet_infos_map(void) .decode = lgtd_lifx_wire_null_packet_encoder_decoder, \ .handle = lgtd_lifx_wire_null_packet_handler - static struct lgtd_lifx_packet_infos packet_table[] = { + static struct lgtd_lifx_packet_info packet_table[] = { // Gateway packets: { REQUEST_ONLY, @@ -192,18 +192,18 @@ lgtd_lifx_wire_load_packet_infos_map(void) for (int i = 0; i != LGTD_ARRAY_SIZE(packet_table); ++i) { RB_INSERT( - lgtd_lifx_packet_infos_map, - &lgtd_lifx_packet_infos, + lgtd_lifx_packet_info_map, + &lgtd_lifx_packet_info, &packet_table[i] ); } } -const struct lgtd_lifx_packet_infos * -lgtd_lifx_wire_get_packet_infos(enum lgtd_lifx_packet_type packet_type) +const struct lgtd_lifx_packet_info * +lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type packet_type) { - struct lgtd_lifx_packet_infos pkt_infos = { .type = packet_type }; - return RB_FIND(lgtd_lifx_packet_infos_map, &lgtd_lifx_packet_infos, &pkt_infos); + struct lgtd_lifx_packet_info pkt_info = { .type = packet_type }; + return RB_FIND(lgtd_lifx_packet_info_map, &lgtd_lifx_packet_info, &pkt_info); } @@ -273,7 +273,7 @@ lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *hdr) hdr->packet_type = le16toh(hdr->packet_type); } -const struct lgtd_lifx_packet_infos * +const struct lgtd_lifx_packet_info * lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, enum lgtd_lifx_target_type target_type, union lgtd_lifx_target target, @@ -282,13 +282,13 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, { assert(hdr); - const struct lgtd_lifx_packet_infos *pkt_infos = - lgtd_lifx_wire_get_packet_infos(packet_type); + const struct lgtd_lifx_packet_info *pkt_info = + lgtd_lifx_wire_get_packet_info(packet_type); - assert(pkt_infos); + assert(pkt_info); memset(hdr, 0, sizeof(*hdr)); - hdr->size = pkt_infos->size + sizeof(*hdr); + hdr->size = pkt_info->size + sizeof(*hdr); hdr->packet_type = packet_type; if (site) { memcpy(hdr->site, site, sizeof(hdr->site)); @@ -313,7 +313,7 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, lgtd_lifx_wire_encode_header(hdr, flags); - return pkt_infos; + return pkt_info; } void diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index f937d20..19e028c 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -265,8 +265,8 @@ extern const struct lgtd_lifx_waveform_string_id lgtd_lifx_waveform_table[]; struct lgtd_lifx_gateway; -struct lgtd_lifx_packet_infos { - RB_ENTRY(lgtd_lifx_packet_infos) link; +struct lgtd_lifx_packet_info { + RB_ENTRY(lgtd_lifx_packet_info) link; const char *name; enum lgtd_lifx_packet_type type; unsigned size; @@ -276,11 +276,11 @@ struct lgtd_lifx_packet_infos { const struct lgtd_lifx_packet_header *, const void *); }; -RB_HEAD(lgtd_lifx_packet_infos_map, lgtd_lifx_packet_infos); +RB_HEAD(lgtd_lifx_packet_info_map, lgtd_lifx_packet_info); static inline int -lgtd_lifx_packet_infos_cmp(struct lgtd_lifx_packet_infos *a, - struct lgtd_lifx_packet_infos *b) +lgtd_lifx_packet_info_cmp(struct lgtd_lifx_packet_info *a, + struct lgtd_lifx_packet_info *b) { return a->type - b->type; } @@ -331,10 +331,10 @@ lgtd_lifx_wire_next_tag_id(int current_tag_id, uint64_t tags) enum lgtd_lifx_waveform_type lgtd_lifx_wire_waveform_string_id_to_type(const char *, int); -const struct lgtd_lifx_packet_infos *lgtd_lifx_wire_get_packet_infos(enum lgtd_lifx_packet_type); -void lgtd_lifx_wire_load_packet_infos_map(void); +const struct lgtd_lifx_packet_info *lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type); +void lgtd_lifx_wire_load_packet_info_map(void); -const struct lgtd_lifx_packet_infos *lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *, +const struct lgtd_lifx_packet_info *lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *, enum lgtd_lifx_target_type, union lgtd_lifx_target, const uint8_t *, diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c index 8cd4235..50e5231 100644 --- a/tests/core/proto/test_proto_tag_create.c +++ b/tests/core/proto/test_proto_tag_create.c @@ -65,7 +65,7 @@ static bool gateway_send_to_site_called = false; bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, enum lgtd_lifx_packet_type pkt_type, - const void *pkt) + void *pkt) { if (!gw) { errx(1, "missing gateway"); diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c index aeba5dd..6fc9fb5 100644 --- a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c +++ b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c @@ -61,7 +61,7 @@ static bool gateway_send_to_site_called = false; bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, enum lgtd_lifx_packet_type pkt_type, - const void *pkt) + void *pkt) { (void)gw; (void)pkt_type; diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c index 069c509..ced8ff4 100644 --- a/tests/core/proto/test_proto_tag_update.c +++ b/tests/core/proto/test_proto_tag_update.c @@ -69,7 +69,7 @@ static bool gateway_send_to_site_called_for_gw_2 = false; bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, enum lgtd_lifx_packet_type pkt_type, - const void *pkt) + void *pkt) { if (!gw) { errx(1, "missing gateway"); diff --git a/tests/core/router/test_router_send_to_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c index f91f09a..a89d7bc 100644 --- a/tests/core/router/test_router_send_to_broadcast.c +++ b/tests/core/router/test_router_send_to_broadcast.c @@ -8,7 +8,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); lgtd_tests_insert_mock_gateway(2); lgtd_tests_insert_mock_gateway(1); diff --git a/tests/core/router/test_router_send_to_device.c b/tests/core/router/test_router_send_to_device.c index a825cc6..a95aefd 100644 --- a/tests/core/router/test_router_send_to_device.c +++ b/tests/core/router/test_router_send_to_device.c @@ -7,7 +7,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); struct lgtd_lifx_bulb *bulb_1 = lgtd_tests_insert_mock_bulb(gw_1, 1); diff --git a/tests/core/router/test_router_send_to_invalid_targets.c b/tests/core/router/test_router_send_to_invalid_targets.c index 27716cf..054dbb1 100644 --- a/tests/core/router/test_router_send_to_invalid_targets.c +++ b/tests/core/router/test_router_send_to_invalid_targets.c @@ -31,7 +31,7 @@ test_target(const char *target) int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); lgtd_tests_insert_mock_bulb(gw_1, 1); diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index 3d64b8d..9cea010 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -7,7 +7,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); struct lgtd_lifx_bulb *bulb_1 = lgtd_tests_insert_mock_bulb(gw_1, 1); diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index 399f03d..fa3167e 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -7,7 +7,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); diff --git a/tests/core/router/test_router_targets_to_devices.c b/tests/core/router/test_router_targets_to_devices.c index a73e178..a11b2af 100644 --- a/tests/core/router/test_router_targets_to_devices.c +++ b/tests/core/router/test_router_targets_to_devices.c @@ -41,7 +41,7 @@ len(const struct lgtd_router_device_list *devices) int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); diff --git a/tests/core/router/tests_router_utils.h b/tests/core/router/tests_router_utils.h index 72beb1b..1e6a13b 100644 --- a/tests/core/router/tests_router_utils.h +++ b/tests/core/router/tests_router_utils.h @@ -13,12 +13,9 @@ struct { void lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, - enum lgtd_lifx_packet_type pkt_type, - const void *pkt, - int pkt_size) + const struct lgtd_lifx_packet_info *pkt_info, + void *pkt) { - (void)pkt_type; - lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].gw = gw; // headers are created on the stack so we need to dup them: lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].hdr = malloc( @@ -30,7 +27,7 @@ lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, sizeof(*hdr) ); lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].pkt = pkt; - lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].pkt_size = pkt_size; + lgtd_tests_gw_pkt_queue[lgtd_tests_gw_pkt_queue_size].pkt_size = pkt_info->size; lgtd_tests_gw_pkt_queue_size++; } diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id.c b/tests/lifx/gateway/test_gateway_allocate_tag_id.c index b74edc3..9d26ecc 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id.c @@ -40,7 +40,7 @@ lgtd_lifx_tagging_incref(const char *label, int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c index 2d152fe..c3d4f74 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c @@ -41,7 +41,7 @@ lgtd_lifx_tagging_incref(const char *label, int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c index 805596b..dcf60a9 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c @@ -41,7 +41,7 @@ lgtd_lifx_tagging_incref(const char *label, int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c index 990822a..15f219a 100644 --- a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c @@ -23,7 +23,7 @@ lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, struct lgtd_lifx_gateway *gw int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet.c b/tests/lifx/gateway/test_gateway_enqueue_packet.c index f96422a..649ea1f 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet.c @@ -5,7 +5,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); @@ -17,7 +17,7 @@ main(void) union lgtd_lifx_target target = { .tags = 0 }; struct lgtd_lifx_packet_header hdr; - lgtd_lifx_wire_setup_header( + const struct lgtd_lifx_packet_info *pkt_info = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_ALL_DEVICES, target, @@ -25,9 +25,7 @@ main(void) LGTD_LIFX_SET_POWER_STATE ); - lgtd_lifx_gateway_enqueue_packet( - &gw, &hdr, LGTD_LIFX_SET_POWER_STATE, &pkt, sizeof(pkt) - ); + lgtd_lifx_gateway_enqueue_packet(&gw, &hdr, pkt_info, &pkt); if (memcmp(gw_write_buf, &hdr, sizeof(hdr))) { errx(1, "header incorrectly buffered"); diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c index 97a940f..869d7f0 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c @@ -5,7 +5,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); @@ -17,7 +17,7 @@ main(void) union lgtd_lifx_target target = { .tags = 0 }; struct lgtd_lifx_packet_header hdr; - lgtd_lifx_wire_setup_header( + const struct lgtd_lifx_packet_info *pkt_info = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_ALL_DEVICES, target, @@ -29,9 +29,7 @@ main(void) gw.pkt_ring_head = 1; gw.pkt_ring_tail = 2; - lgtd_lifx_gateway_enqueue_packet( - &gw, &hdr, LGTD_LIFX_SET_POWER_STATE, &pkt, sizeof(pkt) - ); + lgtd_lifx_gateway_enqueue_packet(&gw, &hdr, pkt_info, &pkt); if (memcmp(gw_write_buf, &hdr, sizeof(hdr))) { errx(1, "header incorrectly buffered"); @@ -65,9 +63,7 @@ main(void) errx(1, "event_add should have been called with gw.write_ev"); } - lgtd_lifx_gateway_enqueue_packet( - &gw, &hdr, LGTD_LIFX_SET_POWER_STATE, &pkt, sizeof(pkt) - ); + lgtd_lifx_gateway_enqueue_packet(&gw, &hdr, pkt_info, &pkt); if (gw_write_buf_idx != sizeof(pkt) + sizeof(hdr)) { errx(1, "nothing should have been buffered"); diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c index 680c753..4cae826 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c @@ -5,7 +5,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); @@ -17,7 +17,7 @@ main(void) union lgtd_lifx_target target = { .tags = 0 }; struct lgtd_lifx_packet_header hdr; - lgtd_lifx_wire_setup_header( + const struct lgtd_lifx_packet_info *pkt_info = lgtd_lifx_wire_setup_header( &hdr, LGTD_LIFX_TARGET_ALL_DEVICES, target, @@ -31,9 +31,7 @@ main(void) gw.pkt_ring_head = pkt_ring_last_idx; gw.pkt_ring_tail = 2; - lgtd_lifx_gateway_enqueue_packet( - &gw, &hdr, LGTD_LIFX_SET_POWER_STATE, &pkt, sizeof(pkt) - ); + lgtd_lifx_gateway_enqueue_packet(&gw, &hdr, pkt_info, &pkt); if (memcmp(gw_write_buf, &hdr, sizeof(hdr))) { errx(1, "header incorrectly buffered"); diff --git a/tests/lifx/gateway/test_gateway_handle_tag_labels.c b/tests/lifx/gateway/test_gateway_handle_tag_labels.c index 89ef2d2..a3f95da 100644 --- a/tests/lifx/gateway/test_gateway_handle_tag_labels.c +++ b/tests/lifx/gateway/test_gateway_handle_tag_labels.c @@ -7,7 +7,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c index ef6e16c..c14abc1 100644 --- a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c +++ b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c @@ -5,7 +5,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback.c b/tests/lifx/gateway/test_gateway_write_callback.c index d39620e..3494b3f 100644 --- a/tests/lifx/gateway/test_gateway_write_callback.c +++ b/tests/lifx/gateway/test_gateway_write_callback.c @@ -43,7 +43,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c index 00a9130..3e3e0d2 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c +++ b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c @@ -43,7 +43,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c index f85329f..cb49625 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c +++ b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c @@ -42,7 +42,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c index eaec56e..6f53fee 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c +++ b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c @@ -50,7 +50,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c index 4e4ddd6..403997b 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c @@ -43,7 +43,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_infos_map(); + lgtd_lifx_wire_load_packet_info_map(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/mock_gateway.h b/tests/lifx/mock_gateway.h index 3757503..dab3601 100644 --- a/tests/lifx/mock_gateway.h +++ b/tests/lifx/mock_gateway.h @@ -11,7 +11,7 @@ struct lgtd_lifx_gateway; bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, enum lgtd_lifx_packet_type pkt_type, - const void *pkt) + void *pkt) { (void)gw; (void)pkt_type; From d3f29c49de1792beef33fbcb75f9ac364f95bf99 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:34:36 -0700 Subject: [PATCH 065/181] Relax different timings a bit (until we get auto-retry on everything) --- lifx/gateway.h | 2 +- lifx/timer.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lifx/gateway.h b/lifx/gateway.h index ebcba31..67dcaba 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -21,7 +21,7 @@ // according to my own tests, aggressively polling a bulb doesn't raise its // consumption at all (and it's interesting to note that a turned off bulb // still draw about 2W in ZigBee and about 3W in WiFi). -enum { LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS = 200 }; +enum { LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS = 800 }; // You can't send more than one lifx packet per UDP datagram. enum { LGTD_LIFX_GATEWAY_PACKET_RING_SIZE = 16 }; diff --git a/lifx/timer.h b/lifx/timer.h index 70cff79..8545cd8 100644 --- a/lifx/timer.h +++ b/lifx/timer.h @@ -17,11 +17,11 @@ #pragma once -enum { LGTD_LIFX_TIMER_WATCHDOG_INTERVAL_MSECS = 200 }; +enum { LGTD_LIFX_TIMER_WATCHDOG_INTERVAL_MSECS = 500 }; enum { LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000 }; enum { LGTD_LIFX_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000 }; -enum { LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS = 2000 }; -enum { LGTD_LIFX_TIMER_DEVICE_FORCE_REFRESH_MSECS = 600 }; +enum { LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS = 3000 }; +enum { LGTD_LIFX_TIMER_DEVICE_FORCE_REFRESH_MSECS = 2000 }; bool lgtd_lifx_timer_setup(void); void lgtd_lifx_timer_close(void); From 6171a673a5f324cb78e8cc21bba0f5c825a693e6 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:34:36 -0700 Subject: [PATCH 066/181] Don't abort on request without an id in debug builds We made it optional. --- core/jsonrpc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/core/jsonrpc.c b/core/jsonrpc.c index aeb6d33..ed1ac96 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -1118,7 +1118,6 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) } assert(request.method); - assert(request.id); for (int i = 0; i != LGTD_ARRAY_SIZE(methods); i++) { int parsed_method_namelen = LGTD_JSONRPC_TOKEN_LEN(request.method); From 4823ad17fb386ec1dc6c00d6b59513c07addd474 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 02:34:36 -0700 Subject: [PATCH 067/181] Fix tag targeting on big endian platforms I think that was the last issue on big endian platforms. I can't wait to setup a buildbot for it. --- lifx/wire_proto.c | 11 ++++++++--- .../wire_proto/test_wire_proto_encode_decode_header.c | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 7d64b14..0750653 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -246,7 +246,7 @@ lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr, int flags) } if (flags & LGTD_LIFX_TAGGED) { hdr->protocol |= LGTD_LIFX_PROTOCOL_TAGGED; - htole64(hdr->target.tags); + hdr->target.tags = htole64(hdr->target.tags); } if (flags & LGTD_LIFX_ACK_REQUIRED) { hdr->flags |= LGTD_LIFX_FLAG_ACK_REQUIRED; @@ -267,8 +267,13 @@ lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *hdr) assert(hdr); hdr->size = le16toh(hdr->size); - hdr->protocol = le16toh(hdr->protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK) - | (hdr->protocol & LGTD_LIFX_PROTOCOL_FLAGS_MASK); + hdr->protocol = ( + le16toh(hdr->protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK) + | (hdr->protocol & LGTD_LIFX_PROTOCOL_FLAGS_MASK) + ); + if (hdr->protocol & LGTD_LIFX_PROTOCOL_TAGGED) { + hdr->target.tags = le64toh(hdr->target.tags); + } hdr->at_time = le64toh(hdr->at_time); hdr->packet_type = le16toh(hdr->packet_type); } diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c index c9bb146..72c7a0d 100644 --- a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c +++ b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c @@ -20,10 +20,10 @@ main(void) if (le16toh(hdr.size) != 42) { lgtd_errx(1, "size = %hu (expected = 42)", le16toh(hdr.size)); } - if (le64toh(hdr.target.tags != 0xbad)) { + if (le64toh(hdr.target.tags) != 0xbad) { lgtd_errx( 1, "tags = %#jx (expected = 0xbad)", - (uintmax_t)le16toh(hdr.target.tags) + (uintmax_t)le64toh(hdr.target.tags) ); } if (le16toh(hdr.packet_type) != LGTD_LIFX_ECHO_REQUEST) { From 82e4ff4c2ed8d39c8010a28143b1adc9e9588825 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 17:55:59 -0700 Subject: [PATCH 068/181] Fix an incorrectly checked snprintf return value. In this case we would have returned some invalid output instead of skipping one bulb. --- core/proto.c | 2 +- tests/core/proto/test_proto_toggle.c | 162 +++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 tests/core/proto/test_proto_toggle.c diff --git a/core/proto.c b/core/proto.c index 1d504a1..2210922 100644 --- a/core/proto.c +++ b/core/proto.c @@ -195,7 +195,7 @@ lgtd_proto_get_light_state(struct lgtd_client *client, bulb->state.power == LGTD_LIFX_POWER_ON ? "true" : "false", bulb->state.label ); - if (written == sizeof(buf)) { + if (written >= (int)sizeof(buf)) { lgtd_warnx( "can't send state of bulb %s (%s) to client " "[%s]:%hu: output buffer to small", diff --git a/tests/core/proto/test_proto_toggle.c b/tests/core/proto/test_proto_toggle.c new file mode 100644 index 0000000..bbe480a --- /dev/null +++ b/tests/core/proto/test_proto_toggle.c @@ -0,0 +1,162 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_SEND_TO_DEVICE +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != (void *)0x2a) { + lgtd_errx(1, "unexpected targets list"); + } + + static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); + + static struct lgtd_lifx_gateway gw_bulb_1 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + }; + static struct lgtd_lifx_bulb bulb_1 = { + .addr = { 1, 2, 3, 4, 5 }, + .state = { + .hue = 0xaaaa, + .saturation = 0xffff, + .brightness = 0xbbbb, + .kelvin = 3600, + .label = "wave", + .power = LGTD_LIFX_POWER_ON, + .tags = 0 + }, + .gw = &gw_bulb_1 + }; + static struct lgtd_router_device device_1 = { .device = &bulb_1 }; + SLIST_INSERT_HEAD(&devices, &device_1, link); + + struct lgtd_lifx_tag *gw_2_tag_1 = lgtd_tests_insert_mock_tag("vapor"); + struct lgtd_lifx_tag *gw_2_tag_2 = lgtd_tests_insert_mock_tag("d^-^b"); + struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~"); + static struct lgtd_lifx_gateway gw_bulb_2 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), + .tag_ids = 0x7 + }; + lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0); + lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); + lgtd_tests_add_tag_to_gw(gw_2_tag_3, &gw_bulb_2, 2); + static struct lgtd_lifx_bulb bulb_2 = { + .addr = { 5, 4, 3, 2, 1 }, + .state = { + .hue = 0x0000, + .saturation = 0x0000, + .brightness = 0xffff, + .kelvin = 4000, + .label = "light", + .power = LGTD_LIFX_POWER_OFF, + .tags = 0x3 + }, + .gw = &gw_bulb_2 + }; + static struct lgtd_router_device device_2 = { .device = &bulb_2 }; + SLIST_INSERT_HEAD(&devices, &device_2, link); + + return &devices; +} + +static int router_send_to_device_call_count = 0; + +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (!bulb) { + errx(1, "lgtd_router_send_to_device called without a device"); + } + + if (pkt_type != LGTD_LIFX_SET_POWER_STATE) { + errx( + 1, "lgtd_router_send_to_device got packet type %#x (expected %#x)", + pkt_type, LGTD_LIFX_SET_POWER_STATE + ); + } + + if (!pkt) { + errx(1, "lgtd_router_send_to_device called without a packet"); + } + + struct lgtd_lifx_packet_power_state *payload = pkt; + + if (!strcmp(bulb->state.label, "light")) { + if (payload->power != LGTD_LIFX_POWER_ON) { + errx(1, "bulb light should be turned off"); + } + } else if (!strcmp(bulb->state.label, "wave")) { + if (payload->power != LGTD_LIFX_POWER_OFF) { + errx(1, "bulb wave should be turned on"); + } + } else { + errx( + 1, "lgtd_router_send_to_deviceg got an unknown bulb: %s", + bulb->state.label + ); + } + + router_send_to_device_call_count++; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_proto_target_list *targets = (void *)0x2a; + + lgtd_proto_toggle(&client, targets); + + const char expected[] = "true"; + + if (client_write_buf_idx != sizeof(expected) - 1) { + errx( + 1, "%d bytes written, expected %lu (got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected) - 1UL, + client_write_buf_idx, client_write_buf, expected + ); + } + + if (memcmp(expected, client_write_buf, sizeof(expected) - 1)) { + errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected + ); + } + + if (!device_list_free_called) { + errx(1, "the list of devices hasn't been freed"); + } + + if (router_send_to_device_call_count != 2) { + errx( + 1, "lgtd_router_send_to_device called %d times (expected 2)", + router_send_to_device_call_count + ); + } + + return 0; +} From b85fbcb1de055a2a67f5bb86121a917d0d3c1f5e Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 17:56:00 -0700 Subject: [PATCH 069/181] Add the toggle command: turn on a bulb if it's off and vice-versa This lets you change the state of a bulb without having to query its state first, super useful with the command pipe. --- README.rst | 4 +- core/jsonrpc.c | 56 +++++------- core/proto.c | 30 ++++++- core/proto.h | 1 + docs/protocol.rst | 4 + examples/lightsc.py | 5 +- ...test_jsonrpc_check_and_call_power_toggle.c | 58 +++++++++++++ tests/core/jsonrpc/test_jsonrpc_utils.h | 10 +++ ...oto_toggle.c => test_proto_power_toggle.c} | 2 +- ...oto_power_toggle_targets_to_device_fails.c | 86 +++++++++++++++++++ 10 files changed, 215 insertions(+), 41 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c rename tests/core/proto/{test_proto_toggle.c => test_proto_power_toggle.c} (98%) create mode 100644 tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c diff --git a/README.rst b/README.rst index e71a911..51d0870 100644 --- a/README.rst +++ b/README.rst @@ -26,11 +26,11 @@ following commands through a JSON-RPC_ interface: - power_off (with auto-retry); - power_on (with auto-retry); +- power_toggle (power on if off and vice-versa, with auto-retry); - set_light_from_hsbk; - set_waveform (change the light according to a function like SAW or SINE); - get_light_state; -- tag/untag (group/ungroup bulbs together); -- toggle (power on if off and vice-versa, coming up). +- tag/untag (group/ungroup bulbs together). The JSON-RPC interface works on top of TCP/IPv4/v6, Unix sockets (coming up) or over a command pipe (named pipe, see mkfifo(1)). diff --git a/core/jsonrpc.c b/core/jsonrpc.c index ed1ac96..6bc6618 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -938,44 +938,24 @@ lgtd_jsonrpc_extract_target_list(struct lgtd_proto_target_list *targets, ); } -static void -lgtd_jsonrpc_check_and_call_power_on(struct lgtd_client *client) -{ - struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); - bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); - if (!ok) { - return; - } - - lgtd_proto_power_on(client, &targets); - lgtd_proto_target_list_clear(&targets); +#define CHECK_AND_CALL_TARGETS_ONLY_METHOD(proto_method) \ +static void \ +lgtd_jsonrpc_check_and_call_##proto_method(struct lgtd_client *client) \ +{ \ + struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); \ + bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); \ + if (!ok) { \ + return; \ + } \ + \ + lgtd_proto_##proto_method(client, &targets); \ + lgtd_proto_target_list_clear(&targets); \ } -static void -lgtd_jsonrpc_check_and_call_power_off(struct lgtd_client *client) -{ - struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); - bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); - if (!ok) { - return; - } - - lgtd_proto_power_off(client, &targets); - lgtd_proto_target_list_clear(&targets); -} - -static void -lgtd_jsonrpc_check_and_call_get_light_state(struct lgtd_client *client) -{ - struct lgtd_proto_target_list targets = SLIST_HEAD_INITIALIZER(&targets); - bool ok = lgtd_jsonrpc_extract_target_list(&targets, client); - if (!ok) { - return; - } - - lgtd_proto_get_light_state(client, &targets); - lgtd_proto_target_list_clear(&targets); -} +CHECK_AND_CALL_TARGETS_ONLY_METHOD(power_on); +CHECK_AND_CALL_TARGETS_ONLY_METHOD(power_off); +CHECK_AND_CALL_TARGETS_ONLY_METHOD(power_toggle); +CHECK_AND_CALL_TARGETS_ONLY_METHOD(get_light_state); static void lgtd_jsonrpc_check_and_call_proto_tag_or_untag(struct lgtd_client *client, @@ -1073,6 +1053,10 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) "power_off", 1, // t lgtd_jsonrpc_check_and_call_power_off ), + LGTD_JSONRPC_METHOD( + "power_toggle", 1, // t + lgtd_jsonrpc_check_and_call_power_toggle + ), LGTD_JSONRPC_METHOD( "set_light_from_hsbk", 6, // t, h, s, b, k, t lgtd_jsonrpc_check_and_call_set_light_from_hsbk diff --git a/core/proto.c b/core/proto.c index 2210922..e295be6 100644 --- a/core/proto.c +++ b/core/proto.c @@ -70,6 +70,35 @@ lgtd_proto_power_on(struct lgtd_client *client, ); } +void +lgtd_proto_power_toggle(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + assert(targets); + + struct lgtd_router_device_list *devices = NULL; + devices = lgtd_router_targets_to_devices(targets); + if (!devices) { + lgtd_client_send_error( + client, LGTD_CLIENT_INTERNAL_ERROR, "couldn't allocate memory" + ); + return; + } + + struct lgtd_router_device *device; + SLIST_FOREACH(device, devices, link) { + struct lgtd_lifx_bulb *bulb = device->device; + struct lgtd_lifx_packet_power_state pkt = { + .power = ~bulb->state.power + }; + lgtd_router_send_to_device(bulb, LGTD_LIFX_SET_POWER_STATE, &pkt); + } + + SEND_RESULT(client, true); + + lgtd_router_device_list_free(devices); +} + void lgtd_proto_power_off(struct lgtd_client *client, const struct lgtd_proto_target_list *targets) @@ -329,7 +358,6 @@ lgtd_proto_tag(struct lgtd_client *client, ); fini: lgtd_router_device_list_free(devices); - return; } void diff --git a/core/proto.h b/core/proto.h index b1dd25e..7b4e101 100644 --- a/core/proto.h +++ b/core/proto.h @@ -38,6 +38,7 @@ void lgtd_proto_set_waveform(struct lgtd_client *, int, float, int, bool); void lgtd_proto_power_on(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_power_off(struct lgtd_client *, const struct lgtd_proto_target_list *); +void lgtd_proto_power_toggle(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_get_light_state(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_tag(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *); void lgtd_proto_untag(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *); diff --git a/docs/protocol.rst b/docs/protocol.rst index d30225f..b200797 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -37,6 +37,10 @@ Available methods Power on the given bulb(s). +.. function:: power_toggle(target) + + Power on (if they are off) or power off (if they are on) the given bulb(s). + .. function:: set_light_from_hsbk(target, h, s, b, k, t) :param float h: Hue from 0 to 360. diff --git a/examples/lightsc.py b/examples/lightsc.py index cba02de..c94786f 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -1,7 +1,6 @@ #!/usr/bin/env python import json -import pprint import socket import sys import time @@ -69,6 +68,10 @@ def power_off(socket, target): return jsonrpc_call(socket, "power_off", {"target": target}) +def power_toggle(socket, target): + return jsonrpc_call(socket, "power_toggle", {"target": target}) + + def get_light_state(socket, target): return jsonrpc_call(socket, "get_light_state", [target]) diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c new file mode 100644 index 0000000..74e2f32 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c @@ -0,0 +1,58 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" + +#define MOCKED_LGTD_PROTO_POWER_TOGGLE +#include "test_jsonrpc_utils.h" + +static bool power_toggle_called = false; + +void +lgtd_proto_power_toggle(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + power_toggle_called = true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"power_toggle\"," + "\"params\": {\"target\": \"*\"}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_power_toggle(&client); + + if (!power_toggle_called) { + errx(1, "lgtd_proto_power_toggle wasn't called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index 5d29c8e..5599f3d 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -121,3 +121,13 @@ lgtd_proto_untag(struct lgtd_client *client, (void)tag_label; } #endif + +#ifndef MOCKED_LGTD_PROTO_POWER_TOGGLE +void +lgtd_proto_power_toggle(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + (void)client; + (void)targets; +} +#endif diff --git a/tests/core/proto/test_proto_toggle.c b/tests/core/proto/test_proto_power_toggle.c similarity index 98% rename from tests/core/proto/test_proto_toggle.c rename to tests/core/proto/test_proto_power_toggle.c index bbe480a..9710261 100644 --- a/tests/core/proto/test_proto_toggle.c +++ b/tests/core/proto/test_proto_power_toggle.c @@ -128,7 +128,7 @@ main(void) struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; struct lgtd_proto_target_list *targets = (void *)0x2a; - lgtd_proto_toggle(&client, targets); + lgtd_proto_power_toggle(&client, targets); const char expected[] = "true"; diff --git a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c new file mode 100644 index 0000000..bd418a8 --- /dev/null +++ b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c @@ -0,0 +1,86 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_SEND_TO_DEVICE +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#define MOCKED_CLIENT_SEND_ERROR +#include "tests_proto_utils.h" + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != (void *)0x2a) { + lgtd_errx(1, "unexpected targets list"); + } + + return NULL; +} + +static int router_send_to_device_call_count = 0; + +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)bulb; + (void)pkt_type; + (void)pkt; + + router_send_to_device_call_count++; +} + +static int client_send_error_call_count = 0; + +void +lgtd_client_send_error(struct lgtd_client *client, + enum lgtd_client_error_code error, + const char *msg) +{ + (void)client; + (void)error; + (void)msg; + + client_send_error_call_count++; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_proto_target_list *targets = (void *)0x2a; + + lgtd_proto_power_toggle(&client, targets); + + if (client_send_error_call_count != 1) { + errx(1, "lgtd_client_send_error called %d times (expected 1)", + client_send_error_call_count + ); + } + + if (router_send_to_device_call_count) { + errx( + 1, "lgtd_router_send_to_device called %d times (expected 0)", + router_send_to_device_call_count + ); + } + + return 0; +} From 4e735960fe5c30536047c53c04afa28285a9f4b3 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 17:58:45 -0700 Subject: [PATCH 070/181] Document tag/untag and update known issues --- README.rst | 22 +++++++++++++--------- docs/protocol.rst | 27 ++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 51d0870..f48b9c4 100644 --- a/README.rst +++ b/README.rst @@ -82,11 +82,11 @@ Clone this repository and then: .../lightsd/build$ make -j5 Finally, to start lightsd with the jsonrpc interface listening on localhost -port 1234: +port 1234 and a command pipe named lightsd.cmd: :: - .../lightsd/build$ core/lightsd -v info -l ::1:1234 + .../lightsd/build$ core/lightsd -v info -l ::1:1234 -c lightsd.cmd lightsd forks in the background by default, display running processes and check how we are doing: @@ -106,18 +106,22 @@ Use the ``-f`` option to run lightsd in the foreground. Known issues ------------ -The grouping (tagging) code of the LIFX White 800 is bugged: after a tagging -operation the LIFX White 800 keep saying it has no tags. Reboot the bulb to make -the tags appears. +The grouping (tagging) code of the LIFX White 800 (and presumably the Color 650 +as well) is bugged: after a tagging operation the LIFX White 800 keep saying it +has no tags. Reboot the bulb to make the tags appears. Power ON/OFF are the only commands with auto-retry, i.e: lightsd will keep sending the command to the bulb until its state changes. This is not implemented (yet) for ``set_light_from_hsbk``, ``set_waveform``, ``tag`` and ``untag``. -While lighsd appears to be pretty stable, if you want to run lightsd in the -background, I recommend doing so in a processor supervisor (e.g: Supervisor_) -that can restart lightsd if it crashes. Otherwise, please feel free to report -crashes to me. +In general, crappy WiFi network with latency, jitter or packet loss are gonna be +challenging until lightsd has an auto-retry mechanism, there is also room for +optimizations in how lightsd communicates with the bulbs. + +While lightsd appears to be pretty stable, if you want to run lightsd in the +background, I recommend doing so in a process supervisor (e.g: Supervisor_) that +can restart lightsd if it crashes. Otherwise, please feel free to report crashes +to me. .. _Supervisor: http://www.supervisord.org/ diff --git a/docs/protocol.rst b/docs/protocol.rst index b200797..8db7e29 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -12,7 +12,7 @@ Commands that manipulate bulbs will take a *target* argument to define on which bulb(s) the operation should apply: +-----------------------------+-----------------------------------------------+ -| ``\*`` | targets all bulbs | +| ``*`` | targets all bulbs | +-----------------------------+-----------------------------------------------+ | ``#TagName`` | targets bulbs tagged with *TagName* | +-----------------------------+-----------------------------------------------+ @@ -75,4 +75,29 @@ Available methods - power: boolean, true when the bulb is powered on false otherwise; - tags: list of tags applied to the bulb (utf-8 encoded strings). +.. function:: tag(target, label) + + Tag (group) the given target bulb(s) with the given label (group name), then + label can be used as a target by prefixing it with ``#``. + + To add a device to an existing "group" simply do: + + :: + + tag(["#myexistingtag", "bulbtoadd"], "myexistingtag") + + .. note:: + + Notice how ``#`` is prepended to the tag label depending on whether it's + used as a target or a regular argument. + +.. function:: untag(target, label) + + Remove the given tag from the given target bulb(s). To completely delete a + tag (group), simple do: + + :: + + untag("#myexistingtag", "myexistingtag") + .. vim: set tw=80 spelllang=en spell: From ade73dcef90f2c79408a151e3a5ecd2ca4fce76b Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 17:58:56 -0700 Subject: [PATCH 071/181] Properly do type punning on float endian conversion functions This only showed up with optimizations enabled. --- lifx/wire_proto.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 19e028c..bc50060 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -28,15 +28,17 @@ typedef float floatle_t; static inline floatle_t lgtd_lifx_wire_htolefloat(float f) { - *(uint32_t *)&f = htole32(*(uint32_t *)&f); - return f; + union { float f; uint32_t i; } u = { .f = f }; + htole32(u.i); + return u.f; } static inline floatle_t lgtd_lifx_wire_lefloattoh(float f) { - *(uint32_t *)&f = le32toh(*(uint32_t *)&f); - return f; + union { float f; uint32_t i; } u = { .f = f }; + le32toh(u.i); + return u.f; } enum { LGTD_LIFX_PROTOCOL_PORT = 56700 }; From 02c3ca58e9f4ab7b33f68252e22ab13c0acac30e Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 8 Aug 2015 17:58:56 -0700 Subject: [PATCH 072/181] Fix a couple tests and some warning on mips32 --- tests/core/pipe/test_pipe_read_callback.c | 4 +++- tests/core/pipe/test_pipe_read_callback_extra_data.c | 6 ++++-- tests/core/pipe/test_pipe_read_callback_multiple_requests.c | 4 +++- tests/core/proto/test_proto_untag.c | 4 ++-- tests/core/router/test_router_send_to_tag.c | 4 ++++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/core/pipe/test_pipe_read_callback.c b/tests/core/pipe/test_pipe_read_callback.c index 07bfbff..aef57aa 100644 --- a/tests/core/pipe/test_pipe_read_callback.c +++ b/tests/core/pipe/test_pipe_read_callback.c @@ -130,7 +130,9 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) } if (size != -1) { - errx(1, "got unexpected size %ld in pullup (expected -1)", size); + errx( + 1, "got unexpected size %jd in pullup (expected -1)", (intmax_t)size + ); } return &request[evbuffer_pullup_call_count++ ? sizeof(request) - 1 : 0]; diff --git a/tests/core/pipe/test_pipe_read_callback_extra_data.c b/tests/core/pipe/test_pipe_read_callback_extra_data.c index e8847ce..d5cdd0c 100644 --- a/tests/core/pipe/test_pipe_read_callback_extra_data.c +++ b/tests/core/pipe/test_pipe_read_callback_extra_data.c @@ -121,7 +121,7 @@ evbuffer_drain(struct evbuffer *buf, size_t len) if (len != sizeof(request) - sizeof(REQUEST_1)) { errx( 1, "trying to drain %ju bytes (expected %ju)", - (uintmax_t)len, sizeof(request) - sizeof(REQUEST_1) + (uintmax_t)len, (uintmax_t)(sizeof(request) - sizeof(REQUEST_1)) ); } break; @@ -143,7 +143,9 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) } if (size != -1) { - errx(1, "got unexpected size %ld in pullup (expected -1)", size); + errx( + 1, "got unexpected size %jd in pullup (expected -1)", (intmax_t)size + ); } return &request[evbuffer_pullup_call_count++ ? sizeof(request) - 1 : 0]; diff --git a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c index f6bd0d4..ce7c84d 100644 --- a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c +++ b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c @@ -160,7 +160,9 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) } if (size != -1) { - errx(1, "got unexpected size %ld in pullup (expected -1)", size); + errx( + 1, "got unexpected size %jd in pullup (expected -1)", (intmax_t)size + ); } int offset; diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c index 7a73f27..5e5ec4d 100644 --- a/tests/core/proto/test_proto_untag.c +++ b/tests/core/proto/test_proto_untag.c @@ -119,10 +119,10 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, } struct lgtd_lifx_packet_tags *pkt_tags = pkt; - if (pkt_tags->tags != 0x2) { + if (le64toh(pkt_tags->tags) != 0x2) { errx( 1, "invalid SET_TAGS payload=%#jx (expected %#x)", - (uintmax_t)pkt_tags->tags, 0x2 + (uintmax_t)le64toh(pkt_tags->tags), 0x2 ); } diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index fa3167e..daec583 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -31,6 +31,8 @@ main(void) const void *pkt_queued = lgtd_tests_gw_pkt_queue[0].pkt; int pkt_size = lgtd_tests_gw_pkt_queue[0].pkt_size; + lgtd_lifx_wire_decode_header(hdr_queued); + if (recpt_gw != gw_1) { lgtd_errx(1, "the packet has been sent to the wrong gateway"); } @@ -82,6 +84,8 @@ main(void) pkt_queued = lgtd_tests_gw_pkt_queue[i].pkt; pkt_size = lgtd_tests_gw_pkt_queue[i].pkt_size; + lgtd_lifx_wire_decode_header(hdr_queued); + if (!lgtd_tests_lifx_header_has_flags(hdr_queued, expected_flags)) { lgtd_errx(1, "the packet header doesn't have the right protocol flags"); } From f84f5e369df2eabbf46470c3bd26917b3eae465c Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 9 Aug 2015 15:56:49 -0700 Subject: [PATCH 073/181] Return the bulb address in get_light_state if the bulb label is empty --- core/proto.c | 2 +- tests/core/proto/test_proto_get_light_state.c | 2 +- tests/core/proto/test_proto_get_light_state_unknown_tag_id.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/proto.c b/core/proto.c index e295be6..be8c781 100644 --- a/core/proto.c +++ b/core/proto.c @@ -222,7 +222,7 @@ lgtd_proto_get_light_state(struct lgtd_client *client, buf, sizeof(buf), state_fmt, h, s, b, bulb->state.kelvin, bulb->state.power == LGTD_LIFX_POWER_ON ? "true" : "false", - bulb->state.label + bulb->state.label[0] ? bulb->state.label : lgtd_addrtoa(bulb->addr) ); if (written >= (int)sizeof(buf)) { lgtd_warnx( diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c index b5fcefa..9271db6 100644 --- a/tests/core/proto/test_proto_get_light_state.c +++ b/tests/core/proto/test_proto_get_light_state.c @@ -92,7 +92,7 @@ main(void) "{" "\"hsbk\":[0,0,1,4000]," "\"power\":false," - "\"label\":\"\"," + "\"label\":\"05:04:03:02:01:00\"," "\"tags\":[\"vapor\",\"d^-^b\"]" "}," "{" diff --git a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c index d9da2f0..0e79870 100644 --- a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c +++ b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c @@ -92,7 +92,7 @@ main(void) "{" "\"hsbk\":[0,0,1,4000]," "\"power\":false," - "\"label\":\"\"," + "\"label\":\"05:04:03:02:01:00\"," "\"tags\":[\"vapor\",\"d^-^b\"]" "}," "{" From f81b98986834ffa659863c36a020e3ce70150e06 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 15 Aug 2015 15:38:30 -0700 Subject: [PATCH 074/181] Use jq when available in lightsc.sh --- examples/lightsc.sh | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/examples/lightsc.sh b/examples/lightsc.sh index 315e7a5..e57e0d3 100644 --- a/examples/lightsc.sh +++ b/examples/lightsc.sh @@ -24,7 +24,7 @@ # tags and labels into double quotes '"likethis"'. Also keep in mind # that the pipe is write-only you cannot read any result back. -_b64e() { +_lightsc_b64e() { if type base64 >/dev/null 2>&1 ; then base64 elif type b64encode >/dev/null 2>&1 ; then @@ -35,14 +35,22 @@ _b64e() { fi } -_gen_request_id() { +_lightsc_gen_request_id() { if type dd >/dev/null 2>&1 ; then - printf '"%s"' `dd if=/dev/urandom bs=8 count=1 2>&- | _b64e` + printf '"%s"' `dd if=/dev/urandom bs=8 count=1 2>&- | _lightsc_b64e` else echo null fi } +_lightsc_jq() { + if type jq >/dev/null 2>&1 ; then + jq . + else + cat + fi +} + lightsc() { if [ $# -lt 2 ] ; then echo >&2 "Usage: $0 METHOD PARAMS ..." @@ -61,12 +69,13 @@ lightsc() { params=$params,$target done - tee $pipe < Date: Sat, 15 Aug 2015 16:35:09 -0700 Subject: [PATCH 075/181] Fix IPv4 address formatting --- core/lightsd.h | 4 ++++ core/log.c | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/core/lightsd.h b/core/lightsd.h index d364fe9..0e338f6 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -28,6 +28,10 @@ .tv_sec = (v) / 1000, \ .tv_usec = ((v) % 1000) * 1000 \ } +#define LGTD_SNPRINTF_APPEND(buf, i, bufsz, ...) do { \ + int n = snprintf(&(buf)[(i)], bufsz - i, __VA_ARGS__); \ + (i) = LGTD_MIN((i) + n, bufsz); \ +} while (0) enum lgtd_verbosity { LGTD_DEBUG = 0, diff --git a/core/log.c b/core/log.c index f9d53bf..f9b6ad3 100644 --- a/core/log.c +++ b/core/log.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #if LGTD_HAVE_LIBBSD @@ -100,12 +101,22 @@ lgtd_sockaddrtoa(const struct sockaddr_storage *peer, char *buf, int buflen) assert(buf); assert(buflen > 0); + const char *printed; if (peer->ss_family == AF_INET) { const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; - inet_ntop(AF_INET, &in_peer->sin_addr, buf, buflen); + int i = 0; + LGTD_SNPRINTF_APPEND(buf, i, buflen, "::ffff:"); + printed = inet_ntop(AF_INET, &in_peer->sin_addr, &buf[i], buflen - i); } else { const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; - inet_ntop(AF_INET6, &in6_peer->sin6_addr, buf, buflen); + printed = inet_ntop(AF_INET6, &in6_peer->sin6_addr, buf, buflen); + } + if (!printed) { + buf[0] = 0; + lgtd_warnx("not enough space to log an ip address"); +#ifndef NDEBUG + abort(); +#endif } } From a29669b07c6c9aedf4fbd6c639c4d61add7197cf Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 15 Aug 2015 16:35:17 -0700 Subject: [PATCH 076/181] Fix infinite loop in the command pipe read callback Looks like I suck at Unix, EOF & EAGAIN weren't handled properly. The pipe is now re-opened on EOF. We could probably make the command pipe bi-directional at this point. I wish conky wasn't fucked nowadays, that would have let me spot this earlier. --- core/pipe.c | 73 +++-- tests/core/pipe/test_pipe_read_callback.c | 25 ++ .../test_pipe_read_callback_yield_on_eagain.c | 269 ++++++++++++++++++ 3 files changed, 340 insertions(+), 27 deletions(-) create mode 100644 tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c diff --git a/core/pipe.c b/core/pipe.c index edcf93d..d1f6718 100644 --- a/core/pipe.c +++ b/core/pipe.c @@ -41,7 +41,7 @@ struct lgtd_command_pipe_list lgtd_command_pipes = SLIST_HEAD_INITIALIZER(&lgtd_command_pipes); static void -lgtd_command_pipe_close(struct lgtd_command_pipe *pipe) +_lgtd_command_pipe_close(struct lgtd_command_pipe *pipe) { assert(pipe); @@ -49,15 +49,23 @@ lgtd_command_pipe_close(struct lgtd_command_pipe *pipe) if (pipe->fd != -1) { close(pipe->fd); } - unlink(pipe->path); SLIST_REMOVE(&lgtd_command_pipes, pipe, lgtd_command_pipe, link); evbuffer_free(pipe->read_buf); event_free(pipe->read_ev); - - lgtd_info("closed command pipe %s", pipe->path); free(pipe); } +static void +lgtd_command_pipe_close(struct lgtd_command_pipe *pipe) +{ + const char *path = pipe->path; + _lgtd_command_pipe_close(pipe); + unlink(path); + lgtd_info("closed command pipe %s", path); +} + +static void lgtd_command_pipe_reset(struct lgtd_command_pipe *); + static void lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) { @@ -70,7 +78,6 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) struct lgtd_command_pipe *pipe = ctx; bool drain = false; - int read = 0; for (int nbytes = evbuffer_read(pipe->read_buf, pipe->fd, -1); nbytes; nbytes = evbuffer_read(pipe->read_buf, pipe->fd, -1)) { @@ -80,16 +87,12 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) } if (errno != EAGAIN) { lgtd_warn("read error on command pipe %s", pipe->path); - const char *path = pipe->path; - lgtd_command_pipe_close(pipe); - lgtd_command_pipe_open(path); - return; + break; } - continue; + return; // EAGAIN, go back to the event loop } if (!drain) { - read += nbytes; next_request: (void)0; const char *buf = (char *)evbuffer_pullup(pipe->read_buf, -1); @@ -127,25 +130,22 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) jsmn_init(&pipe->client.jsmn_ctx); int request_size = pipe->client.jsmn_tokens[0].end; evbuffer_drain(pipe->read_buf, request_size); - read -= request_size; - if (read) { + if (request_size < bufsz) { goto next_request; } break; } - } else { - evbuffer_drain(pipe->read_buf, read + nbytes); - read = 0; } - } - if (read) { - lgtd_debug( - "pipe %s: discarding %d bytes of unusable data", pipe->path, read - ); - evbuffer_drain(pipe->read_buf, read); + if (drain) { + ssize_t bufsz = evbuffer_get_length(pipe->read_buf); + evbuffer_drain(pipe->read_buf, bufsz); + drain = false; + jsmn_init(&pipe->client.jsmn_ctx); + } } - jsmn_init(&pipe->client.jsmn_ctx); + + lgtd_command_pipe_reset(pipe); } static mode_t @@ -156,8 +156,8 @@ lgtd_command_pipe_get_umask(void) return mask; } -bool -lgtd_command_pipe_open(const char *path) +static bool +_lgtd_command_pipe_open(const char *path) { assert(path); @@ -218,8 +218,6 @@ lgtd_command_pipe_open(const char *path) goto error; } - lgtd_info("command pipe ready at %s", pipe->path); - SLIST_INSERT_HEAD(&lgtd_command_pipes, pipe, link); return true; @@ -239,6 +237,27 @@ lgtd_command_pipe_open(const char *path) return false; } +static void +lgtd_command_pipe_reset(struct lgtd_command_pipe *pipe) +{ + const char *path = pipe->path; + // we could optimize a bit to avoid re-allocations here: + _lgtd_command_pipe_close(pipe); + if (!_lgtd_command_pipe_open(path)) { + lgtd_warn("can't re-open pipe %s", path); + } +} + +bool +lgtd_command_pipe_open(const char *path) +{ + if (_lgtd_command_pipe_open(path)) { + lgtd_info("command pipe ready at %s", path); + return true; + } + return false; +} + void lgtd_command_pipe_close_all(void) { diff --git a/tests/core/pipe/test_pipe_read_callback.c b/tests/core/pipe/test_pipe_read_callback.c index aef57aa..9ac43cd 100644 --- a/tests/core/pipe/test_pipe_read_callback.c +++ b/tests/core/pipe/test_pipe_read_callback.c @@ -7,6 +7,7 @@ #include "lifx/wire_proto.h" #define MOCKED_EVENT_NEW +#define MOCKED_EVENT_DEL #define MOCKED_EVBUFFER_NEW #define MOCKED_EVBUFFER_READ #define MOCKED_EVBUFFER_PULLUP @@ -69,6 +70,16 @@ event_new(struct event_base *base, return (void *)1; } +static int event_del_call_count = 0; + +int +event_del(struct event *ev) +{ + (void)ev; + event_del_call_count++; + return 0; +} + static int get_nbytes_read(int call_count) { @@ -186,8 +197,22 @@ main(void) } struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + lgtd_command_pipe_read_callback(pipe->fd, EV_READ, pipe); + if (event_del_call_count != 1) { + errx(1, "the pipe wasn't reset"); + } + jsonrpc_dispatch_request_call_count = 0; + evbuffer_drain_call_count = 0; + evbuffer_read_call_count = 0; + evbuffer_pullup_call_count = 0; + evbuffer_get_length_call_count = 0; + event_del_call_count = 0; + pipe = SLIST_FIRST(&lgtd_command_pipes); lgtd_command_pipe_read_callback(pipe->fd, EV_READ, pipe); + if (event_del_call_count != 1) { + errx(1, "the pipe wasn't reset"); + } return 0; } diff --git a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c new file mode 100644 index 0000000..9e645f9 --- /dev/null +++ b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c @@ -0,0 +1,269 @@ +#include "pipe.c" + +#include +#include +#include + +#include "lifx/wire_proto.h" + +#define MOCKED_EVENT_NEW +#define MOCKED_EVBUFFER_NEW +#define MOCKED_EVBUFFER_READ +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_GET_LENGTH +#define MOCKED_EVBUFFER_DRAIN +#include "mock_event2.h" +#include "mock_gateway.h" + +#include "tests_utils.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "tests_pipe_utils.h" + +#define REQUEST_1 "{" \ + "\"jsonrpc\": \"2.0\"," \ + "\"method\": \"get_light_state\"," \ + "\"params\": [\"*\"]," \ + "\"id\": 42" \ +"}" + +#define REQUEST_2 "{" \ + "\"jsonrpc\": \"2.0\"," \ + "\"method\": \"power_on\"," \ + "\"params\": [\"*\"]," \ + "\"id\": 43" \ +"}" + +static unsigned char request[] = ( + REQUEST_1 + REQUEST_2 +); + +static char *tmpdir = NULL; + +void +cleanup_tmpdir(void) +{ + lgtd_tests_remove_temp_dir(tmpdir); +} + +static int jsonrpc_dispatch_request_call_count = 0; + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + if (!parsed) { + errx(1, "number of parsed json tokens not passed in"); + } + + switch (jsonrpc_dispatch_request_call_count) { + case 0: + if (memcmp(client->json, request, sizeof(request) - 1)) { + errx( + 1, "got unexpected json %s (expected %s)", + client->json, request + ); + } + break; + case 1: + if (memcmp(client->json, REQUEST_2, sizeof(REQUEST_2) - 1)) { + errx( + 1, "got unexpected json %s (expected %s)", + client->json, REQUEST_2 + ); + } + break; + default: + errx( + 1, "jsonrpc_dispatch_request_call_count = %d", + jsonrpc_dispatch_request_call_count + ); + break; + } + + jsonrpc_dispatch_request_call_count++; +} + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + (void)base; + (void)fd; + (void)events; + (void)cb; + (void)ctx; + + return (void *)1; +} + +struct evbuffer * +evbuffer_new(void) +{ + return (void *)2; +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + jsmn_parser jsmn_ctx; + jsmn_init(&jsmn_ctx); + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { + errx(1, "the client json parser context wasn't re-initialized"); + } + + switch (evbuffer_drain_call_count) { + case 0: + if (len != sizeof(REQUEST_1) - 1) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)sizeof(REQUEST_1) - 1 + ); + } + break; + case 1: + if (len != sizeof(REQUEST_2) - 1) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)sizeof(REQUEST_2) - 1 + ); + } + break; + default: + errx(1, "evbuffer_drain_call_count = %d", evbuffer_drain_call_count); + break; + } + evbuffer_drain_call_count++; + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + if (size != -1) { + errx( + 1, "got unexpected size %jd in pullup (expected -1)", (intmax_t)size + ); + } + + int offset; + switch (evbuffer_pullup_call_count) { + case 0: + offset = 0; + break; + case 1: + offset = sizeof(REQUEST_1) - 1; + break; + default: + offset = sizeof(request); + break; + } + evbuffer_pullup_call_count++; + + return &request[offset]; +} + +static int evbuffer_get_length_call_count = 0; + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + size_t len; + switch (evbuffer_get_length_call_count) { + case 0: + len = sizeof(REQUEST_1) - 1; + break; + case 1: + len = sizeof(request) - sizeof(REQUEST_1); + break; + default: + len = 0; + break; + } + evbuffer_get_length_call_count++; + + return len; +} + +static int evbuffer_read_call_count = 0; + +int +evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch) +{ + if (buf != (void *)2) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (fd != pipe->fd) { + errx(1, "got unexpected fd %d (expected %d)", fd, pipe->fd); + } + + if (howmuch != -1) { + errx( + 1, "got unexpected howmuch bytes to read %d (expected -1)", howmuch + ); + } + + int rv = 0; + switch (evbuffer_read_call_count) { + case 0: + rv = sizeof(REQUEST_1) - 1; + break; + case 1: + rv = -1; + errno = EAGAIN; + break; + case 2: + rv = sizeof(request) - sizeof(REQUEST_1); + break; + default: + break; + } + evbuffer_read_call_count++; + + return rv; +} + +int +main(void) +{ + tmpdir = lgtd_tests_make_temp_dir(); + atexit(cleanup_tmpdir); + + char path[PATH_MAX] = { 0 }; + snprintf(path, sizeof(path), "%s/lightsd.pipe", tmpdir); + if (!lgtd_command_pipe_open(path)) { + errx(1, "couldn't open pipe"); + } + + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + + lgtd_command_pipe_read_callback(pipe->fd, EV_READ, pipe); + lgtd_command_pipe_read_callback(pipe->fd, EV_READ, pipe); + + return 0; +} From 9c0fad2cac2e95b813146330bbcbd7a685a3ceba Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 15:27:55 -0700 Subject: [PATCH 077/181] Correctly convert LIFX addresses to ASCII Rename the conversion function to something more explicit and make it re-entrant so you can print more than one address per printf call... --- core/lightsd.h | 4 +- core/log.c | 11 ++-- core/proto.c | 14 ++-- core/router.c | 4 +- lifx/bulb.c | 3 +- lifx/gateway.c | 64 +++++++++++++------ lifx/tagging.c | 6 +- lifx/wire_proto.h | 1 + tests/core/proto/test_proto_tag_create.c | 4 +- tests/core/proto/test_proto_tag_update.c | 4 +- tests/core/proto/test_proto_untag.c | 4 +- tests/lifx/bulb/test_bulb_open.c | 4 +- .../test_wire_proto_encode_decode_header.c | 8 ++- 13 files changed, 88 insertions(+), 43 deletions(-) diff --git a/core/lightsd.h b/core/lightsd.h index 0e338f6..8fdada9 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -51,7 +51,9 @@ struct lgtd_opts { extern struct lgtd_opts lgtd_opts; extern struct event_base *lgtd_ev_base; -const char *lgtd_addrtoa(const uint8_t *); +char *lgtd_iee8023mactoa(const uint8_t *addr, char *buf, int buflen); +#define LGTD_IEEE8023MACTOA(addr, buf) \ + lgtd_iee8023mactoa((addr), (buf), sizeof(buf)) void lgtd_sockaddrtoa(const struct sockaddr_storage *, char *buf, int buflen); short lgtd_sockaddrport(const struct sockaddr_storage *); diff --git a/core/log.c b/core/log.c index f9b6ad3..5d4ba37 100644 --- a/core/log.c +++ b/core/log.c @@ -81,17 +81,18 @@ lgtd_log_header(const char *loglvl, bool showprogname) fprintf(stderr, "[%s] %s", loglvl, showprogname ? "lightsd: " : ""); } -const char * -lgtd_addrtoa(const uint8_t *addr) +char * +lgtd_iee8023mactoa(const uint8_t *addr, char *buf, int buflen) { assert(addr); + assert(buf); + assert(buflen >= 2 * 6 + 5 + 1); - static char str[LGTD_LIFX_ADDR_LENGTH * 2 + LGTD_LIFX_ADDR_LENGTH - 1 + 1]; snprintf( - str, sizeof(str), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + buf, buflen, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] ); - return str; + return buf; } void diff --git a/core/proto.c b/core/proto.c index be8c781..38f4c47 100644 --- a/core/proto.c +++ b/core/proto.c @@ -217,19 +217,22 @@ lgtd_proto_get_light_state(struct lgtd_client *client, PRINT_COMPONENT(bulb->state.saturation, s, 0, 1); PRINT_COMPONENT(bulb->state.brightness, b, 0, 1); - char buf[3072]; + char buf[3072], + bulb_addr[LGTD_LIFX_ADDR_STRLEN], + site_addr[LGTD_LIFX_ADDR_STRLEN]; + LGTD_IEEE8023MACTOA(bulb->addr, bulb_addr); + LGTD_IEEE8023MACTOA(bulb->gw->site.as_array, site_addr); int written = snprintf( buf, sizeof(buf), state_fmt, h, s, b, bulb->state.kelvin, bulb->state.power == LGTD_LIFX_POWER_ON ? "true" : "false", - bulb->state.label[0] ? bulb->state.label : lgtd_addrtoa(bulb->addr) + bulb->state.label[0] ? bulb->state.label : bulb_addr ); if (written >= (int)sizeof(buf)) { lgtd_warnx( "can't send state of bulb %s (%s) to client " "[%s]:%hu: output buffer to small", - bulb->state.label, lgtd_addrtoa(bulb->addr), - client->ip_addr, client->port + bulb->state.label, bulb_addr, client->ip_addr, client->port ); continue; } @@ -248,8 +251,7 @@ lgtd_proto_get_light_state(struct lgtd_client *client, "tag_id %d on bulb %.*s (%s) doesn't " "exist on gw [%s]:%hu (site %s)", tag_id, (int)sizeof(bulb->state.label), bulb->state.label, - lgtd_addrtoa(bulb->addr), bulb->gw->ip_addr, bulb->gw->port, - lgtd_addrtoa(bulb->gw->site.as_array) + bulb_addr, bulb->gw->ip_addr, bulb->gw->port, site_addr ); } } diff --git a/core/router.c b/core/router.c index b238de4..f69cce4 100644 --- a/core/router.c +++ b/core/router.c @@ -106,7 +106,9 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, bulb->expected_power_on = payload->power; } - lgtd_info("sending %s to %s", pkt_info->name, lgtd_addrtoa(bulb->addr)); + char addr[LGTD_LIFX_ADDR_STRLEN]; + LGTD_IEEE8023MACTOA(bulb->addr, addr); + lgtd_info("sending %s to %s", pkt_info->name, addr); } void diff --git a/lifx/bulb.c b/lifx/bulb.c index 3d35a0b..847f811 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -100,11 +100,12 @@ lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *bulb) LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(bulbs_powered_on, -1); } RB_REMOVE(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, bulb); + char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( "closed bulb \"%.*s\" (%s) on [%s]:%hu", LGTD_LIFX_LABEL_SIZE, bulb->state.label, - lgtd_addrtoa(bulb->addr), + LGTD_IEEE8023MACTOA(bulb->addr, addr), bulb->gw->ip_addr, bulb->gw->port ); diff --git a/lifx/gateway.c b/lifx/gateway.c index 0cb1c57..5e3b48a 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -76,9 +76,10 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) lgtd_lifx_gateway_remove_and_close_bulb(gw, bulb); } + char site[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( "connection with gateway bulb [%s]:%hu (site %s) closed", - gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) + gw->ip_addr, gw->port, LGTD_IEEE8023MACTOA(gw->site.as_array, site) ); free(gw); } @@ -197,9 +198,10 @@ lgtd_lifx_gateway_send_to_site_quiet(struct lgtd_lifx_gateway *gw, gw, pkt_type, pkt, &pkt_info ); + char site[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "sending %s to site %s", - pkt_info->name, lgtd_addrtoa(gw->site.as_array) + pkt_info->name, LGTD_IEEE8023MACTOA(gw->site.as_array, site) ); return rv; // FIXME, have real return values on the send paths... @@ -215,9 +217,10 @@ lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, gw, pkt_type, pkt, &pkt_info ); + char site[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( "sending %s to site %s", - pkt_info->name, lgtd_addrtoa(gw->site.as_array) + pkt_info->name, LGTD_IEEE8023MACTOA(gw->site.as_array, site) ); return rv; // FIXME, have real return values on the send paths... @@ -266,9 +269,10 @@ lgtd_lifx_gateway_get_or_open_bulb(struct lgtd_lifx_gateway *gw, bulb = lgtd_lifx_bulb_open(gw, bulb_addr); if (bulb) { SLIST_INSERT_HEAD(&gw->bulbs, bulb, link_by_gw); + char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( "bulb %s on [%s]:%hu", - lgtd_addrtoa(bulb_addr), gw->ip_addr, gw->port + LGTD_IEEE8023MACTOA(bulb->addr, addr), gw->ip_addr, gw->port ); } } @@ -328,9 +332,11 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, goto error_allocate; } + char site_addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( "gateway for site %s at [%s]:%hu", - lgtd_addrtoa(gw->site.as_array), gw->ip_addr, gw->port + LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr), + gw->ip_addr, gw->port ); LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); @@ -444,10 +450,12 @@ lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, removed_tags) { assert(gw->tag_refcounts[tag_id] > 0); if (--gw->tag_refcounts[tag_id] == 0) { + char site[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( "deleting unused tag [%s] (%d) from gw [%s]:%hu (site %s)", - gw->tags[tag_id] ? gw->tags[tag_id]->label : NULL, tag_id, - gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) + gw->tags[tag_id] ? gw->tags[tag_id]->label : NULL, + tag_id, gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(gw->site.as_array, site) ); struct lgtd_lifx_packet_tag_labels pkt = { .tags = ~(gw->tag_ids & ~LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id)) @@ -465,10 +473,12 @@ lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, { assert(gw && hdr && pkt); + char addr[LGTD_LIFX_ADDR_STRLEN], site[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "SET_PAN_GATEWAY <-- [%s]:%hu - %s site=%s, service_type=%d", - gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), - lgtd_addrtoa(hdr->site), pkt->service_type + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + LGTD_IEEE8023MACTOA(hdr->site, site), pkt->service_type ); } @@ -479,11 +489,13 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, { assert(gw && hdr && pkt); + char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "SET_LIGHT_STATE <-- [%s]:%hu - %s " "hue=%#hx, saturation=%#hx, brightness=%#hx, " "kelvin=%d, dim=%#hx, power=%#hx, label=%.*s, tags=%#jx", - gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), pkt->hue, pkt->saturation, pkt->brightness, pkt->kelvin, pkt->dim, pkt->power, LGTD_LIFX_LABEL_SIZE, pkt->label, (uintmax_t)pkt->tags @@ -554,9 +566,11 @@ lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, { assert(gw && hdr && pkt); + char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "SET_POWER_STATE <-- [%s]:%hu - %s power=%#hx", - gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), pkt->power + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), pkt->power ); struct lgtd_lifx_bulb *b = lgtd_lifx_gateway_get_or_open_bulb( @@ -596,13 +610,15 @@ lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, assert(tag_id >= -1); assert(tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); + char site[LGTD_LIFX_ADDR_STRLEN]; + LGTD_IEEE8023MACTOA(gw->site.as_array, site); + if (tag_id == -1) { tag_id = lgtd_lifx_wire_bitscan64_forward(~gw->tag_ids); if (tag_id == -1) { lgtd_warnx( "no tag_id left for new tag [%s] on gw [%s]:%hu (site %s)", - tag_label, gw->ip_addr, gw->port, - lgtd_addrtoa(gw->site.as_array) + tag_label, gw->ip_addr, gw->port, site ); return -1; } @@ -614,14 +630,13 @@ lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, if (!tag) { lgtd_warn( "couldn't allocate a new reference to tag [%s] (site %s)", - tag_label, lgtd_addrtoa(gw->site.as_array) + tag_label, site ); return -1; } lgtd_debug( "tag_id %d allocated for tag [%s] on gw [%s]:%hu (site %s)", - tag_id, tag_label, gw->ip_addr, gw->port, - lgtd_addrtoa(gw->site.as_array) + tag_id, tag_label, gw->ip_addr, gw->port, site ); gw->tag_ids |= LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); gw->tags[tag_id] = tag; @@ -638,11 +653,12 @@ lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id) assert(tag_id < LGTD_LIFX_GATEWAY_MAX_TAGS); if (gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id)) { + char site[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "tag_id %d deallocated for tag [%s] on gw [%s]:%hu (site %s)", tag_id, gw->tags[tag_id]->label, gw->ip_addr, gw->port, - lgtd_addrtoa(gw->site.as_array) + LGTD_IEEE8023MACTOA(gw->site.as_array, site) ); lgtd_lifx_tagging_decref(gw->tags[tag_id], gw); gw->tag_ids &= ~LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); @@ -657,9 +673,11 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, { assert(gw && hdr && pkt); + char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "SET_TAG_LABELS <-- [%s]:%hu - %s label=%.*s, tags=%jx", - gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), LGTD_LIFX_LABEL_SIZE, pkt->label, (uintmax_t)pkt->tags ); @@ -679,9 +697,11 @@ void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, { assert(gw && hdr && pkt); + char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "SET_TAGS <-- [%s]:%hu - %s tags=%#jx", - gw->ip_addr, gw->port, lgtd_addrtoa(hdr->target.device_addr), + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), (uintmax_t)pkt->tags ); @@ -692,6 +712,7 @@ void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, return; } + char bulb_addr[LGTD_LIFX_ADDR_STRLEN], site_addr[LGTD_LIFX_ADDR_STRLEN]; int tag_id; LGTD_LIFX_WIRE_FOREACH_TAG_ID(tag_id, pkt->tags) { if (!(gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id))) { @@ -699,8 +720,9 @@ void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, "trying to set unknown tag_id %d (%#jx) " "on bulb %s (%.*s), gw [%s]:%hu (site %s)", tag_id, LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id), - lgtd_addrtoa(b->addr), LGTD_LIFX_LABEL_SIZE, b->state.label, - gw->ip_addr, gw->port, lgtd_addrtoa(gw->site.as_array) + LGTD_IEEE8023MACTOA(b->addr, bulb_addr), + LGTD_LIFX_LABEL_SIZE, b->state.label, gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr) ); } } diff --git a/lifx/tagging.c b/lifx/tagging.c index 864b3ed..56a9cae 100644 --- a/lifx/tagging.c +++ b/lifx/tagging.c @@ -123,10 +123,11 @@ lgtd_lifx_tagging_incref(const char *tag_label, if (dealloc_tag) { lgtd_info("discovered tag [%s]", tag_label); } + char site_addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( "tag [%s] added to gw [%s]:%hu (site %s) with tag_id %d", tag_label, gw->ip_addr, gw->port, - lgtd_addrtoa(gw->site.as_array), tag_id + LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr), tag_id ); site->gw = gw; site->tag_id = tag_id; @@ -147,10 +148,11 @@ lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, struct lgtd_lifx_site *site; site = lgtd_lifx_tagging_find_site(&tag->sites, gw); if (site) { + char site_addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "tag [%s] removed from gw [%s]:%hu (site %s)", tag->label, gw->ip_addr, gw->port, - lgtd_addrtoa(gw->site.as_array) + LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr) ); LIST_REMOVE(site, link); free(site); diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index bc50060..cd7601e 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -44,6 +44,7 @@ lgtd_lifx_wire_lefloattoh(float f) enum { LGTD_LIFX_PROTOCOL_PORT = 56700 }; enum { LGTD_LIFX_ADDR_LENGTH = 6 }; +enum { LGTD_LIFX_ADDR_STRLEN = 32 }; #pragma pack(push, 1) diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c index 50e5231..e151543 100644 --- a/tests/core/proto/test_proto_tag_create.c +++ b/tests/core/proto/test_proto_tag_create.c @@ -31,10 +31,12 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, } uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { 1, 2, 3, 4, 5 }; + char addr[LGTD_LIFX_ADDR_STRLEN], expected[LGTD_LIFX_ADDR_STRLEN]; if (memcmp(bulb->addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { errx( 1, "got bulb with addr %s (expected %s)", - lgtd_addrtoa(bulb->addr), lgtd_addrtoa(expected_addr) + LGTD_IEEE8023MACTOA(bulb->addr, addr), + LGTD_IEEE8023MACTOA(expected_addr, expected) ); } diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c index ced8ff4..21c880b 100644 --- a/tests/core/proto/test_proto_tag_update.c +++ b/tests/core/proto/test_proto_tag_update.c @@ -33,10 +33,12 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, } uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { 5, 4, 3, 2, 1 }; + char addr[LGTD_LIFX_ADDR_STRLEN], expected[LGTD_LIFX_ADDR_STRLEN]; if (memcmp(bulb->addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { errx( 1, "got bulb with addr %s (expected %s)", - lgtd_addrtoa(bulb->addr), lgtd_addrtoa(expected_addr) + LGTD_IEEE8023MACTOA(bulb->addr, addr), + LGTD_IEEE8023MACTOA(expected_addr, expected) ); } diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c index 5e5ec4d..b34014e 100644 --- a/tests/core/proto/test_proto_untag.c +++ b/tests/core/proto/test_proto_untag.c @@ -101,10 +101,12 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, } uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { 5, 4, 3, 2, 1 }; + char addr[LGTD_LIFX_ADDR_STRLEN], expected[LGTD_LIFX_ADDR_STRLEN]; if (memcmp(bulb->addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { errx( 1, "got bulb with addr %s (expected %s)", - lgtd_addrtoa(bulb->addr), lgtd_addrtoa(expected_addr) + LGTD_IEEE8023MACTOA(bulb->addr, addr), + LGTD_IEEE8023MACTOA(expected_addr, expected) ); } diff --git a/tests/lifx/bulb/test_bulb_open.c b/tests/lifx/bulb/test_bulb_open.c index 3695e3f..129fd72 100644 --- a/tests/lifx/bulb/test_bulb_open.c +++ b/tests/lifx/bulb/test_bulb_open.c @@ -14,10 +14,12 @@ main(void) errx(1, "lgtd_lifx_bulb_open didn't return any bulb"); } + char addr[LGTD_LIFX_ADDR_STRLEN], expected[LGTD_LIFX_ADDR_STRLEN]; if (memcmp(bulb->addr, bulb_addr, LGTD_LIFX_ADDR_LENGTH)) { errx( 1, "got bulb addr %s (expected %s)", - lgtd_addrtoa(bulb->addr), lgtd_addrtoa(bulb_addr) + LGTD_IEEE8023MACTOA(bulb->addr, addr), + LGTD_IEEE8023MACTOA(bulb_addr, expected) ); } diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c index 72c7a0d..5fc287f 100644 --- a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c +++ b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c @@ -78,10 +78,13 @@ main(void) uint8_t expected_addr[LGTD_LIFX_ADDR_LENGTH] = { 0, 0, 44, 0, 0, 0 }; + char expected_addr_buf[LGTD_LIFX_ADDR_STRLEN]; + char dev_addr[LGTD_LIFX_ADDR_STRLEN]; if (memcmp(hdr.target.device_addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { lgtd_errx( 1, "device addr = %s (expected = %s)", - lgtd_addrtoa(hdr.target.device_addr), lgtd_addrtoa(expected_addr) + LGTD_IEEE8023MACTOA(hdr.target.device_addr, dev_addr), + LGTD_IEEE8023MACTOA(expected_addr, expected_addr_buf) ); } if (le16toh(hdr.packet_type) != LGTD_LIFX_ECHO_REQUEST) { @@ -109,7 +112,8 @@ main(void) if (memcmp(hdr.target.device_addr, expected_addr, LGTD_LIFX_ADDR_LENGTH)) { lgtd_errx( 1, "device addr = %s (expected = %s)", - lgtd_addrtoa(hdr.target.device_addr), lgtd_addrtoa(expected_addr) + LGTD_IEEE8023MACTOA(hdr.target.device_addr, dev_addr), + LGTD_IEEE8023MACTOA(expected_addr, expected_addr_buf) ); } if (hdr.size != 42) { From 4b19bed8b05b77f17bbdbc49f0d884d6a8800e22 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 15:27:55 -0700 Subject: [PATCH 078/181] Bump the device timeout to 2.5x device force refresh This will let us do more retries before we drop devices. --- lifx/timer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lifx/timer.h b/lifx/timer.h index 8545cd8..915fd46 100644 --- a/lifx/timer.h +++ b/lifx/timer.h @@ -20,7 +20,7 @@ enum { LGTD_LIFX_TIMER_WATCHDOG_INTERVAL_MSECS = 500 }; enum { LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000 }; enum { LGTD_LIFX_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000 }; -enum { LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS = 3000 }; +enum { LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS = 5000 }; enum { LGTD_LIFX_TIMER_DEVICE_FORCE_REFRESH_MSECS = 2000 }; bool lgtd_lifx_timer_setup(void); From 75e2e32eff5638c6840b4967e26cbf7015b03b03 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 15:27:55 -0700 Subject: [PATCH 079/181] Ignore SIGPIPE It would be sad to die on this one because a client decided to leave at the wrong moment. --- core/lightsd.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/lightsd.c b/core/lightsd.c index d28cabf..9c52d78 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -123,6 +124,11 @@ lgtd_configure_signal_handling(void) ); evsignal_add(&sigevs[i], NULL); } + + struct sigaction act = { .sa_handler = SIG_IGN }; + if (sigaction(SIGPIPE, &act, NULL)) { + lgtd_err(1, "can't configure signal handling"); + } } static void From 349773d2d081108c2124f023d302bcc28c7efbb8 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:17:07 -0700 Subject: [PATCH 080/181] Add an interface to start and stop standalone timers And rename lifx/timer to lifx/watchdog. --- core/CMakeLists.txt | 1 + core/lightsd.c | 9 +- core/timer.c | 104 +++++++++++++ core/timer.h | 77 ++++++++++ lifx/CMakeLists.txt | 2 +- lifx/gateway.c | 4 +- lifx/gateway.h | 2 +- lifx/{timer.c => watchdog.c} | 86 ++++++----- lifx/{timer.h => watchdog.h} | 20 +-- .../daemon/test_daemon_update_proctitle.c | 2 + tests/core/mock_event2.h | 28 +++- tests/core/mock_router.h | 81 ++++++++++ tests/core/mock_timer.h | 33 ++++ tests/core/pipe/test_pipe_close.c | 2 + tests/core/pipe/test_pipe_open.c | 2 + .../pipe/test_pipe_open_fifo_already_exists.c | 2 + tests/core/pipe/test_pipe_read_callback.c | 2 + .../pipe/test_pipe_read_callback_extra_data.c | 2 + ...est_pipe_read_callback_multiple_requests.c | 2 + .../test_pipe_read_callback_yield_on_eagain.c | 2 + tests/core/proto/CMakeLists.txt | 1 - tests/core/proto/test_proto_get_light_state.c | 2 + ..._proto_get_light_state_empty_device_list.c | 2 + ...t_proto_get_light_state_null_device_list.c | 2 + ...est_proto_get_light_state_unknown_tag_id.c | 2 + tests/core/proto/test_proto_power_off.c | 2 + .../test_proto_power_off_routing_error.c | 2 + tests/core/proto/test_proto_power_on.c | 2 + .../proto/test_proto_power_on_routing_error.c | 2 + tests/core/proto/test_proto_power_toggle.c | 2 + ...oto_power_toggle_targets_to_device_fails.c | 2 + .../proto/test_proto_set_light_from_hsbk.c | 2 + ...oto_set_light_from_hsbk_on_routing_error.c | 2 + tests/core/proto/test_proto_set_waveform.c | 2 + ...test_proto_set_waveform_on_routing_error.c | 2 + tests/core/proto/test_proto_tag_create.c | 2 + ...st_proto_tag_create_lifx_gw_tag_ids_full.c | 2 + tests/core/proto/test_proto_tag_update.c | 2 + tests/core/proto/test_proto_untag.c | 2 + .../test_proto_untag_tag_does_not_exist.c | 2 + tests/core/router/CMakeLists.txt | 1 - .../router/test_router_send_to_broadcast.c | 1 + .../core/router/test_router_send_to_device.c | 1 + .../test_router_send_to_invalid_targets.c | 1 + tests/core/router/test_router_send_to_label.c | 1 + tests/core/router/test_router_send_to_tag.c | 1 + .../router/test_router_targets_to_devices.c | 1 + tests/core/tests_shims.c | 4 +- tests/core/tests_shims.h | 4 +- tests/core/timer/CMakeLists.txt | 13 ++ tests/core/timer/test_timer_ispending.c | 45 ++++++ tests/core/timer/test_timer_reschedule.c | 45 ++++++ .../timer/test_timer_start_activate_now.c | 145 ++++++++++++++++++ .../core/timer/test_timer_start_persistent.c | 129 ++++++++++++++++ tests/lifx/CMakeLists.txt | 2 + tests/lifx/bulb/CMakeLists.txt | 1 - tests/lifx/bulb/test_bulb_close.c | 2 + tests/lifx/bulb/test_bulb_open.c | 2 + tests/lifx/bulb/test_bulb_set_light_state.c | 2 + tests/lifx/bulb/test_bulb_set_power_state.c | 2 + tests/lifx/bulb/test_bulb_set_tags.c | 2 + tests/lifx/gateway/CMakeLists.txt | 2 +- 62 files changed, 844 insertions(+), 65 deletions(-) create mode 100644 core/timer.c create mode 100644 core/timer.h rename lifx/{timer.c => watchdog.c} (62%) rename lifx/{timer.h => watchdog.h} (61%) create mode 100644 tests/core/mock_router.h create mode 100644 tests/core/mock_timer.h create mode 100644 tests/core/timer/CMakeLists.txt create mode 100644 tests/core/timer/test_timer_ispending.c create mode 100644 tests/core/timer/test_timer_reschedule.c create mode 100644 tests/core/timer/test_timer_start_activate_now.c create mode 100644 tests/core/timer/test_timer_start_persistent.c diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 6e23a5f..50524a2 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -25,6 +25,7 @@ ADD_EXECUTABLE( proto.c router.c stats.c + timer.c ) TARGET_LINK_LIBRARIES( diff --git a/core/lightsd.c b/core/lightsd.c index 9c52d78..5293a12 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -41,12 +41,13 @@ #include "lifx/bulb.h" #include "lifx/gateway.h" #include "lifx/broadcast.h" -#include "lifx/timer.h" +#include "lifx/watchdog.h" #include "version.h" #include "jsmn.h" #include "jsonrpc.h" #include "client.h" #include "pipe.h" +#include "timer.h" #include "listen.h" #include "daemon.h" #include "lightsd.h" @@ -65,9 +66,9 @@ lgtd_cleanup(void) lgtd_listen_close_all(); lgtd_command_pipe_close_all(); lgtd_client_close_all(); - lgtd_lifx_timer_close(); lgtd_lifx_broadcast_close(); lgtd_lifx_gateway_close_all(); + lgtd_timer_stop_all(); event_base_free(lgtd_ev_base); #if LIBEVENT_VERSION_NUMBER >= 0x02010100 libevent_global_shutdown(); @@ -227,7 +228,7 @@ main(int argc, char *argv[], char *envp[]) argv += optind; lgtd_lifx_wire_load_packet_info_map(); - if (!lgtd_lifx_timer_setup() || !lgtd_lifx_broadcast_setup()) { + if (!lgtd_lifx_watchdog_setup() || !lgtd_lifx_broadcast_setup()) { lgtd_err(1, "can't setup lightsd"); } @@ -235,7 +236,7 @@ main(int argc, char *argv[], char *envp[]) lgtd_err(1, "can't fork to the background"); } - lgtd_lifx_timer_start_discovery(); + lgtd_lifx_watchdog_start_discovery(); event_base_dispatch(lgtd_ev_base); diff --git a/core/timer.c b/core/timer.c new file mode 100644 index 0000000..a32772d --- /dev/null +++ b/core/timer.c @@ -0,0 +1,104 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "timer.h" +#include "lightsd.h" + +static struct lgtd_timer_list lgtd_timers = LIST_HEAD_INITIALIZER(&lgtd_timers); + +static void +lgtd_timer_callback(evutil_socket_t socket, short events, void *ctx) +{ + assert(ctx); + + (void)socket; + (void)events; + + struct lgtd_timer *timer = ctx; + timer->callback(timer, timer->ctx); +} + +struct lgtd_timer * +lgtd_timer_start(int flags, + int ms, + void (*cb)(struct lgtd_timer *, + union lgtd_timer_ctx), + union lgtd_timer_ctx ctx) +{ + assert(ms > 0); + assert(cb); + + struct lgtd_timer *timer = calloc(1, sizeof(*timer)); + if (!timer) { + return false; + } + timer->callback = cb; + timer->ctx = ctx; + LIST_INSERT_HEAD(&lgtd_timers, timer, link); + + struct timeval tv = LGTD_MSECS_TO_TIMEVAL(ms); + timer->event = event_new( + lgtd_ev_base, + -1, + flags & LGTD_TIMER_PERSISTENT ? EV_PERSIST : 0, + lgtd_timer_callback, + timer + ); + if (!timer->event || evtimer_add(timer->event, &tv)) { + LIST_REMOVE(timer, link); + if (timer->event) { + event_free(timer->event); + } + free(timer); + return NULL; + } + + if (flags & LGTD_TIMER_ACTIVATE_NOW) { + lgtd_timer_activate(timer); + } + return timer; +} + +void +lgtd_timer_stop(struct lgtd_timer *timer) +{ + assert(timer); + + LIST_REMOVE(timer, link); + event_del(timer->event); + event_free(timer->event); + free(timer); +} + +void +lgtd_timer_stop_all(void) +{ + struct lgtd_timer *timer, *next_timer; + LIST_FOREACH_SAFE(timer, &lgtd_timers, link, next_timer) { + lgtd_timer_stop(timer); + } +} diff --git a/core/timer.h b/core/timer.h new file mode 100644 index 0000000..d54c6f2 --- /dev/null +++ b/core/timer.h @@ -0,0 +1,77 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#pragma once + +struct timeval; + +union lgtd_timer_ctx { + uint64_t as_uint; + void *as_ptr; +}; + +struct lgtd_timer { + LIST_ENTRY(lgtd_timer) link; + void (*callback)(struct lgtd_timer *, + union lgtd_timer_ctx); + union lgtd_timer_ctx ctx; + struct event *event; +}; +LIST_HEAD(lgtd_timer_list, lgtd_timer); + +enum lgtd_timer_flags { + LGTD_TIMER_DEFAULT_FLAGS = 0, + LGTD_TIMER_ACTIVATE_NOW = 1, + LGTD_TIMER_PERSISTENT = 1 << 1, +}; + +// Activate the timer now, in other words make the callback pending: +static inline void +lgtd_timer_activate(struct lgtd_timer *timer) +{ + assert(timer); + + event_active(timer->event, 0, 0); +} + +// Re-schedule a non-persistent timer with the given timeout: +static inline bool +lgtd_timer_reschedule(struct lgtd_timer *timer, const struct timeval *tv) +{ + assert(timer); + assert(tv); + + return !evtimer_add(timer->event, tv); +} + +static inline bool +lgtd_timer_ispending(const struct lgtd_timer *timer) +{ + assert(timer); + + return evtimer_pending(timer->event, NULL); +} + +void lgtd_timer_stop(struct lgtd_timer *); +void lgtd_timer_stop_all(void); +// NOTE: if you start a persistent timer and don't keep track of it, make sure +// you don't end up in a callback using a context that has been freed. +struct lgtd_timer *lgtd_timer_start(int, + int, // ms + void (*)(struct lgtd_timer *, + union lgtd_timer_ctx), + union lgtd_timer_ctx); diff --git a/lifx/CMakeLists.txt b/lifx/CMakeLists.txt index 119314f..9b904de 100644 --- a/lifx/CMakeLists.txt +++ b/lifx/CMakeLists.txt @@ -11,6 +11,6 @@ ADD_LIBRARY( bulb.c gateway.c tagging.c - timer.c + watchdog.c wire_proto.c ) diff --git a/lifx/gateway.c b/lifx/gateway.c index 5e3b48a..20868aa 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -36,8 +36,8 @@ #include "core/time_monotonic.h" #include "bulb.h" #include "gateway.h" +#include "watchdog.h" #include "broadcast.h" -#include "timer.h" #include "tagging.h" #include "core/jsmn.h" #include "core/jsonrpc.h" @@ -342,7 +342,7 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, // In case this is the first bulb (re-)discovered, start the watchdog, it // will stop by itself: - lgtd_lifx_timer_start_watchdog(); + lgtd_lifx_watchdog_start(); LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, 1); diff --git a/lifx/gateway.h b/lifx/gateway.h index 67dcaba..ae74e27 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -53,7 +53,7 @@ struct lgtd_lifx_gateway { struct lgtd_lifx_tag *tags[LGTD_LIFX_GATEWAY_MAX_TAGS]; uint8_t tag_refcounts[LGTD_LIFX_GATEWAY_MAX_TAGS]; evutil_socket_t socket; - // Those three timers let us measure the latency of the gateway. If we + // Those three timestamps let us measure the latency of the gateway. If we // aren't the only client on the network then this won't be accurate since // we will get pushed packets we didn't ask for, but good enough for our // purpose of rate limiting our requests to the gateway: diff --git a/lifx/timer.c b/lifx/watchdog.c similarity index 62% rename from lifx/timer.c rename to lifx/watchdog.c index 72b4df9..91181c0 100644 --- a/lifx/timer.c +++ b/lifx/watchdog.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -32,48 +33,48 @@ #include "broadcast.h" #include "bulb.h" #include "gateway.h" -#include "timer.h" +#include "watchdog.h" #include "core/lightsd.h" static struct { struct event *watchdog_interval_ev; struct event *discovery_timeout_ev; -} lgtd_lifx_timer_context = { +} lgtd_lifx_watchdog_context = { .watchdog_interval_ev = NULL, .discovery_timeout_ev = NULL }; static void -lgtd_lifx_timer_discovery_timeout_event_callback(evutil_socket_t socket, - short events, - void *ctx) +lgtd_lifx_watchdog_discovery_timeout_event_callback(evutil_socket_t socket, + short events, + void *ctx) { (void)socket; (void)events; (void)ctx; - int timeout = LGTD_LIFX_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS; + int timeout = LGTD_LIFX_WATCHDOG_PASSIVE_DISCOVERY_INTERVAL_MSECS; if (LIST_EMPTY(&lgtd_lifx_gateways)) { lgtd_debug( "discovery didn't returned anything in %dms, restarting it", - LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS + LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS ); - timeout = LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS; + timeout = LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; } else { lgtd_debug("sending periodic discovery packet"); } struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); - if (event_add(lgtd_lifx_timer_context.discovery_timeout_ev, &tv) + if (event_add(lgtd_lifx_watchdog_context.discovery_timeout_ev, &tv) || !lgtd_lifx_broadcast_discovery()) { lgtd_err(1, "can't start discovery"); } } static void -lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, - short events, - void *ctx) +lgtd_lifx_watchdog_timeout_event_callback(evutil_socket_t socket, + short events, + void *ctx) { (void)socket; (void)events; @@ -90,7 +91,7 @@ lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, next_bulb ) { int light_state_lag = now - bulb->last_light_state_at; - if (light_state_lag >= LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS) { + if (light_state_lag >= LGTD_LIFX_WATCHDOG_DEVICE_TIMEOUT_MSECS) { lgtd_info( "closing bulb \"%.*s\" that hasn't been updated for %dms", LGTD_LIFX_LABEL_SIZE, bulb->state.label, light_state_lag @@ -107,7 +108,7 @@ lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, struct lgtd_lifx_gateway *gw, *next_gw; LIST_FOREACH_SAFE(gw, &lgtd_lifx_gateways, link, next_gw) { int gw_lag = now - gw->last_pkt_at; - if (gw_lag >= LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS) { + if (gw_lag >= LGTD_LIFX_WATCHDOG_DEVICE_TIMEOUT_MSECS) { lgtd_info( "closing bulb gateway [%s]:%hu that " "hasn't received traffic for %dms", @@ -116,7 +117,7 @@ lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, ); lgtd_lifx_gateway_close(gw); start_discovery = true; - } else if (gw_lag >= LGTD_LIFX_TIMER_DEVICE_FORCE_REFRESH_MSECS) { + } else if (gw_lag >= LGTD_LIFX_WATCHDOG_DEVICE_FORCE_REFRESH_MSECS) { lgtd_info( "no update on bulb gateway [%s]:%hu for %dms, forcing refresh", gw->ip_addr, gw->port, gw_lag @@ -133,64 +134,67 @@ lgtd_lifx_timer_watchdog_timeout_event_callback(evutil_socket_t socket, } bool -lgtd_lifx_timer_setup(void) +lgtd_lifx_watchdog_setup(void) { - assert(!lgtd_lifx_timer_context.watchdog_interval_ev); - assert(!lgtd_lifx_timer_context.discovery_timeout_ev); + assert(!lgtd_lifx_watchdog_context.watchdog_interval_ev); + assert(!lgtd_lifx_watchdog_context.discovery_timeout_ev); - lgtd_lifx_timer_context.discovery_timeout_ev = event_new( + lgtd_lifx_watchdog_context.discovery_timeout_ev = event_new( lgtd_ev_base, -1, 0, - lgtd_lifx_timer_discovery_timeout_event_callback, + lgtd_lifx_watchdog_discovery_timeout_event_callback, NULL ); - lgtd_lifx_timer_context.watchdog_interval_ev = event_new( + lgtd_lifx_watchdog_context.watchdog_interval_ev = event_new( lgtd_ev_base, -1, EV_PERSIST, - lgtd_lifx_timer_watchdog_timeout_event_callback, + lgtd_lifx_watchdog_timeout_event_callback, NULL ); - if (lgtd_lifx_timer_context.discovery_timeout_ev - && lgtd_lifx_timer_context.watchdog_interval_ev) { + if (lgtd_lifx_watchdog_context.discovery_timeout_ev + && lgtd_lifx_watchdog_context.watchdog_interval_ev) { return true; } int errsave = errno; - lgtd_lifx_timer_close(); + lgtd_lifx_watchdog_close(); errno = errsave; return false; } void -lgtd_lifx_timer_close(void) +lgtd_lifx_watchdog_close(void) { - if (lgtd_lifx_timer_context.discovery_timeout_ev) { - event_del(lgtd_lifx_timer_context.discovery_timeout_ev); - event_free(lgtd_lifx_timer_context.discovery_timeout_ev); - lgtd_lifx_timer_context.discovery_timeout_ev = NULL; + if (lgtd_lifx_watchdog_context.discovery_timeout_ev) { + event_del(lgtd_lifx_watchdog_context.discovery_timeout_ev); + event_free(lgtd_lifx_watchdog_context.discovery_timeout_ev); + lgtd_lifx_watchdog_context.discovery_timeout_ev = NULL; } - if (lgtd_lifx_timer_context.watchdog_interval_ev) { - event_del(lgtd_lifx_timer_context.watchdog_interval_ev); - event_free(lgtd_lifx_timer_context.watchdog_interval_ev); - lgtd_lifx_timer_context.watchdog_interval_ev = NULL; + if (lgtd_lifx_watchdog_context.watchdog_interval_ev) { + event_del(lgtd_lifx_watchdog_context.watchdog_interval_ev); + event_free(lgtd_lifx_watchdog_context.watchdog_interval_ev); + lgtd_lifx_watchdog_context.watchdog_interval_ev = NULL; } } void -lgtd_lifx_timer_start_watchdog(void) +lgtd_lifx_watchdog_start(void) { assert( !RB_EMPTY(&lgtd_lifx_bulbs_table) || !LIST_EMPTY(&lgtd_lifx_gateways) ); - if (!evtimer_pending(lgtd_lifx_timer_context.watchdog_interval_ev, NULL)) { + bool pending = evtimer_pending( + lgtd_lifx_watchdog_context.watchdog_interval_ev, NULL + ); + if (!pending) { struct timeval tv = LGTD_MSECS_TO_TIMEVAL( - LGTD_LIFX_TIMER_WATCHDOG_INTERVAL_MSECS + LGTD_LIFX_WATCHDOG_INTERVAL_MSECS ); - if (event_add(lgtd_lifx_timer_context.watchdog_interval_ev, &tv)) { + if (event_add(lgtd_lifx_watchdog_context.watchdog_interval_ev, &tv)) { lgtd_err(1, "can't start watchdog"); } lgtd_debug("starting watchdog timer"); @@ -198,12 +202,12 @@ lgtd_lifx_timer_start_watchdog(void) } void -lgtd_lifx_timer_start_discovery(void) +lgtd_lifx_watchdog_start_discovery(void) { assert(!evtimer_pending( - lgtd_lifx_timer_context.discovery_timeout_ev, NULL + lgtd_lifx_watchdog_context.discovery_timeout_ev, NULL )); - lgtd_lifx_timer_discovery_timeout_event_callback(-1, 0, NULL); + lgtd_lifx_watchdog_discovery_timeout_event_callback(-1, 0, NULL); lgtd_debug("starting discovery timer"); } diff --git a/lifx/timer.h b/lifx/watchdog.h similarity index 61% rename from lifx/timer.h rename to lifx/watchdog.h index 915fd46..9c1c9c7 100644 --- a/lifx/timer.h +++ b/lifx/watchdog.h @@ -17,13 +17,15 @@ #pragma once -enum { LGTD_LIFX_TIMER_WATCHDOG_INTERVAL_MSECS = 500 }; -enum { LGTD_LIFX_TIMER_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000 }; -enum { LGTD_LIFX_TIMER_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000 }; -enum { LGTD_LIFX_TIMER_DEVICE_TIMEOUT_MSECS = 5000 }; -enum { LGTD_LIFX_TIMER_DEVICE_FORCE_REFRESH_MSECS = 2000 }; +enum lgtd_lifx_watchdog_constants { + LGTD_LIFX_WATCHDOG_INTERVAL_MSECS = 500, + LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000, + LGTD_LIFX_WATCHDOG_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000, + LGTD_LIFX_WATCHDOG_DEVICE_TIMEOUT_MSECS = 3000, + LGTD_LIFX_WATCHDOG_DEVICE_FORCE_REFRESH_MSECS = 2000 +}; -bool lgtd_lifx_timer_setup(void); -void lgtd_lifx_timer_close(void); -void lgtd_lifx_timer_start_watchdog(void); -void lgtd_lifx_timer_start_discovery(void); +bool lgtd_lifx_watchdog_setup(void); +void lgtd_lifx_watchdog_start(void); +void lgtd_lifx_watchdog_close(void); +void lgtd_lifx_watchdog_start_discovery(void); diff --git a/tests/core/daemon/test_daemon_update_proctitle.c b/tests/core/daemon/test_daemon_update_proctitle.c index 10d6ff3..c2d2762 100644 --- a/tests/core/daemon/test_daemon_update_proctitle.c +++ b/tests/core/daemon/test_daemon_update_proctitle.c @@ -11,6 +11,8 @@ void mock_setproctitle(const char *fmt, ...) #include "mock_gateway.h" #include "mock_pipe.h" +#include "mock_router.h" +#include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/mock_event2.h b/tests/core/mock_event2.h index 42b2c00..8af5187 100644 --- a/tests/core/mock_event2.h +++ b/tests/core/mock_event2.h @@ -1,5 +1,10 @@ #pragma once +#include +#include + +#define MOCK_EVENT_NEW_EVENT_PTR ((void *)0xdadadada) + #ifndef MOCKED_EVBUFFER_DRAIN int evbuffer_drain(struct evbuffer *buf, size_t len) @@ -95,7 +100,28 @@ event_new(struct event_base *base, (void)events; (void)cb; (void)ctx; - return NULL; + return MOCK_EVENT_NEW_EVENT_PTR; +} +#endif + +#ifndef MOCKED_EVENT_ACTIVE +void +event_active(struct event *ev, int res, short ncalls) +{ + (void)ev; + (void)res; + (void)ncalls; +} +#endif + +#ifndef MOCKED_EVENT_PENDING +int +event_pending(const struct event *ev, short events, struct timeval *tv) +{ + (void)ev; + (void)events; + (void)tv; + return 0; } #endif diff --git a/tests/core/mock_router.h b/tests/core/mock_router.h new file mode 100644 index 0000000..36fcec5 --- /dev/null +++ b/tests/core/mock_router.h @@ -0,0 +1,81 @@ +#pragma once + +#include "lifx/wire_proto.h" // enum lgtd_lifx_packet_type + +struct lgtd_proto_target_list; +struct lgtd_router_device_list; + +#ifndef MOCKED_LGTD_ROUTER_SEND +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)targets; + (void)pkt_type; + (void)pkt; + return true; +} +#endif + +#ifndef MOCKED_LGTD_ROUTER_SEND_TO_DEVICE +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)bulb; + (void)pkt_type; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_ROUTER_SEND_TO_TAG +void +lgtd_router_send_to_tag(const struct lgtd_lifx_tag *tag, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)tag; + (void)pkt_type; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_ROUTER_SEND_TO_LABEL +void +lgtd_router_send_to_label(const char *label, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + (void)label; + (void)pkt_type; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_ROUTER_BROADCAST +void +lgtd_router_broadcast(enum lgtd_lifx_packet_type pkt_type, void *pkt) +{ + (void)pkt_type; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_ROUTER_TARGETS_TO_DEVICES +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + (void)targets; + return NULL; +} +#endif + +#ifndef MOCKED_LGTD_ROUTER_DEVICE_LIST_FREE +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + (void)devices; +} +#endif diff --git a/tests/core/mock_timer.h b/tests/core/mock_timer.h new file mode 100644 index 0000000..d6179be --- /dev/null +++ b/tests/core/mock_timer.h @@ -0,0 +1,33 @@ +#pragma once + +// we need those two because mock_timer.h is being used where it shouldn't be +// because it is a dependency for modules (e.g: bulb) that haven't be mocked +// yet: +#include +#include + +#include "core/timer.h" // to pull the union definition + +#ifndef MOCKED_LGTD_TIMER_START +struct lgtd_timer * +lgtd_timer_start(int flags, + int ms, + void (*cb)(struct lgtd_timer *, + union lgtd_timer_ctx), + union lgtd_timer_ctx ctx) +{ + (void)flags; + (void)ms; + (void)cb; + (void)ctx; + return NULL; +} +#endif + +#ifndef MOCKED_LGTD_TIMER_STOP +void +lgtd_timer_stop(struct lgtd_timer *timer) +{ + (void)timer; +} +#endif diff --git a/tests/core/pipe/test_pipe_close.c b/tests/core/pipe/test_pipe_close.c index 2dcdca9..c8d3257 100644 --- a/tests/core/pipe/test_pipe_close.c +++ b/tests/core/pipe/test_pipe_close.c @@ -13,6 +13,8 @@ #define MOCKED_EVENT_FREE #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" #include "tests_utils.h" #include "tests_pipe_utils.h" diff --git a/tests/core/pipe/test_pipe_open.c b/tests/core/pipe/test_pipe_open.c index 7b1de43..8dbe921 100644 --- a/tests/core/pipe/test_pipe_open.c +++ b/tests/core/pipe/test_pipe_open.c @@ -12,6 +12,8 @@ #define MOCKED_EVENT_ADD #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_OPEN_FROM_PIPE diff --git a/tests/core/pipe/test_pipe_open_fifo_already_exists.c b/tests/core/pipe/test_pipe_open_fifo_already_exists.c index 79c5e7f..17c32bf 100644 --- a/tests/core/pipe/test_pipe_open_fifo_already_exists.c +++ b/tests/core/pipe/test_pipe_open_fifo_already_exists.c @@ -12,6 +12,8 @@ #define MOCKED_EVENT_ADD #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_OPEN_FROM_PIPE diff --git a/tests/core/pipe/test_pipe_read_callback.c b/tests/core/pipe/test_pipe_read_callback.c index 9ac43cd..e6b5f70 100644 --- a/tests/core/pipe/test_pipe_read_callback.c +++ b/tests/core/pipe/test_pipe_read_callback.c @@ -15,6 +15,8 @@ #define MOCKED_EVBUFFER_DRAIN #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST diff --git a/tests/core/pipe/test_pipe_read_callback_extra_data.c b/tests/core/pipe/test_pipe_read_callback_extra_data.c index d5cdd0c..0692757 100644 --- a/tests/core/pipe/test_pipe_read_callback_extra_data.c +++ b/tests/core/pipe/test_pipe_read_callback_extra_data.c @@ -14,6 +14,8 @@ #define MOCKED_EVBUFFER_DRAIN #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST diff --git a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c index ce7c84d..1c39a1a 100644 --- a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c +++ b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c @@ -14,6 +14,8 @@ #define MOCKED_EVBUFFER_DRAIN #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST diff --git a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c index 9e645f9..3bce598 100644 --- a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c +++ b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c @@ -14,6 +14,8 @@ #define MOCKED_EVBUFFER_DRAIN #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt index 5708142..1c4600b 100644 --- a/tests/core/proto/CMakeLists.txt +++ b/tests/core/proto/CMakeLists.txt @@ -10,7 +10,6 @@ ADD_CORE_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c - ${LIGHTSD_SOURCE_DIR}/lifx/timer.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c index 9271db6..79debe6 100644 --- a/tests/core/proto/test_proto_get_light_state.c +++ b/tests/core/proto/test_proto_get_light_state.c @@ -3,6 +3,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_empty_device_list.c b/tests/core/proto/test_proto_get_light_state_empty_device_list.c index 8c09869..f08e456 100644 --- a/tests/core/proto/test_proto_get_light_state_empty_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_empty_device_list.c @@ -2,6 +2,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_null_device_list.c b/tests/core/proto/test_proto_get_light_state_null_device_list.c index 868d3db..604c291 100644 --- a/tests/core/proto/test_proto_get_light_state_null_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_null_device_list.c @@ -2,6 +2,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c index 0e79870..e71a7cc 100644 --- a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c +++ b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c @@ -3,6 +3,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_power_off.c b/tests/core/proto/test_proto_power_off.c index 720673e..b8a95a5 100644 --- a/tests/core/proto/test_proto_power_off.c +++ b/tests/core/proto/test_proto_power_off.c @@ -2,6 +2,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c index 0c375e2..e3133ba 100644 --- a/tests/core/proto/test_proto_power_off_routing_error.c +++ b/tests/core/proto/test_proto_power_off_routing_error.c @@ -2,6 +2,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_on.c b/tests/core/proto/test_proto_power_on.c index 07ad5d1..3b7e94b 100644 --- a/tests/core/proto/test_proto_power_on.c +++ b/tests/core/proto/test_proto_power_on.c @@ -2,6 +2,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_on_routing_error.c b/tests/core/proto/test_proto_power_on_routing_error.c index 55a5a45..85777e6 100644 --- a/tests/core/proto/test_proto_power_on_routing_error.c +++ b/tests/core/proto/test_proto_power_on_routing_error.c @@ -2,6 +2,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_toggle.c b/tests/core/proto/test_proto_power_toggle.c index 9710261..45ea936 100644 --- a/tests/core/proto/test_proto_power_toggle.c +++ b/tests/core/proto/test_proto_power_toggle.c @@ -3,6 +3,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_SEND_TO_DEVICE diff --git a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c index bd418a8..9a018ed 100644 --- a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c +++ b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c @@ -3,6 +3,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_SEND_TO_DEVICE diff --git a/tests/core/proto/test_proto_set_light_from_hsbk.c b/tests/core/proto/test_proto_set_light_from_hsbk.c index cfd4ced..9a915f2 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk.c @@ -4,6 +4,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c index 5726d30..cd641db 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c @@ -4,6 +4,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index 8fbc5fb..f997a97 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -4,6 +4,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index 0d4317a..6b1e259 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -4,6 +4,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c index e151543..cd2951e 100644 --- a/tests/core/proto/test_proto_tag_create.c +++ b/tests/core/proto/test_proto_tag_create.c @@ -5,6 +5,8 @@ #define MOCKED_LIFX_GATEWAY_SEND_TO_SITE #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c index 6fc9fb5..c838ab8 100644 --- a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c +++ b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c @@ -5,6 +5,8 @@ #define MOCKED_LIFX_GATEWAY_SEND_TO_SITE #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_ERROR diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c index 21c880b..a7a9fa5 100644 --- a/tests/core/proto/test_proto_tag_update.c +++ b/tests/core/proto/test_proto_tag_update.c @@ -5,6 +5,8 @@ #define MOCKED_LIFX_GATEWAY_SEND_TO_SITE #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c index b34014e..f1d4868 100644 --- a/tests/core/proto/test_proto_untag.c +++ b/tests/core/proto/test_proto_untag.c @@ -3,6 +3,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_untag_tag_does_not_exist.c b/tests/core/proto/test_proto_untag_tag_does_not_exist.c index 485cfe6..918405f 100644 --- a/tests/core/proto/test_proto_untag_tag_does_not_exist.c +++ b/tests/core/proto/test_proto_untag_tag_does_not_exist.c @@ -3,6 +3,8 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/router/CMakeLists.txt b/tests/core/router/CMakeLists.txt index cff412f..3f75838 100644 --- a/tests/core/router/CMakeLists.txt +++ b/tests/core/router/CMakeLists.txt @@ -10,7 +10,6 @@ ADD_CORE_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c - ${LIGHTSD_SOURCE_DIR}/lifx/timer.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c diff --git a/tests/core/router/test_router_send_to_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c index a89d7bc..5d5c058 100644 --- a/tests/core/router/test_router_send_to_broadcast.c +++ b/tests/core/router/test_router_send_to_broadcast.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_device.c b/tests/core/router/test_router_send_to_device.c index a95aefd..7000770 100644 --- a/tests/core/router/test_router_send_to_device.c +++ b/tests/core/router/test_router_send_to_device.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_invalid_targets.c b/tests/core/router/test_router_send_to_invalid_targets.c index 054dbb1..c9ae736 100644 --- a/tests/core/router/test_router_send_to_invalid_targets.c +++ b/tests/core/router/test_router_send_to_invalid_targets.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index 9cea010..c893b85 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index daec583..077e066 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_targets_to_devices.c b/tests/core/router/test_router_targets_to_devices.c index a11b2af..90a4e69 100644 --- a/tests/core/router/test_router_targets_to_devices.c +++ b/tests/core/router/test_router_targets_to_devices.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c index 4c2ef68..c83b235 100644 --- a/tests/core/tests_shims.c +++ b/tests/core/tests_shims.c @@ -24,7 +24,9 @@ struct lgtd_opts lgtd_opts = { .verbosity = LGTD_DEBUG }; -struct event_base *lgtd_ev_base = NULL; +#define MOCK_LGTD_EV_BASE ((void *)2222) + +struct event_base *lgtd_ev_base = MOCK_LGTD_EV_BASE; void lgtd_cleanup(void) diff --git a/tests/core/tests_shims.h b/tests/core/tests_shims.h index c3e9da1..df46480 100644 --- a/tests/core/tests_shims.h +++ b/tests/core/tests_shims.h @@ -6,7 +6,9 @@ struct lgtd_opts lgtd_opts = { .verbosity = LGTD_DEBUG }; -struct event_base *lgtd_ev_base = NULL; +#define MOCK_LGTD_EV_BASE ((void *)2222) + +struct event_base *lgtd_ev_base = MOCK_LGTD_EV_BASE; const char *lgtd_binds = NULL; diff --git a/tests/core/timer/CMakeLists.txt b/tests/core/timer/CMakeLists.txt new file mode 100644 index 0000000..3c76a7a --- /dev/null +++ b/tests/core/timer/CMakeLists.txt @@ -0,0 +1,13 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +FUNCTION(ADD_TIMER_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE}) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_TIMER_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/core/timer/test_timer_ispending.c b/tests/core/timer/test_timer_ispending.c new file mode 100644 index 0000000..e6261e6 --- /dev/null +++ b/tests/core/timer/test_timer_ispending.c @@ -0,0 +1,45 @@ +#include + +#include "core/timer.c" + +#define MOCKED_EVENT_PENDING +#include "mock_event2.h" +#include "tests_shims.h" + +static int event_pending_call_count = 0; + +int +event_pending(const struct event *ev, short events, struct timeval *tv) +{ + (void)events; + + if (ev != MOCK_EVENT_NEW_EVENT_PTR) { + errx(1, "got event %p (expected %p)", ev, MOCK_EVENT_NEW_EVENT_PTR); + } + + if (tv) { + errx(1, "got unexpected parameter tv"); + } + + if (event_pending_call_count++) { + errx(1, "event_pending should have been called once"); + } + + return true; +} + +int +main(void) +{ + struct lgtd_timer timer = { .event = MOCK_EVENT_NEW_EVENT_PTR }; + + if (!lgtd_timer_ispending(&timer)) { + errx(1, "lgtd_timer_ispending returned false (expected true)"); + } + + if (!event_pending_call_count) { + errx(1, "event_pending wasn't called"); + } + + return 0; +} diff --git a/tests/core/timer/test_timer_reschedule.c b/tests/core/timer/test_timer_reschedule.c new file mode 100644 index 0000000..9645af1 --- /dev/null +++ b/tests/core/timer/test_timer_reschedule.c @@ -0,0 +1,45 @@ +#include + +#include "core/timer.c" + +#define MOCKED_EVENT_ADD +#include "mock_event2.h" +#include "tests_shims.h" + +static int event_add_call_count = 0; + +int +event_add(struct event *ev, const struct timeval *tv) +{ + if (ev != MOCK_EVENT_NEW_EVENT_PTR) { + errx(1, "got event %p (expected %p)", ev, MOCK_EVENT_NEW_EVENT_PTR); + } + + struct timeval expected_tv = LGTD_MSECS_TO_TIMEVAL(5); + if (memcmp(tv, &expected_tv, sizeof(*tv))) { + errx(1, "got unexpected timeout"); + } + + if (event_add_call_count++) { + errx(1, "event_add should have been called once"); + } + + return 0; +} + +int +main(void) +{ + struct lgtd_timer timer = { .event = MOCK_EVENT_NEW_EVENT_PTR }; + struct timeval tv = LGTD_MSECS_TO_TIMEVAL(5); + + if (!lgtd_timer_reschedule(&timer, &tv)) { + errx(1, "wrong return value"); + } + + if (!event_add_call_count) { + errx(1, "event_add wasn't called"); + } + + return 0; +} diff --git a/tests/core/timer/test_timer_start_activate_now.c b/tests/core/timer/test_timer_start_activate_now.c new file mode 100644 index 0000000..1b34929 --- /dev/null +++ b/tests/core/timer/test_timer_start_activate_now.c @@ -0,0 +1,145 @@ +#include + +#include "core/timer.c" + +#define MOCKED_EVENT_NEW +#define MOCKED_EVENT_ADD +#define MOCKED_EVENT_ACTIVE +#include "mock_event2.h" +#include "tests_shims.h" + +static void +my_test_callback(struct lgtd_timer *timer, union lgtd_timer_ctx ctx) +{ + (void)timer; + (void)ctx; +} + +static int event_active_call_count = 0; + +void +event_active(struct event *ev, int res, short ncalls) +{ + if (ev != MOCK_EVENT_NEW_EVENT_PTR) { + errx(1, "got event %p (expected %p)", ev, MOCK_EVENT_NEW_EVENT_PTR); + } + + if (res) { + errx(1, "got res = %d (expected 0)", res); + } + + if (ncalls) { + errx(1, "got ncalls = %d (expected 0)", ncalls); + } + + if (event_active_call_count++) { + errx(1, "event_active should be called once"); + } +} + +static int event_new_call_count = 0; + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + if (base != MOCK_LGTD_EV_BASE) { + errx(1, "got base %p (expected %p)", base, MOCK_LGTD_EV_BASE); + } + + if (fd != -1) { + errx(1, "got fd %d (expected -1)", fd); + } + + if (events) { + errx(1, "got events %#x (expected 0)", events); + } + + if (cb != lgtd_timer_callback) { + errx(1, "got cb %p (expected %p)", cb, lgtd_timer_callback); + } + + if (!ctx) { + errx(1, "didn't get any context"); + } + + if (event_new_call_count++) { + errx(1, "event_new should be called once"); + } + + return MOCK_EVENT_NEW_EVENT_PTR; +} + +static int event_add_call_count = 0; + +int +event_add(struct event *ev, const struct timeval *timeout) +{ + if (ev != MOCK_EVENT_NEW_EVENT_PTR) { + errx(1, "got ev %p (expected %p)", ev, MOCK_EVENT_NEW_EVENT_PTR); + } + + if (!timeout) { + errx(1, "a timeout should have been passed in"); + } + // so i don't know wth clang and mac os x are doing but memcmp + // gets whacked in -O2 and returns a difference when there isn't, + // so we have to do it the painful way: + struct timeval expected_tv = LGTD_MSECS_TO_TIMEVAL(5); + if (timeout->tv_sec != expected_tv.tv_sec + || timeout->tv_usec != expected_tv.tv_usec) { + errx(1, "got invalid timeout"); + } + + if (event_add_call_count++) { + errx(1, "event_add should be called once"); + } + + return 0; +} + +int +main(void) +{ + union lgtd_timer_ctx ctx = { .as_uint = 7614 }; + struct lgtd_timer *timer = lgtd_timer_start( + LGTD_TIMER_ACTIVATE_NOW, 5, my_test_callback, ctx + ); + + if (timer->event != MOCK_EVENT_NEW_EVENT_PTR) { + errx( + 1, "timer has event %p (expected %p)", + timer->event, MOCK_EVENT_NEW_EVENT_PTR + ); + } + if (timer->ctx.as_uint != ctx.as_uint) { + errx( + 1, "timer ctx is %ju (expected %ju)", + (uintmax_t)timer->ctx.as_uint, (uintmax_t)ctx.as_uint + ); + } + if (timer->callback != my_test_callback) { + errx( + 1, "timer callback is %p (expected %p)", + timer->callback, my_test_callback + ); + } + if (LIST_FIRST(&lgtd_timers) != timer) { + errx(1, "the timer wasn't inserted in the timers list"); + } + + if (!event_new_call_count) { + errx(1, "event_new wasn't called"); + } + if (!event_add_call_count) { + errx(1, "event_add wasn't called"); + } + if (!event_active_call_count) { + errx(1, "the timer wasn't activated"); + } + + return 0; +} diff --git a/tests/core/timer/test_timer_start_persistent.c b/tests/core/timer/test_timer_start_persistent.c new file mode 100644 index 0000000..878c6bc --- /dev/null +++ b/tests/core/timer/test_timer_start_persistent.c @@ -0,0 +1,129 @@ +#include + +#include "core/timer.c" + +#define MOCKED_EVENT_NEW +#define MOCKED_EVENT_ADD +#define MOCKED_EVENT_ACTIVE +#include "mock_event2.h" +#include "tests_shims.h" + +static void +my_test_callback(struct lgtd_timer *timer, union lgtd_timer_ctx ctx) +{ + (void)timer; + (void)ctx; +} + +void +event_active(struct event *ev, int res, short ncalls) +{ + (void)ev; + (void)res; + (void)ncalls; + errx(1, "event_active shouldn't have been called"); +} + +static int event_new_call_count = 0; + +struct event * +event_new(struct event_base *base, + evutil_socket_t fd, + short events, + event_callback_fn cb, + void *ctx) +{ + if (base != MOCK_LGTD_EV_BASE) { + errx(1, "got base %p (expected %p)", base, MOCK_LGTD_EV_BASE); + } + + if (fd != -1) { + errx(1, "got fd %d (expected -1)", fd); + } + + if (events != EV_PERSIST) { + errx(1, "got events %#x (expected %#x)", events, EV_PERSIST); + } + + if (cb != lgtd_timer_callback) { + errx(1, "got cb %p (expected %p)", cb, lgtd_timer_callback); + } + + if (!ctx) { + errx(1, "didn't get any context"); + } + + if (event_new_call_count++) { + errx(1, "event_new should be called once"); + } + + return MOCK_EVENT_NEW_EVENT_PTR; +} + +static int event_add_call_count = 0; + +int +event_add(struct event *ev, const struct timeval *timeout) +{ + if (ev != MOCK_EVENT_NEW_EVENT_PTR) { + errx(1, "got ev %p (expected %p)", ev, MOCK_EVENT_NEW_EVENT_PTR); + } + + if (!timeout) { + errx(1, "a timeout should have been passed in"); + } + // so i don't know wth clang and mac os x are doing but memcmp + // gets whacked in -O2 and returns a difference when there isn't, + // so we have to do it the painful way: + struct timeval expected_tv = LGTD_MSECS_TO_TIMEVAL(5); + if (timeout->tv_sec != expected_tv.tv_sec + || timeout->tv_usec != expected_tv.tv_usec) { + errx(1, "got invalid timeout"); + } + + if (event_add_call_count++) { + errx(1, "event_add should be called once"); + } + + return 0; +} + +int +main(void) +{ + union lgtd_timer_ctx ctx = { .as_uint = 7614 }; + struct lgtd_timer *timer = lgtd_timer_start( + LGTD_TIMER_PERSISTENT, 5, my_test_callback, ctx + ); + + if (timer->event != MOCK_EVENT_NEW_EVENT_PTR) { + errx( + 1, "timer has event %p (expected %p)", + timer->event, MOCK_EVENT_NEW_EVENT_PTR + ); + } + if (timer->ctx.as_uint != ctx.as_uint) { + errx( + 1, "timer ctx is %ju (expected %ju)", + (uintmax_t)timer->ctx.as_uint, (uintmax_t)ctx.as_uint + ); + } + if (timer->callback != my_test_callback) { + errx( + 1, "timer callback is %p (expected %p)", + timer->callback, my_test_callback + ); + } + if (LIST_FIRST(&lgtd_timers) != timer) { + errx(1, "the timer wasn't inserted in the timers list"); + } + + if (!event_new_call_count) { + errx(1, "event_new wasn't called"); + } + if (!event_add_call_count) { + errx(1, "event_add wasn't called"); + } + + return 0; +} diff --git a/tests/lifx/CMakeLists.txt b/tests/lifx/CMakeLists.txt index da2ad41..01dbb06 100644 --- a/tests/lifx/CMakeLists.txt +++ b/tests/lifx/CMakeLists.txt @@ -2,9 +2,11 @@ INCLUDE_DIRECTORIES( ${LIGHTSD_SOURCE_DIR} ${LIGHTSD_SOURCE_DIR}/lifx/ ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../core ${LIGHTSD_BINARY_DIR} ${LIGHTSD_BINARY_DIR}/lifx/ ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/../core ) ADD_ALL_SUBDIRECTORIES() diff --git a/tests/lifx/bulb/CMakeLists.txt b/tests/lifx/bulb/CMakeLists.txt index 530e5d1..51a4e86 100644 --- a/tests/lifx/bulb/CMakeLists.txt +++ b/tests/lifx/bulb/CMakeLists.txt @@ -6,7 +6,6 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_lifx_bulb_core STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c - ${LIGHTSD_SOURCE_DIR}/core/router.c ${LIGHTSD_SOURCE_DIR}/core/stats.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ) diff --git a/tests/lifx/bulb/test_bulb_close.c b/tests/lifx/bulb/test_bulb_close.c index 315f8fd..b599301 100644 --- a/tests/lifx/bulb/test_bulb_close.c +++ b/tests/lifx/bulb/test_bulb_close.c @@ -1,6 +1,8 @@ #include "bulb.c" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" int main(void) diff --git a/tests/lifx/bulb/test_bulb_open.c b/tests/lifx/bulb/test_bulb_open.c index 129fd72..5bf8a12 100644 --- a/tests/lifx/bulb/test_bulb_open.c +++ b/tests/lifx/bulb/test_bulb_open.c @@ -1,6 +1,8 @@ #include "bulb.c" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" int main(void) diff --git a/tests/lifx/bulb/test_bulb_set_light_state.c b/tests/lifx/bulb/test_bulb_set_light_state.c index 6e080cd..c5cfcf3 100644 --- a/tests/lifx/bulb/test_bulb_set_light_state.c +++ b/tests/lifx/bulb/test_bulb_set_light_state.c @@ -2,6 +2,8 @@ #define MOCKED_LGTD_LIFX_GATEWAY_UPDATE_TAG_REFCOUNTS #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" static int update_tag_refcouts_call_counts = 0; diff --git a/tests/lifx/bulb/test_bulb_set_power_state.c b/tests/lifx/bulb/test_bulb_set_power_state.c index 09ff816..6129df4 100644 --- a/tests/lifx/bulb/test_bulb_set_power_state.c +++ b/tests/lifx/bulb/test_bulb_set_power_state.c @@ -1,6 +1,8 @@ #include "bulb.c" #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" int main(void) diff --git a/tests/lifx/bulb/test_bulb_set_tags.c b/tests/lifx/bulb/test_bulb_set_tags.c index e6968bd..53bb5cd 100644 --- a/tests/lifx/bulb/test_bulb_set_tags.c +++ b/tests/lifx/bulb/test_bulb_set_tags.c @@ -2,6 +2,8 @@ #define MOCKED_LGTD_LIFX_GATEWAY_UPDATE_TAG_REFCOUNTS #include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" static bool update_tag_refcouts_called = false; diff --git a/tests/lifx/gateway/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt index 1d606db..7d14429 100644 --- a/tests/lifx/gateway/CMakeLists.txt +++ b/tests/lifx/gateway/CMakeLists.txt @@ -16,7 +16,7 @@ ADD_LIBRARY( test_lifx_gateway STATIC ${LIGHTSD_SOURCE_DIR}/lifx/broadcast.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c - ${LIGHTSD_SOURCE_DIR}/lifx/timer.c + ${LIGHTSD_SOURCE_DIR}/lifx/watchdog.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ) From 4cfa13d4f75269591021168c1c2e2ab761de7817 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:17:09 -0700 Subject: [PATCH 081/181] Speed-up bulbs discovery By querying a gateway's bulbs as soon as it's discovered. This is the exact same optimization done earlier with the gateway discovery timer. This changeset also updates the gateway code to use the new timer interface. --- lifx/gateway.c | 47 ++++++++++--------- lifx/gateway.h | 2 +- .../gateway/test_gateway_allocate_tag_id.c | 1 + ...ateway_allocate_tag_id_from_lifx_network.c | 1 + ...t_gateway_allocate_tag_id_no_tag_id_left.c | 1 + ...eway_deallocate_tag_id_from_lifx_network.c | 1 + .../gateway/test_gateway_enqueue_packet.c | 1 + .../test_gateway_enqueue_packet_ring_full.c | 1 + ...t_gateway_enqueue_packet_ring_wraparound.c | 1 + .../gateway/test_gateway_handle_tag_labels.c | 1 + .../test_gateway_update_tag_refcounts.c | 1 + .../gateway/test_gateway_write_callback.c | 1 + ...way_write_callback_clears_ring_full_flag.c | 1 + ...teway_write_callback_last_packet_on_ring.c | 1 + ...est_gateway_write_callback_partial_write.c | 1 + ...t_gateway_write_callback_ring_wraparound.c | 1 + 16 files changed, 39 insertions(+), 24 deletions(-) diff --git a/lifx/gateway.c b/lifx/gateway.c index 20868aa..361117e 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -38,6 +38,7 @@ #include "gateway.h" #include "watchdog.h" #include "broadcast.h" +#include "core/timer.h" #include "tagging.h" #include "core/jsmn.h" #include "core/jsonrpc.h" @@ -57,13 +58,12 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) assert(gw); LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, -1); - event_del(gw->refresh_ev); + lgtd_timer_stop(gw->refresh_timer); event_del(gw->write_ev); if (gw->socket != -1) { evutil_closesocket(gw->socket); LIST_REMOVE(gw, link); } - event_free(gw->refresh_ev); event_free(gw->write_ev); evbuffer_free(gw->write_buf); for (int i = 0; i != LGTD_LIFX_GATEWAY_MAX_TAGS; i++) { @@ -240,13 +240,12 @@ lgtd_lifx_gateway_send_get_all_light_state(struct lgtd_lifx_gateway *gw) } static void -lgtd_lifx_gateway_refresh_callback(evutil_socket_t socket, - short events, - void *ctx) +lgtd_lifx_gateway_refresh_callback(struct lgtd_timer *timer, + union lgtd_timer_ctx ctx) { - (void)socket; - (void)events; - lgtd_lifx_gateway_send_get_all_light_state((struct lgtd_lifx_gateway *)ctx); + (void)timer; + struct lgtd_lifx_gateway *gw = ctx.as_ptr; + lgtd_lifx_gateway_send_get_all_light_state(gw); } void @@ -254,7 +253,7 @@ lgtd_lifx_gateway_force_refresh(struct lgtd_lifx_gateway *gw) { assert(gw); - event_active(gw->refresh_ev, 0, 0); + lgtd_timer_activate(gw->refresh_timer); } static struct lgtd_lifx_bulb * @@ -303,6 +302,7 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, lgtd_warn("can't open a new socket"); goto error_connect; } + gw->write_ev = event_new( lgtd_ev_base, gw->socket, @@ -311,9 +311,11 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, gw ); gw->write_buf = evbuffer_new(); - gw->refresh_ev = evtimer_new( - lgtd_ev_base, lgtd_lifx_gateway_refresh_callback, gw - ); + if (!gw->write_ev || !gw->write_buf) { + lgtd_warn("can't allocate a new gateway bulb"); + goto error_allocate; + } + memcpy(&gw->peer, peer, sizeof(gw->peer)); lgtd_sockaddrtoa(peer, gw->ip_addr, sizeof(gw->ip_addr)); gw->port = lgtd_sockaddrport(peer); @@ -322,13 +324,15 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, gw->next_req_at = received_at; gw->last_pkt_at = received_at; - struct timeval refresh_interval = LGTD_MSECS_TO_TIMEVAL( - LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS + union lgtd_timer_ctx ctx = { .as_ptr = gw }; + gw->refresh_timer = lgtd_timer_start( + LGTD_TIMER_ACTIVATE_NOW, + LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS, + lgtd_lifx_gateway_refresh_callback, + ctx ); - - if (!gw->write_ev || !gw->write_buf || !gw->refresh_ev - || event_add(gw->refresh_ev, &refresh_interval) != 0) { - lgtd_warn("can't allocate a new gateway bulb"); + if (!gw->refresh_timer) { + lgtd_warn("can't allocate a new timer"); goto error_allocate; } @@ -355,9 +359,6 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, if (gw->write_buf) { evbuffer_free(gw->write_buf); } - if (gw->refresh_ev) { - event_free(gw->refresh_ev); - } error_connect: evutil_closesocket(gw->socket); error_socket: @@ -533,10 +534,10 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, int latency = gw->last_pkt_at - gw->last_req_at; if (latency < LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS) { - if (!event_pending(gw->refresh_ev, EV_TIMEOUT, NULL)) { + if (!lgtd_timer_ispending(gw->refresh_timer)) { int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); - evtimer_add(gw->refresh_ev, &tv); + lgtd_timer_reschedule(gw->refresh_timer, &tv); lgtd_debug( "[%s]:%hu latency is %dms, scheduling next GET_LIGHT_STATE in %dms", gw->ip_addr, gw->port, latency, timeout diff --git a/lifx/gateway.h b/lifx/gateway.h index ae74e27..7e4ce97 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -71,7 +71,7 @@ struct lgtd_lifx_gateway { struct event *write_ev; struct evbuffer *write_buf; bool pending_refresh_req; - struct event *refresh_ev; + struct lgtd_timer *refresh_timer; }; LIST_HEAD(lgtd_lifx_gateway_list, lgtd_lifx_gateway); diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id.c b/tests/lifx/gateway/test_gateway_allocate_tag_id.c index 9d26ecc..88604dd 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id.c @@ -4,6 +4,7 @@ #define MOCKED_LIFX_TAGGING_INCREF #include "test_gateway_utils.h" +#include "mock_timer.h" static bool tagging_incref_called = false; diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c index c3d4f74..f1d676d 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c @@ -4,6 +4,7 @@ #define MOCKED_LIFX_TAGGING_INCREF #include "test_gateway_utils.h" +#include "mock_timer.h" static bool tagging_incref_called = false; diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c index dcf60a9..b1df7d6 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c @@ -5,6 +5,7 @@ #define MOCKED_LIFX_TAGGING_INCREF #include "test_gateway_utils.h" +#include "mock_timer.h" static bool tagging_incref_called = false; diff --git a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c index 15f219a..2d1c4f6 100644 --- a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c @@ -4,6 +4,7 @@ #define MOCKED_LIFX_TAGGING_DECREF #include "test_gateway_utils.h" +#include "mock_timer.h" static bool tagging_decref_called = false; diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet.c b/tests/lifx/gateway/test_gateway_enqueue_packet.c index 649ea1f..117bc1c 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet.c @@ -1,6 +1,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_timer.h" int main(void) diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c index 869d7f0..d3ea07b 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c @@ -1,6 +1,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_timer.h" int main(void) diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c index 4cae826..57324c8 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c @@ -1,6 +1,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_timer.h" int main(void) diff --git a/tests/lifx/gateway/test_gateway_handle_tag_labels.c b/tests/lifx/gateway/test_gateway_handle_tag_labels.c index a3f95da..dc330d1 100644 --- a/tests/lifx/gateway/test_gateway_handle_tag_labels.c +++ b/tests/lifx/gateway/test_gateway_handle_tag_labels.c @@ -3,6 +3,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_timer.h" int main(void) diff --git a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c index c14abc1..575849f 100644 --- a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c +++ b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c @@ -1,6 +1,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_timer.h" int main(void) diff --git a/tests/lifx/gateway/test_gateway_write_callback.c b/tests/lifx/gateway/test_gateway_write_callback.c index 3494b3f..007dfa0 100644 --- a/tests/lifx/gateway/test_gateway_write_callback.c +++ b/tests/lifx/gateway/test_gateway_write_callback.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_timer.h" size_t evbuffer_get_length(const struct evbuffer *buf) diff --git a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c index 3e3e0d2..e94a2cb 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c +++ b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_timer.h" size_t evbuffer_get_length(const struct evbuffer *buf) diff --git a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c index cb49625..322cf70 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c +++ b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_timer.h" size_t evbuffer_get_length(const struct evbuffer *buf) diff --git a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c index 6f53fee..e95f9cc 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c +++ b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_timer.h" size_t evbuffer_get_length(const struct evbuffer *buf) diff --git a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c index 403997b..f9cbfa0 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_timer.h" size_t evbuffer_get_length(const struct evbuffer *buf) From f9f37d18d4bfae79a438b3c4d0950e25d1d90343 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:17:09 -0700 Subject: [PATCH 082/181] Expose the bulb address, model and firmware version in get_light_state The bulb's gateway is also exposed. Even more will be returned if you run lightsd with -v debug. --- README.rst | 6 +- core/daemon.c | 7 +- core/lightsd.h | 29 +- core/log.c | 30 +- core/proto.c | 144 +++++++-- core/router.c | 5 +- examples/lightsc.py | 2 +- lifx/bulb.c | 151 +++++++++ lifx/bulb.h | 75 ++++- lifx/gateway.c | 163 ++++++++-- lifx/gateway.h | 26 ++ lifx/wire_proto.c | 293 +++++++++++++++++- lifx/wire_proto.h | 39 ++- tests/core/proto/test_proto_get_light_state.c | 185 ++++++++++- ...est_proto_get_light_state_unknown_tag_id.c | 54 +++- tests/lifx/bulb/CMakeLists.txt | 2 + .../lifx/bulb/test_bulb_fetch_hardware_info.c | 143 +++++++++ tests/lifx/bulb/test_bulb_open.c | 46 +++ .../test_gateway_handle_ip_firmware_info.c | 54 ++++ .../gateway/test_gateway_handle_ip_state.c | 54 ++++ .../test_gateway_handle_product_info.c | 43 +++ .../test_gateway_handle_runtime_info.c | 40 +++ tests/lifx/mock_gateway.h | 48 +++ .../test_wire_proto_encode_decode_header.c | 2 +- tests/lifx/wire_proto/test_wire_proto_utils.h | 46 --- .../test_wire_proto_waveform_table.c | 2 +- 26 files changed, 1544 insertions(+), 145 deletions(-) create mode 100644 tests/lifx/bulb/test_bulb_fetch_hardware_info.c create mode 100644 tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c create mode 100644 tests/lifx/gateway/test_gateway_handle_ip_state.c create mode 100644 tests/lifx/gateway/test_gateway_handle_product_info.c create mode 100644 tests/lifx/gateway/test_gateway_handle_runtime_info.c delete mode 100644 tests/lifx/wire_proto/test_wire_proto_utils.h diff --git a/README.rst b/README.rst index f48b9c4..7206fae 100644 --- a/README.rst +++ b/README.rst @@ -106,9 +106,9 @@ Use the ``-f`` option to run lightsd in the foreground. Known issues ------------ -The grouping (tagging) code of the LIFX White 800 (and presumably the Color 650 -as well) is bugged: after a tagging operation the LIFX White 800 keep saying it -has no tags. Reboot the bulb to make the tags appears. +The White 800 appears to be less reliable than the LIFX Original or Color 650. +The grouping (tagging) code of the LIFX White 800 in particular appears to be +bugged: after a tagging operation the LIFX White 800 keep saying it has no tags. Power ON/OFF are the only commands with auto-retry, i.e: lightsd will keep sending the command to the bulb until its state changes. This is not implemented diff --git a/core/daemon.c b/core/daemon.c index 7bf2f0a..19cb3c2 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -106,10 +106,9 @@ lgtd_daemon_update_proctitle(void) char title[LGTD_DAEMON_TITLE_SIZE] = { 0 }; int i = 0; -#define TITLE_APPEND(fmt, ...) do { \ - int n = snprintf((&title[i]), (sizeof(title) - i), (fmt), __VA_ARGS__); \ - i = LGTD_MIN(i + n, (int)sizeof(title)); \ -} while (0) +#define TITLE_APPEND(fmt, ...) LGTD_SNPRINTF_APPEND( \ + title, i, (int)sizeof(title), (fmt), __VA_ARGS__ \ +) #define PREFIX(fmt, ...) TITLE_APPEND( \ "%s" fmt, (i && title[i - 1] == ')' ? "; " : ""), __VA_ARGS__ \ diff --git a/core/lightsd.h b/core/lightsd.h index 8fdada9..3c87fce 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -24,10 +24,33 @@ #define LGTD_ABS(v) ((v) >= 0 ? (v) : (v) * -1) #define LGTD_MIN(a, b) ((a) < (b) ? (a) : (b)) #define LGTD_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) -#define LGTD_MSECS_TO_TIMEVAL(v) { \ +#define LGTD_MSECS_TO_TIMEVAL(v) { \ .tv_sec = (v) / 1000, \ .tv_usec = ((v) % 1000) * 1000 \ } +#define LGTD_NSECS_TO_USECS(v) ((v) / (unsigned int)1E6) +#define LGTD_NSECS_TO_SECS(v) ((v) / (unsigned int)1E9) +#define LGTD_SECS_TO_NSECS(v) ((v) * (unsigned int)1E9) +#define LGTD_TM_TO_ISOTIME(tm, sbuf, bufsz, usec) do { \ + /* '2015-01-02T10:13:16.132222+00:00' */ \ + if ((usec)) { \ + snprintf( \ + (sbuf), (bufsz), "%d-%02d-%02dT%02d:%02d:%02d.%jd%c%02ld:%02ld", \ + 1900 + (tm)->tm_year, 1 + (tm)->tm_mon, (tm)->tm_mday, \ + (tm)->tm_hour, (tm)->tm_min, (tm)->tm_sec, (intmax_t)usec, \ + (tm)->tm_gmtoff >= 0 ? '+' : '-', /* %+02ld doesn't work */ \ + LGTD_ABS((tm)->tm_gmtoff / 60 / 60), (tm)->tm_gmtoff % (60 * 60) \ + ); \ + } else { \ + snprintf( \ + (sbuf), (bufsz), "%d-%02d-%02dT%02d:%02d:%02d%c%02ld:%02ld", \ + 1900 + (tm)->tm_year, 1 + (tm)->tm_mon, (tm)->tm_mday, \ + (tm)->tm_hour, (tm)->tm_min, (tm)->tm_sec, \ + (tm)->tm_gmtoff >= 0 ? '+' : '-', /* %+02ld doesn't work */ \ + LGTD_ABS((tm)->tm_gmtoff / 60 / 60), (tm)->tm_gmtoff % (60 * 60) \ + ); \ + } \ +} while (0) #define LGTD_SNPRINTF_APPEND(buf, i, bufsz, ...) do { \ int n = snprintf(&(buf)[(i)], bufsz - i, __VA_ARGS__); \ (i) = LGTD_MIN((i) + n, bufsz); \ @@ -57,6 +80,10 @@ char *lgtd_iee8023mactoa(const uint8_t *addr, char *buf, int buflen); void lgtd_sockaddrtoa(const struct sockaddr_storage *, char *buf, int buflen); short lgtd_sockaddrport(const struct sockaddr_storage *); +char *lgtd_print_duration(uint64_t, char *, int); +#define LGTD_PRINT_DURATION(secs, arr) \ + lgtd_print_duration((secs), (arr), sizeof((arr))) + void _lgtd_err(void (*)(int, const char *, ...), int, const char *, ...) __attribute__((format(printf, 3, 4))); #define lgtd_err(eval, fmt, ...) _lgtd_err(err, (eval), (fmt), ##__VA_ARGS__); diff --git a/core/log.c b/core/log.c index 5d4ba37..a9eb923 100644 --- a/core/log.c +++ b/core/log.c @@ -53,14 +53,7 @@ lgtd_isotime_now(char *strbuf, int bufsz) if (!localtime_r(&now.tv_sec, &tm_now)) { goto error; } - // '2015-01-02T10:13:16.132222+00:00' - snprintf( - strbuf, bufsz, "%d-%02d-%02dT%02d:%02d:%02d.%jd%c%02ld:%02ld", - 1900 + tm_now.tm_year, 1 + tm_now.tm_mon, tm_now.tm_mday, - tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec, - (intmax_t)now.tv_usec, tm_now.tm_gmtoff >= 0 ? '+' : '-', // %+02ld doesn't work - LGTD_ABS(tm_now.tm_gmtoff / 60 / 60), tm_now.tm_gmtoff % (60 * 60) - ); + LGTD_TM_TO_ISOTIME(&tm_now, strbuf, bufsz, now.tv_usec); return; error: strbuf[0] = '\0'; @@ -121,6 +114,27 @@ lgtd_sockaddrtoa(const struct sockaddr_storage *peer, char *buf, int buflen) } } +char * +lgtd_print_duration(uint64_t secs, char *buf, int bufsz) +{ + assert(buf); + assert(bufsz > 0); + + int days = secs / (60 * 60 * 24); + int minutes = secs / 60; + int hours = minutes / 60; + hours = hours % 24; + minutes = minutes % 60; + + int i = 0; + if (days) { + int n = snprintf(buf, bufsz, "%d days ", days); + i = LGTD_MIN(i + n, bufsz); + } + snprintf(&buf[i], bufsz - i, "%02d:%02d", hours, minutes); + return buf; +} + void _lgtd_err(void (*errfn)(int, const char *, ...), int eval, diff --git a/core/proto.c b/core/proto.c index 38f4c47..fe0c426 100644 --- a/core/proto.c +++ b/core/proto.c @@ -195,40 +195,146 @@ lgtd_proto_get_light_state(struct lgtd_client *client, return; } - static const char *state_fmt = ("{" - "\"hsbk\":[%s,%s,%s,%hu]," - "\"power\":%s," - "\"label\":\"%s\"," - "\"tags\":["); - -#define PRINT_COMPONENT(src, dst, start, stop) \ - lgtd_jsonrpc_uint16_range_to_float_string( \ - (src), (start), (stop), (dst), sizeof((dst)) \ - ) - lgtd_client_start_send_response(client); lgtd_client_write_string(client, "["); struct lgtd_router_device *device; SLIST_FOREACH(device, devices, link) { struct lgtd_lifx_bulb *bulb = device->device; + char buf[2048], + site_addr[LGTD_LIFX_ADDR_STRLEN], + bulb_addr[LGTD_LIFX_ADDR_STRLEN]; + int i = 0; + + LGTD_IEEE8023MACTOA(bulb->addr, bulb_addr); + LGTD_IEEE8023MACTOA(bulb->gw->site.as_array, site_addr); + + LGTD_SNPRINTF_APPEND( + buf, i, (int)sizeof(buf), + "{" + "\"_lifx\":{" + "\"addr\":\"%s\"," + "\"gateway\":{" + "\"site\":\"%s\"," + "\"url\":\"tcp://[%s]:%hu\"," + "\"latency\":%ju" + "}", + bulb_addr, site_addr, + bulb->gw->ip_addr, bulb->gw->port, + (uintmax_t)LGTD_LIFX_GATEWAY_LATENCY(bulb->gw) + ); + +#define PRINT_LIFX_FW_TIMESTAMPS(fw_info, built_at_buf, installed_at_buf) \ + LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP((fw_info)->built_at, (built_at_buf)); \ + LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP( \ + (fw_info)->installed_at, (installed_at_buf) \ + ) + + for (int ip = 0; ip != LGTD_LIFX_BULB_IP_COUNT; ip++) { + if (lgtd_opts.verbosity == LGTD_DEBUG) { + char fw_built_at[64], fw_installed_at[64]; + PRINT_LIFX_FW_TIMESTAMPS( + &bulb->ips[ip].fw_info, fw_built_at, fw_installed_at + ); + + LGTD_SNPRINTF_APPEND( + buf, i, (int)sizeof(buf), + ",\"%s\":{" + "\"firmware_built_at\":\"%s\"," + "\"firmware_installed_at\":\"%s\"," + "\"firmware_version\":\"%u.%u\"," + "\"signal_strength\":%f," + "\"tx_bytes\":%u," + "\"rx_bytes\":%u," + "\"temperature\":%u" + "}", + lgtd_lifx_bulb_ip_names[ip], + fw_built_at, fw_installed_at, + (bulb->ips[ip].fw_info.version & 0xffff0000) >> 16, + bulb->ips[ip].fw_info.version & 0xffff, + bulb->ips[ip].state.signal_strength, + bulb->ips[ip].state.tx_bytes, + bulb->ips[ip].state.rx_bytes, + bulb->ips[ip].state.temperature + ); + } else { + LGTD_SNPRINTF_APPEND( + buf, i, (int)sizeof(buf), + ",\"%s\":{\"firmware_version\":\"%u.%u\"}", + lgtd_lifx_bulb_ip_names[ip], + (bulb->ips[ip].fw_info.version & 0xffff0000) >> 16, + bulb->ips[ip].fw_info.version & 0xffff + ); + } + } + + if (lgtd_opts.verbosity == LGTD_DEBUG) { + LGTD_SNPRINTF_APPEND( + buf, i, (int)sizeof(buf), + ",\"product_info\":{" + "\"vendor_id\":\"%x\"," + "\"product_id\":\"%x\"," + "\"version\":%u" + "}", + bulb->product_info.vendor_id, + bulb->product_info.product_id, + bulb->product_info.version + ); + + char bulb_time[64]; + LGTD_SNPRINTF_APPEND( + buf, i, (int)sizeof(buf), + ",\"runtime_info\":{" + "\"time\":\"%s\"," + "\"uptime\":%ju," + "\"downtime\":%ju" + "}" + "}", + LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP( + bulb->runtime_info.time, bulb_time + ), + (uintmax_t)LGTD_NSECS_TO_SECS(bulb->runtime_info.uptime), + (uintmax_t)LGTD_NSECS_TO_SECS(bulb->runtime_info.downtime) + ); + } else { + LGTD_SNPRINTF_APPEND(buf, i, (int)sizeof(buf), "}"); + } + +#define PRINT_STRING_OR_NULL(buf, i, bufsz, v) do { \ + if ((v)) { \ + LGTD_SNPRINTF_APPEND((buf), (i), (bufsz), "\"%s\"", (v)); \ + } else { \ + LGTD_SNPRINTF_APPEND((buf), (i), (bufsz), "null"); \ + } \ +} while (0) + + LGTD_SNPRINTF_APPEND(buf, i, (int)sizeof(buf), ",\"_model\":"); + PRINT_STRING_OR_NULL(buf, i, (int)sizeof(buf), bulb->model); + LGTD_SNPRINTF_APPEND(buf, i, (int)sizeof(buf), ",\"_vendor\":"); + PRINT_STRING_OR_NULL(buf, i, (int)sizeof(buf), bulb->vendor); + +#define PRINT_COMPONENT(src, dst, start, stop) \ + lgtd_jsonrpc_uint16_range_to_float_string( \ + (src), (start), (stop), (dst), sizeof((dst)) \ + ) + char h[16], s[16], b[16]; PRINT_COMPONENT(bulb->state.hue, h, 0, 360); PRINT_COMPONENT(bulb->state.saturation, s, 0, 1); PRINT_COMPONENT(bulb->state.brightness, b, 0, 1); - char buf[3072], - bulb_addr[LGTD_LIFX_ADDR_STRLEN], - site_addr[LGTD_LIFX_ADDR_STRLEN]; - LGTD_IEEE8023MACTOA(bulb->addr, bulb_addr); - LGTD_IEEE8023MACTOA(bulb->gw->site.as_array, site_addr); - int written = snprintf( - buf, sizeof(buf), state_fmt, + LGTD_SNPRINTF_APPEND( + buf, i, (int)sizeof(buf), + ",\"hsbk\":[%s,%s,%s,%hu]," + "\"power\":%s," + "\"label\":\"%s\"," + "\"tags\":[", h, s, b, bulb->state.kelvin, bulb->state.power == LGTD_LIFX_POWER_ON ? "true" : "false", bulb->state.label[0] ? bulb->state.label : bulb_addr ); - if (written >= (int)sizeof(buf)) { + + if (i >= (int)sizeof(buf)) { lgtd_warnx( "can't send state of bulb %s (%s) to client " "[%s]:%hu: output buffer to small", diff --git a/core/router.c b/core/router.c index f69cce4..27e888c 100644 --- a/core/router.c +++ b/core/router.c @@ -108,7 +108,10 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, char addr[LGTD_LIFX_ADDR_STRLEN]; LGTD_IEEE8023MACTOA(bulb->addr, addr); - lgtd_info("sending %s to %s", pkt_info->name, addr); + lgtd_info( + "sending %s to %s (%.*s)", + pkt_info->name, addr, LGTD_LIFX_LABEL_SIZE, bulb->state.label + ); } void diff --git a/examples/lightsc.py b/examples/lightsc.py index c94786f..4bca03a 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -17,7 +17,7 @@ def jsonrpc_call(socket, method, params): "id": str(uuid.uuid4()), } socket.send(json.dumps(payload).encode("utf-8")) - response = socket.recv(2048).decode("utf-8") + response = socket.recv(8192).decode("utf-8") try: response = json.loads(response) except ValueError: diff --git a/lifx/bulb.c b/lifx/bulb.c index 847f811..e8200a4 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -26,6 +26,7 @@ #include #include +#include #include #include "wire_proto.h" @@ -33,12 +34,94 @@ #include "bulb.h" #include "gateway.h" #include "core/daemon.h" +#include "core/timer.h" #include "core/stats.h" +#include "core/jsmn.h" +#include "core/jsonrpc.h" +#include "core/client.h" +#include "core/proto.h" +#include "core/router.h" #include "core/lightsd.h" struct lgtd_lifx_bulb_map lgtd_lifx_bulbs_table = RB_INITIALIZER(&lgtd_lifx_bulbs_table); +const char * const lgtd_lifx_bulb_ip_names[] = { "mcu", "wifi" }; + +static const char * +lgtd_lifx_bulb_get_model_name(uint32_t vendor_id, uint32_t product_id) +{ + if (vendor_id != LGTD_LIFX_VENDOR_ID) { + return "Unknown"; + } + + switch (product_id) { + case 0x1: + case 0x2: + return "A21 (Original)"; + case 0x3: + return "GU10 (Color 650)"; + case 0xa: + return "A19 (White 800)"; + default: + return "Unknown"; + } +} + +static const char * +lgtd_lifx_bulb_get_vendor_name(uint32_t vendor_id) +{ + switch (vendor_id) { + case LGTD_LIFX_VENDOR_ID: + return "LIFX"; + default: + return "Unknown"; + } +} + +static void +lgtd_lifx_bulb_fetch_hardware_info(struct lgtd_timer *timer, + union lgtd_timer_ctx ctx) +{ + assert(timer); + assert(ctx.as_uint); + + // Get the bulb again, it might have been closed while we were waiting: + const uint8_t *bulb_addr = (const uint8_t *)&ctx.as_uint; + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_get(bulb_addr); + if (!bulb) { + lgtd_timer_stop(timer); + return; + } + +#define RESEND_IF(test, pkt_type) do { \ + if ((test)) { \ + stop = false; \ + lgtd_router_send_to_device(bulb, (pkt_type), NULL); \ + } \ +} while (0) + + bool stop = true; + RESEND_IF(!bulb->product_info.vendor_id, LGTD_LIFX_GET_VERSION); + RESEND_IF( + !bulb->ips[LGTD_LIFX_BULB_MCU_IP].fw_info.version, + LGTD_LIFX_GET_MESH_FIRMWARE + ); + lgtd_time_mono_t state_updated_at; + state_updated_at = bulb->ips[LGTD_LIFX_BULB_MCU_IP].state_updated_at; + lgtd_time_mono_t timeout = LGTD_LIFX_BULB_FETCH_WIFI_FW_INFO_TIMEOUT_MSECS; + RESEND_IF( + ( + !bulb->ips[LGTD_LIFX_BULB_WIFI_IP].fw_info.version + && (lgtd_time_monotonic_msecs() - state_updated_at < timeout) + ), + LGTD_LIFX_GET_WIFI_FIRMWARE_STATE + ); + if (stop) { + lgtd_timer_stop(timer); + } +} + struct lgtd_lifx_bulb * lgtd_lifx_bulb_get(const uint8_t *addr) { @@ -68,6 +151,18 @@ lgtd_lifx_bulb_open(struct lgtd_lifx_gateway *gw, const uint8_t *addr) bulb->last_light_state_at = lgtd_time_monotonic_msecs(); + union lgtd_timer_ctx ctx = { .as_uint = 0 }; + memcpy(&ctx.as_uint, addr, LGTD_LIFX_ADDR_LENGTH); + struct lgtd_timer *timer = lgtd_timer_start( + LGTD_TIMER_ACTIVATE_NOW|LGTD_TIMER_PERSISTENT, + LGTD_LIFX_BULB_FETCH_HARDWARE_INFO_TIMER_MSECS, + lgtd_lifx_bulb_fetch_hardware_info, + ctx + ); + if (!timer) { + lgtd_warn("can't start a timer to fetch the bulb hardware specs"); + } + return bulb; } @@ -124,6 +219,7 @@ lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *bulb, LGTD_STATS_ADD_AND_UPDATE_PROCTITLE( bulbs_powered_on, state->power == LGTD_LIFX_POWER_ON ? 1 : -1 ); + lgtd_router_send_to_device(bulb, LGTD_LIFX_GET_INFO, NULL); } lgtd_lifx_gateway_update_tag_refcounts(bulb->gw, bulb->state.tags, state->tags); @@ -141,6 +237,7 @@ lgtd_lifx_bulb_set_power_state(struct lgtd_lifx_bulb *bulb, uint16_t power) LGTD_STATS_ADD_AND_UPDATE_PROCTITLE( bulbs_powered_on, power == LGTD_LIFX_POWER_ON ? 1 : -1 ); + lgtd_router_send_to_device(bulb, LGTD_LIFX_GET_INFO, NULL); } bulb->state.power = power; @@ -155,3 +252,57 @@ lgtd_lifx_bulb_set_tags(struct lgtd_lifx_bulb *bulb, uint64_t tags) bulb->state.tags = tags; } + +void +lgtd_lifx_bulb_set_ip_state(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_bulb_ips ip_id, + const struct lgtd_lifx_ip_state *state, + lgtd_time_mono_t received_at) +{ + assert(bulb); + assert(state); + + struct lgtd_lifx_bulb_ip *ip = &bulb->ips[ip_id]; + ip->state_updated_at = received_at; + memcpy(&ip->state, state, sizeof(ip->state)); +} + +void +lgtd_lifx_bulb_set_ip_firmware_info(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_bulb_ips ip_id, + const struct lgtd_lifx_ip_firmware_info *info, + lgtd_time_mono_t received_at) +{ + assert(bulb); + assert(info); + + struct lgtd_lifx_bulb_ip *ip = &bulb->ips[ip_id]; + ip->fw_info_updated_at = received_at; + memcpy(&ip->fw_info, info, sizeof(ip->fw_info)); +} + +void +lgtd_lifx_bulb_set_product_info(struct lgtd_lifx_bulb *bulb, + const struct lgtd_lifx_product_info *info) +{ + assert(bulb); + assert(info); + + memcpy(&bulb->product_info, info, sizeof(bulb->product_info)); + bulb->vendor = lgtd_lifx_bulb_get_vendor_name(info->vendor_id); + bulb->model = lgtd_lifx_bulb_get_model_name( + info->vendor_id, info->product_id + ); +} + +void +lgtd_lifx_bulb_set_runtime_info(struct lgtd_lifx_bulb *bulb, + const struct lgtd_lifx_runtime_info *info, + lgtd_time_mono_t received_at) +{ + assert(bulb); + assert(info); + + bulb->runtime_info_updated_at = received_at; + memcpy(&bulb->runtime_info, info, sizeof(bulb->runtime_info)); +} diff --git a/lifx/bulb.h b/lifx/bulb.h index 866e0c3..19ff5e3 100644 --- a/lifx/bulb.h +++ b/lifx/bulb.h @@ -30,17 +30,72 @@ struct lgtd_lifx_light_state { char label[LGTD_LIFX_LABEL_SIZE]; uint64_t tags; }; + +struct lgtd_lifx_ip_state { + float signal_strength; // mW + uint32_t tx_bytes; + uint32_t rx_bytes; + uint16_t temperature; // Deci-celcius: e.g 24.3 -> 2430 +}; + +struct lgtd_lifx_ip_firmware_info { + uint64_t built_at; // ns since epoch + uint64_t installed_at; // ns since epoch + uint32_t version; +}; + +struct lgtd_lifx_product_info { + uint32_t vendor_id; + uint32_t product_id; + uint32_t version; +}; + +struct lgtd_lifx_runtime_info { + uint64_t time; // ns since epoch + uint64_t uptime; // ns + uint64_t downtime; // ns, last power off period duration +}; #pragma pack(pop) +enum { LGTD_LIFX_BULB_FETCH_HARDWARE_INFO_TIMER_MSECS = 5000 }; +// non-gateway fw 1.1 bulbs will never send this information, so just bail out +// after a few tries: +enum { + LGTD_LIFX_BULB_FETCH_WIFI_FW_INFO_TIMEOUT_MSECS = + LGTD_LIFX_BULB_FETCH_HARDWARE_INFO_TIMER_MSECS * 4 +}; + +enum lgtd_lifx_bulb_ips { + LGTD_LIFX_BULB_MCU_IP = 0, + LGTD_LIFX_BULB_WIFI_IP, + LGTD_LIFX_BULB_IP_COUNT, +}; + +// keyed with enum lgtd_lifx_bulb_ips: +extern const char * const lgtd_lifx_bulb_ip_names[]; + +struct lgtd_lifx_bulb_ip { + struct lgtd_lifx_ip_state state; + lgtd_time_mono_t state_updated_at; + struct lgtd_lifx_ip_firmware_info fw_info; + lgtd_time_mono_t fw_info_updated_at; +}; + struct lgtd_lifx_bulb { RB_ENTRY(lgtd_lifx_bulb) link; SLIST_ENTRY(lgtd_lifx_bulb) link_by_gw; - struct lgtd_lifx_gateway *gw; - uint8_t addr[LGTD_LIFX_ADDR_LENGTH]; - struct lgtd_lifx_light_state state; lgtd_time_mono_t last_light_state_at; + lgtd_time_mono_t runtime_info_updated_at; lgtd_time_mono_t dirty_at; uint16_t expected_power_on; + uint8_t addr[LGTD_LIFX_ADDR_LENGTH]; + const char *model; + const char *vendor; + struct lgtd_lifx_gateway *gw; + struct lgtd_lifx_light_state state; + struct lgtd_lifx_bulb_ip ips[LGTD_LIFX_BULB_IP_COUNT]; + struct lgtd_lifx_product_info product_info; + struct lgtd_lifx_runtime_info runtime_info; }; RB_HEAD(lgtd_lifx_bulb_map, lgtd_lifx_bulb); SLIST_HEAD(lgtd_lifx_bulb_list, lgtd_lifx_bulb); @@ -69,3 +124,17 @@ void lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *, lgtd_time_mono_t); void lgtd_lifx_bulb_set_power_state(struct lgtd_lifx_bulb *, uint16_t); void lgtd_lifx_bulb_set_tags(struct lgtd_lifx_bulb *, uint64_t); + +void lgtd_lifx_bulb_set_ip_state(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_bulb_ips ip_id, + const struct lgtd_lifx_ip_state *state, + lgtd_time_mono_t received_at); +void lgtd_lifx_bulb_set_ip_firmware_info(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_bulb_ips ip_id, + const struct lgtd_lifx_ip_firmware_info *info, + lgtd_time_mono_t received_at); +void lgtd_lifx_bulb_set_product_info(struct lgtd_lifx_bulb *, + const struct lgtd_lifx_product_info *); +void lgtd_lifx_bulb_set_runtime_info(struct lgtd_lifx_bulb *, + const struct lgtd_lifx_runtime_info *, + lgtd_time_mono_t); diff --git a/lifx/gateway.c b/lifx/gateway.c index 361117e..2099832 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -502,12 +502,8 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, (uintmax_t)pkt->tags ); - struct lgtd_lifx_bulb *b = lgtd_lifx_gateway_get_or_open_bulb( - gw, hdr->target.device_addr - ); - if (!b) { - return; - } + struct lgtd_lifx_bulb *b; + LGTD_LIFX_GATEWAY_GET_BULB_OR_RETURN(b, gw, hdr->target.device_addr); assert(sizeof(*pkt) == sizeof(b->state)); lgtd_lifx_bulb_set_light_state( @@ -532,7 +528,7 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, } } - int latency = gw->last_pkt_at - gw->last_req_at; + int latency = LGTD_LIFX_GATEWAY_LATENCY(gw); if (latency < LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS) { if (!lgtd_timer_ispending(gw->refresh_timer)) { int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; @@ -574,14 +570,9 @@ lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), pkt->power ); - struct lgtd_lifx_bulb *b = lgtd_lifx_gateway_get_or_open_bulb( - gw, hdr->target.device_addr + LGTD_LIFX_GATEWAY_SET_BULB_ATTR( + gw, hdr->target.device_addr, lgtd_lifx_bulb_set_power_state, pkt->power ); - if (!b) { - return; - } - - lgtd_lifx_bulb_set_power_state(b, pkt->power); } int @@ -692,9 +683,10 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, } } -void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_tags *pkt) +void +lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_tags *pkt) { assert(gw && hdr && pkt); @@ -706,12 +698,8 @@ void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, (uintmax_t)pkt->tags ); - struct lgtd_lifx_bulb *b = lgtd_lifx_gateway_get_or_open_bulb( - gw, hdr->target.device_addr - ); - if (!b) { - return; - } + struct lgtd_lifx_bulb *b; + LGTD_LIFX_GATEWAY_GET_BULB_OR_RETURN(b, gw, hdr->target.device_addr); char bulb_addr[LGTD_LIFX_ADDR_STRLEN], site_addr[LGTD_LIFX_ADDR_STRLEN]; int tag_id; @@ -730,3 +718,132 @@ void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, lgtd_lifx_bulb_set_tags(b, pkt->tags); } + +void +lgtd_lifx_gateway_handle_ip_state(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_ip_state *pkt) +{ + assert(gw && hdr && pkt); + + const char *type; + enum lgtd_lifx_bulb_ips ip_id; + switch (hdr->packet_type) { + case LGTD_LIFX_MESH_INFO: + type = "MCU_STATE"; + ip_id = LGTD_LIFX_BULB_MCU_IP; + break; + case LGTD_LIFX_WIFI_INFO: + type = "WIFI_STATE"; + ip_id = LGTD_LIFX_BULB_WIFI_IP; + break; + default: + lgtd_info("invalid ip state packet_type %#hx", hdr->packet_type); +#ifndef NDEBUG + abort(); +#endif + return; + } + + char addr[LGTD_LIFX_ADDR_STRLEN]; + lgtd_debug( + "%s <-- [%s]:%hu - %s " + "signal_strength=%f, rx_bytes=%u, tx_bytes=%u, temperature=%hu", + type, gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + pkt->signal_strength, pkt->rx_bytes, pkt->tx_bytes, pkt->temperature + ); + + LGTD_LIFX_GATEWAY_SET_BULB_ATTR( + gw, hdr->target.device_addr, lgtd_lifx_bulb_set_ip_state, + ip_id, (const struct lgtd_lifx_ip_state *)pkt, gw->last_pkt_at + ); +} + +void +lgtd_lifx_gateway_handle_ip_firmware_info(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_ip_firmware_info *pkt) +{ + assert(gw && hdr && pkt); + + const char *type; + enum lgtd_lifx_bulb_ips ip_id; + switch (hdr->packet_type) { + case LGTD_LIFX_MESH_FIRMWARE: + type = "MCU_FIRMWARE_INFO"; + ip_id = LGTD_LIFX_BULB_MCU_IP; + break; + case LGTD_LIFX_WIFI_FIRMWARE_STATE: + type = "WIFI_FIRMWARE_INFO"; + ip_id = LGTD_LIFX_BULB_WIFI_IP; + break; + default: + lgtd_info("invalid ip firmware packet_type %#hx", hdr->packet_type); +#ifndef NDEBUG + abort(); +#endif + return; + } + + char built_at[64], installed_at[64], addr[LGTD_LIFX_ADDR_STRLEN]; + lgtd_debug( + "%s <-- [%s]:%hu - %s " + "built_at=%s, installed_at=%s, version=%u", + type, gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(pkt->built_at, built_at), + LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(pkt->installed_at, installed_at), + pkt->version + ); + + LGTD_LIFX_GATEWAY_SET_BULB_ATTR( + gw, hdr->target.device_addr, lgtd_lifx_bulb_set_ip_firmware_info, + ip_id, (const struct lgtd_lifx_ip_firmware_info *)pkt, gw->last_pkt_at + ); +} + +void +lgtd_lifx_gateway_handle_product_info(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_product_info *pkt) +{ + assert(gw && hdr && pkt); + + char addr[LGTD_LIFX_ADDR_STRLEN]; + lgtd_debug( + "PRODUCT_INFO <-- [%s]:%hu - %s " + "vendor_id=%#x, product_id=%#x, version=%u", + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + pkt->vendor_id, pkt->product_id, pkt->version + ); + + LGTD_LIFX_GATEWAY_SET_BULB_ATTR( + gw, hdr->target.device_addr, lgtd_lifx_bulb_set_product_info, + (const struct lgtd_lifx_product_info *)pkt + ); +} + +void +lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_runtime_info *pkt) +{ + assert(gw && hdr && pkt); + + char device_time[64], uptime[64], downtime[64], addr[LGTD_LIFX_ADDR_STRLEN]; + lgtd_debug( + "PRODUCT_INFO <-- [%s]:%hu - %s time=%s, uptime=%s, downtime=%s", + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(pkt->time, device_time), + LGTD_PRINT_DURATION(LGTD_NSECS_TO_SECS(pkt->uptime), uptime), + LGTD_PRINT_DURATION(LGTD_NSECS_TO_SECS(pkt->downtime), downtime) + ); + + LGTD_LIFX_GATEWAY_SET_BULB_ATTR( + gw, hdr->target.device_addr, lgtd_lifx_bulb_set_runtime_info, + (const struct lgtd_lifx_runtime_info *)pkt, gw->last_pkt_at + ); +} diff --git a/lifx/gateway.h b/lifx/gateway.h index 7e4ce97..f32edd4 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -36,12 +36,19 @@ struct lgtd_lifx_message { struct lgtd_lifx_gateway { LIST_ENTRY(lgtd_lifx_gateway) link; struct lgtd_lifx_bulb_list bulbs; +#define LGTD_LIFX_GATEWAY_GET_BULB_OR_RETURN(b, gw, bulb_addr) do { \ + (b) = lgtd_lifx_gateway_get_or_open_bulb((gw), (bulb_addr)); \ + if (!(b)) { \ + return; \ + } \ +} while (0) // Multiple gateways can share the same site (that happens when bulbs are // far away enough that ZigBee can't be used). Moreover the SET_PAN_GATEWAY // packet doesn't include the device address in the header (i.e: site and // device_addr have the same value) so we have no choice but to use the // remote ip address to identify a gateway: struct sockaddr_storage peer; + uint8_t addr[LGTD_LIFX_ADDR_LENGTH]; char ip_addr[INET6_ADDRSTRLEN]; uint16_t port; // TODO: just use an integer and rename it to site_id: @@ -60,6 +67,7 @@ struct lgtd_lifx_gateway { lgtd_time_mono_t last_req_at; lgtd_time_mono_t next_req_at; lgtd_time_mono_t last_pkt_at; +#define LGTD_LIFX_GATEWAY_LATENCY(gw) ((gw)->last_pkt_at - (gw)->last_req_at) struct lgtd_lifx_message pkt_ring[LGTD_LIFX_GATEWAY_PACKET_RING_SIZE]; #define LGTD_LIFX_GATEWAY_INC_MESSAGE_RING_INDEX(idx) do { \ (idx) += 1; \ @@ -77,6 +85,12 @@ LIST_HEAD(lgtd_lifx_gateway_list, lgtd_lifx_gateway); extern struct lgtd_lifx_gateway_list lgtd_lifx_gateways; +#define LGTD_LIFX_GATEWAY_SET_BULB_ATTR(gw, bulb_addr, bulb_fn, ...) do { \ + struct lgtd_lifx_bulb *b; \ + LGTD_LIFX_GATEWAY_GET_BULB_OR_RETURN(b, gw, bulb_addr); \ + (bulb_fn)(b, __VA_ARGS__); \ +} while (0) + struct lgtd_lifx_gateway *lgtd_lifx_gateway_get(const struct sockaddr_storage *); struct lgtd_lifx_gateway *lgtd_lifx_gateway_open(const struct sockaddr_storage *, ev_socklen_t, @@ -119,3 +133,15 @@ void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *, void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, const struct lgtd_lifx_packet_tags *); +void lgtd_lifx_gateway_handle_ip_state(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_ip_state *); +void lgtd_lifx_gateway_handle_ip_firmware_info(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_ip_firmware_info *); +void lgtd_lifx_gateway_handle_product_info(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_product_info *); +void lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_runtime_info *); diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 0750653..d864e86 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -24,8 +24,10 @@ #include #include #include +#include #include #include +#include #include @@ -74,12 +76,32 @@ lgtd_lifx_wire_null_packet_handler(struct lgtd_lifx_gateway *gw, (void)pkt; } +static void +lgtd_lifx_wire_enosys_packet_handler(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const void *pkt) +{ + (void)pkt; + + const struct lgtd_lifx_packet_info *pkt_info; + pkt_info = lgtd_lifx_wire_get_packet_info(hdr->packet_type); + bool addressable = hdr->protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE; + bool tagged = hdr->protocol & LGTD_LIFX_PROTOCOL_TAGGED; + unsigned int protocol = hdr->protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; + lgtd_info( + "%s <-- [%s]:%hu - (Unimplemented, header info: " + "addressable=%d, tagged=%d, protocol=%d)", + pkt_info->name, gw->ip_addr, gw->port, + addressable, tagged, protocol + ); +} + void lgtd_lifx_wire_load_packet_info_map(void) { #define DECODER(x) ((void (*)(void *))(x)) #define ENCODER(x) ((void (*)(void *))(x)) -#define HANDLER(x) \ +#define HANDLER(x) \ ((void (*)(struct lgtd_lifx_gateway *, \ const struct lgtd_lifx_packet_header *, \ const void *))(x)) @@ -90,6 +112,10 @@ lgtd_lifx_wire_load_packet_info_map(void) #define REQUEST_ONLY \ .decode = lgtd_lifx_wire_null_packet_encoder_decoder, \ .handle = lgtd_lifx_wire_null_packet_handler +#define UNIMPLEMENTED \ + .decode = lgtd_lifx_wire_null_packet_encoder_decoder, \ + .encode = lgtd_lifx_wire_null_packet_encoder_decoder, \ + .handle = lgtd_lifx_wire_enosys_packet_handler static struct lgtd_lifx_packet_info packet_table[] = { // Gateway packets: @@ -187,6 +213,211 @@ lgtd_lifx_wire_load_packet_info_map(void) .size = sizeof(struct lgtd_lifx_packet_tags), .decode = DECODER(lgtd_lifx_wire_decode_tags), .handle = HANDLER(lgtd_lifx_gateway_handle_tags) + }, + { + REQUEST_ONLY, + NO_PAYLOAD, + .name = "GET_MESH_INFO", + .type = LGTD_LIFX_GET_MESH_INFO + }, + { + RESPONSE_ONLY, + .name = "MESH_INFO", + .type = LGTD_LIFX_MESH_INFO, + .size = sizeof(struct lgtd_lifx_packet_ip_state), + .decode = DECODER(lgtd_lifx_wire_decode_ip_state), + .handle = HANDLER(lgtd_lifx_gateway_handle_ip_state) + }, + { + REQUEST_ONLY, + NO_PAYLOAD, + .name = "GET_MESH_FIRMWARE", + .type = LGTD_LIFX_GET_MESH_FIRMWARE + }, + { + RESPONSE_ONLY, + .name = "MESH_FIRMWARE", + .type = LGTD_LIFX_MESH_FIRMWARE, + .size = sizeof(struct lgtd_lifx_packet_ip_firmware_info), + .decode = DECODER(lgtd_lifx_wire_decode_ip_firmware_info), + .handle = HANDLER(lgtd_lifx_gateway_handle_ip_firmware_info) + }, + { + REQUEST_ONLY, + NO_PAYLOAD, + .name = "GET_WIFI_INFO", + .type = LGTD_LIFX_GET_WIFI_INFO, + }, + { + RESPONSE_ONLY, + .name = "WIFI_INFO", + .type = LGTD_LIFX_WIFI_INFO, + .size = sizeof(struct lgtd_lifx_packet_ip_state), + .decode = DECODER(lgtd_lifx_wire_decode_ip_state), + .handle = HANDLER(lgtd_lifx_gateway_handle_ip_state) + }, + { + REQUEST_ONLY, + NO_PAYLOAD, + .name = "GET_WIFI_FIRMWARE_STATE", + .type = LGTD_LIFX_GET_WIFI_FIRMWARE_STATE + }, + { + RESPONSE_ONLY, + .name = "WIFI_FIRMWARE_STATE", + .type = LGTD_LIFX_WIFI_FIRMWARE_STATE, + .size = sizeof(struct lgtd_lifx_packet_ip_firmware_info), + .decode = DECODER(lgtd_lifx_wire_decode_ip_firmware_info), + .handle = HANDLER(lgtd_lifx_gateway_handle_ip_firmware_info) + }, + { + REQUEST_ONLY, + NO_PAYLOAD, + .name = "GET_VERSION", + .type = LGTD_LIFX_GET_VERSION + }, + { + RESPONSE_ONLY, + .name = "VERSION_STATE", + .type = LGTD_LIFX_VERSION_STATE, + .size = sizeof(struct lgtd_lifx_packet_product_info), + .decode = DECODER(lgtd_lifx_wire_decode_product_info), + .handle = HANDLER(lgtd_lifx_gateway_handle_product_info) + }, + { + REQUEST_ONLY, + NO_PAYLOAD, + .name = "GET_INFO", + .type = LGTD_LIFX_GET_INFO + }, + { + RESPONSE_ONLY, + .name = "INFO_STATE", + .type = LGTD_LIFX_INFO_STATE, + .size = sizeof(struct lgtd_lifx_packet_runtime_info), + .decode = DECODER(lgtd_lifx_wire_decode_runtime_info), + .handle = HANDLER(lgtd_lifx_gateway_handle_runtime_info) + }, + // Unimplemented but "known" packets + { + UNIMPLEMENTED, + .name = "GET_TIME", + .type = LGTD_LIFX_GET_TIME + }, + { + UNIMPLEMENTED, + .name = "SET_TIME", + .type = LGTD_LIFX_SET_TIME + }, + { + UNIMPLEMENTED, + .name = "TIME_STATE", + .type = LGTD_LIFX_TIME_STATE + }, + { + UNIMPLEMENTED, + .name = "GET_RESET_SWITCH_STATE", + .type = LGTD_LIFX_GET_RESET_SWITCH_STATE + }, + { + UNIMPLEMENTED, + .name = "RESET_SWITCH_STATE", + .type = LGTD_LIFX_RESET_SWITCH_STATE + }, + { + UNIMPLEMENTED, + .name = "GET_BULB_LABEL", + .type = LGTD_LIFX_GET_BULB_LABEL + }, + { + UNIMPLEMENTED, + .name = "SET_BULB_LABEL", + .type = LGTD_LIFX_SET_BULB_LABEL + }, + { + UNIMPLEMENTED, + .name = "BULB_LABEL", + .type = LGTD_LIFX_BULB_LABEL + }, + { + UNIMPLEMENTED, + .name = "GET_MCU_RAIL_VOLTAGE", + .type = LGTD_LIFX_GET_MCU_RAIL_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "MCU_RAIL_VOLTAGE", + .type = LGTD_LIFX_MCU_RAIL_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "REBOOT", + .type = LGTD_LIFX_REBOOT + }, + { + UNIMPLEMENTED, + .name = "SET_FACTORY_TEST_MODE", + .type = LGTD_LIFX_SET_FACTORY_TEST_MODE + }, + { + UNIMPLEMENTED, + .name = "DISABLE_FACTORY_TEST_MODE", + .type = LGTD_LIFX_DISABLE_FACTORY_TEST_MODE + }, + { + UNIMPLEMENTED, + .name = "ACK", + .type = LGTD_LIFX_ACK + }, + { + UNIMPLEMENTED, + .name = "ECHO_REQUEST", + .type = LGTD_LIFX_ECHO_REQUEST + }, + { + UNIMPLEMENTED, + .name = "ECHO_RESPONSE", + .type = LGTD_LIFX_ECHO_RESPONSE + }, + { + UNIMPLEMENTED, + .name = "SET_DIM_ABSOLUTE", + .type = LGTD_LIFX_SET_DIM_ABSOLUTE + }, + { + UNIMPLEMENTED, + .name = "SET_DIM_RELATIVE", + .type = LGTD_LIFX_SET_DIM_RELATIVE + }, + { + UNIMPLEMENTED, + .name = "GET_WIFI_STATE", + .type = LGTD_LIFX_GET_WIFI_STATE + }, + { + UNIMPLEMENTED, + .name = "SET_WIFI_STATE", + .type = LGTD_LIFX_SET_WIFI_STATE + }, + { + UNIMPLEMENTED, + .name = "WIFI_STATE", + .type = LGTD_LIFX_WIFI_STATE + }, + { + UNIMPLEMENTED, + .name = "GET_ACCESS_POINTS", + .type = LGTD_LIFX_GET_ACCESS_POINTS + }, + { + UNIMPLEMENTED, + .name = "SET_ACCESS_POINTS", + .type = LGTD_LIFX_SET_ACCESS_POINTS + }, + { + UNIMPLEMENTED, + .name = "ACCESS_POINT", + .type = LGTD_LIFX_ACCESS_POINT } }; @@ -234,6 +465,25 @@ lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) return LGTD_LIFX_WAVEFORM_INVALID; } +char * +lgtd_lifx_wire_print_nsec_timestamp(uint64_t nsec_ts, char *buf, int bufsz) +{ + assert(buf); + assert(bufsz > 0); + + time_t ts = LGTD_NSECS_TO_SECS(nsec_ts); + + struct tm tm_utc; + if (gmtime_r(&ts, &tm_utc)) { + int64_t usecs = LGTD_NSECS_TO_USECS(nsec_ts - LGTD_SECS_TO_NSECS(ts)); + LGTD_TM_TO_ISOTIME(&tm_utc, buf, bufsz, usecs); + } else { + buf[0] = '\0'; + } + + return buf; +} + static void lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr, int flags) { @@ -423,3 +673,44 @@ lgtd_lifx_wire_decode_tags(struct lgtd_lifx_packet_tags *pkt) pkt->tags = le64toh(pkt->tags); } + +void +lgtd_lifx_wire_decode_ip_state(struct lgtd_lifx_packet_ip_state *pkt) +{ + assert(pkt); + + pkt->signal_strength = lgtd_lifx_wire_lefloattoh(pkt->signal_strength); + pkt->tx_bytes = le32toh(pkt->tx_bytes); + pkt->rx_bytes = le32toh(pkt->rx_bytes); + pkt->temperature = le16toh(pkt->temperature); +} + +void +lgtd_lifx_wire_decode_ip_firmware_info(struct lgtd_lifx_packet_ip_firmware_info *pkt) +{ + assert(pkt); + + pkt->built_at = le64toh(pkt->built_at); + pkt->installed_at = le64toh(pkt->installed_at); + pkt->version = le32toh(pkt->version); +} + +void +lgtd_lifx_wire_decode_product_info(struct lgtd_lifx_packet_product_info *pkt) +{ + assert(pkt); + + pkt->vendor_id = le32toh(pkt->vendor_id); + pkt->product_id = le32toh(pkt->product_id); + pkt->version = le32toh(pkt->version); +} + +void +lgtd_lifx_wire_decode_runtime_info(struct lgtd_lifx_packet_runtime_info *pkt) +{ + assert(pkt); + + pkt->time = le64toh(pkt->time); + pkt->uptime = le64toh(pkt->uptime); + pkt->downtime = le64toh(pkt->downtime); +} diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index cd7601e..8dee0d3 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -28,7 +28,7 @@ typedef float floatle_t; static inline floatle_t lgtd_lifx_wire_htolefloat(float f) { - union { float f; uint32_t i; } u = { .f = f }; + union { float f; uint32_t i; } u = { .f = f }; htole32(u.i); return u.f; } @@ -36,7 +36,7 @@ lgtd_lifx_wire_htolefloat(float f) static inline floatle_t lgtd_lifx_wire_lefloattoh(float f) { - union { float f; uint32_t i; } u = { .f = f }; + union { float f; uint32_t i; } u = { .f = f }; le32toh(u.i); return u.f; } @@ -250,8 +250,34 @@ struct lgtd_lifx_packet_tag_labels { char label[LGTD_LIFX_LABEL_SIZE]; }; +struct lgtd_lifx_packet_ip_state { + floatle_t signal_strength; + uint32le_t tx_bytes; + uint32le_t rx_bytes; + uint16le_t temperature; +}; + +struct lgtd_lifx_packet_ip_firmware_info { + uint64le_t built_at; + uint64le_t installed_at; + uint32le_t version; +}; + +struct lgtd_lifx_packet_product_info { + uint32le_t vendor_id; + uint32le_t product_id; + uint32le_t version; +}; + +struct lgtd_lifx_packet_runtime_info { + uint64le_t time; + uint64le_t uptime; + uint64le_t downtime; +}; #pragma pack(pop) +enum { LGTD_LIFX_VENDOR_ID = 1 }; + enum lgtd_lifx_header_flags { LGTD_LIFX_ADDRESSABLE = 1, LGTD_LIFX_TAGGED = 1 << 1, @@ -331,8 +357,10 @@ lgtd_lifx_wire_next_tag_id(int current_tag_id, uint64_t tags) (tag_id_varname) != -1; \ (tag_id_varname) = lgtd_lifx_wire_next_tag_id((tag_id_varname), (tags))) - enum lgtd_lifx_waveform_type lgtd_lifx_wire_waveform_string_id_to_type(const char *, int); +char* lgtd_lifx_wire_print_nsec_timestamp(uint64_t, char *, int); +#define LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(ts, arr) \ + lgtd_lifx_wire_print_nsec_timestamp((ts), (arr), sizeof((arr))) const struct lgtd_lifx_packet_info *lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type); void lgtd_lifx_wire_load_packet_info_map(void); @@ -357,3 +385,8 @@ void lgtd_lifx_wire_encode_tags(struct lgtd_lifx_packet_tags *); void lgtd_lifx_wire_decode_tags(struct lgtd_lifx_packet_tags *); void lgtd_lifx_wire_encode_tag_labels(struct lgtd_lifx_packet_tag_labels *); void lgtd_lifx_wire_decode_tag_labels(struct lgtd_lifx_packet_tag_labels *); + +void lgtd_lifx_wire_decode_ip_state(struct lgtd_lifx_packet_ip_state *); +void lgtd_lifx_wire_decode_ip_firmware_info(struct lgtd_lifx_packet_ip_firmware_info *); +void lgtd_lifx_wire_decode_product_info(struct lgtd_lifx_packet_product_info *); +void lgtd_lifx_wire_decode_runtime_info(struct lgtd_lifx_packet_runtime_info *); diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c index 79debe6..85cdee4 100644 --- a/tests/core/proto/test_proto_get_light_state.c +++ b/tests/core/proto/test_proto_get_light_state.c @@ -32,6 +32,9 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) static struct lgtd_router_device_list devices = SLIST_HEAD_INITIALIZER(&devices); + if (SLIST_FIRST(&devices)) { + return &devices; + } static struct lgtd_lifx_gateway gw_bulb_1 = { .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) @@ -47,6 +50,15 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) .power = LGTD_LIFX_POWER_ON, .tags = 0 }, + .ips[LGTD_LIFX_BULB_WIFI_IP] = { + .fw_info.version = 0x10001 + }, + .model = "testbulb", + .product_info = { + .vendor_id = 1, + .product_id = 0xa, + .version = 9 + }, .gw = &gw_bulb_1 }; static struct lgtd_router_device device_1 = { .device = &bulb_1 }; @@ -73,6 +85,14 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) .power = LGTD_LIFX_POWER_OFF, .tags = 0x3 }, + .ips[LGTD_LIFX_BULB_MCU_IP] = { + .fw_info.version = 0x20001 + }, + .vendor = "martine", + .runtime_info = { + .uptime = 42E9, + .downtime = 1337E9 + }, .gw = &gw_bulb_2 }; static struct lgtd_router_device device_2 = { .device = &bulb_2 }; @@ -89,22 +109,95 @@ main(void) lgtd_proto_get_light_state(&client, targets); - const char expected[] = ( - "[" - "{" - "\"hsbk\":[0,0,1,4000]," - "\"power\":false," - "\"label\":\"05:04:03:02:01:00\"," - "\"tags\":[\"vapor\",\"d^-^b\"]" + const char expected[] = ("[" + "{" + "\"_lifx\":{" + "\"addr\":\"05:04:03:02:01:00\"," + "\"gateway\":{" + "\"site\":\"00:00:00:00:00:00\"," + "\"url\":\"tcp://[]:0\"," + "\"latency\":0" + "}," + "\"mcu\":{" + "\"firmware_built_at\":\"1970-01-01T00:00:00+00:00\"," + "\"firmware_installed_at\":\"1970-01-01T00:00:00+00:00\"," + "\"firmware_version\":\"2.1\"," + "\"signal_strength\":0.000000," + "\"tx_bytes\":0," + "\"rx_bytes\":0," + "\"temperature\":0" + "}," + "\"wifi\":{" + "\"firmware_built_at\":\"1970-01-01T00:00:00+00:00\"," + "\"firmware_installed_at\":\"1970-01-01T00:00:00+00:00\"," + "\"firmware_version\":\"0.0\"," + "\"signal_strength\":0.000000," + "\"tx_bytes\":0," + "\"rx_bytes\":0," + "\"temperature\":0" + "}," + "\"product_info\":{" + "\"vendor_id\":\"0\"," + "\"product_id\":\"0\"," + "\"version\":0" + "}," + "\"runtime_info\":{" + "\"time\":\"1970-01-01T00:00:00+00:00\"," + "\"uptime\":42," + "\"downtime\":1337" + "}" + "}," + "\"_model\":null," + "\"_vendor\":\"martine\"," + "\"hsbk\":[0,0,1,4000]," + "\"power\":false," + "\"label\":\"05:04:03:02:01:00\"," + "\"tags\":[\"vapor\",\"d^-^b\"]" + "},{" + "\"_lifx\":{" + "\"addr\":\"01:02:03:04:05:00\"," + "\"gateway\":{" + "\"site\":\"00:00:00:00:00:00\"," + "\"url\":\"tcp://[]:0\"," + "\"latency\":0" + "}," + "\"mcu\":{" + "\"firmware_built_at\":\"1970-01-01T00:00:00+00:00\"," + "\"firmware_installed_at\":\"1970-01-01T00:00:00+00:00\"," + "\"firmware_version\":\"0.0\"," + "\"signal_strength\":0.000000," + "\"tx_bytes\":0," + "\"rx_bytes\":0," + "\"temperature\":0" + "}," + "\"wifi\":{" + "\"firmware_built_at\":\"1970-01-01T00:00:00+00:00\"," + "\"firmware_installed_at\":\"1970-01-01T00:00:00+00:00\"," + "\"firmware_version\":\"1.1\"," + "\"signal_strength\":0.000000," + "\"tx_bytes\":0," + "\"rx_bytes\":0," + "\"temperature\":0" + "}," + "\"product_info\":{" + "\"vendor_id\":\"1\"," + "\"product_id\":\"a\"," + "\"version\":9" + "}," + "\"runtime_info\":{" + "\"time\":\"1970-01-01T00:00:00+00:00\"," + "\"uptime\":0," + "\"downtime\":0" + "}" "}," - "{" - "\"hsbk\":[240,1,0.733333,3600]," - "\"power\":true," - "\"label\":\"wave\"," - "\"tags\":[]" - "}" - "]" - ); + "\"_model\":\"testbulb\"," + "\"_vendor\":null," + "\"hsbk\":[240,1,0.733333,3600]," + "\"power\":true," + "\"label\":\"wave\"," + "\"tags\":[]" + "}" + "]"); if (client_write_buf_idx != sizeof(expected) - 1) { lgtd_errx( @@ -127,5 +220,67 @@ main(void) lgtd_errx(1, "the list of devices hasn't been freed"); } + lgtd_opts.verbosity = LGTD_INFO; + + char expected_info[] = ("[" + "{" + "\"_lifx\":{" + "\"addr\":\"05:04:03:02:01:00\"," + "\"gateway\":{" + "\"site\":\"00:00:00:00:00:00\"," + "\"url\":\"tcp://[]:0\"," + "\"latency\":0" + "}," + "\"mcu\":{\"firmware_version\":\"2.1\"}," + "\"wifi\":{\"firmware_version\":\"0.0\"}" + "}," + "\"_model\":null," + "\"_vendor\":\"martine\"," + "\"hsbk\":[0,0,1,4000]," + "\"power\":false," + "\"label\":\"05:04:03:02:01:00\"," + "\"tags\":[\"vapor\",\"d^-^b\"]" + "}," + "{" + "\"_lifx\":{" + "\"addr\":\"01:02:03:04:05:00\"," + "\"gateway\":{" + "\"site\":\"00:00:00:00:00:00\"," + "\"url\":\"tcp://[]:0\"," + "\"latency\":0" + "}," + "\"mcu\":{\"firmware_version\":\"0.0\"}," + "\"wifi\":{\"firmware_version\":\"1.1\"}" + "}," + "\"_model\":\"testbulb\"," + "\"_vendor\":null," + "\"hsbk\":[240,1,0.733333,3600]," + "\"power\":true," + "\"label\":\"wave\"," + "\"tags\":[]" + "}" + "]"); + + reset_client_write_buf(); + + lgtd_proto_get_light_state(&client, targets); + + if (client_write_buf_idx != sizeof(expected_info) - 1) { + lgtd_errx( + 1, + "%d bytes written, expected_info %lu " + "(got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected_info) - 1UL, + client_write_buf_idx, client_write_buf, expected_info + ); + } + + if (memcmp(expected_info, client_write_buf, sizeof(expected_info) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected_info + ); + } + return 0; } diff --git a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c index e71a7cc..e493371 100644 --- a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c +++ b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c @@ -87,24 +87,48 @@ main(void) struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; struct lgtd_proto_target_list *targets = (void *)0x2a; + lgtd_opts.verbosity = LGTD_INFO; + lgtd_proto_get_light_state(&client, targets); - const char expected[] = ( - "[" - "{" - "\"hsbk\":[0,0,1,4000]," - "\"power\":false," - "\"label\":\"05:04:03:02:01:00\"," - "\"tags\":[\"vapor\",\"d^-^b\"]" + const char expected[] = ("[" + "{" + "\"_lifx\":{" + "\"addr\":\"05:04:03:02:01:00\"," + "\"gateway\":{" + "\"site\":\"00:00:00:00:00:00\"," + "\"url\":\"tcp://[]:0\"," + "\"latency\":0" + "}," + "\"mcu\":{\"firmware_version\":\"0.0\"}," + "\"wifi\":{\"firmware_version\":\"0.0\"}" + "}," + "\"_model\":null," + "\"_vendor\":null," + "\"hsbk\":[0,0,1,4000]," + "\"power\":false," + "\"label\":\"05:04:03:02:01:00\"," + "\"tags\":[\"vapor\",\"d^-^b\"]" + "}," + "{" + "\"_lifx\":{" + "\"addr\":\"01:02:03:04:05:00\"," + "\"gateway\":{" + "\"site\":\"00:00:00:00:00:00\"," + "\"url\":\"tcp://[]:0\"," + "\"latency\":0" + "}," + "\"mcu\":{\"firmware_version\":\"0.0\"}," + "\"wifi\":{\"firmware_version\":\"0.0\"}" "}," - "{" - "\"hsbk\":[240,1,0.733333,3600]," - "\"power\":true," - "\"label\":\"wave\"," - "\"tags\":[]" - "}" - "]" - ); + "\"_model\":null," + "\"_vendor\":null," + "\"hsbk\":[240,1,0.733333,3600]," + "\"power\":true," + "\"label\":\"wave\"," + "\"tags\":[]" + "}" + "]"); if (client_write_buf_idx != sizeof(expected) - 1) { lgtd_errx( diff --git a/tests/lifx/bulb/CMakeLists.txt b/tests/lifx/bulb/CMakeLists.txt index 51a4e86..3e06214 100644 --- a/tests/lifx/bulb/CMakeLists.txt +++ b/tests/lifx/bulb/CMakeLists.txt @@ -8,11 +8,13 @@ ADD_CORE_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../core/tests_utils.c ) ADD_LIBRARY( test_lifx_bulb STATIC ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ) diff --git a/tests/lifx/bulb/test_bulb_fetch_hardware_info.c b/tests/lifx/bulb/test_bulb_fetch_hardware_info.c new file mode 100644 index 0000000..246a85f --- /dev/null +++ b/tests/lifx/bulb/test_bulb_fetch_hardware_info.c @@ -0,0 +1,143 @@ +#include "bulb.c" + +#include "mock_gateway.h" +#define MOCKED_LGTD_ROUTER_SEND_TO_DEVICE +#include "mock_router.h" +#define MOCKED_LGTD_TIMER_STOP +#include "mock_timer.h" + +#include "tests_utils.h" + +#define FAKE_TIMER (void *)4 + +static struct lgtd_lifx_bulb *test_bulb = NULL; + +static int timer_stop_call_count = 0; + +void +lgtd_timer_stop(struct lgtd_timer *timer) +{ + if (timer != FAKE_TIMER) { + errx(1, "got timer %p (expected %p)", timer, FAKE_TIMER); + } + + timer_stop_call_count++; +} + +static int get_version_sent = 0; +static int get_mesh_firmware_state_sent = 0; +static int get_wifi_firmware_state_sent = 0; + +void +lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (bulb != test_bulb) { + errx( + 1, "router_send_to_device got bulb %p (expected %p)", + bulb, test_bulb + ); + } + + if (pkt) { + errx(1, "got unexpected pkt"); + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" // we don't test the whole enum + switch (pkt_type) { + case LGTD_LIFX_GET_VERSION: + get_version_sent++; + break; + case LGTD_LIFX_GET_MESH_FIRMWARE: + get_mesh_firmware_state_sent++; + break; + case LGTD_LIFX_GET_WIFI_FIRMWARE_STATE: + get_wifi_firmware_state_sent++; + break; + } +#pragma GCC diagnostic pop +} + +static void +test_counters(int get_version, int mcu_fw_info, int wifi_fw_info) +{ + if (get_version_sent != get_version) { + errx( + 1, "get_version_sent %d (expected %d)", + get_version_sent, get_version + ); + } + if (get_mesh_firmware_state_sent != mcu_fw_info) { + errx( + 1, "get_mesh_firmware_state_sent %d (expected %d)", + get_mesh_firmware_state_sent, mcu_fw_info + ); + } + if (get_wifi_firmware_state_sent != wifi_fw_info) { + errx( + 1, "get_wifi_firmware_state_sent %d (expected %d)", + get_wifi_firmware_state_sent, wifi_fw_info + ); + } +} + +int +main(void) +{ + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + test_bulb = lgtd_tests_insert_mock_bulb(&gw, 75); + union lgtd_timer_ctx ctx = { .as_uint = 1 }; + + // make sure it handles non-existent bulbs: + lgtd_lifx_bulb_fetch_hardware_info(FAKE_TIMER, ctx); + if (timer_stop_call_count != 1) { + errx(1, "timer_stop wasn't called"); + } + + ctx.as_uint = 0; + memcpy(&ctx.as_uint, test_bulb->addr, LGTD_LIFX_ADDR_LENGTH); + lgtd_lifx_bulb_fetch_hardware_info(FAKE_TIMER, ctx); + test_counters(1, 1, 0); + + memset(&test_bulb->product_info, 1, sizeof(test_bulb->product_info)); + lgtd_lifx_bulb_fetch_hardware_info(FAKE_TIMER, ctx); + test_counters(1, 2, 0); + struct lgtd_lifx_bulb_ip *ip = &test_bulb->ips[LGTD_LIFX_BULB_MCU_IP]; + memset(ip, 1, sizeof(*ip)); + + // the retry logic for the wifi firmware is a bit more complex because of + // that v1.1 bug for non-gateway bulbs: + + // state_updated_at for the mcu ip is at zero so we give up and stop the + // timer instead of sending a new packet: + ip->state_updated_at = 0; + lgtd_lifx_bulb_fetch_hardware_info(FAKE_TIMER, ctx); + test_counters(1, 2, 0); + if (timer_stop_call_count != 2) { + errx(1, "timer_stop wasn't called"); + } + + // set it to the current time, the packet should be sent again: + ip = &test_bulb->ips[LGTD_LIFX_BULB_MCU_IP]; + ip->state_updated_at = lgtd_time_monotonic_msecs(); + lgtd_lifx_bulb_fetch_hardware_info(FAKE_TIMER, ctx); + test_counters(1, 2, 1); + if (timer_stop_call_count != 2) { + errx(1, "timer_stop shouldn't have been called"); + } + + // finally make sure we stop the timer if we got the info alright as it + // should just be without the bug: + ip = &test_bulb->ips[LGTD_LIFX_BULB_WIFI_IP]; + memset(ip, 1, sizeof(*ip)); + lgtd_lifx_bulb_fetch_hardware_info(FAKE_TIMER, ctx); + test_counters(1, 2, 1); + if (timer_stop_call_count != 3) { + errx(1, "timer_stop wasn't called"); + } + + return 0; +} diff --git a/tests/lifx/bulb/test_bulb_open.c b/tests/lifx/bulb/test_bulb_open.c index 5bf8a12..c06008e 100644 --- a/tests/lifx/bulb/test_bulb_open.c +++ b/tests/lifx/bulb/test_bulb_open.c @@ -2,8 +2,50 @@ #include "mock_gateway.h" #include "mock_router.h" +#define MOCKED_LGTD_TIMER_START #include "mock_timer.h" +static int timer_start_call_count = 0; + +struct lgtd_timer * +lgtd_timer_start(int flags, + int ms, + void (*cb)(struct lgtd_timer *, + union lgtd_timer_ctx), + union lgtd_timer_ctx ctx) +{ + if (flags != (LGTD_TIMER_PERSISTENT|LGTD_TIMER_ACTIVATE_NOW)) { + errx( + 1, "got flags %#x (expected %#x)", + flags, LGTD_TIMER_PERSISTENT|LGTD_TIMER_ACTIVATE_NOW + ); + } + + if (ms != LGTD_LIFX_BULB_FETCH_HARDWARE_INFO_TIMER_MSECS) { + errx( + 1, "got timeout %d (expected %d)", + ms, LGTD_LIFX_BULB_FETCH_HARDWARE_INFO_TIMER_MSECS + ); + } + + if (cb != lgtd_lifx_bulb_fetch_hardware_info) { + errx( + 1, "got callback %p (expected %p)", + cb, lgtd_lifx_bulb_fetch_hardware_info + ); + } + + if (!ctx.as_uint) { + errx(1, "ctx wasn't set"); + } + + if (timer_start_call_count++) { + errx(1, "timer_start should have been called once"); + } + + return (void *)42; +} + int main(void) { @@ -44,5 +86,9 @@ main(void) errx(1, "bulbs counter is %d (expected 1)", LGTD_STATS_GET(bulbs)); } + if (!timer_start_call_count) { + errx(1, "timer_start_call_count = 0 (expected 1)"); + } + return 0; } diff --git a/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c b/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c new file mode 100644 index 0000000..e5530fa --- /dev/null +++ b/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c @@ -0,0 +1,54 @@ +#include + +#include "gateway.c" + +#include "mock_timer.h" +#include "test_gateway_utils.h" +#include "tests_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_info_map(); + + struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; + + struct lgtd_lifx_bulb *b = lgtd_tests_insert_mock_bulb(&gw, 42); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + memcpy(&hdr.target.device_addr, &b->addr, sizeof(hdr.target.device_addr)); + + struct lgtd_lifx_packet_ip_firmware_info pkt; + struct lgtd_lifx_bulb_ip *ip; + + memset(&pkt, 'A', sizeof(pkt)); + hdr.packet_type = LGTD_LIFX_MESH_FIRMWARE; + lgtd_lifx_gateway_handle_ip_firmware_info(&gw, &hdr, &pkt); + ip = &b->ips[LGTD_LIFX_BULB_MCU_IP]; + if (memcmp(&ip->fw_info, &pkt, sizeof(pkt))) { + errx(1, "The MCU ip firmware info wasn't set properly"); + } + if (ip->fw_info_updated_at != 42) { + errx( + 1, "state_updated_at %ju (expected 42)", + (uintmax_t)ip->fw_info_updated_at + ); + } + + memset(&pkt, 'B', sizeof(pkt)); + hdr.packet_type = LGTD_LIFX_WIFI_FIRMWARE_STATE; + lgtd_lifx_gateway_handle_ip_firmware_info(&gw, &hdr, &pkt); + ip = &b->ips[LGTD_LIFX_BULB_WIFI_IP]; + if (memcmp(&ip->fw_info, &pkt, sizeof(pkt))) { + errx(1, "The WIFI firmware info wasn't set properly"); + } + if (ip->fw_info_updated_at != 42) { + errx( + 1, "state_updated_at %ju (expected 42)", + (uintmax_t)ip->fw_info_updated_at + ); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_handle_ip_state.c b/tests/lifx/gateway/test_gateway_handle_ip_state.c new file mode 100644 index 0000000..e26fd17 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_handle_ip_state.c @@ -0,0 +1,54 @@ +#include + +#include "gateway.c" + +#include "mock_timer.h" +#include "test_gateway_utils.h" +#include "tests_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_info_map(); + + struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; + + struct lgtd_lifx_bulb *b = lgtd_tests_insert_mock_bulb(&gw, 42); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + memcpy(&hdr.target.device_addr, &b->addr, sizeof(hdr.target.device_addr)); + + struct lgtd_lifx_packet_ip_state pkt; + struct lgtd_lifx_bulb_ip *ip; + + memset(&pkt, 'A', sizeof(pkt)); + hdr.packet_type = LGTD_LIFX_MESH_INFO; + lgtd_lifx_gateway_handle_ip_state(&gw, &hdr, &pkt); + ip = &b->ips[LGTD_LIFX_BULB_MCU_IP]; + if (memcmp(&ip->state, &pkt, sizeof(pkt))) { + errx(1, "The MCU ip state wasn't set properly"); + } + if (ip->state_updated_at != 42) { + errx( + 1, "state_updated_at %ju (expected 42)", + (uintmax_t)ip->state_updated_at + ); + } + + memset(&pkt, 'B', sizeof(pkt)); + hdr.packet_type = LGTD_LIFX_WIFI_INFO; + lgtd_lifx_gateway_handle_ip_state(&gw, &hdr, &pkt); + ip = &b->ips[LGTD_LIFX_BULB_WIFI_IP]; + if (memcmp(&ip->state, &pkt, sizeof(pkt))) { + errx(1, "The WIFI ip state wasn't set properly"); + } + if (ip->state_updated_at != 42) { + errx( + 1, "state_updated_at %ju (expected 42)", + (uintmax_t)ip->state_updated_at + ); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_handle_product_info.c b/tests/lifx/gateway/test_gateway_handle_product_info.c new file mode 100644 index 0000000..a2e3bea --- /dev/null +++ b/tests/lifx/gateway/test_gateway_handle_product_info.c @@ -0,0 +1,43 @@ +#include + +#include "gateway.c" + +#include "mock_timer.h" +#include "test_gateway_utils.h" +#include "tests_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_info_map(); + + struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; + + struct lgtd_lifx_bulb *b = lgtd_tests_insert_mock_bulb(&gw, 42); + + struct lgtd_lifx_packet_header hdr = { + .packet_type = LGTD_LIFX_VERSION_STATE + }; + memcpy(&hdr.target.device_addr, &b->addr, sizeof(hdr.target.device_addr)); + struct lgtd_lifx_packet_product_info pkt = { + .vendor_id = 1, .product_id = 3, .version = 42 + }; + + lgtd_lifx_gateway_handle_product_info(&gw, &hdr, &pkt); + + if (memcmp(&b->product_info, &pkt, sizeof(pkt))) { + errx(1, "the product info weren't set correctly on the bulb"); + } + + const char *expected_model = "GU10 (Color 650)"; + if (strcmp(b->model, expected_model)) { + errx(1, "model %s (expected %s)", b->model, expected_model); + } + + const char *expected_vendor = "LIFX"; + if (strcmp(b->vendor, expected_vendor)) { + errx(1, "vendor %s (expected %s)", b->vendor, expected_vendor); + } + + return 0; +} diff --git a/tests/lifx/gateway/test_gateway_handle_runtime_info.c b/tests/lifx/gateway/test_gateway_handle_runtime_info.c new file mode 100644 index 0000000..a17680d --- /dev/null +++ b/tests/lifx/gateway/test_gateway_handle_runtime_info.c @@ -0,0 +1,40 @@ +#include + +#include "gateway.c" + +#include "mock_timer.h" +#include "test_gateway_utils.h" +#include "tests_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_info_map(); + + struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; + + struct lgtd_lifx_bulb *b = lgtd_tests_insert_mock_bulb(&gw, 42); + + struct lgtd_lifx_packet_header hdr = { + .packet_type = LGTD_LIFX_INFO_STATE + }; + memcpy(&hdr.target.device_addr, &b->addr, sizeof(hdr.target.device_addr)); + struct lgtd_lifx_packet_runtime_info pkt = { + .time = 1, .downtime = 2, .uptime = 3 + }; + + lgtd_lifx_gateway_handle_runtime_info(&gw, &hdr, &pkt); + + if (memcmp(&b->runtime_info, &pkt, sizeof(pkt))) { + errx(1, "the product info weren't set correctly on the bulb"); + } + + if (b->runtime_info_updated_at != gw.last_pkt_at) { + errx( + 1, "runtime_info_updated_at = %ju (expected 42)", + (uintmax_t)b->runtime_info_updated_at + ); + } + + return 0; +} diff --git a/tests/lifx/mock_gateway.h b/tests/lifx/mock_gateway.h index dab3601..7873d33 100644 --- a/tests/lifx/mock_gateway.h +++ b/tests/lifx/mock_gateway.h @@ -129,3 +129,51 @@ lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, (void)pkt_tags; } #endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_IP_STATE +void +lgtd_lifx_gateway_handle_ip_state(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_ip_state *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_IP_FIRMWARE_INFO +void +lgtd_lifx_gateway_handle_ip_firmware_info(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_ip_firmware_info *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_PRODUCT_INFO +void +lgtd_lifx_gateway_handle_product_info(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_product_info *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_RUNTIME_INFO +void +lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_runtime_info *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c index 5fc287f..062c213 100644 --- a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c +++ b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c @@ -2,7 +2,7 @@ #include "wire_proto.c" -#include "test_wire_proto_utils.h" +#include "mock_gateway.h" int main(void) diff --git a/tests/lifx/wire_proto/test_wire_proto_utils.h b/tests/lifx/wire_proto/test_wire_proto_utils.h deleted file mode 100644 index b79ebd4..0000000 --- a/tests/lifx/wire_proto/test_wire_proto_utils.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_pan_gateway *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} - -void lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_light_status *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} - -void lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_power_state *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} - -void lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_tag_labels *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} - -void lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, - const struct lgtd_lifx_packet_header *hdr, - const struct lgtd_lifx_packet_tags *pkt) -{ - (void)gw; - (void)hdr; - (void)pkt; -} diff --git a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c index 1e4f5e4..de65e52 100644 --- a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c +++ b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c @@ -1,6 +1,6 @@ #include "wire_proto.c" -#include "test_wire_proto_utils.h" +#include "mock_gateway.h" int main(void) From 598e223a9138e754a7f076d83e512bdc051177c5 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:17:09 -0700 Subject: [PATCH 083/181] Add all waveform types and the adjust_brightness command in lightsc.py --- examples/lightsc.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/examples/lightsc.py b/examples/lightsc.py index 4bca03a..ef03a3b 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -60,6 +60,38 @@ def sine(socket, target, h, s, b, k, period, cycles, peak=0.5, transient=True): ) +def half_sine(socket, target, h, s, b, k, period, cycles, transient=True): + return set_waveform( + socket, target, "HALF_SINE", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=0.5, + transient=transient + ) + + +def triangle(socket, target, h, s, b, k, + period, cycles, peak=0.5, transient=True): + return set_waveform( + socket, target, "TRIANGLE", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=peak, + transient=transient + ) + + +def pulse(socket, target, h, s, b, k, period, cycles, + duty_cycle=0.5, transient=True): + return set_waveform( + socket, target, "PULSE", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=duty_cycle, + transient=transient + ) + + def power_on(socket, target): return jsonrpc_call(socket, "power_on", {"target": target}) @@ -83,6 +115,16 @@ def tag(socket, target, tag): def untag(socket, target, tag): return jsonrpc_call(socket, "untag", [target, tag]) + +def adjust_brightness(socket, target, adjustment): + bulbs = get_light_state(socket, target)["result"] + for bulb in bulbs: + h, s, b, k = bulb["hsbk"] + b += adjustment + b = max(min(b, 1.0), 0.0) + set_light_from_hsbk(socket, bulb["label"], h, s, b, k, 500) + + if __name__ == "__main__": s = socket.create_connection(("localhost", 1234)) h = 0 From 52270051e13ddb5dac69b38028f53d1804bf118e Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:17:09 -0700 Subject: [PATCH 084/181] Rename PULSE to SQUARE and document set_waveform's skew_ratio argument I think SQUARE is the convention in this context. --- core/proto.c | 2 +- docs/protocol.rst | 19 ++++++++++++++++++- examples/lightsc.py | 6 +++--- lifx/wire_proto.c | 2 +- lifx/wire_proto.h | 2 +- .../test_wire_proto_waveform_table.c | 6 +++--- 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/core/proto.c b/core/proto.c index fe0c426..d54f364 100644 --- a/core/proto.c +++ b/core/proto.c @@ -156,7 +156,7 @@ lgtd_proto_set_waveform(struct lgtd_client *client, assert(saturation >= 0 && saturation <= UINT16_MAX); assert(brightness >= 0 && brightness <= UINT16_MAX); assert(kelvin >= 2500 && kelvin <= 9000); - assert(waveform <= LGTD_LIFX_WAVEFORM_PULSE); + assert(waveform <= LGTD_LIFX_WAVEFORM_SQUARE); assert(skew_ratio >= -32767 && skew_ratio <= 32768); assert(period >= 0); assert(cycles >= 0); diff --git a/docs/protocol.rst b/docs/protocol.rst index 8db7e29..edcbdc7 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -52,7 +52,7 @@ Available methods .. function:: set_waveform(target, waveform, h, s, b, k, period, cycles, skew_ratio, transient) :param string waveform: One of ``SAW``, ``SINE``, ``HALF_SINE``, - ``TRIANGLE``, ``PULSE``. + ``TRIANGLE``, ``SQUARE``. :param float h: Hue from 0 to 360. :param float s: Saturation from 0 to 1. :param float b: Brightness from 0 to 1. @@ -64,6 +64,23 @@ Available methods end of the waveform, otherwise it will revert back to its original state. + The meaning of the ``skew_ratio`` argument depends on the type of waveform: + + +---------------+-----------------------------------------------------------+ + | ``SAW`` | Should be 0.5. | + +---------------+-----------------------------------------------------------+ + | ``SINE`` | Defines the peak point of the function, 0.5 gives you a | + | | sine and 1 or 0 will give you cosine. Ignored by firmware | + | | 1.1. | + +---------------+-----------------------------------------------------------+ + | ``HALF_SINE`` | Should be 0.5. | + +---------------+-----------------------------------------------------------+ + | ``TRIANGLE`` | Defines the peak point of the function like ``SINE``. | + | | Ignored by firmware 1.1. | + +---------------+-----------------------------------------------------------+ + | ``SQUARE`` | Ratio of a cycle the targets are set to the given color. | + +---------------+-----------------------------------------------------------+ + .. function:: get_light_state(target) Return a list of dictionnaries, each dict representing the state of one diff --git a/examples/lightsc.py b/examples/lightsc.py index ef03a3b..227155e 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -81,10 +81,10 @@ def triangle(socket, target, h, s, b, k, ) -def pulse(socket, target, h, s, b, k, period, cycles, - duty_cycle=0.5, transient=True): +def square(socket, target, h, s, b, k, period, cycles, + duty_cycle=0.5, transient=True): return set_waveform( - socket, target, "PULSE", h, s, b, k, + socket, target, "SQUARE", h, s, b, k, cycles=cycles, period=period, skew_ratio=duty_cycle, diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index d864e86..cb93ae8 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -444,7 +444,7 @@ const struct lgtd_lifx_waveform_string_id lgtd_lifx_waveform_table[] = { WAVEFORM_ENTRY("SINE"), WAVEFORM_ENTRY("HALF_SINE"), WAVEFORM_ENTRY("TRIANGLE"), - WAVEFORM_ENTRY("PULSE"), + WAVEFORM_ENTRY("SQUARE"), WAVEFORM_ENTRY("INVALID") }; diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 8dee0d3..6b0f247 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -223,7 +223,7 @@ enum lgtd_lifx_waveform_type { LGTD_LIFX_WAVEFORM_SINE = 1, LGTD_LIFX_WAVEFORM_HALF_SINE = 2, LGTD_LIFX_WAVEFORM_TRIANGLE = 3, - LGTD_LIFX_WAVEFORM_PULSE = 4, + LGTD_LIFX_WAVEFORM_SQUARE = 4, LGTD_LIFX_WAVEFORM_INVALID = 5, }; diff --git a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c index de65e52..41f0ba5 100644 --- a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c +++ b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c @@ -27,9 +27,9 @@ main(void) errx(1, "Expected WAVEFORM_TRIANGLE"); } - rv = lgtd_lifx_wire_waveform_string_id_to_type("PULSE", 5); - if (rv != LGTD_LIFX_WAVEFORM_PULSE) { - errx(1, "Expected WAVEFORM_PULSE"); + rv = lgtd_lifx_wire_waveform_string_id_to_type("SQUARE", 6); + if (rv != LGTD_LIFX_WAVEFORM_SQUARE) { + errx(1, "Expected WAVEFORM_SQUARE"); } rv = lgtd_lifx_wire_waveform_string_id_to_type("TEST", 4); From 27da3226a0e56b53256d8300cdcd2024228210ee Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:22:57 -0700 Subject: [PATCH 085/181] Feed the clang compilation database to YouCompleteMe Works with my setup and it should also work if your build directory is at the root of the repo. It's still doesn't cover headers but that's a nice improvement. --- .ycm_extra_conf.py | 7 +++---- CMakeLists.txt | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index 7ae1491..d9077a3 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -67,10 +67,9 @@ # Set this to the absolute path to the folder (NOT the file!) containing the # compile_commands.json file to use that instead of 'flags'. See here for # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html -# -# Most projects will NOT need to set this to anything; you can just change the -# 'flags' list of compilation flags. -compilation_database_folder = '' +compilation_database_folder = os.getenv( + "MY_BUILD", os.path.join(os.getcwd(), "build") +) if os.path.exists( compilation_database_folder ): database = ycm_core.CompilationDatabase( compilation_database_folder ) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b1f1a8..eae60e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,8 @@ TEST_BIG_ENDIAN(BIG_ENDIAN_SYSTEM) INCLUDE(AddAllSubdirectories) INCLUDE(AddTestFromSources) +SET(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}} -pipe") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Wstrict-prototypes -std=c99") SET(CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE} "") From 1254ed54dab41c40f6196bbfd267516374d08970 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:23:02 -0700 Subject: [PATCH 086/181] Add the set_label command --- README.rst | 3 +- core/jsonrpc.c | 47 ++++++---- core/lightsd.h | 1 + core/proto.c | 21 +++++ core/proto.h | 1 + docs/protocol.rst | 26 ++++-- examples/lightsc.py | 4 + lifx/bulb.c | 9 ++ lifx/bulb.h | 2 + lifx/gateway.c | 20 +++++ lifx/gateway.h | 3 + lifx/wire_proto.c | 25 +++--- lifx/wire_proto.h | 4 + .../test_jsonrpc_check_and_call_set_label.c | 65 ++++++++++++++ .../jsonrpc/test_jsonrpc_check_and_call_tag.c | 4 +- ...sonrpc_check_and_call_tag_missing_params.c | 2 +- .../test_jsonrpc_check_and_call_untag.c | 2 +- ...nrpc_check_and_call_untag_invalid_params.c | 2 +- tests/core/jsonrpc/test_jsonrpc_utils.h | 12 +++ tests/core/proto/test_proto_set_label.c | 85 ++++++++++++++++++ .../proto/test_proto_set_label_too_long.c | 87 +++++++++++++++++++ tests/core/tests_utils.c | 3 - tests/lifx/gateway/CMakeLists.txt | 1 + .../gateway/test_gateway_handle_bulb_label.c | 34 ++++++++ tests/lifx/mock_gateway.h | 16 +++- 25 files changed, 437 insertions(+), 42 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c create mode 100644 tests/core/proto/test_proto_set_label.c create mode 100644 tests/core/proto/test_proto_set_label_too_long.c create mode 100644 tests/lifx/gateway/test_gateway_handle_bulb_label.c diff --git a/README.rst b/README.rst index 7206fae..c43014b 100644 --- a/README.rst +++ b/README.rst @@ -112,7 +112,8 @@ bugged: after a tagging operation the LIFX White 800 keep saying it has no tags. Power ON/OFF are the only commands with auto-retry, i.e: lightsd will keep sending the command to the bulb until its state changes. This is not implemented -(yet) for ``set_light_from_hsbk``, ``set_waveform``, ``tag`` and ``untag``. +(yet) for ``set_light_from_hsbk``, ``set_waveform``, ``set_label``, ``tag`` and +``untag``. In general, crappy WiFi network with latency, jitter or packet loss are gonna be challenging until lightsd has an auto-retry mechanism, there is also room for diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 6bc6618..38c726e 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -958,16 +958,17 @@ CHECK_AND_CALL_TARGETS_ONLY_METHOD(power_toggle); CHECK_AND_CALL_TARGETS_ONLY_METHOD(get_light_state); static void -lgtd_jsonrpc_check_and_call_proto_tag_or_untag(struct lgtd_client *client, - void (*lgtd_proto_fn)(struct lgtd_client *, - const struct lgtd_proto_target_list *, - const char *)) +lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label( + struct lgtd_client *client, + void (*lgtd_proto_fn)(struct lgtd_client *, + const struct lgtd_proto_target_list *, + const char *)) { struct lgtd_jsonrpc_target_args { const jsmntok_t *target; int target_ntokens; - const jsmntok_t *tag; + const jsmntok_t *label; } params = { NULL, 0, NULL }; static const struct lgtd_jsonrpc_node schema[] = { LGTD_JSONRPC_NODE( @@ -978,8 +979,8 @@ lgtd_jsonrpc_check_and_call_proto_tag_or_untag(struct lgtd_client *client, false ), LGTD_JSONRPC_NODE( - "tag", - offsetof(struct lgtd_jsonrpc_target_args, tag), + "label", + offsetof(struct lgtd_jsonrpc_target_args, label), -1, lgtd_jsonrpc_type_string, false @@ -1010,20 +1011,20 @@ lgtd_jsonrpc_check_and_call_proto_tag_or_untag(struct lgtd_client *client, return; } - char *tag = strndup( - &client->json[params.tag->start], LGTD_JSONRPC_TOKEN_LEN(params.tag) + char *label = strndup( + &client->json[params.label->start], LGTD_JSONRPC_TOKEN_LEN(params.label) ); - if (!tag) { - lgtd_warn("can't allocate a tag"); + if (!label) { + lgtd_warn("can't allocate a label"); lgtd_jsonrpc_send_error( client, LGTD_JSONRPC_INTERNAL_ERROR, "Can't allocate memory" ); goto error_strdup; } - lgtd_proto_fn(client, &targets, tag); + lgtd_proto_fn(client, &targets, label); - free(tag); + free(label); error_strdup: lgtd_proto_target_list_clear(&targets); @@ -1032,13 +1033,25 @@ lgtd_jsonrpc_check_and_call_proto_tag_or_untag(struct lgtd_client *client, static void lgtd_jsonrpc_check_and_call_tag(struct lgtd_client *client) { - return lgtd_jsonrpc_check_and_call_proto_tag_or_untag(client, lgtd_proto_tag); + return lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label( + client, lgtd_proto_tag + ); } static void lgtd_jsonrpc_check_and_call_untag(struct lgtd_client *client) { - return lgtd_jsonrpc_check_and_call_proto_tag_or_untag(client, lgtd_proto_untag); + return lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label( + client, lgtd_proto_untag + ); +} + +static void +lgtd_jsonrpc_check_and_call_set_label(struct lgtd_client *client) +{ + return lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label( + client, lgtd_proto_set_label + ); } void @@ -1077,6 +1090,10 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) LGTD_JSONRPC_METHOD( "untag", 2, // t, tag lgtd_jsonrpc_check_and_call_untag + ), + LGTD_JSONRPC_METHOD( + "set_label", 2, // t, label + lgtd_jsonrpc_check_and_call_set_label ) }; diff --git a/core/lightsd.h b/core/lightsd.h index 3c87fce..7ef97c0 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -23,6 +23,7 @@ #define LGTD_ABS(v) ((v) >= 0 ? (v) : (v) * -1) #define LGTD_MIN(a, b) ((a) < (b) ? (a) : (b)) +#define LGTD_MAX(a, b) ((a) > (b) ? (a) : (b)) #define LGTD_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #define LGTD_MSECS_TO_TIMEVAL(v) { \ .tv_sec = (v) / 1000, \ diff --git a/core/proto.c b/core/proto.c index d54f364..3753160 100644 --- a/core/proto.c +++ b/core/proto.c @@ -512,3 +512,24 @@ lgtd_proto_untag(struct lgtd_client *client, lgtd_router_device_list_free(devices); } + +void +lgtd_proto_set_label(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *label) +{ + assert(client); + assert(targets); + assert(label); + + int label_len = strlen(label); + struct lgtd_lifx_packet_label pkt; + memcpy(pkt.label, label, LGTD_MIN(label_len, (int)sizeof(pkt.label))); + // this will go out on the network don't leave garbage in it: + memset(&pkt.label[label_len], 0, LGTD_MAX( + (int)sizeof(pkt.label) - label_len, 0 + )); + SEND_RESULT( + client, lgtd_router_send(targets, LGTD_LIFX_SET_BULB_LABEL, &pkt) + ); +} diff --git a/core/proto.h b/core/proto.h index 7b4e101..585c3d1 100644 --- a/core/proto.h +++ b/core/proto.h @@ -42,3 +42,4 @@ void lgtd_proto_power_toggle(struct lgtd_client *, const struct lgtd_proto_targe void lgtd_proto_get_light_state(struct lgtd_client *, const struct lgtd_proto_target_list *); void lgtd_proto_tag(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *); void lgtd_proto_untag(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *); +void lgtd_proto_set_label(struct lgtd_client *, const struct lgtd_proto_target_list *, const char *); diff --git a/docs/protocol.rst b/docs/protocol.rst index edcbdc7..b8ff97c 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -26,6 +26,10 @@ bulb(s) the operation should apply: A target is either a string, a hexadecimal number (without any prefix like 0x) or an array of targets. +.. note:: + + The maximum supported length for labels and tag names by LIFX bulbs is 32. + Available methods ----------------- @@ -83,14 +87,22 @@ Available methods .. function:: get_light_state(target) - Return a list of dictionnaries, each dict representing the state of one - targeted bulb, the list is not in any specific order. Each dict has the - following fields: + Return a list of dictionnaries, each dict representing the state of one + targeted bulb, the list is not in any specific order. Each dict has the + following fields: + + - hsbk: tuple (h, s, b, k) see function:`set_light_from_hsbk`; + - label: bulb label (utf-8 encoded string); + - power: boolean, true when the bulb is powered on false otherwise; + - tags: list of tags applied to the bulb (utf-8 encoded strings). + +.. function:: set_label(target, label) + + Label the target bulb(s) with the given label. + + .. note:: - - hsbk: tuple (h, s, b, k) see function:`set_light_from_hsbk`; - - label: bulb label (utf-8 encoded string); - - power: boolean, true when the bulb is powered on false otherwise; - - tags: list of tags applied to the bulb (utf-8 encoded strings). + Use :func:`tag` instead set_label to give a common name to multiple bulbs. .. function:: tag(target, label) diff --git a/examples/lightsc.py b/examples/lightsc.py index 227155e..a879bfa 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -116,6 +116,10 @@ def untag(socket, target, tag): return jsonrpc_call(socket, "untag", [target, tag]) +def set_label(socket, target, label): + return jsonrpc_call(socket, "set_label", [target, label]) + + def adjust_brightness(socket, target, adjustment): bulbs = get_light_state(socket, target)["result"] for bulb in bulbs: diff --git a/lifx/bulb.c b/lifx/bulb.c index e8200a4..0a645c7 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -306,3 +306,12 @@ lgtd_lifx_bulb_set_runtime_info(struct lgtd_lifx_bulb *bulb, bulb->runtime_info_updated_at = received_at; memcpy(&bulb->runtime_info, info, sizeof(bulb->runtime_info)); } + +void +lgtd_lifx_bulb_set_label(struct lgtd_lifx_bulb *bulb, + const char label[LGTD_LIFX_LABEL_SIZE]) +{ + assert(bulb); + + memcpy(bulb->state.label, label, LGTD_LIFX_LABEL_SIZE); +} diff --git a/lifx/bulb.h b/lifx/bulb.h index 19ff5e3..31152d9 100644 --- a/lifx/bulb.h +++ b/lifx/bulb.h @@ -138,3 +138,5 @@ void lgtd_lifx_bulb_set_product_info(struct lgtd_lifx_bulb *, void lgtd_lifx_bulb_set_runtime_info(struct lgtd_lifx_bulb *, const struct lgtd_lifx_runtime_info *, lgtd_time_mono_t); +void lgtd_lifx_bulb_set_label(struct lgtd_lifx_bulb *, + const char [LGTD_LIFX_LABEL_SIZE]); diff --git a/lifx/gateway.c b/lifx/gateway.c index 2099832..5a0cf07 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -847,3 +847,23 @@ lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *gw, (const struct lgtd_lifx_runtime_info *)pkt, gw->last_pkt_at ); } + +void +lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_label *pkt) +{ + assert(gw && hdr && pkt); + + char addr[LGTD_LIFX_ADDR_STRLEN]; + lgtd_info( + "BULB_LABEL <-- [%s]:%hu - %s label=%.*s", + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + (int)sizeof(pkt->label), pkt->label + ); + + LGTD_LIFX_GATEWAY_SET_BULB_ATTR( + gw, hdr->target.device_addr, lgtd_lifx_bulb_set_label, pkt->label + ); +} diff --git a/lifx/gateway.h b/lifx/gateway.h index f32edd4..1db3d5c 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -145,3 +145,6 @@ void lgtd_lifx_gateway_handle_product_info(struct lgtd_lifx_gateway *, void lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, const struct lgtd_lifx_packet_runtime_info *); +void lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_label *); diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index cb93ae8..02d91f6 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -298,6 +298,21 @@ lgtd_lifx_wire_load_packet_info_map(void) .decode = DECODER(lgtd_lifx_wire_decode_runtime_info), .handle = HANDLER(lgtd_lifx_gateway_handle_runtime_info) }, + { + REQUEST_ONLY, + .encode = lgtd_lifx_wire_null_packet_encoder_decoder, + .name = "SET_BULB_LABEL", + .type = LGTD_LIFX_SET_BULB_LABEL, + .size = sizeof(struct lgtd_lifx_packet_label) + }, + { + RESPONSE_ONLY, + .name = "BULB_LABEL", + .type = LGTD_LIFX_BULB_LABEL, + .size = sizeof(struct lgtd_lifx_packet_label), + .decode = lgtd_lifx_wire_null_packet_encoder_decoder, + .handle = HANDLER(lgtd_lifx_gateway_handle_bulb_label) + }, // Unimplemented but "known" packets { UNIMPLEMENTED, @@ -329,16 +344,6 @@ lgtd_lifx_wire_load_packet_info_map(void) .name = "GET_BULB_LABEL", .type = LGTD_LIFX_GET_BULB_LABEL }, - { - UNIMPLEMENTED, - .name = "SET_BULB_LABEL", - .type = LGTD_LIFX_SET_BULB_LABEL - }, - { - UNIMPLEMENTED, - .name = "BULB_LABEL", - .type = LGTD_LIFX_BULB_LABEL - }, { UNIMPLEMENTED, .name = "GET_MCU_RAIL_VOLTAGE", diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 6b0f247..3989547 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -245,6 +245,10 @@ struct lgtd_lifx_packet_tags { uint64le_t tags; }; +struct lgtd_lifx_packet_label { + char label[LGTD_LIFX_LABEL_SIZE]; +}; + struct lgtd_lifx_packet_tag_labels { uint64le_t tags; char label[LGTD_LIFX_LABEL_SIZE]; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c new file mode 100644 index 0000000..04e690a --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c @@ -0,0 +1,65 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#include "mock_gateway.h" + +#define MOCKED_LGTD_PROTO_SET_LABEL +#include "test_jsonrpc_utils.h" + +static bool tag_called = false; + +void +lgtd_proto_set_label(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *label) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (strcmp(label, "candle")) { + errx(1, "Invalid label [%s] (expected=[candle])", label); + } + + tag_called = true; +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"set_label\"," + "\"params\": {\"target\": \"*\", \"label\": \"candle\"}," + "\"id\": \"42\"" + "}"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + bool ok; + struct lgtd_jsonrpc_request req = TEST_REQUEST_INITIALIZER; + struct lgtd_client client = { + .io = NULL, .current_request = &req, .json = json + }; + ok = lgtd_jsonrpc_check_and_extract_request(&req, tokens, parsed, json); + if (!ok) { + errx(1, "can't parse request"); + } + + lgtd_jsonrpc_check_and_call_set_label(&client); + + if (!tag_called) { + errx(1, "lgtd_proto_label wasn't called"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c index 10cc9c9..e140de9 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c @@ -38,7 +38,7 @@ main(void) const char json[] = ("{" "\"jsonrpc\": \"2.0\"," "\"method\": \"tag\"," - "\"params\": {\"target\": \"*\", \"tag\": \"suspensions\"}," + "\"params\": {\"target\": \"*\", \"label\": \"suspensions\"}," "\"id\": \"42\"" "}"); int parsed = parse_json( @@ -55,7 +55,7 @@ main(void) errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_proto_tag_or_untag(&client, lgtd_proto_tag); + lgtd_jsonrpc_check_and_call_tag(&client); if (!tag_called) { errx(1, "lgtd_proto_tag wasn't called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c index 138a8ad..48341a1 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c @@ -43,7 +43,7 @@ main(void) errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_proto_tag_or_untag(&client, lgtd_proto_tag); + lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label(&client, lgtd_proto_tag); if (tag_called) { errx(1, "lgtd_proto_tag was called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c index eb078bf..36549b5 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c @@ -55,7 +55,7 @@ main(void) errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_proto_tag_or_untag(&client, lgtd_proto_untag); + lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label(&client, lgtd_proto_untag); if (!untag_called) { errx(1, "lgtd_proto_tag wasn't called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c index 153e876..af2b0eb 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c @@ -43,7 +43,7 @@ main(void) errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_proto_tag_or_untag(&client, lgtd_proto_untag); + lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label(&client, lgtd_proto_untag); if (untag_called) { errx(1, "lgtd_proto_tag was called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index 5599f3d..7368218 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -131,3 +131,15 @@ lgtd_proto_power_toggle(struct lgtd_client *client, (void)targets; } #endif + +#ifndef MOCKED_LGTD_PROTO_SET_LABEL +void +lgtd_proto_set_label(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *label) +{ + (void)client; + (void)targets; + (void)label; +} +#endif diff --git a/tests/core/proto/test_proto_set_label.c b/tests/core/proto/test_proto_set_label.c new file mode 100644 index 0000000..bee22dd --- /dev/null +++ b/tests/core/proto/test_proto_set_label.c @@ -0,0 +1,85 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" +#include "tests_utils.h" + +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + +static int router_send_call_count = 0; + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_BULB_LABEL) { + errx(1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_BULB_LABEL + ); + } + + struct lgtd_lifx_packet_label *set_label = pkt; + if (strcmp(set_label->label, "test")) { + errx( + 1, "invalid packet label %.*s (expected test)", + LGTD_LIFX_LABEL_SIZE, set_label->label + ); + } + + if (router_send_call_count++) { + errx(1, "lgtd_router_send should be called once"); + } + + return true; +} + +static int client_send_response_call_count = 0; + +void +lgtd_client_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't be NULL"); + } + + if (strcmp(msg, "true")) { + errx(1, "unexpected response [%s] (expected [true])", msg); + } + + if (client_send_response_call_count++) { + errx(1, "lgtd_client_send_response should be called once"); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + lgtd_proto_set_label(&client, targets, "test"); + + if (!router_send_call_count) { + errx(1, "lgtd_router_send wasn't called"); + } + + if (!client_send_response_call_count) { + errx(1, "lgtd_client_send_response wasn't called"); + } + + return 0; +} diff --git a/tests/core/proto/test_proto_set_label_too_long.c b/tests/core/proto/test_proto_set_label_too_long.c new file mode 100644 index 0000000..10bc2ab --- /dev/null +++ b/tests/core/proto/test_proto_set_label_too_long.c @@ -0,0 +1,87 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_event2.h" +#include "mock_timer.h" +#include "tests_utils.h" + +#define MOCKED_CLIENT_SEND_RESPONSE +#define MOCKED_ROUTER_SEND +#include "tests_proto_utils.h" + +static const char *test_label = "test of a very long label over 32 chars"; + +static int router_send_call_count = 0; + +bool +lgtd_router_send(const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_packet_type pkt_type, + void *pkt) +{ + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (pkt_type != LGTD_LIFX_SET_BULB_LABEL) { + errx(1, "invalid packet type %d (expected %d)", + pkt_type, LGTD_LIFX_SET_BULB_LABEL + ); + } + + struct lgtd_lifx_packet_label *set_label = pkt; + if (memcmp(set_label->label, test_label, sizeof(set_label->label))) { + errx( + 1, "invalid packet label %2$.*1$s (expected %3$.*1$s)", + LGTD_LIFX_LABEL_SIZE, set_label->label, test_label + ); + } + + if (router_send_call_count++) { + errx(1, "lgtd_router_send should be called once"); + } + + return true; +} + +static int client_send_response_call_count = 0; + +void +lgtd_client_send_response(struct lgtd_client *client, const char *msg) +{ + if (!client) { + errx(1, "client shouldn't be NULL"); + } + + if (strcmp(msg, "true")) { + errx(1, "unexpected response [%s] (expected [true])", msg); + } + + if (client_send_response_call_count++) { + errx(1, "lgtd_client_send_response should be called once"); + } +} + +int +main(void) +{ + struct lgtd_proto_target_list *targets; + targets = lgtd_tests_build_target_list("*", NULL); + + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + lgtd_proto_set_label(&client, targets, test_label); + + if (!router_send_call_count) { + errx(1, "lgtd_router_send wasn't called"); + } + + if (!client_send_response_call_count) { + errx(1, "lgtd_client_send_response wasn't called"); + } + + return 0; +} diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index 9e49bf9..4d5836d 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -31,9 +31,6 @@ #include "lifx/gateway.h" #include "tests_utils.h" -struct lgtd_lifx_gateway_list lgtd_lifx_gateways = - LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); - struct lgtd_listen_list lgtd_listeners = SLIST_HEAD_INITIALIZER(&lgtd_listeners); diff --git a/tests/lifx/gateway/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt index 7d14429..eb7f4c1 100644 --- a/tests/lifx/gateway/CMakeLists.txt +++ b/tests/lifx/gateway/CMakeLists.txt @@ -10,6 +10,7 @@ ADD_CORE_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/router.c ${LIGHTSD_SOURCE_DIR}/core/stats.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../core/tests_utils.c ) ADD_LIBRARY( diff --git a/tests/lifx/gateway/test_gateway_handle_bulb_label.c b/tests/lifx/gateway/test_gateway_handle_bulb_label.c new file mode 100644 index 0000000..8e970d0 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_handle_bulb_label.c @@ -0,0 +1,34 @@ +#include + +#include "gateway.c" + +#include "mock_timer.h" +#include "test_gateway_utils.h" +#include "tests_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_info_map(); + + struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; + + struct lgtd_lifx_bulb *b = lgtd_tests_insert_mock_bulb(&gw, 42); + + struct lgtd_lifx_packet_header hdr = { + .packet_type = LGTD_LIFX_BULB_LABEL + }; + memcpy(&hdr.target.device_addr, &b->addr, sizeof(hdr.target.device_addr)); + struct lgtd_lifx_packet_label pkt = { .label = "TEST LABEL" }; + + lgtd_lifx_gateway_handle_bulb_label(&gw, &hdr, &pkt); + + if (memcmp(&b->state.label, &pkt.label, LGTD_LIFX_LABEL_SIZE)) { + errx( + 1, "got label %2$.*1$s, (expected %3$.*1$s)", + LGTD_LIFX_LABEL_SIZE, b->state.label, pkt.label + ); + } + + return 0; +} diff --git a/tests/lifx/mock_gateway.h b/tests/lifx/mock_gateway.h index 7873d33..923f82a 100644 --- a/tests/lifx/mock_gateway.h +++ b/tests/lifx/mock_gateway.h @@ -5,7 +5,9 @@ #include "lifx/gateway.h" struct lgtd_lifx_tag; -struct lgtd_lifx_gateway; + +struct lgtd_lifx_gateway_list lgtd_lifx_gateways = + LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); #ifndef MOCKED_LIFX_GATEWAY_SEND_TO_SITE bool @@ -177,3 +179,15 @@ lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *gw, (void)pkt; } #endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_BULB_LABEL +void +lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_label *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif From 096acff2e43f0a63b7cd8e2a2d7b3b828c7367aa Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:23:02 -0700 Subject: [PATCH 087/181] Properly handle 32 chars long LIFX labels in get_light_state They aren't nul terminated. --- core/proto.c | 14 +- ...est_proto_get_light_state_label_overflow.c | 121 ++++++++++++++++++ 2 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 tests/core/proto/test_proto_get_light_state_label_overflow.c diff --git a/core/proto.c b/core/proto.c index 3753160..c37072d 100644 --- a/core/proto.c +++ b/core/proto.c @@ -323,15 +323,25 @@ lgtd_proto_get_light_state(struct lgtd_client *client, PRINT_COMPONENT(bulb->state.saturation, s, 0, 1); PRINT_COMPONENT(bulb->state.brightness, b, 0, 1); + const char *label; + int label_size; + if (bulb->state.label[0]) { + label = bulb->state.label; + label_size = (int)sizeof(bulb->state.label); + } else { + label = bulb_addr; + label_size = (int)sizeof(bulb_addr); + } + LGTD_SNPRINTF_APPEND( buf, i, (int)sizeof(buf), ",\"hsbk\":[%s,%s,%s,%hu]," "\"power\":%s," - "\"label\":\"%s\"," + "\"label\":\"%.*s\"," "\"tags\":[", h, s, b, bulb->state.kelvin, bulb->state.power == LGTD_LIFX_POWER_ON ? "true" : "false", - bulb->state.label[0] ? bulb->state.label : bulb_addr + label_size, label ); if (i >= (int)sizeof(buf)) { diff --git a/tests/core/proto/test_proto_get_light_state_label_overflow.c b/tests/core/proto/test_proto_get_light_state_label_overflow.c new file mode 100644 index 0000000..7548c45 --- /dev/null +++ b/tests/core/proto/test_proto_get_light_state_label_overflow.c @@ -0,0 +1,121 @@ +#include "proto.c" + +#include "mock_client_buf.h" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "mock_event2.h" +#include "mock_timer.h" +#include "tests_utils.h" + +#define MOCKED_ROUTER_TARGETS_TO_DEVICES +#define MOCKED_ROUTER_DEVICE_LIST_FREE +#include "tests_proto_utils.h" + +static bool device_list_free_called = false; + +void +lgtd_router_device_list_free(struct lgtd_router_device_list *devices) +{ + if (!devices) { + lgtd_errx(1, "the device list must be passed"); + } + + device_list_free_called = true; +} + +struct lgtd_router_device_list * +lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) +{ + if (targets != (void *)0x2a) { + lgtd_errx(1, "unexpected targets list"); + } + + static struct lgtd_router_device_list devices = + SLIST_HEAD_INITIALIZER(&devices); + if (SLIST_FIRST(&devices)) { + return &devices; + } + + static struct lgtd_lifx_gateway gw_bulb_1 = { + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + }; + static struct lgtd_lifx_bulb bulb_1 = { + .addr = { 1, 2, 3, 4, 5 }, + .state = { + .hue = 0xaaaa, + .saturation = 0xffff, + .brightness = 0xbbbb, + .kelvin = 3600, + .power = LGTD_LIFX_POWER_ON, + .tags = 0 + }, + .ips[LGTD_LIFX_BULB_WIFI_IP] = { + .fw_info.version = 0x10001 + }, + .model = "testbulb", + .product_info = { + .vendor_id = 1, + .product_id = 0xa, + .version = 9 + }, + .gw = &gw_bulb_1 + }; + memset(&bulb_1.state.label, 'a', sizeof(bulb_1.state.label)); + static struct lgtd_router_device device_1 = { .device = &bulb_1 }; + SLIST_INSERT_HEAD(&devices, &device_1, link); + + return &devices; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_proto_target_list *targets = (void *)0x2a; + + lgtd_opts.verbosity = LGTD_INFO; + + char expected_info[] = ("[" + "{" + "\"_lifx\":{" + "\"addr\":\"01:02:03:04:05:00\"," + "\"gateway\":{" + "\"site\":\"00:00:00:00:00:00\"," + "\"url\":\"tcp://[]:0\"," + "\"latency\":0" + "}," + "\"mcu\":{\"firmware_version\":\"0.0\"}," + "\"wifi\":{\"firmware_version\":\"1.1\"}" + "}," + "\"_model\":\"testbulb\"," + "\"_vendor\":null," + "\"hsbk\":[240,1,0.733333,3600]," + "\"power\":true," + "\"label\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"," + "\"tags\":[]" + "}" + "]"); + + reset_client_write_buf(); + + lgtd_proto_get_light_state(&client, targets); + + if (client_write_buf_idx != sizeof(expected_info) - 1) { + lgtd_errx( + 1, + "%d bytes written, expected_info %lu " + "(got %.*s instead of %s)", + client_write_buf_idx, sizeof(expected_info) - 1UL, + client_write_buf_idx, client_write_buf, expected_info + ); + } + + if (memcmp(expected_info, client_write_buf, sizeof(expected_info) - 1)) { + lgtd_errx( + 1, "got %.*s instead of %s", + client_write_buf_idx, client_write_buf, expected_info + ); + } + + return 0; +} From 8506649670fbce9251231535d5655102fa9bdae2 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:23:02 -0700 Subject: [PATCH 088/181] Do not stop at the first match when targeting a label People should use tags, but that seems like the correct behavior to have. --- core/router.c | 28 ++++---- lifx/bulb.c | 15 +++++ lifx/bulb.h | 3 + tests/core/router/test_router_send_to_label.c | 66 ++++++++++--------- .../router/test_router_targets_to_devices.c | 15 +++++ tests/lifx/bulb/test_bulb_has_label.c | 47 +++++++++++++ 6 files changed, 133 insertions(+), 41 deletions(-) create mode 100644 tests/lifx/bulb/test_bulb_has_label.c diff --git a/core/router.c b/core/router.c index 27e888c..66d57d0 100644 --- a/core/router.c +++ b/core/router.c @@ -168,7 +168,7 @@ lgtd_router_send_to_label(const char *label, struct lgtd_lifx_bulb *bulb; RB_FOREACH(bulb, lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table) { - if (!strcmp(bulb->state.label, label)) { + if (lgtd_lifx_bulb_has_label(bulb, label)) { struct lgtd_lifx_packet_header hdr; union lgtd_lifx_target target = { .addr = bulb->addr }; pkt_info = lgtd_lifx_wire_setup_header( @@ -341,19 +341,25 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) struct lgtd_lifx_bulb *bulb = NULL; if (isxdigit(target->target[0])) { bulb = lgtd_router_device_addr_to_device(target->target); - } - if (!bulb) { - RB_FOREACH(bulb, lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table) { - if (!strcmp(bulb->state.label, target->target)) { - break; + if (bulb) { + bool ok = lgtd_router_insert_device_if_not_in_list( + devices, bulb + ); + if (!ok) { + goto device_alloc_error; } + continue; } } - if (!bulb) { - continue; - } - if (!lgtd_router_insert_device_if_not_in_list(devices, bulb)) { - goto device_alloc_error; + RB_FOREACH(bulb, lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table) { + if (lgtd_lifx_bulb_has_label(bulb, target->target)) { + bool ok = lgtd_router_insert_device_if_not_in_list( + devices, bulb + ); + if (!ok) { + goto device_alloc_error; + } + } } } } diff --git a/lifx/bulb.c b/lifx/bulb.c index 0a645c7..a6dc493 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -207,6 +207,21 @@ lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *bulb) free(bulb); } +bool +lgtd_lifx_bulb_has_label(const struct lgtd_lifx_bulb *bulb, + const char *label) +{ + assert(bulb); + assert(label); + + const char *bulb_label = &bulb->state.label[0]; + const char *endp = memchr(bulb_label, 0, LGTD_LIFX_LABEL_SIZE); + int bulb_label_len = endp ? endp - bulb_label : LGTD_LIFX_LABEL_SIZE; + // clipping the label at 32 chars seems like the desired default behavior: + int label_len = LGTD_MIN(strlen(label), LGTD_LIFX_LABEL_SIZE); + return label_len == bulb_label_len && !memcmp(bulb_label, label, label_len); +} + void lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *bulb, const struct lgtd_lifx_light_state *state, diff --git a/lifx/bulb.h b/lifx/bulb.h index 31152d9..e357ecb 100644 --- a/lifx/bulb.h +++ b/lifx/bulb.h @@ -119,6 +119,9 @@ struct lgtd_lifx_bulb *lgtd_lifx_bulb_get(const uint8_t *); struct lgtd_lifx_bulb *lgtd_lifx_bulb_open(struct lgtd_lifx_gateway *, const uint8_t *); void lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *); +bool lgtd_lifx_bulb_has_label(const struct lgtd_lifx_bulb *, + const char *); + void lgtd_lifx_bulb_set_light_state(struct lgtd_lifx_bulb *, const struct lgtd_lifx_light_state *, lgtd_time_mono_t); diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index c893b85..dc54cdd 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -14,9 +14,11 @@ main(void) struct lgtd_lifx_bulb *bulb_1 = lgtd_tests_insert_mock_bulb(gw_1, 1); struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); struct lgtd_lifx_bulb *bulb_2 = lgtd_tests_insert_mock_bulb(gw_2, 2); + struct lgtd_lifx_bulb *bulb_3 = lgtd_tests_insert_mock_bulb(gw_2, 3); const char *label = "feed"; strcpy(bulb_1->state.label, label); + strcpy(bulb_3->state.label, label); strcpy(bulb_2->state.label, "trololo"); struct lgtd_lifx_packet_power_state payload = { @@ -26,40 +28,44 @@ main(void) targets = lgtd_tests_build_target_list(label, NULL); lgtd_router_send(targets, LGTD_LIFX_SET_POWER_STATE, &payload); - if (lgtd_tests_gw_pkt_queue_size != 1) { - lgtd_errx(1, "1 packet should have been sent"); + if (lgtd_tests_gw_pkt_queue_size != 2) { + lgtd_errx(1, "2 packet should have been sent"); } - struct lgtd_lifx_gateway *recpt_gw = lgtd_tests_gw_pkt_queue[0].gw; - struct lgtd_lifx_packet_header *hdr_queued = lgtd_tests_gw_pkt_queue[0].hdr; - const void *pkt_queued = lgtd_tests_gw_pkt_queue[0].pkt; - int pkt_size = lgtd_tests_gw_pkt_queue[0].pkt_size; + for (int i = 0; i != lgtd_tests_gw_pkt_queue_size; i++) { + struct lgtd_lifx_gateway *recpt_gw = lgtd_tests_gw_pkt_queue[0].gw; + struct lgtd_lifx_packet_header *hdr_queued = lgtd_tests_gw_pkt_queue[0].hdr; + const void *pkt_queued = lgtd_tests_gw_pkt_queue[0].pkt; + int pkt_size = lgtd_tests_gw_pkt_queue[0].pkt_size; - if (recpt_gw != gw_1) { - lgtd_errx(1, "the packet has been sent to the wrong gateway"); - } - - if (!lgtd_tests_lifx_header_has_flags(hdr_queued, LGTD_LIFX_ADDRESSABLE)) { - lgtd_errx(1, "the packet header doesn't have the right protocol flags"); - } - - if (memcmp(hdr_queued->target.device_addr, bulb_1->addr, sizeof(bulb_1->addr))) { - lgtd_errx(1, "the packet header doesn't have the right target address"); - } - - if (memcmp(gw_1->site.as_array, hdr_queued->site, sizeof(hdr_queued->site))) { - lgtd_errx(1, "incorrect site in the headers"); - } - - if (pkt_queued != &payload) { - lgtd_errx(1, "invalid payload"); - } + if (!lgtd_tests_lifx_header_has_flags(hdr_queued, LGTD_LIFX_ADDRESSABLE)) { + lgtd_errx(1, "the packet header doesn't have the right protocol flags"); + } + if (pkt_queued != &payload) { + lgtd_errx(1, "invalid payload"); + } + if (pkt_size != sizeof(payload)) { + lgtd_errx( + 1, "unexpected pkt size %d (expected %ju)", + pkt_size, (uintmax_t)sizeof(payload) + ); + } - if (pkt_size != sizeof(payload)) { - lgtd_errx( - 1, "unexpected pkt size %d (expected %ju)", - pkt_size, (uintmax_t)sizeof(payload) - ); + if (recpt_gw == gw_1) { + if (memcmp(hdr_queued->target.device_addr, bulb_1->addr, sizeof(bulb_1->addr))) { + lgtd_errx(1, "the packet header doesn't have the right target address"); + } + if (memcmp(gw_1->site.as_array, hdr_queued->site, sizeof(hdr_queued->site))) { + lgtd_errx(1, "incorrect site in the headers"); + } + } else if (recpt_gw == gw_2) { + if (memcmp(hdr_queued->target.device_addr, bulb_3->addr, sizeof(bulb_3->addr))) { + lgtd_errx(1, "the packet header doesn't have the right target address"); + } + if (memcmp(gw_2->site.as_array, hdr_queued->site, sizeof(hdr_queued->site))) { + lgtd_errx(1, "incorrect site in the headers"); + } + } } return 0; diff --git a/tests/core/router/test_router_targets_to_devices.c b/tests/core/router/test_router_targets_to_devices.c index 90a4e69..5359177 100644 --- a/tests/core/router/test_router_targets_to_devices.c +++ b/tests/core/router/test_router_targets_to_devices.c @@ -169,5 +169,20 @@ main(void) lgtd_errx(1, "expected 4 device but got %d", count); } + // targeting a label shouldn't break at the first match: + struct lgtd_lifx_bulb *bulb_3_gw_2 = lgtd_tests_insert_mock_bulb(gw_2, 7); + strcpy(bulb_3_gw_2->state.label, "desk"); + targets = lgtd_tests_build_target_list("desk", NULL); + devices = lgtd_router_targets_to_devices(targets); + if ((count = count_device(devices, bulb_1_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_1_gw_2 found %d times, expected 1", count); + } + if ((count = count_device(devices, bulb_3_gw_2)) != 1) { + lgtd_errx(1, "bulb bulb_3_gw_2 found %d times, expected 1", count); + } + if ((count = len(devices)) != 2) { + lgtd_errx(1, "expected 2 device but got %d", count); + } + return 0; } diff --git a/tests/lifx/bulb/test_bulb_has_label.c b/tests/lifx/bulb/test_bulb_has_label.c new file mode 100644 index 0000000..bead14a --- /dev/null +++ b/tests/lifx/bulb/test_bulb_has_label.c @@ -0,0 +1,47 @@ +#include "bulb.c" + +#include "mock_gateway.h" +#include "mock_router.h" +#include "mock_timer.h" + +void +test_label(struct lgtd_lifx_bulb *bulb, + const char *bulb_label, + const char *label, + bool expected) +{ + memcpy(bulb->state.label, bulb_label, LGTD_MIN( + strlen(bulb_label) + 1, LGTD_LIFX_LABEL_SIZE + )); + bool rv = lgtd_lifx_bulb_has_label(bulb, label); + if (rv != expected) { + errx( + 1, "bulb_has_label(%s, %s) -> %s (expected %s)", + bulb_label, label, + rv ? "true" : "false", + expected ? "true" : "false" + ); + } +} + +int +main(void) +{ + struct lgtd_lifx_gateway gw; + uint8_t bulb_addr[LGTD_LIFX_ADDR_LENGTH] = { 5, 4, 3, 2, 1, 0 }; + struct lgtd_lifx_bulb *bulb = lgtd_lifx_bulb_open(&gw, bulb_addr); + + test_label(bulb, "", "test", false); + test_label(bulb, "test", "test", true); + test_label(bulb, "testtest", "test", false); + test_label(bulb, "test", "testtest", false); + test_label(bulb, "", "", true); + test_label( + bulb, + "testtesttesttesttesttesttesttest", + "testtesttesttesttesttesttesttesttest", + true + ); + + return 0; +} From 53cef92f2a9ad8c5f33b54cafffb25a897bc2f6c Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:23:02 -0700 Subject: [PATCH 089/181] Add missing unit-tests on tag/untag --- ...sonrpc_check_and_call_tag_missing_params.c | 2 +- .../test_jsonrpc_check_and_call_untag.c | 2 +- ...nrpc_check_and_call_untag_invalid_params.c | 2 +- tests/lifx/gateway/test_gateway_handle_tags.c | 36 +++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/lifx/gateway/test_gateway_handle_tags.c diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c index 48341a1..ab14530 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c @@ -43,7 +43,7 @@ main(void) errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label(&client, lgtd_proto_tag); + lgtd_jsonrpc_check_and_call_tag(&client); if (tag_called) { errx(1, "lgtd_proto_tag was called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c index 36549b5..c8009b9 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c @@ -55,7 +55,7 @@ main(void) errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label(&client, lgtd_proto_untag); + lgtd_jsonrpc_check_and_call_untag(&client); if (!untag_called) { errx(1, "lgtd_proto_tag wasn't called"); diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c index af2b0eb..359dc6b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c @@ -43,7 +43,7 @@ main(void) errx(1, "can't parse request"); } - lgtd_jsonrpc_check_and_call_proto_tag_or_untag_or_set_label(&client, lgtd_proto_untag); + lgtd_jsonrpc_check_and_call_untag(&client); if (untag_called) { errx(1, "lgtd_proto_tag was called"); diff --git a/tests/lifx/gateway/test_gateway_handle_tags.c b/tests/lifx/gateway/test_gateway_handle_tags.c new file mode 100644 index 0000000..443a8b4 --- /dev/null +++ b/tests/lifx/gateway/test_gateway_handle_tags.c @@ -0,0 +1,36 @@ +#include + +#include "gateway.c" + +#include "mock_timer.h" +#include "test_gateway_utils.h" +#include "tests_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_info_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + struct lgtd_lifx_bulb *bulb = lgtd_tests_insert_mock_bulb(&gw, 42); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + memcpy( + &hdr.target.device_addr, &bulb->addr, sizeof(hdr.target.device_addr) + ); + + struct lgtd_lifx_packet_tags pkt = { .tags = 0x7 }; + + lgtd_lifx_gateway_handle_tags(&gw, &hdr, &pkt); + + if (bulb->state.tags != 0x7) { + errx( + 1, "bulb->tags = %#jx (expected 0x7)", (uintmax_t)bulb->state.tags + ); + } + + return 0; +} From 210a36216da3ff403d4660e901ab95658efbd1da Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:23:02 -0700 Subject: [PATCH 090/181] Log something before forking into the background This will hopefully avoid some confusion when forgetting -f. --- core/lightsd.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/lightsd.c b/core/lightsd.c index 5293a12..1177542 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -232,8 +232,11 @@ main(int argc, char *argv[], char *envp[]) lgtd_err(1, "can't setup lightsd"); } - if (!lgtd_opts.foreground && !lgtd_daemon_unleash()) { - lgtd_err(1, "can't fork to the background"); + if (!lgtd_opts.foreground) { + lgtd_info("forking into the background now..."); + if (!lgtd_daemon_unleash()) { + lgtd_err(1, "can't fork to the background"); + } } lgtd_lifx_watchdog_start_discovery(); From 69975ba9072af94d13cc57ad649d6cdf0fb56cd2 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 23 Aug 2015 17:26:56 -0700 Subject: [PATCH 091/181] set_label didn't totally make it in the README, fix that. --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index c43014b..8ab523f 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,7 @@ following commands through a JSON-RPC_ interface: - set_light_from_hsbk; - set_waveform (change the light according to a function like SAW or SINE); - get_light_state; +- set_label; - tag/untag (group/ungroup bulbs together). The JSON-RPC interface works on top of TCP/IPv4/v6, Unix sockets (coming up) or From 1f8c8f994ef112d16323cbad435fdcc7b24f0f8f Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 24 Aug 2015 00:38:46 -0700 Subject: [PATCH 092/181] Properly walk over json objects and arrays --- core/jsonrpc.c | 19 ++++++----- .../test_jsonrpc_consume_object_or_array.c | 34 +++++++++++++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 38c726e..0ce30ec 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -295,19 +295,20 @@ lgtd_jsonrpc_consume_object_or_array(const jsmntok_t *tokens, int parsed, const char *json) { - assert(tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY); + assert(lgtd_jsonrpc_type_object_or_array(&tokens[ti], json)); assert(ti < parsed); - int objsize = tokens[ti].size; - if (tokens[ti++].type == JSMN_OBJECT) { - ti++; // move to the value - } + jsmntype_t container_type = tokens[ti].type; + int objsize = tokens[ti++].size; while (objsize-- && ti < parsed) { - if (tokens[ti].type == JSMN_OBJECT || tokens[ti].type == JSMN_ARRAY) { + if (container_type == JSMN_OBJECT) { + ti++; // move to the value + } + if (lgtd_jsonrpc_type_object_or_array(&tokens[ti], json)) { ti = lgtd_jsonrpc_consume_object_or_array(tokens, ti, parsed, json); - } else { - ti += tokens[ti].size + 1; // move to the next value - } + } else { + ti++; + } } return ti; diff --git a/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c b/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c new file mode 100644 index 0000000..e0a79be --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c @@ -0,0 +1,34 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + jsmntok_t tokens[32]; + memset(tokens, 0, sizeof(tokens)); + const char json[] = ("[" + "{" + "\"method\": \"power_on\"," + "\"id\": \"004daf12-0561-4fbc-bfdb-bfe69cfbf4b5\"," + "\"params\": [\"*\"], \"jsonrpc\": \"2.0\"" + "}," + "{" + "\"method\": \"get_light_state\"," + "\"id\": \"1f7a32c8-6741-4ee7-bec1-8431c7d514dc\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}" + "]"); + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + int ti = lgtd_jsonrpc_consume_object_or_array(tokens, 0, parsed, json); + if (ti != parsed) { + errx(1, "ti %d (expected %d)", ti, parsed); + } + + return 0; +} From 16513e994c11282ba1d19f354991901e22971461 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 24 Aug 2015 00:44:24 -0700 Subject: [PATCH 093/181] Stop the LIFX watchdog at cleanup Looks like this got lost during the rename. --- core/lightsd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/core/lightsd.c b/core/lightsd.c index 1177542..1296872 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -63,6 +63,7 @@ struct event_base *lgtd_ev_base = NULL; void lgtd_cleanup(void) { + lgtd_lifx_watchdog_close(); lgtd_listen_close_all(); lgtd_command_pipe_close_all(); lgtd_client_close_all(); From 6706badd5de7cf33093a2b2abc94f2569e9429bf Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:55 -0700 Subject: [PATCH 094/181] Add support for JSON-RPC batch requests/responses --- core/jsonrpc.c | 98 ++++++++++--- core/jsonrpc.h | 3 +- tests/core/jsonrpc/test_jsonrpc_batch.c | 94 ++++++++++++ .../test_jsonrpc_batch_one_invalid_request.c | 73 ++++++++++ .../jsonrpc/test_jsonrpc_build_target_list.c | 1 + .../test_jsonrpc_check_and_call_power_off.c | 3 +- ..._check_and_call_power_off_missing_target.c | 3 +- .../test_jsonrpc_check_and_call_power_on.c | 3 +- ...c_check_and_call_power_on_missing_target.c | 3 +- ...test_jsonrpc_check_and_call_power_toggle.c | 3 +- .../test_jsonrpc_check_and_call_set_label.c | 3 +- ...onrpc_check_and_call_set_light_from_hsbk.c | 3 +- ..._and_call_set_light_from_hsbk_from_array.c | 3 +- ..._call_set_light_from_hsbk_invalid_params.c | 3 +- ...test_jsonrpc_check_and_call_set_waveform.c | 3 +- ...eck_and_call_set_waveform_invalid_params.c | 3 +- .../jsonrpc/test_jsonrpc_check_and_call_tag.c | 3 +- ...sonrpc_check_and_call_tag_missing_params.c | 3 +- .../test_jsonrpc_check_and_call_untag.c | 3 +- ...nrpc_check_and_call_untag_invalid_params.c | 3 +- .../test_jsonrpc_consume_object_or_array.c | 4 +- .../test_jsonrpc_extract_request_no_params.c | 1 + ...c_extract_request_notification_no_params.c | 1 + ...est_jsonrpc_extract_request_params_array.c | 1 + .../test_jsonrpc_extract_request_params_obj.c | 1 + ...onrpc_extract_request_valid_notification.c | 1 + ...ues_from_schema_and_array_honors_objsize.c | 61 ++++++++ tests/core/jsonrpc/test_jsonrpc_send_error.c | 1 + .../core/jsonrpc/test_jsonrpc_send_response.c | 1 + ...onrpc_type_float_between_0_and_1_invalid.c | 1 + ...jsonrpc_type_float_between_0_and_1_valid.c | 1 + ...rpc_type_float_between_0_and_360_invalid.c | 1 + ...onrpc_type_float_between_0_and_360_valid.c | 1 + .../core/jsonrpc/test_jsonrpc_type_integer.c | 1 + ..._jsonrpc_type_integer_invalid_characters.c | 1 + .../test_jsonrpc_type_integer_too_big.c | 1 + .../test_jsonrpc_type_integer_too_small.c | 1 + ...est_jsonrpc_uint16_range_to_float_string.c | 1 + tests/core/jsonrpc/test_jsonrpc_utils.h | 134 +----------------- tests/core/mock_proto.h | 133 +++++++++++++++++ 40 files changed, 493 insertions(+), 169 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_batch.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c create mode 100644 tests/core/mock_proto.h diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 0ce30ec..4bc4219 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -162,6 +162,13 @@ lgtd_jsonrpc_type_array(const jsmntok_t *t, const char *json) return t->type == JSMN_ARRAY; } +static bool +lgtd_jsonrpc_type_object(const jsmntok_t *t, const char *json) +{ + (void)json; + return t->type == JSMN_OBJECT; +} + static bool lgtd_jsonrpc_type_object_or_array(const jsmntok_t *t, const char *json) { @@ -327,9 +334,9 @@ lgtd_jsonrpc_extract_values_from_schema_and_dict(void *output, } for (int ti = 1; ti < ntokens;) { - // make sure it's a key: + // make sure it's a key, otherwise we reached the end of the object: if (tokens[ti].type != JSMN_STRING) { - return false; + break; } int si = 0; @@ -420,8 +427,8 @@ lgtd_jsonrpc_extract_values_from_schema_and_array(void *output, return false; } - int si, ti; - for (si = 0, ti = 1; si < schema_size && ti < ntokens; si++) { + int si, ti, objsize = tokens[0].size; + for (si = 0, ti = 1; si < schema_size && ti < ntokens && objsize--; si++) { if (!schema[si].type_cmp(&tokens[ti], json)) { lgtd_debug( "jsonrpc client sent an invalid value for %s", @@ -480,7 +487,7 @@ lgtd_jsonrpc_extract_and_validate_params_against_schema(void *output, static void lgtd_jsonrpc_write_id(struct lgtd_client *client) { - if (!client->current_request->id) { + if (!client->current_request || !client->current_request->id) { lgtd_client_write_string(client, "null"); return; } @@ -578,7 +585,7 @@ lgtd_jsonrpc_check_and_extract_request(struct lgtd_jsonrpc_request *request, ) }; - return lgtd_jsonrpc_extract_values_from_schema_and_dict( + bool ok = lgtd_jsonrpc_extract_values_from_schema_and_dict( request, request_schema, LGTD_ARRAY_SIZE(request_schema), @@ -586,6 +593,19 @@ lgtd_jsonrpc_check_and_extract_request(struct lgtd_jsonrpc_request *request, ntokens, json ); + if (!ok) { + return false; + } + + request->request_ntokens = 1 + 2 + 2; // dict itself + jsonrpc + method + if (request->params) { + request->request_ntokens += 1 + request->params_ntokens; + } + if (request->id) { + request->request_ntokens += 2; + } + + return true; } static bool @@ -1055,8 +1075,10 @@ lgtd_jsonrpc_check_and_call_set_label(struct lgtd_client *client) ); } -void -lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +static int +lgtd_jsonrpc_dispatch_one(struct lgtd_client *client, + const jsmntok_t *tokens, + int ntokens) { static const struct lgtd_jsonrpc_method methods[] = { LGTD_JSONRPC_METHOD( @@ -1098,25 +1120,19 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) ) }; - assert(client); - assert(parsed >= 0); - - // TODO: batch requests - struct lgtd_jsonrpc_request request; memset(&request, 0, sizeof(request)); bool ok = lgtd_jsonrpc_check_and_extract_request( - &request, - client->jsmn_tokens, - parsed, - client->json + &request, tokens, ntokens, client->json ); client->current_request = &request; if (!ok) { lgtd_jsonrpc_send_error( client, LGTD_JSONRPC_INVALID_REQUEST, "Invalid request" ); - return; + return lgtd_jsonrpc_consume_object_or_array( + tokens, 0, ntokens, client->json + ); } assert(request.method); @@ -1140,7 +1156,7 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) } methods[i].method(client); client->current_request = NULL; - return; + return request.request_ntokens; } } @@ -1149,4 +1165,48 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) ); error: client->current_request = NULL; + return request.request_ntokens; +} + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + assert(client); + assert(parsed >= 0); + + if (!parsed || !client->jsmn_tokens[0].size) { + lgtd_jsonrpc_send_error( + client, LGTD_JSONRPC_INVALID_REQUEST, "Invalid request" + ); + return; + } + + if (!lgtd_jsonrpc_type_array(client->jsmn_tokens, client->json)) { + lgtd_jsonrpc_dispatch_one(client, client->jsmn_tokens, parsed); + return; + } + + bool comma = false; + for (int ti = 1; ti < parsed;) { + const jsmntok_t *tok = &client->jsmn_tokens[ti]; + + lgtd_client_write_string(client, comma ? "," : "["); + comma = true; + + if (lgtd_jsonrpc_type_object(tok, client->json)) { + ti += lgtd_jsonrpc_dispatch_one(client, tok, parsed - ti); + } else { + lgtd_jsonrpc_send_error( + client, LGTD_JSONRPC_INVALID_REQUEST, "Invalid request" + ); + if (lgtd_jsonrpc_type_array(tok, client->json)) { + ti = lgtd_jsonrpc_consume_object_or_array( + client->jsmn_tokens, ti, parsed, client->json + ); + } else { + ti++; + } + } + } + lgtd_client_write_string(client, "]"); } diff --git a/core/jsonrpc.h b/core/jsonrpc.h index da7c5bb..8bb2417 100644 --- a/core/jsonrpc.h +++ b/core/jsonrpc.h @@ -26,6 +26,7 @@ struct lgtd_jsonrpc_request { const jsmntok_t *params; int params_ntokens; const jsmntok_t *id; + int request_ntokens; }; struct lgtd_jsonrpc_node { @@ -45,7 +46,7 @@ struct lgtd_jsonrpc_node { *(const jsmntok_t **)(&((char *)(object))[value_offset]); #define LGTD_JSONRPC_SET_NTOKENS(object, ntokens_offset, ntokens) do { \ - *(int *)(&(((char *)(object))[ntokens_offset])) = (ntokens); \ + *(int *)(&(((char *)(object))[ntokens_offset])) = (ntokens); \ } while (0) #define LGTD_JSONRPC_NODE(key_, value_offset_, ntokens_offset_, fn_type_cmp, optional_) { \ diff --git a/tests/core/jsonrpc/test_jsonrpc_batch.c b/tests/core/jsonrpc/test_jsonrpc_batch.c new file mode 100644 index 0000000..ce57067 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_batch.c @@ -0,0 +1,94 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_GET_LIGHT_STATE +#define MOCKED_LGTD_PROTO_POWER_ON +#include "mock_proto.h" +#include "test_jsonrpc_utils.h" + +static int power_on_call_count = 0; + +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (power_on_call_count++) { + errx(1, "proto_power_on should have been called once"); + } +} + +static int get_light_state_call_count = 0; + +void +lgtd_proto_get_light_state(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (get_light_state_call_count++) { + errx(1, "proto_power_on should have been called once"); + } +} + +int +main(void) +{ + const char json[] = ("[" + "{" + "\"method\": \"power_on\"," + "\"id\": \"004daf12-0561-4fbc-bfdb-bfe69cfbf4b5\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}," + "{" + "\"method\": \"get_light_state\"," + "\"id\": \"1f7a32c8-6741-4ee7-bec1-8431c7d514dc\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}" + "]"); + struct lgtd_client client = { .json = json }; + int parsed = parse_json( + client.jsmn_tokens, + LGTD_ARRAY_SIZE(client.jsmn_tokens), + json, + sizeof(json) + ); + + lgtd_jsonrpc_dispatch_request(&client, parsed); + + if (!power_on_call_count) { + errx(1, "power_on was never called"); + } + + if (!get_light_state_call_count) { + errx(1, "get_light_state was never called"); + } + + const char expected[] = "[,]"; // we mocked the functions + if (strcmp(expected, client_write_buf)) { + errx(1, "got client buf %s (expected %s)", client_write_buf, expected); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c b/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c new file mode 100644 index 0000000..1a9cec1 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c @@ -0,0 +1,73 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_POWER_ON +#include "mock_proto.h" +#include "test_jsonrpc_utils.h" + +static int power_on_call_count = 0; + +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (power_on_call_count++) { + errx(1, "proto_power_on should have been called once"); + } +} + +int +main(void) +{ + const char json[] = ("[" + "{" + "\"method\": \"power_on\"," + "\"id\": \"004daf12-0561-4fbc-bfdb-bfe69cfbf4b5\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}," + "{" + "\"method\": \"la rache\"," + "\"id\": \"1f7a32c8-6741-4ee7-bec1-8431c7d514dc\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}" + "]"); + struct lgtd_client client = { .json = json }; + int parsed = parse_json( + client.jsmn_tokens, + LGTD_ARRAY_SIZE(client.jsmn_tokens), + json, + sizeof(json) + ); + + lgtd_jsonrpc_dispatch_request(&client, parsed); + + if (!power_on_call_count) { + errx(1, "power_on was never called"); + } + + const char expected[] = ("[," + "{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"1f7a32c8-6741-4ee7-bec1-8431c7d514dc\", " + "\"error\": {\"code\": -32601, \"message\": \"Method not found\"}" + "}" + "]"); + if (strcmp(expected, client_write_buf)) { + errx(1, "got client buf %s (expected %s)", client_write_buf, expected); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c index 3a3d8ab..69ba33d 100644 --- a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c +++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c index b56f625..aa75be5 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_POWER_OFF +#include "mock_proto.h" -#define LGTD_TESTING_POWER_OFF #include "test_jsonrpc_utils.h" static bool power_off_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c index e30174a..0d0a4af 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_POWER_OFF +#include "mock_proto.h" -#define LGTD_TESTING_POWER_OFF #include "test_jsonrpc_utils.h" static bool power_off_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c index 33e0262..78b82e6 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_POWER_ON +#include "mock_proto.h" -#define LGTD_TESTING_POWER_ON #include "test_jsonrpc_utils.h" static bool power_on_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c index e1e54ab..79f8def 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_POWER_ON +#include "mock_proto.h" -#define LGTD_TESTING_POWER_ON #include "test_jsonrpc_utils.h" static bool power_on_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c index 74e2f32..7646d5b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" - #define MOCKED_LGTD_PROTO_POWER_TOGGLE +#include "mock_proto.h" + #include "test_jsonrpc_utils.h" static bool power_toggle_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c index 04e690a..a7bc3f9 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c @@ -1,9 +1,10 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_SET_LABEL +#include "mock_proto.h" #include "mock_gateway.h" -#define MOCKED_LGTD_PROTO_SET_LABEL #include "test_jsonrpc_utils.h" static bool tag_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c index c5bb0ce..e0eafaf 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK +#include "mock_proto.h" -#define LGTD_TESTING_SET_LIGHT_FROM_HSBK #include "test_jsonrpc_utils.h" static bool set_light_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c index 221ff90..981572c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK +#include "mock_proto.h" -#define LGTD_TESTING_SET_LIGHT_FROM_HSBK #include "test_jsonrpc_utils.h" static bool set_light_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c index d77d691..01c053c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK +#include "mock_proto.h" -#define LGTD_TESTING_SET_LIGHT_FROM_HSBK #include "test_jsonrpc_utils.h" static bool set_light_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c index e1c71d8..0cc2f40 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_SET_WAVEFORM +#include "mock_proto.h" -#define LGTD_TESTING_SET_WAVEFORM #include "test_jsonrpc_utils.h" static bool set_waveform_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c index 4d38d2b..9dc39ba 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c @@ -1,8 +1,9 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_SET_WAVEFORM +#include "mock_proto.h" -#define LGTD_TESTING_SET_WAVEFORM #include "test_jsonrpc_utils.h" static bool set_waveform_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c index e140de9..e3f0835 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c @@ -1,9 +1,10 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_TAG +#include "mock_proto.h" #include "mock_gateway.h" -#define MOCKED_LGTD_TAG #include "test_jsonrpc_utils.h" static bool tag_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c index ab14530..f8aa11b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c @@ -1,9 +1,10 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_TAG +#include "mock_proto.h" #include "mock_gateway.h" -#define MOCKED_LGTD_TAG #include "test_jsonrpc_utils.h" static bool tag_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c index c8009b9..370374f 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c @@ -1,9 +1,10 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_UNTAG +#include "mock_proto.h" #include "mock_gateway.h" -#define MOCKED_LGTD_UNTAG #include "test_jsonrpc_utils.h" static bool untag_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c index 359dc6b..eb42787 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c @@ -1,9 +1,10 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_UNTAG +#include "mock_proto.h" #include "mock_gateway.h" -#define MOCKED_LGTD_UNTAG #include "test_jsonrpc_utils.h" static bool untag_called = false; diff --git a/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c b/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c index e0a79be..1d9d6e7 100644 --- a/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int @@ -12,7 +13,8 @@ main(void) "{" "\"method\": \"power_on\"," "\"id\": \"004daf12-0561-4fbc-bfdb-bfe69cfbf4b5\"," - "\"params\": [\"*\"], \"jsonrpc\": \"2.0\"" + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" "}," "{" "\"method\": \"get_light_state\"," diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c index b496592..c4d2f28 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c index ad89a97..8ae62d1 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c index 9529f98..fd9d466 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c index 1823c09..14e35e6 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c index 4f37324..102c6aa 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c b/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c new file mode 100644 index 0000000..38e6acb --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c @@ -0,0 +1,61 @@ +#include + +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#include "mock_proto.h" +#include "test_jsonrpc_utils.h" + +int +main(void) +{ + jsmntok_t tokens[32]; + memset(tokens, 0, sizeof(tokens)); + const char json[] = "[[\"*\"],[1,2,3,4]]"; + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + struct lgtd_jsonrpc_target_args { + const jsmntok_t *target; + int target_ntokens; + const jsmntok_t *label; + } params = { NULL, 0, NULL }; + static const struct lgtd_jsonrpc_node schema[] = { + LGTD_JSONRPC_NODE( + "target", + offsetof(struct lgtd_jsonrpc_target_args, target), + offsetof(struct lgtd_jsonrpc_target_args, target_ntokens), + lgtd_jsonrpc_type_string_number_or_array, + false + ), + LGTD_JSONRPC_NODE( + "label", + offsetof(struct lgtd_jsonrpc_target_args, label), + -1, + // this must dereference json from the what's in the token (see + // next comment): + lgtd_jsonrpc_type_number, + false + ) + }; + + // invalidate all the tokens so that the test will crash if we go beyond + // the first list: + for (int i = 3; i != LGTD_ARRAY_SIZE(tokens); i++) { + tokens[i].start = INT_MIN; + tokens[i].end = INT_MIN; + tokens[i].size = INT_MAX; + tokens[i].type = JSMN_PRIMITIVE; + } + + bool ok = lgtd_jsonrpc_extract_and_validate_params_against_schema( + ¶ms, schema, LGTD_ARRAY_SIZE(schema), &tokens[1], parsed - 1, json + ); + + if (ok) { + errx(1, "the schema shouldn't have been validated"); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c index bea6a50..e0c8759 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_error.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_send_response.c b/tests/core/jsonrpc/test_jsonrpc_send_response.c index 5f0d895..c54e382 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_response.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_response.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c index 2e6346a..10c016a 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c index a6820d1..8b83b73 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c index 3651308..6ce5f8b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c index 03ee592..d524e3b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer.c b/tests/core/jsonrpc/test_jsonrpc_type_integer.c index b90cc13..64df2fa 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c index b6ef418..b0943cc 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c index 80893a1..b38e993 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c index b96b76b..5775e48 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c index 7cad76d..1098d00 100644 --- a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c +++ b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c @@ -3,6 +3,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h index 7368218..cc488e7 100644 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h @@ -2,7 +2,7 @@ #include "mock_gateway.h" -#define TEST_REQUEST_INITIALIZER { NULL, NULL, 0, NULL } +#define TEST_REQUEST_INITIALIZER { NULL, NULL, 0, NULL, 0 } static inline int parse_json(jsmntok_t *tokens, size_t capacity, const char *json , size_t len) @@ -11,135 +11,3 @@ parse_json(jsmntok_t *tokens, size_t capacity, const char *json , size_t len) jsmn_init(&ctx); return jsmn_parse(&ctx, json, len, tokens, capacity); } - -void -lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) -{ - (void)targets; -} - -void -lgtd_proto_list_tags(struct lgtd_client *client) -{ - (void)client; -} - -#ifndef LGTD_TESTING_SET_LIGHT_FROM_HSBK -void -lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets, - int hue, - int saturation, - int brightness, - int kelvin, - int transition_msecs) -{ - (void)client; - (void)targets; - (void)hue; - (void)saturation; - (void)brightness; - (void)kelvin; - (void)transition_msecs; -} -#endif - -#ifndef LGTD_TESTING_POWER_ON -void -lgtd_proto_power_on(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets) -{ - (void)client; - (void)targets; -} -#endif - -#ifndef LGTD_TESTING_POWER_OFF -void -lgtd_proto_power_off(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets) -{ - (void)client; - (void)targets; -} -#endif - -#ifndef LGTD_TESTING_SET_WAVEFORM -void -lgtd_proto_set_waveform(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets, - enum lgtd_lifx_waveform_type waveform, - int hue, int saturation, - int brightness, int kelvin, - int period, float cycles, - int skew_ratio, bool transient) -{ - (void)client; - (void)targets; - (void)waveform; - (void)hue; - (void)saturation; - (void)brightness; - (void)kelvin; - (void)period; - (void)cycles; - (void)skew_ratio; - (void)transient; -} -#endif - -#ifndef MOCKED_LGTD_GET_LIGHT_STATE -void -lgtd_proto_get_light_state(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets) -{ - (void)client; - (void)targets; -} -#endif - -#ifndef MOCKED_LGTD_TAG -void -lgtd_proto_tag(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets, - const char *tag_label) -{ - (void)client; - (void)targets; - (void)tag_label; -} -#endif - -#ifndef MOCKED_LGTD_UNTAG -void -lgtd_proto_untag(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets, - const char *tag_label) -{ - (void)client; - (void)targets; - (void)tag_label; -} -#endif - -#ifndef MOCKED_LGTD_PROTO_POWER_TOGGLE -void -lgtd_proto_power_toggle(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets) -{ - (void)client; - (void)targets; -} -#endif - -#ifndef MOCKED_LGTD_PROTO_SET_LABEL -void -lgtd_proto_set_label(struct lgtd_client *client, - const struct lgtd_proto_target_list *targets, - const char *label) -{ - (void)client; - (void)targets; - (void)label; -} -#endif diff --git a/tests/core/mock_proto.h b/tests/core/mock_proto.h new file mode 100644 index 0000000..c213508 --- /dev/null +++ b/tests/core/mock_proto.h @@ -0,0 +1,133 @@ +#pragma once + +void +lgtd_proto_target_list_clear(struct lgtd_proto_target_list *targets) +{ + (void)targets; +} + +void +lgtd_proto_list_tags(struct lgtd_client *client) +{ + (void)client; +} + +#ifndef MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK +void +lgtd_proto_set_light_from_hsbk(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + int hue, + int saturation, + int brightness, + int kelvin, + int transition_msecs) +{ + (void)client; + (void)targets; + (void)hue; + (void)saturation; + (void)brightness; + (void)kelvin; + (void)transition_msecs; +} +#endif + +#ifndef MOCKED_LGTD_PROTO_POWER_ON +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + (void)client; + (void)targets; +} +#endif + +#ifndef MOCKED_LGTD_PROTO_POWER_OFF +void +lgtd_proto_power_off(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + (void)client; + (void)targets; +} +#endif + +#ifndef MOCKED_LGTD_PROTO_SET_WAVEFORM +void +lgtd_proto_set_waveform(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + enum lgtd_lifx_waveform_type waveform, + int hue, int saturation, + int brightness, int kelvin, + int period, float cycles, + int skew_ratio, bool transient) +{ + (void)client; + (void)targets; + (void)waveform; + (void)hue; + (void)saturation; + (void)brightness; + (void)kelvin; + (void)period; + (void)cycles; + (void)skew_ratio; + (void)transient; +} +#endif + +#ifndef MOCKED_LGTD_PROTO_GET_LIGHT_STATE +void +lgtd_proto_get_light_state(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + (void)client; + (void)targets; +} +#endif + +#ifndef MOCKED_LGTD_PROTO_TAG +void +lgtd_proto_tag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag_label) +{ + (void)client; + (void)targets; + (void)tag_label; +} +#endif + +#ifndef MOCKED_LGTD_PROTO_UNTAG +void +lgtd_proto_untag(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *tag_label) +{ + (void)client; + (void)targets; + (void)tag_label; +} +#endif + +#ifndef MOCKED_LGTD_PROTO_POWER_TOGGLE +void +lgtd_proto_power_toggle(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + (void)client; + (void)targets; +} +#endif + +#ifndef MOCKED_LGTD_PROTO_SET_LABEL +void +lgtd_proto_set_label(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets, + const char *label) +{ + (void)client; + (void)targets; + (void)label; +} +#endif From 7b38573b2ec2f0b559e04820c2a0b7ed5c03d968 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:55 -0700 Subject: [PATCH 095/181] Rewrite lightsc.py to be simpler to use, more re-usable & BSD licensed And to make things clear: unless clearly indicated like here; everything in this repository is GPLv3, except: - compat/generic/sys/{queue,tree}.h that I stole from OpenBSD (again); - core/jsmn.{h,c} by Serge A. Zaitsev. --- examples/lightsc.py | 352 +++++++++++++++++++++++++++----------------- 1 file changed, 214 insertions(+), 138 deletions(-) diff --git a/examples/lightsc.py b/examples/lightsc.py index a879bfa..da729e6 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -1,154 +1,230 @@ -#!/usr/bin/env python - +#!/usr/bin/env python3 +# Copyright (c) 2015, Louis Opter +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import argparse +import contextlib import json import socket import sys -import time import uuid -from IPython.terminal.embed import InteractiveShellEmbed +class LightsClient: + + def __init__(self, host, port): + self.host = host + self.port = port + self._socket = socket.create_connection((host, port)) + self._pipeline = [] + self._batch = False + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_tb): + self.close() + + @classmethod + def _make_payload(cls, method, params): + return { + "method": method, + "params": params, + "jsonrpc": "2.0", + "id": str(uuid.uuid4()), + } + + def _execute_payload(self, payload): + self._socket.send(json.dumps(payload).encode("utf-8")) + # FIXME: proper read loop + response = self._socket.recv(8192).decode("utf-8") + try: + response = json.loads(response) + except Exception: + print("received invalid json: {}".format(response)) + + return response + + def _jsonrpc_call(self, method, params): + payload = self._make_payload(method, params) + if self._batch: + self._pipeline.append(payload) + return + return self._execute_payload(payload) + + def close(self): + self._socket.close() + + @contextlib.contextmanager + def batch(self): + self._batch = True + response = [] + yield response + self._batch = False + result = self._execute_payload(self._pipeline) + if isinstance(result, list): + response.extend(result) + else: + response.append(result) + self._pipeline = [] + + def set_light_from_hsbk(self, target, h, s, b, k, t): + return self._jsonrpc_call("set_light_from_hsbk", [ + target, h, s, b, k, t + ]) + + def set_waveform(self, target, waveform, + h, s, b, k, + period, cycles, skew_ratio, transient): + return self._jsonrpc_call("set_waveform", [ + target, waveform, h, s, b, k, period, cycles, skew_ratio, transient + ]) + + def saw(self, target, h, s, b, k, period, cycles, transient=True): + return self.set_waveform( + target, "SAW", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=0.5, + transient=transient + ) + + def sine(self, target, h, s, b, k, + period, cycles, peak=0.5, transient=True): + return self.set_waveform( + target, "SINE", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=peak, + transient=transient + ) + + def half_sine(self, target, h, s, b, k, period, cycles, transient=True): + return self.set_waveform( + target, "HALF_SINE", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=0.5, + transient=transient + ) + + def triangle(self, target, h, s, b, k, + period, cycles, peak=0.5, transient=True): + return self.set_waveform( + target, "TRIANGLE", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=peak, + transient=transient + ) + + def square(self, target, h, s, b, k, period, cycles, + duty_cycle=0.5, transient=True): + return self.set_waveform( + target, "SQUARE", h, s, b, k, + cycles=cycles, + period=period, + skew_ratio=duty_cycle, + transient=transient + ) + + def power_on(self, target): + return self._jsonrpc_call("power_on", {"target": target}) + + def power_off(self, target): + return self._jsonrpc_call("power_off", {"target": target}) + + def power_toggle(self, target): + return self._jsonrpc_call("power_toggle", {"target": target}) + + def get_light_state(self, target): + return self._jsonrpc_call("get_light_state", [target]) + + def tag(self, target, tag): + return self._jsonrpc_call("tag", [target, tag]) + + def untag(self, target, tag): + return self._jsonrpc_call("untag", [target, tag]) + + def set_label(self, target, label): + return self._jsonrpc_call("set_label", [target, label]) + + def adjust_brightness(self, target, adjustment): + bulbs = self.get_light_state(target)["result"] + for bulb in bulbs: + h, s, b, k = bulb["hsbk"] + b = max(min(b + adjustment, 1.0), 0.0) + self.set_light_from_hsbk(bulb["label"], h, s, b, k, 500) + + +def _drop_to_shell(lightsc): + c = lightsc # noqa + nb = "d073d501a0d5" # noqa + fugu = "d073d500603b" # noqa + neko = "d073d5018fb6" # noqa + middle = "d073d502e530" # noqa + + banner = ( + "Connected to {}:{}, use the variable c to interact with your " + "bulbs:\n\n>>> r = c.get_light_state(\"*\")".format(c.host, c.port) + ) -def jsonrpc_call(socket, method, params): - payload = { - "method": method, - "params": params, - "jsonrpc": "2.0", - "id": str(uuid.uuid4()), - } - socket.send(json.dumps(payload).encode("utf-8")) - response = socket.recv(8192).decode("utf-8") try: - response = json.loads(response) - except ValueError: - print("received invalid json: {}".format(response)) - return None - return response - - -def set_light_from_hsbk(socket, target, h, s, b, k, t): - return jsonrpc_call(socket, "set_light_from_hsbk", [ - target, h, s, b, k, t - ]) - - -def set_waveform(socket, target, waveform, - h, s, b, k, - period, cycles, skew_ratio, transient): - return jsonrpc_call(socket, "set_waveform", [ - target, waveform, h, s, b, k, period, cycles, skew_ratio, transient - ]) - - -def saw(socket, target, h, s, b, k, period, cycles, transient=True): - return set_waveform( - socket, target, "SAW", h, s, b, k, - cycles=cycles, - period=period, - skew_ratio=0.5, - transient=transient - ) + from IPython import embed + embed(header=banner + "\n>>> r") + return + except ImportError: + pass -def sine(socket, target, h, s, b, k, period, cycles, peak=0.5, transient=True): - return set_waveform( - socket, target, "SINE", h, s, b, k, - cycles=cycles, - period=period, - skew_ratio=peak, - transient=transient - ) + import code + banner += "\n>>> from pprint import pprint\n>>> pprint(r)\n" + code.interact(banner=banner, local=locals()) -def half_sine(socket, target, h, s, b, k, period, cycles, transient=True): - return set_waveform( - socket, target, "HALF_SINE", h, s, b, k, - cycles=cycles, - period=period, - skew_ratio=0.5, - transient=transient +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="lightsc.py is an interactive lightsd Python client" ) - - -def triangle(socket, target, h, s, b, k, - period, cycles, peak=0.5, transient=True): - return set_waveform( - socket, target, "TRIANGLE", h, s, b, k, - cycles=cycles, - period=period, - skew_ratio=peak, - transient=transient + parser.add_argument( + "host", type=str, help="The hostname or ip where lightsd is running" ) - - -def square(socket, target, h, s, b, k, period, cycles, - duty_cycle=0.5, transient=True): - return set_waveform( - socket, target, "SQUARE", h, s, b, k, - cycles=cycles, - period=period, - skew_ratio=duty_cycle, - transient=transient + parser.add_argument( + "port", type=int, help="The port on which lightsd is listening on" ) - - -def power_on(socket, target): - return jsonrpc_call(socket, "power_on", {"target": target}) - - -def power_off(socket, target): - return jsonrpc_call(socket, "power_off", {"target": target}) - - -def power_toggle(socket, target): - return jsonrpc_call(socket, "power_toggle", {"target": target}) - - -def get_light_state(socket, target): - return jsonrpc_call(socket, "get_light_state", [target]) - - -def tag(socket, target, tag): - return jsonrpc_call(socket, "tag", [target, tag]) - - -def untag(socket, target, tag): - return jsonrpc_call(socket, "untag", [target, tag]) - - -def set_label(socket, target, label): - return jsonrpc_call(socket, "set_label", [target, label]) - - -def adjust_brightness(socket, target, adjustment): - bulbs = get_light_state(socket, target)["result"] - for bulb in bulbs: - h, s, b, k = bulb["hsbk"] - b += adjustment - b = max(min(b, 1.0), 0.0) - set_light_from_hsbk(socket, bulb["label"], h, s, b, k, 500) - - -if __name__ == "__main__": - s = socket.create_connection(("localhost", 1234)) - h = 0 - id = 0 - nb = "d073d501a0d5" - fugu = "d073d500603b" - neko = "d073d5018fb6" - middle = "d073d502e530" - target = "*" + args = parser.parse_args() try: - if len(sys.argv) == 2 and sys.argv[1] == "shell": - ipshell = InteractiveShellEmbed() - ipshell() - sys.exit(0) - power_on(s, target) - while True: - h = (h + 5) % 360 - id += 1 - set_light_from_hsbk(s, target, h, 0.8, 0.1, 2500, 450) - time.sleep(0.5) - power_off(s, target) - finally: - s.close() + _drop_to_shell(LightsClient(args.host, args.port)) + except socket.error as ex: + print( + "Couldn't connect to lightsd@{}:{}, is it running? " + "({})".format(args.host, args.port, ex.strerror), + file=sys.stderr + ) + sys.exit(1) From 6730a7aa528f2cfcaad49adb78434179a91f62c6 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:55 -0700 Subject: [PATCH 096/181] Add batch support in lightsc.sh (somewhat) --- examples/lightsc.sh | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/examples/lightsc.sh b/examples/lightsc.sh index e57e0d3..922e9c5 100644 --- a/examples/lightsc.sh +++ b/examples/lightsc.sh @@ -51,15 +51,27 @@ _lightsc_jq() { fi } -lightsc() { - if [ $# -lt 2 ] ; then - echo >&2 "Usage: $0 METHOD PARAMS ..." - return 1 - fi - +_lightsc_get_pipe() { local pipe=${COMMAND_PIPE:-/run/lightsd.cmd} if [ ! -p $pipe ] ; then echo >&2 "$pipe cannot be found, is lightsd running?" + exit 1 + fi + echo $pipe +} + +# Can be used to build batch request: +# +# tee $COMMAND_PIPE <&2 "Usage: $0 METHOD PARAMS ..." return 1 fi @@ -69,7 +81,7 @@ lightsc() { params=$params,$target done - tee $pipe <&2 "Usage: $0 METHOD PARAMS ..." + return 1 + fi + + lightsc_make_request $* | tee `_lightsc_get_pipe` | _lightsc_jq } From 48b9554b54ef7879840efc5678bce43983c04dcf Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:55 -0700 Subject: [PATCH 097/181] Add support for the ambient light sensor (left unimplemented by LIFX) If this turns out to be real someday then we can just schedule GET_AMBIENT_LIGHT from the gateway refresh loop. This changeset also adds a bunch of other LIFX packet types definitions. --- lifx/bulb.c | 8 ++++ lifx/bulb.h | 2 + lifx/gateway.c | 23 +++++++++++- lifx/gateway.h | 3 ++ lifx/wire_proto.c | 32 ++++++++++++++++ lifx/wire_proto.h | 22 +++++++++++ .../test_gateway_handle_ambient_light.c | 37 +++++++++++++++++++ tests/lifx/mock_gateway.h | 12 ++++++ 8 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 tests/lifx/gateway/test_gateway_handle_ambient_light.c diff --git a/lifx/bulb.c b/lifx/bulb.c index a6dc493..4216e26 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -330,3 +330,11 @@ lgtd_lifx_bulb_set_label(struct lgtd_lifx_bulb *bulb, memcpy(bulb->state.label, label, LGTD_LIFX_LABEL_SIZE); } + +void +lgtd_lifx_bulb_set_ambient_light(struct lgtd_lifx_bulb *bulb, float illuminance) +{ + assert(bulb); + + bulb->ambient_light = illuminance; +} diff --git a/lifx/bulb.h b/lifx/bulb.h index e357ecb..b4b82e5 100644 --- a/lifx/bulb.h +++ b/lifx/bulb.h @@ -89,6 +89,7 @@ struct lgtd_lifx_bulb { lgtd_time_mono_t dirty_at; uint16_t expected_power_on; uint8_t addr[LGTD_LIFX_ADDR_LENGTH]; + float ambient_light; // lux const char *model; const char *vendor; struct lgtd_lifx_gateway *gw; @@ -143,3 +144,4 @@ void lgtd_lifx_bulb_set_runtime_info(struct lgtd_lifx_bulb *, lgtd_time_mono_t); void lgtd_lifx_bulb_set_label(struct lgtd_lifx_bulb *, const char [LGTD_LIFX_LABEL_SIZE]); +void lgtd_lifx_bulb_set_ambient_light(struct lgtd_lifx_bulb *, float); diff --git a/lifx/gateway.c b/lifx/gateway.c index 5a0cf07..29a5516 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -856,7 +856,7 @@ lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *gw, assert(gw && hdr && pkt); char addr[LGTD_LIFX_ADDR_STRLEN]; - lgtd_info( + lgtd_debug( "BULB_LABEL <-- [%s]:%hu - %s label=%.*s", gw->ip_addr, gw->port, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), @@ -867,3 +867,24 @@ lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *gw, gw, hdr->target.device_addr, lgtd_lifx_bulb_set_label, pkt->label ); } + +void +lgtd_lifx_gateway_handle_ambient_light(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_ambient_light *pkt) +{ + assert(gw && hdr && pkt); + + char addr[LGTD_LIFX_ADDR_STRLEN]; + lgtd_debug( + "AMBIENT_LIGHT <-- [%s]:%hu - %s ambient_light=%flx", + gw->ip_addr, gw->port, + LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + pkt->illuminance + ); + + LGTD_LIFX_GATEWAY_SET_BULB_ATTR( + gw, hdr->target.device_addr, + lgtd_lifx_bulb_set_ambient_light, pkt->illuminance + ); +} diff --git a/lifx/gateway.h b/lifx/gateway.h index 1db3d5c..5f510fc 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -148,3 +148,6 @@ void lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *, void lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, const struct lgtd_lifx_packet_label *); +void lgtd_lifx_gateway_handle_ambient_light(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const struct lgtd_lifx_packet_ambient_light *); diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 02d91f6..49f8a55 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -313,6 +313,20 @@ lgtd_lifx_wire_load_packet_info_map(void) .decode = lgtd_lifx_wire_null_packet_encoder_decoder, .handle = HANDLER(lgtd_lifx_gateway_handle_bulb_label) }, + { + REQUEST_ONLY, + NO_PAYLOAD, + .name = "GET_AMBIENT_LIGHT", + .type = LGTD_LIFX_GET_AMBIENT_LIGHT + }, + { + RESPONSE_ONLY, + .name = "STATE_AMBIENT_LIGHT", + .type = LGTD_LIFX_STATE_AMBIENT_LIGHT, + .size = sizeof(struct lgtd_lifx_packet_ambient_light), + .decode = DECODER(lgtd_lifx_wire_decode_ambient_light), + .handle = HANDLER(lgtd_lifx_gateway_handle_ambient_light) + }, // Unimplemented but "known" packets { UNIMPLEMENTED, @@ -423,6 +437,16 @@ lgtd_lifx_wire_load_packet_info_map(void) UNIMPLEMENTED, .name = "ACCESS_POINT", .type = LGTD_LIFX_ACCESS_POINT + }, + { + UNIMPLEMENTED, + .name = "GET_DIMMER_VOLTAGE", + .type = LGTD_LIFX_GET_DIMMER_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "STATE_DIMMER_VOLTAGE", + .type = LGTD_LIFX_STATE_DIMMER_VOLTAGE } }; @@ -719,3 +743,11 @@ lgtd_lifx_wire_decode_runtime_info(struct lgtd_lifx_packet_runtime_info *pkt) pkt->uptime = le64toh(pkt->uptime); pkt->downtime = le64toh(pkt->downtime); } + +void +lgtd_lifx_wire_decode_ambient_light(struct lgtd_lifx_packet_ambient_light *pkt) +{ + assert(pkt); + + pkt->illuminance = lgtd_lifx_wire_lefloattoh(pkt->illuminance); +} diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 3989547..bfefab1 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -116,6 +116,8 @@ enum lgtd_lifx_flags { enum { LGTD_LIFX_MAX_PACKET_SIZE = 4096 }; enum lgtd_lifx_packet_type { + // Device + LGTD_LIFX_SET_SITE = 0x01, LGTD_LIFX_GET_PAN_GATEWAY = 0x02, LGTD_LIFX_PAN_GATEWAY = 0x03, LGTD_LIFX_GET_TIME = 0x04, @@ -155,18 +157,32 @@ enum lgtd_lifx_packet_type { LGTD_LIFX_ACK = 0x2d, LGTD_LIFX_ECHO_REQUEST = 0x3a, LGTD_LIFX_ECHO_RESPONSE = 0x3b, + // Light LGTD_LIFX_GET_LIGHT_STATE = 0x65, LGTD_LIFX_SET_LIGHT_COLOR = 0x66, LGTD_LIFX_SET_WAVEFORM = 0x67, LGTD_LIFX_SET_DIM_ABSOLUTE = 0x68, LGTD_LIFX_SET_DIM_RELATIVE = 0x69, LGTD_LIFX_LIGHT_STATUS = 0x6b, + // Wan + LGTD_LIFX_CONNECT_PLAIN = 0xc9, + LGTD_LIFX_CONNECT_KEY = 0xca, + LGTD_LIFX_STATE_CONNECT = 0xcb, + LGTD_LIFX_SUB = 0xcc, + LGTD_LIFX_UNSUB = 0xcd, + LGTD_LIFX_STATE_SUB = 0xcd, + // Wifi LGTD_LIFX_GET_WIFI_STATE = 0x12d, LGTD_LIFX_SET_WIFI_STATE = 0x12e, LGTD_LIFX_WIFI_STATE = 0x12f, LGTD_LIFX_GET_ACCESS_POINTS = 0x130, LGTD_LIFX_SET_ACCESS_POINTS = 0x131, LGTD_LIFX_ACCESS_POINT = 0x132, + // Sensor + LGTD_LIFX_GET_AMBIENT_LIGHT = 0x191, + LGTD_LIFX_STATE_AMBIENT_LIGHT = 0x192, + LGTD_LIFX_GET_DIMMER_VOLTAGE = 0x193, + LGTD_LIFX_STATE_DIMMER_VOLTAGE = 0x194 }; enum { LGTD_LIFX_LABEL_SIZE = 32 }; @@ -278,6 +294,11 @@ struct lgtd_lifx_packet_runtime_info { uint64le_t uptime; uint64le_t downtime; }; + +struct lgtd_lifx_packet_ambient_light { + floatle_t illuminance; // lux +}; + #pragma pack(pop) enum { LGTD_LIFX_VENDOR_ID = 1 }; @@ -394,3 +415,4 @@ void lgtd_lifx_wire_decode_ip_state(struct lgtd_lifx_packet_ip_state *); void lgtd_lifx_wire_decode_ip_firmware_info(struct lgtd_lifx_packet_ip_firmware_info *); void lgtd_lifx_wire_decode_product_info(struct lgtd_lifx_packet_product_info *); void lgtd_lifx_wire_decode_runtime_info(struct lgtd_lifx_packet_runtime_info *); +void lgtd_lifx_wire_decode_ambient_light(struct lgtd_lifx_packet_ambient_light *); diff --git a/tests/lifx/gateway/test_gateway_handle_ambient_light.c b/tests/lifx/gateway/test_gateway_handle_ambient_light.c new file mode 100644 index 0000000..423ca6d --- /dev/null +++ b/tests/lifx/gateway/test_gateway_handle_ambient_light.c @@ -0,0 +1,37 @@ +#include + +#include "gateway.c" + +#include "mock_timer.h" +#include "test_gateway_utils.h" +#include "tests_utils.h" + +int +main(void) +{ + lgtd_lifx_wire_load_packet_info_map(); + + struct lgtd_lifx_gateway gw; + memset(&gw, 0, sizeof(gw)); + + struct lgtd_lifx_bulb *bulb = lgtd_tests_insert_mock_bulb(&gw, 42); + + struct lgtd_lifx_packet_header hdr; + memset(&hdr, 0, sizeof(hdr)); + memcpy( + &hdr.target.device_addr, &bulb->addr, sizeof(hdr.target.device_addr) + ); + + struct lgtd_lifx_packet_ambient_light pkt = { .illuminance = 3.14 }; + + lgtd_lifx_gateway_handle_ambient_light(&gw, &hdr, &pkt); + + if (bulb->ambient_light != pkt.illuminance) { + errx( + 1, "bulb->ambient_light = %f (expected %f)", + bulb->ambient_light, pkt.illuminance + ); + } + + return 0; +} diff --git a/tests/lifx/mock_gateway.h b/tests/lifx/mock_gateway.h index 923f82a..9790982 100644 --- a/tests/lifx/mock_gateway.h +++ b/tests/lifx/mock_gateway.h @@ -191,3 +191,15 @@ lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *gw, (void)pkt; } #endif + +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_AMBIENT_LIGHT +void +lgtd_lifx_gateway_handle_ambient_light(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const struct lgtd_lifx_packet_ambient_light *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif From ec9b07ac56a4d08dc7dddbcb68d4ebbc8d6305ab Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:56 -0700 Subject: [PATCH 098/181] Make the pipe/socket read loops fully resilient against incomplete json In other words you can send your request character by character with as much as white space as you want (and as allowed by JSON) and lightsd and it will still sort it out. Among some rework in the socket read loop one big fix was to call jsmn_init before each call to jsmn_parse. So not sure if jsmn is really working as explained in its documentation. This changeset also moves -DJSMN_STRICT=1 to the top-level CMakeLists.txt so that the tests and lightsd use the same code. --- CMakeLists.txt | 2 + core/CMakeLists.txt | 2 - core/client.c | 96 ++++---- core/jsonrpc.c | 8 +- core/pipe.c | 4 +- tests/core/client/CMakeLists.txt | 24 ++ tests/core/client/test_client_read_callback.c | 165 +++++++++++++ .../test_client_read_callback_extra_data.c | 225 ++++++++++++++++++ ...t_client_read_callback_multiple_requests.c | 210 ++++++++++++++++ ...test_client_read_callback_part_too_large.c | 205 ++++++++++++++++ ...est_client_read_callback_yield_on_eagain.c | 210 ++++++++++++++++ tests/core/client/tests_client_utils.h | 4 + .../jsonrpc/test_jsonrpc_build_target_list.c | 10 +- ...onrpc_type_float_between_0_and_1_invalid.c | 14 +- ...jsonrpc_type_float_between_0_and_1_valid.c | 12 +- ...rpc_type_float_between_0_and_360_invalid.c | 14 +- ...onrpc_type_float_between_0_and_360_valid.c | 20 +- .../core/jsonrpc/test_jsonrpc_type_integer.c | 8 +- ..._jsonrpc_type_integer_invalid_characters.c | 4 +- .../test_jsonrpc_type_integer_too_big.c | 4 +- .../test_jsonrpc_type_integer_too_small.c | 4 +- tests/core/mock_client_buf.h | 2 + tests/core/mock_event2.h | 77 ++++++ tests/core/mock_jsonrpc.h | 47 ++++ tests/core/pipe/test_pipe_close.c | 1 + tests/core/pipe/test_pipe_open.c | 1 + .../pipe/test_pipe_open_fifo_already_exists.c | 1 + tests/core/pipe/test_pipe_read_callback.c | 18 +- .../pipe/test_pipe_read_callback_extra_data.c | 25 +- ...est_pipe_read_callback_multiple_requests.c | 17 +- .../test_pipe_read_callback_yield_on_eagain.c | 17 +- tests/core/pipe/tests_pipe_utils.h | 9 - tests/core/tests_shims.c | 15 ++ 33 files changed, 1334 insertions(+), 141 deletions(-) create mode 100644 tests/core/client/CMakeLists.txt create mode 100644 tests/core/client/test_client_read_callback.c create mode 100644 tests/core/client/test_client_read_callback_extra_data.c create mode 100644 tests/core/client/test_client_read_callback_multiple_requests.c create mode 100644 tests/core/client/test_client_read_callback_part_too_large.c create mode 100644 tests/core/client/test_client_read_callback_yield_on_eagain.c create mode 100644 tests/core/client/tests_client_utils.h create mode 100644 tests/core/mock_jsonrpc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eae60e2..7ea2f35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,8 @@ ADD_DEFINITIONS( "-DLGTD_HAVE_LIBBSD=${HAVE_LIBBSD}" "-DLGTD_HAVE_PROCTITLE=${HAVE_PROCTITLE}" + + "-DJSMN_STRICT=1" ) IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 50524a2..6806fa0 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -10,8 +10,6 @@ INCLUDE_DIRECTORIES( CONFIGURE_FILE(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) -ADD_DEFINITIONS("-DJSMN_STRICT=1") - ADD_EXECUTABLE( lightsd client.c diff --git a/core/client.c b/core/client.c index 9fd8e2a..0355d48 100644 --- a/core/client.c +++ b/core/client.c @@ -70,59 +70,64 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) struct lgtd_client *client = ctx; struct evbuffer *input = bufferevent_get_input(bev); - size_t bufsz = evbuffer_get_contiguous_space(input); + size_t nbytes = evbuffer_get_contiguous_space(input); // Get the actual pointer to the beginning of the evbuf: - const char *buf = (char *)evbuffer_pullup(input, bufsz); - jsmnerr_t rv; - -retry_after_pullup: - rv = jsmn_parse( - &client->jsmn_ctx, - buf, - bufsz, - client->jsmn_tokens, - LGTD_ARRAY_SIZE(client->jsmn_tokens) - ); - switch (rv) { - case JSMN_ERROR_NOMEM: - lgtd_warnx( - "dropping client [%s]:%hu: request too big, not " - "enough parser tokens", client->ip_addr, client->port - ); - lgtd_client_close(client); - break; - case JSMN_ERROR_INVAL: - lgtd_warnx( - "dropping client [%s]:%hu: invalid json", - client->ip_addr, client->port + const char *buf = (char *)evbuffer_pullup(input, nbytes); + + do { + jsmn_init(&client->jsmn_ctx); + jsmnerr_t rv = jsmn_parse( + &client->jsmn_ctx, + buf, + nbytes, + client->jsmn_tokens, + LGTD_ARRAY_SIZE(client->jsmn_tokens) ); - // TODO: consume remaining data and send a proper error instead of - // closing the connection: - lgtd_client_close(client); - break; - case JSMN_ERROR_PART: - if (evbuffer_get_length(input) > LGTD_CLIENT_MAX_REQUEST_BUF_SIZE) { + switch (rv) { + case JSMN_ERROR_NOMEM: + case JSMN_ERROR_INVAL: lgtd_warnx( - "dropping client [%s]:%hu: request too big", + "client [%s]:%hu: request too big or invalid", client->ip_addr, client->port ); - lgtd_client_close(client); + evbuffer_drain(input, nbytes); + break; + case JSMN_ERROR_PART: +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" + case 0: +#pragma GCC diagnostic pop + (void)0; + size_t buflen = evbuffer_get_length(input); + if (buflen > LGTD_CLIENT_MAX_REQUEST_BUF_SIZE) { + lgtd_warnx( + "client [%s]:%hu: request too big or invalid", + client->ip_addr, client->port + ); + evbuffer_drain(input, buflen); + } else if (nbytes == buflen) { + return; // We pulled up everything already, wait for more data + } + break; + default: + client->json = buf; + lgtd_jsonrpc_dispatch_request(client, rv); + client->json = NULL; + size_t request_size = client->jsmn_tokens[0].end; + evbuffer_drain(input, request_size); + if (request_size < nbytes) { + buf += request_size; + nbytes -= request_size; + // FIXME: instead of calling jsmn_parse again, return the number + // of tokens consumed from jsonrpc and make this case a loop. + continue; + } break; - } else if (bufsz >= evbuffer_get_length(input)) { - break; // We pulled up everything already, wait for more data } // pullup and resume parsing: buf = (char *)evbuffer_pullup(input, -1); - bufsz = evbuffer_get_length(input); - goto retry_after_pullup; - default: - client->json = buf; - lgtd_jsonrpc_dispatch_request(client, rv); - client->json = NULL; - evbuffer_drain(input, bufsz); - jsmn_init(&client->jsmn_ctx); - break; - } + nbytes = evbuffer_get_contiguous_space(input); + } while (nbytes); } static void @@ -216,7 +221,6 @@ lgtd_client_open(evutil_socket_t peer, const struct sockaddr_storage *peer_addr) ); lgtd_sockaddrtoa(peer_addr, client->ip_addr, sizeof(client->ip_addr)); client->port = lgtd_sockaddrport(peer_addr); - jsmn_init(&client->jsmn_ctx); bufferevent_enable(client->io, EV_READ|EV_WRITE|EV_TIMEOUT); LIST_INSERT_HEAD(&lgtd_clients, client, link); @@ -232,6 +236,4 @@ lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) assert(pipe_client); memset(pipe_client, 0, sizeof(*pipe_client)); - - jsmn_init(&pipe_client->jsmn_ctx); } diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 4bc4219..337c1df 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -617,11 +617,16 @@ lgtd_jsonrpc_build_target_list(struct lgtd_proto_target_list *targets, assert(targets); assert(client); assert(target); - assert(target_ntokens >= 1); + + if (target_ntokens < 1) { + return false; + } if (lgtd_jsonrpc_type_array(target, client->json)) { target_ntokens -= 1; target++; + } else if (target_ntokens != 1) { + return false; } for (int ti = target_ntokens; ti--;) { @@ -1136,6 +1141,7 @@ lgtd_jsonrpc_dispatch_one(struct lgtd_client *client, } assert(request.method); + assert(request.request_ntokens); for (int i = 0; i != LGTD_ARRAY_SIZE(methods); i++) { int parsed_method_namelen = LGTD_JSONRPC_TOKEN_LEN(request.method); diff --git a/core/pipe.c b/core/pipe.c index d1f6718..4f747d6 100644 --- a/core/pipe.c +++ b/core/pipe.c @@ -94,7 +94,7 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) if (!drain) { next_request: - (void)0; + jsmn_init(&pipe->client.jsmn_ctx); const char *buf = (char *)evbuffer_pullup(pipe->read_buf, -1); ssize_t bufsz = evbuffer_get_length(pipe->read_buf); jsmnerr_t rv = jsmn_parse( @@ -127,7 +127,6 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) lgtd_jsonrpc_dispatch_request(&pipe->client, ntokens); pipe->client.json = NULL; - jsmn_init(&pipe->client.jsmn_ctx); int request_size = pipe->client.jsmn_tokens[0].end; evbuffer_drain(pipe->read_buf, request_size); if (request_size < bufsz) { @@ -141,7 +140,6 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) ssize_t bufsz = evbuffer_get_length(pipe->read_buf); evbuffer_drain(pipe->read_buf, bufsz); drain = false; - jsmn_init(&pipe->client.jsmn_ctx); } } diff --git a/tests/core/client/CMakeLists.txt b/tests/core/client/CMakeLists.txt new file mode 100644 index 0000000..9cf3f5c --- /dev/null +++ b/tests/core/client/CMakeLists.txt @@ -0,0 +1,24 @@ +INCLUDE_DIRECTORIES( + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +ADD_LIBRARY( + test_core_client STATIC + ${LIGHTSD_SOURCE_DIR}/core/jsmn.c + ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c +) + +TARGET_LINK_LIBRARIES(test_core_client ${TIME_MONOTONIC_LIBRARY}) + +FUNCTION(ADD_CLIENT_TEST TEST_SOURCE) + ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_client) +ENDFUNCTION() + +FILE(GLOB TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test_*.c") +FOREACH(TEST ${TESTS}) + ADD_CLIENT_TEST(${TEST}) +ENDFOREACH() diff --git a/tests/core/client/test_client_read_callback.c b/tests/core/client/test_client_read_callback.c new file mode 100644 index 0000000..ea919ee --- /dev/null +++ b/tests/core/client/test_client_read_callback.c @@ -0,0 +1,165 @@ +#include "client.c" + +#include "lifx/wire_proto.h" + +#include "mock_daemon.h" +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_DRAIN +#define MOCKED_EVBUFFER_GET_CONTIGUOUS_SPACE +#define MOCKED_BUFFEREVENT_GET_INPUT +#include "mock_event2.h" +#include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" +#include "mock_router.h" +#include "mock_timer.h" + +#include "tests_utils.h" +#include "tests_client_utils.h" + +static unsigned char request[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"get_light_state\"," + "\"params\": [\"*\"]," + "\"id\": 42" +"}"); + +static int +get_nbytes_read(int call_count) +{ + switch (call_count) { + case 0: + return sizeof(request) - 1; // we don't return the '\0' + default: + return 0; + } +} + +struct evbuffer * +bufferevent_get_input(struct bufferevent *bufev) +{ + (void)bufev; + + return FAKE_BUFFEREVENT_INPUT_BUF; +} + +static int evbuffer_get_contiguous_space_call_count = 0; + +size_t +evbuffer_get_contiguous_space(const struct evbuffer *buf) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx( + 1, "evbuffer_get_contiguous_space got buf %p (expected %p)", + buf, FAKE_BUFFEREVENT_INPUT_BUF + ); + } + + return get_nbytes_read(evbuffer_get_contiguous_space_call_count++); +} + +static int jsonrpc_dispatch_request_call_count = 0; + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + if (!parsed) { + errx(1, "number of parsed json tokens not passed in"); + } + + if (memcmp(client->json, request, sizeof(request))) { + errx(1, "got unexpected json"); + } + + if (jsonrpc_dispatch_request_call_count++) { + errx(1, "jsonrpc_dispatch_request should have been called once"); + } +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + switch (evbuffer_drain_call_count++) { + case 0: + if (len != sizeof(request) - 1) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)sizeof(request) - 1 + ); + } + break; + default: + errx(1, "evbuffer_drain should have been called once"); + break; + } + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + int offset; + switch (evbuffer_pullup_call_count++) { + case 0: + if (size != sizeof(request) - 1) { + errx( + 1, "got unexpected size %jd in pullup (expected %ju)", + (intmax_t)size, (uintmax_t)sizeof(request) - 1 + ); + } + offset = 0; + break; + case 1: + if (size != -1) { + errx( + 1, "got unexpected size %jd in pullup (expected -1)", + (intmax_t)size + ); + } + offset = sizeof(request) - 1; + break; + default: + errx(1, "evbuffer_pullup should have been called twice"); + } + + return &request[offset]; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + + if (!evbuffer_get_contiguous_space_call_count) { + errx(1, "evbuffer_get_contiguous_space not called"); + } + if (!jsonrpc_dispatch_request_call_count) { + errx(1, "jsonrpc_dispatch_request not called"); + } + if (!evbuffer_drain_call_count) { + errx(1, "evbuffer_drain not called"); + } + if (!evbuffer_pullup_call_count) { + errx(1, "evbuffer_pullup not called"); + } + + return 0; +} diff --git a/tests/core/client/test_client_read_callback_extra_data.c b/tests/core/client/test_client_read_callback_extra_data.c new file mode 100644 index 0000000..c3b4a66 --- /dev/null +++ b/tests/core/client/test_client_read_callback_extra_data.c @@ -0,0 +1,225 @@ +#include "client.c" + +#include "lifx/wire_proto.h" + +#include "mock_daemon.h" +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_DRAIN +#define MOCKED_EVBUFFER_GET_CONTIGUOUS_SPACE +#define MOCKED_BUFFEREVENT_GET_INPUT +#include "mock_event2.h" +#include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" +#include "mock_router.h" +#include "mock_timer.h" + +#include "tests_utils.h" +#include "tests_client_utils.h" + +static unsigned char request[] = ( + "lollllllllll \n\n\n" + "{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"get_light_state\"," + "\"params\": [\"*\"]," + "\"id\": 42" + "}HALP HALP \\_o< O)))" +); + +enum { GARBAGE_BEFORE_REQUEST_LEN = sizeof("lollllllllll \n\n\n") - 1 }; +enum { + REQUEST_LEN = sizeof("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"get_light_state\"," + "\"params\": [\"*\"]," + "\"id\": 42" + "}") - 1 +}; +enum { GARBAGE_AFTER_REQUEST_LEN = sizeof("HALP HALP \\_o< O)))") - 1 }; + +static int +get_nbytes_read(int call_count) +{ + switch (call_count) { + case 0: + return GARBAGE_BEFORE_REQUEST_LEN; + case 1: + return REQUEST_LEN; + case 2: + return GARBAGE_AFTER_REQUEST_LEN; + default: + return 0; + } +} + +struct evbuffer * +bufferevent_get_input(struct bufferevent *bufev) +{ + (void)bufev; + + return FAKE_BUFFEREVENT_INPUT_BUF; +} + +static int evbuffer_get_contiguous_space_call_count = 0; + +size_t +evbuffer_get_contiguous_space(const struct evbuffer *buf) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx( + 1, "evbuffer_get_contiguous_space got buf %p (expected %p)", + buf, FAKE_BUFFEREVENT_INPUT_BUF + ); + } + + return get_nbytes_read(evbuffer_get_contiguous_space_call_count++); +} + +static int jsonrpc_dispatch_request_call_count = 0; + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + if (!parsed) { + errx(1, "number of parsed json tokens not passed in"); + } + + bool diff = memcmp( + client->json, &request[GARBAGE_BEFORE_REQUEST_LEN], REQUEST_LEN + ); + if (diff) { + errx(1, "got unexpected json"); + } + + if (jsonrpc_dispatch_request_call_count++) { + errx(1, "jsonrpc_dispatch_request should have been called once"); + } +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + switch (evbuffer_drain_call_count++) { + case 0: + if (len != GARBAGE_BEFORE_REQUEST_LEN) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)GARBAGE_BEFORE_REQUEST_LEN + ); + } + break; + case 1: + if (len != REQUEST_LEN) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)REQUEST_LEN + ); + } + break; + case 2: + if (len != GARBAGE_AFTER_REQUEST_LEN) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)REQUEST_LEN + ); + } + break; + default: + errx( + 1, "evbuffer_drain shouldn't have been called %d times", + evbuffer_drain_call_count + ); + } + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + int offset; + switch (evbuffer_pullup_call_count++) { + case 0: + if (size != GARBAGE_BEFORE_REQUEST_LEN) { + errx( + 1, "trying to pullup %ju bytes (expected %ju)", + (intmax_t)size, (uintmax_t)GARBAGE_BEFORE_REQUEST_LEN + ); + } + offset = 0; + break; + case 1: + if (size != -1) { + errx( + 1, "got unexpected size %jd in pullup (expected -1)", + (intmax_t)size + ); + } + offset = GARBAGE_BEFORE_REQUEST_LEN; + break; + case 2: + if (size != -1) { + errx( + 1, "got unexpected size %jd in pullup (expected -1)", + (intmax_t)size + ); + } + offset = GARBAGE_BEFORE_REQUEST_LEN + REQUEST_LEN; + break; + case 3: + if (size != -1) { + errx( + 1, "got unexpected size %jd in pullup (expected -1)", + (intmax_t)size + ); + } + offset = sizeof(request) - 1; + break; + default: + errx( + 1, "evbuffer_pullup shouldn't have been called %d times", + evbuffer_pullup_call_count + ); + } + + return &request[offset]; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + + if (!evbuffer_get_contiguous_space_call_count) { + errx(1, "evbuffer_get_contiguous_space not called"); + } + if (!jsonrpc_dispatch_request_call_count) { + errx(1, "jsonrpc_dispatch_request not called"); + } + if (!evbuffer_drain_call_count) { + errx(1, "evbuffer_drain not called"); + } + if (!evbuffer_pullup_call_count) { + errx(1, "evbuffer_pullup not called"); + } + + return 0; +} diff --git a/tests/core/client/test_client_read_callback_multiple_requests.c b/tests/core/client/test_client_read_callback_multiple_requests.c new file mode 100644 index 0000000..7c5f492 --- /dev/null +++ b/tests/core/client/test_client_read_callback_multiple_requests.c @@ -0,0 +1,210 @@ +#include "client.c" + +#include "lifx/wire_proto.h" + +#include "mock_daemon.h" +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_DRAIN +#define MOCKED_EVBUFFER_GET_CONTIGUOUS_SPACE +#define MOCKED_BUFFEREVENT_GET_INPUT +#include "mock_event2.h" +#include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" +#include "mock_router.h" +#include "mock_timer.h" + +#include "tests_utils.h" +#include "tests_client_utils.h" + +#define REQUEST_1 "{" \ + "\"jsonrpc\": \"2.0\"," \ + "\"method\": \"get_light_state\"," \ + "\"params\": [\"*\"]," \ + "\"id\": 42" \ +"}" +#define REQUEST_1_LEN (sizeof(REQUEST_1) - 1) + +#define REQUEST_2 "{" \ + "\"jsonrpc\": \"2.0\"," \ + "\"method\": \"power_on\"," \ + "\"params\": [\"*\"]," \ + "\"id\": 43" \ +"}" +#define REQUEST_2_LEN (sizeof(REQUEST_2) - 1) + +static unsigned char request[] = ( + REQUEST_1 + REQUEST_2 +); + +#define REQUEST_LEN (REQUEST_1_LEN + REQUEST_2_LEN) + +static int +get_nbytes_read(int call_count) +{ + switch (call_count) { + case 0: + return REQUEST_LEN; + default: + return 0; + } +} + +struct evbuffer * +bufferevent_get_input(struct bufferevent *bufev) +{ + (void)bufev; + + return FAKE_BUFFEREVENT_INPUT_BUF; +} + +static int evbuffer_get_contiguous_space_call_count = 0; + +size_t +evbuffer_get_contiguous_space(const struct evbuffer *buf) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx( + 1, "evbuffer_get_contiguous_space got buf %p (expected %p)", + buf, FAKE_BUFFEREVENT_INPUT_BUF + ); + } + + return get_nbytes_read(evbuffer_get_contiguous_space_call_count++); +} + +static int jsonrpc_dispatch_request_call_count = 0; + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + if (!parsed) { + errx(1, "number of parsed json tokens not passed in"); + } + + bool diff; + switch (jsonrpc_dispatch_request_call_count++) { + case 0: + diff = memcmp(client->json, REQUEST_1, REQUEST_1_LEN); + if (diff) { + errx( + 1, "got unexpected json %s (expected) %s", + client->json, REQUEST_1 + ); + } + break; + case 1: + diff = memcmp(client->json, REQUEST_2, REQUEST_2_LEN); + if (diff) { + errx( + 1, "got unexpected json %s (expected) %s", + client->json, REQUEST_2 + ); + } + break; + default: + errx(1, "jsonrpc_dispatch_request should have been called twice"); + } +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + switch (evbuffer_drain_call_count++) { + case 0: + if (len != REQUEST_1_LEN) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)REQUEST_1_LEN + ); + } + break; + case 1: + if (len != REQUEST_2_LEN) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)REQUEST_2_LEN + ); + } + break; + default: + errx( + 1, "evbuffer_drain shouldn't have been called %d times", + evbuffer_drain_call_count + ); + } + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + int offset; + switch (evbuffer_pullup_call_count++) { + case 0: + if (size != REQUEST_LEN) { + errx( + 1, "trying to pullup %ju bytes (expected %ju)", + (intmax_t)size, (uintmax_t)REQUEST_LEN + ); + } + offset = 0; + break; + case 1: + if (size != -1) { + errx( + 1, "got unexpected size %jd in pullup (expected -1)", + (intmax_t)size + ); + } + offset = REQUEST_LEN; + break; + default: + errx( + 1, "evbuffer_pullup shouldn't have been called %d times", + evbuffer_pullup_call_count + ); + } + + return &request[offset]; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + + if (!evbuffer_get_contiguous_space_call_count) { + errx(1, "evbuffer_get_contiguous_space not called"); + } + if (!jsonrpc_dispatch_request_call_count) { + errx(1, "jsonrpc_dispatch_request not called"); + } + if (!evbuffer_drain_call_count) { + errx(1, "evbuffer_drain not called"); + } + if (!evbuffer_pullup_call_count) { + errx(1, "evbuffer_pullup not called"); + } + + return 0; +} diff --git a/tests/core/client/test_client_read_callback_part_too_large.c b/tests/core/client/test_client_read_callback_part_too_large.c new file mode 100644 index 0000000..563f608 --- /dev/null +++ b/tests/core/client/test_client_read_callback_part_too_large.c @@ -0,0 +1,205 @@ +#include "client.c" + +#include "lifx/wire_proto.h" + +#include "mock_daemon.h" +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_DRAIN +#define MOCKED_EVBUFFER_GET_CONTIGUOUS_SPACE +#define MOCKED_EVBUFFER_GET_LENGTH +#define MOCKED_BUFFEREVENT_GET_INPUT +#include "mock_event2.h" +#include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" +#include "mock_router.h" +#include "mock_timer.h" + +#include "tests_utils.h" +#include "tests_client_utils.h" + +static unsigned char request[] = ("{" + "\"id\": \"verylongidyooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\"," + "\"jsonrpc\": \"2.0\"," + "\"method\": \"get_light_state\"," + "\"params\": [\"*\"]" +"}"); + +#define REQUEST_LEN (sizeof(request) - 1) +#define PART_1_LEN ((LGTD_CLIENT_MAX_REQUEST_BUF_SIZE) + 1) +#define PART_2_LEN ((REQUEST_LEN) - (PART_1_LEN)) + +static int +get_nbytes_read(int call_count) +{ + switch (call_count) { + case 0: + return PART_1_LEN; + case 1: + return PART_2_LEN; + default: + return 0; + } +} + +static int evbuffer_get_length_call_count = 0; + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx( + 1, "evbuffer_get_length got buf %p (expected %p)", + buf, FAKE_BUFFEREVENT_INPUT_BUF + ); + } + + if (evbuffer_get_length_call_count) { + errx(1, "evbuffer_get_length should have been called once"); + } + + return get_nbytes_read(evbuffer_get_length_call_count++); +} + +struct evbuffer * +bufferevent_get_input(struct bufferevent *bufev) +{ + (void)bufev; + + return FAKE_BUFFEREVENT_INPUT_BUF; +} + +static int evbuffer_get_contiguous_space_call_count = 0; + +size_t +evbuffer_get_contiguous_space(const struct evbuffer *buf) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx( + 1, "evbuffer_get_contiguous_space got buf %p (expected %p)", + buf, FAKE_BUFFEREVENT_INPUT_BUF + ); + } + + return get_nbytes_read(evbuffer_get_contiguous_space_call_count++); +} + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + errx(1, "No request should have been dispatched"); +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + switch (evbuffer_drain_call_count++) { + case 0: + if (len != PART_1_LEN) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)PART_1_LEN + ); + } + break; + case 1: + if (len != PART_2_LEN) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)PART_2_LEN + ); + } + break; + default: + errx( + 1, "evbuffer_drain shouldn't have been called %d times", + evbuffer_drain_call_count + ); + } + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + int offset; + switch (evbuffer_pullup_call_count++) { + case 0: + if (size != PART_1_LEN) { + errx( + 1, "trying to pullup %ju bytes (expected %ju)", + (intmax_t)size, (uintmax_t)PART_1_LEN + ); + } + offset = 0; + break; + case 1: + if (size != -1) { + errx( + 1, "trying to pullup %ju bytes (expected %ju)", + (intmax_t)size, (uintmax_t)-1 + ); + } + offset = PART_1_LEN; + break; + case 2: + if (size != -1) { + errx(1, "trying to pullup %ju bytes (expected -1)", (intmax_t)size); + } + offset = REQUEST_LEN; + break; + default: + errx( + 1, "evbuffer_pullup shouldn't have been called %d times", + evbuffer_pullup_call_count + ); + } + + return &request[offset]; +} + +int +main(void) +{ + if (LGTD_CLIENT_MAX_REQUEST_BUF_SIZE >= REQUEST_LEN) { + errx( + 1, "Please adjust this test to copy with the new value of " + "LGTD_CLIENT_MAX_REQUEST_BUF_SIZE" + ); + } + + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + + if (!evbuffer_get_contiguous_space_call_count) { + errx(1, "evbuffer_get_contiguous_space not called"); + } + if (!evbuffer_drain_call_count) { + errx(1, "evbuffer_drain not called"); + } + if (!evbuffer_pullup_call_count) { + errx(1, "evbuffer_pullup not called"); + } + if (!evbuffer_get_length_call_count) { + errx(1, "evbuffer_get_length not called"); + } + + return 0; +} diff --git a/tests/core/client/test_client_read_callback_yield_on_eagain.c b/tests/core/client/test_client_read_callback_yield_on_eagain.c new file mode 100644 index 0000000..194ead3 --- /dev/null +++ b/tests/core/client/test_client_read_callback_yield_on_eagain.c @@ -0,0 +1,210 @@ +#include "client.c" + +#include "lifx/wire_proto.h" + +#include "mock_daemon.h" +#define MOCKED_EVBUFFER_PULLUP +#define MOCKED_EVBUFFER_DRAIN +#define MOCKED_EVBUFFER_GET_CONTIGUOUS_SPACE +#define MOCKED_EVBUFFER_GET_LENGTH +#define MOCKED_BUFFEREVENT_GET_INPUT +#include "mock_event2.h" +#include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" +#include "mock_router.h" +#include "mock_timer.h" + +#include "tests_utils.h" +#include "tests_client_utils.h" + +static unsigned char request[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"get_light_state\"," + "\"params\": [\"*\"]," + "\"id\": 42" +"}"); + +#define REQUEST_LEN (sizeof(request) - 1) +#define PART_1_LEN 16 +#define PART_2_LEN (REQUEST_LEN - PART_1_LEN) + +static int +get_nbytes_read(int call_count) +{ + switch (call_count) { + case 0: + return PART_1_LEN; + case 1: + return REQUEST_LEN; + default: + return 0; + } +} + +static int evbuffer_get_length_call_count = 0; + +size_t +evbuffer_get_length(const struct evbuffer *buf) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx( + 1, "evbuffer_get_length got buf %p (expected %p)", + buf, FAKE_BUFFEREVENT_INPUT_BUF + ); + } + + if (evbuffer_get_length_call_count++ == 2) { + errx(1, "evbuffer_get_length should have been called twice"); + } + + return PART_1_LEN; +} + +struct evbuffer * +bufferevent_get_input(struct bufferevent *bufev) +{ + (void)bufev; + + return FAKE_BUFFEREVENT_INPUT_BUF; +} + +static int evbuffer_get_contiguous_space_call_count = 0; + +size_t +evbuffer_get_contiguous_space(const struct evbuffer *buf) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx( + 1, "evbuffer_get_contiguous_space got buf %p (expected %p)", + buf, FAKE_BUFFEREVENT_INPUT_BUF + ); + } + + return get_nbytes_read(evbuffer_get_contiguous_space_call_count++); +} + +static int jsonrpc_dispatch_request_call_count = 0; + +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; + + if (!parsed) { + errx(1, "number of parsed json tokens not passed in"); + } + + switch (jsonrpc_dispatch_request_call_count++) { + case 0: + if (memcmp(client->json, request, REQUEST_LEN)) { + errx( + 1, "got unexpected json %s (expected) %s", client->json, request + ); + } + break; + default: + errx(1, "jsonrpc_dispatch_request should have been called once"); + } +} + +static int evbuffer_drain_call_count = 0; + +int +evbuffer_drain(struct evbuffer *buf, size_t len) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + switch (evbuffer_drain_call_count++) { + case 0: + if (len != REQUEST_LEN) { + errx( + 1, "trying to drain %ju bytes (expected %ju)", + (uintmax_t)len, (uintmax_t)REQUEST_LEN + ); + } + break; + default: + errx( + 1, "evbuffer_drain shouldn't have been called %d times", + evbuffer_drain_call_count + ); + } + + return 0; +} + +static int evbuffer_pullup_call_count = 0; + +unsigned char * +evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) +{ + if (buf != FAKE_BUFFEREVENT_INPUT_BUF) { + errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); + } + + int offset; + switch (evbuffer_pullup_call_count++) { + case 0: + if (size != PART_1_LEN) { + errx( + 1, "trying to pullup %ju bytes (expected %ju)", + (intmax_t)size, (uintmax_t)PART_1_LEN + ); + } + offset = 0; + break; + case 1: + if (size != REQUEST_LEN) { + errx( + 1, "trying to pullup %ju bytes (expected %ju)", + (intmax_t)size, (uintmax_t)REQUEST_LEN + ); + } + offset = 0; + break; + case 2: + if (size != -1) { + errx(1, "trying to pullup %ju bytes (expected -1)", (intmax_t)size); + } + offset = REQUEST_LEN; + break; + default: + errx( + 1, "evbuffer_pullup shouldn't have been called %d times", + evbuffer_pullup_call_count + ); + } + + return &request[offset]; +} + +int +main(void) +{ + struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + + lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + + if (!evbuffer_get_contiguous_space_call_count) { + errx(1, "evbuffer_get_contiguous_space not called"); + } + if (!jsonrpc_dispatch_request_call_count) { + errx(1, "jsonrpc_dispatch_request not called"); + } + if (!evbuffer_drain_call_count) { + errx(1, "evbuffer_drain not called"); + } + if (!evbuffer_pullup_call_count) { + errx(1, "evbuffer_pullup not called"); + } + if (!evbuffer_get_length_call_count) { + errx(1, "evbuffer_get_length not called"); + } + + return 0; +} diff --git a/tests/core/client/tests_client_utils.h b/tests/core/client/tests_client_utils.h new file mode 100644 index 0000000..1c859a3 --- /dev/null +++ b/tests/core/client/tests_client_utils.h @@ -0,0 +1,4 @@ +#pragma once + +#define FAKE_BUFFEREVENT (void *)0xfeed +#define FAKE_BUFFEREVENT_INPUT_BUF (void *)3412 diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c index 69ba33d..d870cab 100644 --- a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c +++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c @@ -53,14 +53,18 @@ main(void) test_params("[\"on\", 12345, \"6789\"]", expected_1); const char *expected_2[] = {"#tower", NULL}; - test_params("#tower", expected_2); + test_params("\"#tower\"", expected_2); test_params("{\"key\": 42}", NULL); - test_params("null", NULL); - const char *expected_3[] = {NULL}; test_params("[]", expected_3); + test_params("[\"on\", {\"lol\": \"wut\"}, \"6789\"]", NULL); + + // and make sure nothing blows up on plain invalid json/parameters + // (jsmn_parse will return a negative value): + test_params("null", NULL); + return 0; } diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c index 10c016a..e04c4f4 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c @@ -9,7 +9,7 @@ test_float(const char *json) { jsmntok_t tokens[8]; parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); - if (lgtd_jsonrpc_type_float_between_0_and_1(tokens, json)) { + if (lgtd_jsonrpc_type_float_between_0_and_1(&tokens[1], json)) { errx(1, "%s was considered as a valid float >= 0 and <= 1", json); } } @@ -17,12 +17,12 @@ test_float(const char *json) int main(void) { - test_float("1.1234"); - test_float("-0.1234"); - test_float("1.00000001"); - test_float("2.0000"); - test_float("10"); - test_float("0.0.1"); + test_float("[1.1234]"); + test_float("[-0.1234]"); + test_float("[1.00000001]"); + test_float("[2.0000]"); + test_float("[10]"); + test_float("[0.0.1]"); return 0; } diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c index 8b83b73..2fe6e5a 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c @@ -9,7 +9,7 @@ test_float(const char *json) { jsmntok_t tokens[8]; parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); - if (!lgtd_jsonrpc_type_float_between_0_and_1(tokens, json)) { + if (!lgtd_jsonrpc_type_float_between_0_and_1(&tokens[1], json)) { errx(1, "%s wasn't considered as a valid float >= 0 and <= 1", json); } } @@ -17,11 +17,11 @@ test_float(const char *json) int main(void) { - test_float("0.1234"); - test_float("1.0000000"); - test_float("0.9999"); - test_float("0.01"); - test_float("000.01"); + test_float("[0.1234]"); + test_float("[1.0000000]"); + test_float("[0.9999]"); + test_float("[0.01]"); + test_float("[000.01]"); return 0; } diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c index 6ce5f8b..625da59 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c @@ -9,7 +9,7 @@ test_float(const char *json) { jsmntok_t tokens[8]; parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); - if (lgtd_jsonrpc_type_float_between_0_and_360(tokens, json)) { + if (lgtd_jsonrpc_type_float_between_0_and_360(&tokens[1], json)) { errx(1, "%s was considered as a valid float >= 0 and <= 360", json); } } @@ -17,12 +17,12 @@ test_float(const char *json) int main(void) { - test_float("-1.1234"); - test_float("-0.1234"); - test_float("0.1.234"); - test_float("0.1a234"); - test_float("360a"); - test_float("360.1"); + test_float("[-1.1234]"); + test_float("[-0.1234]"); + test_float("[0.1.234]"); + test_float("[0.1a234]"); + test_float("[360a]"); + test_float("[360.1]"); return 0; } diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c index d524e3b..5eafeb6 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c @@ -9,7 +9,7 @@ test_float(const char *json) { jsmntok_t tokens[8]; parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, strlen(json)); - if (!lgtd_jsonrpc_type_float_between_0_and_360(tokens, json)) { + if (!lgtd_jsonrpc_type_float_between_0_and_360(&tokens[1], json)) { errx(1, "%s wasn't considered as a valid float >= 0 and <= 360", json); } } @@ -17,15 +17,15 @@ test_float(const char *json) int main(void) { - test_float("1.1234"); - test_float("1.00000001"); - test_float("2.0000"); - test_float("10"); - test_float("0.1"); - test_float("0"); - test_float("231."); - test_float("359.1"); - test_float("360"); + test_float("[1.1234]"); + test_float("[1.00000001]"); + test_float("[2.0000]"); + test_float("[10]"); + test_float("[0.1]"); + test_float("[0]"); + test_float("[231.]"); + test_float("[359.1]"); + test_float("[360]"); return 0; } diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer.c b/tests/core/jsonrpc/test_jsonrpc_type_integer.c index 64df2fa..3de2c1c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer.c @@ -7,12 +7,14 @@ int main(void) { - const char *json = "1234"; + const char json[] = "[1234]"; jsmntok_t tokens[8]; - parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); + int rv = parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); - bool ok = lgtd_jsonrpc_type_integer(tokens, json); + printf("rv = %d\n", rv); + + bool ok = lgtd_jsonrpc_type_integer(&tokens[1], json); if (!ok) { errx(1, "%s wasn't considered as a valid integer", json); diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c index b0943cc..9d4da65 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c @@ -7,12 +7,12 @@ int main(void) { - const char *json = "-1a"; + const char json[] = "[-1a]"; jsmntok_t tokens[8]; parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); - bool ok = lgtd_jsonrpc_type_integer(tokens, json); + bool ok = lgtd_jsonrpc_type_integer(&tokens[1], json); if (ok) { errx(1, "%s wasn't considered invalid", json); diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c index b38e993..a0a4999 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c @@ -7,12 +7,12 @@ int main(void) { - const char *json = "9999999999"; + const char json[] = "[99999999999999999999999]"; jsmntok_t tokens[8]; parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); - bool ok = lgtd_jsonrpc_type_integer(tokens, json); + bool ok = lgtd_jsonrpc_type_integer(&tokens[1], json); if (ok) { errx(1, "%s wasn't considered invalid", json); diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c index 5775e48..ef39326 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c @@ -7,12 +7,12 @@ int main(void) { - const char *json = "-9999999999"; + const char json[] = "[-999999999999999999999]"; jsmntok_t tokens[8]; parse_json(tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json)); - bool ok = lgtd_jsonrpc_type_integer(tokens, json); + bool ok = lgtd_jsonrpc_type_integer(&tokens[1], json); if (ok) { errx(1, "%s wasn't considered invalid", json); diff --git a/tests/core/mock_client_buf.h b/tests/core/mock_client_buf.h index 19fa440..5dc2754 100644 --- a/tests/core/mock_client_buf.h +++ b/tests/core/mock_client_buf.h @@ -1,5 +1,7 @@ #pragma once +#define MOCKED_BUFFEREVENT_WRITE + static char client_write_buf[4096] = { 0 }; static int client_write_buf_idx = 0; diff --git a/tests/core/mock_event2.h b/tests/core/mock_event2.h index 8af5187..9af79dd 100644 --- a/tests/core/mock_event2.h +++ b/tests/core/mock_event2.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #define MOCK_EVENT_NEW_EVENT_PTR ((void *)0xdadadada) @@ -60,6 +61,15 @@ evbuffer_read(struct evbuffer *buffer, evutil_socket_t fd, int howmuch) } #endif +#ifndef MOCKED_EVBUFFER_GET_CONTIGUOUS_SPACE +size_t +evbuffer_get_contiguous_space(const struct evbuffer *buf) +{ + (void)buf; + return 0; +} +#endif + #ifndef MOCKED_EVENT_ADD int event_add(struct event *ev, const struct timeval *timeout) @@ -133,3 +143,70 @@ evutil_make_socket_nonblocking(evutil_socket_t fd) return 0; } #endif + +#ifndef MOCKED_BUFFEREVENT_GET_INPUT +struct evbuffer * +bufferevent_get_input(struct bufferevent *bufev) +{ + (void)bufev; + return NULL; +} +#endif + +#ifndef MOCKED_BUFFEREVENT_ENABLE +int +bufferevent_enable(struct bufferevent *bufev, short event) +{ + (void)bufev; + (void)event; + return 0; +} +#endif + +#ifndef MOCKED_BUFFEREVENT_FREE +void +bufferevent_free(struct bufferevent *bufev) +{ + (void)bufev; +} +#endif + +#ifndef MOCKED_BUFFEREVENT_SETCB +void +bufferevent_setcb(struct bufferevent *bufev, + bufferevent_data_cb readcb, + bufferevent_data_cb writecb, + bufferevent_event_cb eventcb, + void *cbarg) +{ + (void)bufev; + (void)readcb; + (void)writecb; + (void)eventcb; + (void)cbarg; +} +#endif + +#ifndef MOCKED_BUFFEREVENT_SOCKET_NEW +struct bufferevent * +bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options) +{ + (void)base; + (void)fd; + (void)options; + return NULL; +} +#endif + +#ifndef MOCKED_BUFFEREVENT_WRITE +int +bufferevent_write(struct bufferevent *bufev, + const void *data, + size_t size) +{ + (void)bufev; + (void)data; + (void)size; + return 0; +} +#endif diff --git a/tests/core/mock_jsonrpc.h b/tests/core/mock_jsonrpc.h new file mode 100644 index 0000000..5558411 --- /dev/null +++ b/tests/core/mock_jsonrpc.h @@ -0,0 +1,47 @@ +#pragma once + +#ifndef MOCKED_JSONRPC_DISPATCH_REQUEST +void +lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) +{ + (void)client; + (void)parsed; +} +#endif + +#ifndef MOCKED_JSONRPC_SEND_ERROR +void +lgtd_jsonrpc_send_error(struct lgtd_client *client, + enum lgtd_jsonrpc_error_code code, + const char *msg) +{ + (void)client; + (void)code; + (void)msg; +} +#endif + +#ifndef MOCKED_JSONRPC_SEND_RESPONSE +void +lgtd_jsonrpc_send_response(struct lgtd_client *client, const char *msg) +{ + (void)client; + (void)msg; +} +#endif + +#ifndef MOCKED_JSONRPC_START_SEND_RESPONSE +void +lgtd_jsonrpc_start_send_response(struct lgtd_client *client) +{ + (void)client; +} +#endif + +#ifndef MOCKED_JSONRPC_END_SEND_RESPONSE +void +lgtd_jsonrpc_end_send_response(struct lgtd_client *client) +{ + (void)client; +} +#endif diff --git a/tests/core/pipe/test_pipe_close.c b/tests/core/pipe/test_pipe_close.c index c8d3257..5bd771c 100644 --- a/tests/core/pipe/test_pipe_close.c +++ b/tests/core/pipe/test_pipe_close.c @@ -13,6 +13,7 @@ #define MOCKED_EVENT_FREE #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_jsonrpc.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_open.c b/tests/core/pipe/test_pipe_open.c index 8dbe921..19d17a2 100644 --- a/tests/core/pipe/test_pipe_open.c +++ b/tests/core/pipe/test_pipe_open.c @@ -12,6 +12,7 @@ #define MOCKED_EVENT_ADD #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_jsonrpc.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_open_fifo_already_exists.c b/tests/core/pipe/test_pipe_open_fifo_already_exists.c index 17c32bf..b563d83 100644 --- a/tests/core/pipe/test_pipe_open_fifo_already_exists.c +++ b/tests/core/pipe/test_pipe_open_fifo_already_exists.c @@ -12,6 +12,7 @@ #define MOCKED_EVENT_ADD #include "mock_event2.h" #include "mock_gateway.h" +#include "mock_jsonrpc.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_read_callback.c b/tests/core/pipe/test_pipe_read_callback.c index e6b5f70..aebb185 100644 --- a/tests/core/pipe/test_pipe_read_callback.c +++ b/tests/core/pipe/test_pipe_read_callback.c @@ -15,11 +15,12 @@ #define MOCKED_EVBUFFER_DRAIN #include "mock_event2.h" #include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" #include "mock_router.h" #include "mock_timer.h" #include "tests_utils.h" -#define MOCKED_JSONRPC_DISPATCH_REQUEST #include "tests_pipe_utils.h" static unsigned char request[] = ("{" @@ -108,14 +109,6 @@ evbuffer_drain(struct evbuffer *buf, size_t len) errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); } - jsmn_parser jsmn_ctx; - jsmn_init(&jsmn_ctx); - struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); - if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { - errx(1, "the client json parser context wasn't re-initialized"); - } - - switch (evbuffer_drain_call_count) { case 0: if (len != sizeof(request) - 1) { @@ -148,6 +141,13 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) ); } + jsmn_parser jsmn_ctx; + jsmn_init(&jsmn_ctx); + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { + errx(1, "the client json parser context wasn't re-initialized"); + } + return &request[evbuffer_pullup_call_count++ ? sizeof(request) - 1 : 0]; } diff --git a/tests/core/pipe/test_pipe_read_callback_extra_data.c b/tests/core/pipe/test_pipe_read_callback_extra_data.c index 0692757..b90131a 100644 --- a/tests/core/pipe/test_pipe_read_callback_extra_data.c +++ b/tests/core/pipe/test_pipe_read_callback_extra_data.c @@ -14,11 +14,12 @@ #define MOCKED_EVBUFFER_DRAIN #include "mock_event2.h" #include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" #include "mock_router.h" #include "mock_timer.h" #include "tests_utils.h" -#define MOCKED_JSONRPC_DISPATCH_REQUEST #include "tests_pipe_utils.h" #define REQUEST_1 "{" \ @@ -103,27 +104,20 @@ evbuffer_drain(struct evbuffer *buf, size_t len) errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); } - jsmn_parser jsmn_ctx; - jsmn_init(&jsmn_ctx); - struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); - if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { - errx(1, "the client json parser context wasn't re-initialized"); - } - switch (evbuffer_drain_call_count) { case 0: - if (len != sizeof(REQUEST_1) - 1) { + if (len != sizeof(request) - sizeof(REQUEST_1)) { errx( 1, "trying to drain %ju bytes (expected %ju)", - (uintmax_t)len, (uintmax_t)sizeof(request) - 1 + (uintmax_t)len, (uintmax_t)(sizeof(request) - sizeof(REQUEST_1)) ); } break; case 1: - if (len != sizeof(request) - sizeof(REQUEST_1)) { + if (len != sizeof(REQUEST_1) - 1) { errx( 1, "trying to drain %ju bytes (expected %ju)", - (uintmax_t)len, (uintmax_t)(sizeof(request) - sizeof(REQUEST_1)) + (uintmax_t)len, (uintmax_t)sizeof(request) - 1 ); } break; @@ -150,6 +144,13 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) ); } + jsmn_parser jsmn_ctx; + jsmn_init(&jsmn_ctx); + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { + errx(1, "the client json parser context wasn't re-initialized"); + } + return &request[evbuffer_pullup_call_count++ ? sizeof(request) - 1 : 0]; } diff --git a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c index 1c39a1a..843de32 100644 --- a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c +++ b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c @@ -14,11 +14,12 @@ #define MOCKED_EVBUFFER_DRAIN #include "mock_event2.h" #include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" #include "mock_router.h" #include "mock_timer.h" #include "tests_utils.h" -#define MOCKED_JSONRPC_DISPATCH_REQUEST #include "tests_pipe_utils.h" #define REQUEST_1 "{" \ @@ -119,13 +120,6 @@ evbuffer_drain(struct evbuffer *buf, size_t len) errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); } - jsmn_parser jsmn_ctx; - jsmn_init(&jsmn_ctx); - struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); - if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { - errx(1, "the client json parser context wasn't re-initialized"); - } - switch (evbuffer_drain_call_count) { case 0: if (len != sizeof(REQUEST_1) - 1) { @@ -167,6 +161,13 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) ); } + jsmn_parser jsmn_ctx; + jsmn_init(&jsmn_ctx); + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { + errx(1, "the client json parser context wasn't re-initialized"); + } + int offset; switch (evbuffer_pullup_call_count) { case 0: diff --git a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c index 3bce598..6453153 100644 --- a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c +++ b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c @@ -14,11 +14,12 @@ #define MOCKED_EVBUFFER_DRAIN #include "mock_event2.h" #include "mock_gateway.h" +#define MOCKED_JSONRPC_DISPATCH_REQUEST +#include "mock_jsonrpc.h" #include "mock_router.h" #include "mock_timer.h" #include "tests_utils.h" -#define MOCKED_JSONRPC_DISPATCH_REQUEST #include "tests_pipe_utils.h" #define REQUEST_1 "{" \ @@ -119,13 +120,6 @@ evbuffer_drain(struct evbuffer *buf, size_t len) errx(1, "got unexpected buf %p (expected %p)", buf, (void *)2); } - jsmn_parser jsmn_ctx; - jsmn_init(&jsmn_ctx); - struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); - if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { - errx(1, "the client json parser context wasn't re-initialized"); - } - switch (evbuffer_drain_call_count) { case 0: if (len != sizeof(REQUEST_1) - 1) { @@ -167,6 +161,13 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) ); } + jsmn_parser jsmn_ctx; + jsmn_init(&jsmn_ctx); + struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); + if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { + errx(1, "the client json parser context wasn't re-initialized"); + } + int offset; switch (evbuffer_pullup_call_count) { case 0: diff --git a/tests/core/pipe/tests_pipe_utils.h b/tests/core/pipe/tests_pipe_utils.h index 2ff0e39..1128108 100644 --- a/tests/core/pipe/tests_pipe_utils.h +++ b/tests/core/pipe/tests_pipe_utils.h @@ -10,12 +10,3 @@ lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) jsmn_init(&pipe_client->jsmn_ctx); } #endif - -#ifndef MOCKED_JSONRPC_DISPATCH_REQUEST -void -lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) -{ - (void)client; - (void)parsed; -} -#endif diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c index c83b235..60835f7 100644 --- a/tests/core/tests_shims.c +++ b/tests/core/tests_shims.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -32,3 +33,17 @@ void lgtd_cleanup(void) { } + +short +lgtd_sockaddrport(const struct sockaddr_storage *peer) +{ + assert(peer); + + if (peer->ss_family == AF_INET) { + const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; + return ntohs(in_peer->sin_port); + } else { + const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; + return ntohs(in6_peer->sin6_port); + } +} From cac8fffe154895785f5bb9930fea8a01813e50ff Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:56 -0700 Subject: [PATCH 099/181] params are optional in JSON-RPC don't crash if they are missing --- core/jsonrpc.c | 2 +- .../test_jsonrpc_dispatch_one_no_params.c | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c diff --git a/core/jsonrpc.c b/core/jsonrpc.c index 337c1df..a46642d 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -1152,7 +1152,7 @@ lgtd_jsonrpc_dispatch_one(struct lgtd_client *client, methods[i].name, &client->json[request.method->start], methods[i].namelen ); if (!diff) { - int params_count = request.params->size; + int params_count = request.params ? request.params->size : 0; if (params_count != methods[i].params_count) { lgtd_jsonrpc_send_error( client, LGTD_JSONRPC_INVALID_PARAMS, diff --git a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c new file mode 100644 index 0000000..9f7e986 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c @@ -0,0 +1,55 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_POWER_ON +#include "mock_proto.h" + +#include "test_jsonrpc_utils.h" + +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + (void)client; + (void)targets; + + errx(1, "lgtd_proto_power_on shouldn't have been called"); +} + +int +main(void) +{ + const char json[] = ("{" + "\"jsonrpc\": \"2.0\"," + "\"method\": \"power_on\"," + "\"id\": \"42\"" + "}"); + struct lgtd_client client = { .json = json }; + int parsed = parse_json( + client.jsmn_tokens, + LGTD_ARRAY_SIZE(client.jsmn_tokens), + json, + sizeof(json) + ); + + lgtd_jsonrpc_dispatch_one(&client, client.jsmn_tokens, parsed); + + const char expected[] = ("{" + "\"jsonrpc\": \"2.0\", " + "\"id\": \"42\", " + "\"error\": {" + "\"code\": -32602, " + "\"message\": " + "\"Invalid number of parameters\"" + "}" + "}"); + + if (memcmp(expected, client_write_buf, sizeof(expected))) { + errx( + 1, "got %.*s back (expected %s)", + client_write_buf_idx, client_write_buf, expected + ); + } + + return 0; +} From c2f40a8e48c97550d9bc77f722ebe4709182c3fc Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:56 -0700 Subject: [PATCH 100/181] Add packet type definitions for GET_LOCATION/GROUP STATE_LOCATION/GROUP --- lifx/wire_proto.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index bfefab1..1a03bff 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -155,6 +155,10 @@ enum lgtd_lifx_packet_type { LGTD_LIFX_SET_FACTORY_TEST_MODE = 0x27, LGTD_LIFX_DISABLE_FACTORY_TEST_MODE = 0x28, LGTD_LIFX_ACK = 0x2d, + LGTD_LIFX_GET_LOCATION = 0x30, // I wonder what 0x31 and 0x34 are... + LGTD_LIFX_STATE_LOCATION = 0x32, + LGTD_LIFX_GET_GROUP = 0x33, // TODO: replace GET/SET_TAG_LABELS ? + LGTD_LIFX_STATE_GROUP = 0x35, LGTD_LIFX_ECHO_REQUEST = 0x3a, LGTD_LIFX_ECHO_RESPONSE = 0x3b, // Light From 29ac6d453087b26a4b6b7142f9cfb88cd4994b38 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:56 -0700 Subject: [PATCH 101/181] Set the verbosity to INFO by default Debug is very rarely needed even when actually debugging something. --- core/lightsd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lightsd.c b/core/lightsd.c index 1296872..cf9d758 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -55,7 +55,7 @@ struct lgtd_opts lgtd_opts = { .foreground = false, .log_timestamps = true, - .verbosity = LGTD_DEBUG + .verbosity = LGTD_INFO }; struct event_base *lgtd_ev_base = NULL; From 72c4ed3320b030a301a89ee5e058b99e5d57c379 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 30 Aug 2015 22:06:56 -0700 Subject: [PATCH 102/181] Use a function to get a gateway's latency And cope with bad values, I should probably look into this closer, but the latency situation is quite complex actually, and hard to get right on pre 2.0 firmwares. --- core/proto.c | 2 +- lifx/gateway.c | 21 ++++++++++++++++----- lifx/gateway.h | 2 +- lifx/watchdog.c | 2 +- tests/lifx/mock_gateway.h | 9 +++++++++ 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/core/proto.c b/core/proto.c index c37072d..054cb6b 100644 --- a/core/proto.c +++ b/core/proto.c @@ -221,7 +221,7 @@ lgtd_proto_get_light_state(struct lgtd_client *client, "}", bulb_addr, site_addr, bulb->gw->ip_addr, bulb->gw->port, - (uintmax_t)LGTD_LIFX_GATEWAY_LATENCY(bulb->gw) + (uintmax_t)lgtd_lifx_gateway_latency(bulb->gw) ); #define PRINT_LIFX_FW_TIMESTAMPS(fw_info, built_at_buf, installed_at_buf) \ diff --git a/lifx/gateway.c b/lifx/gateway.c index 29a5516..6cfa0a3 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -467,6 +467,17 @@ lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, } } +lgtd_time_mono_t +lgtd_lifx_gateway_latency(const struct lgtd_lifx_gateway *gw) +{ + assert(gw); + + if (gw->last_req_at < gw->last_pkt_at) { // this doesn't look right + return gw->last_pkt_at - gw->last_req_at; + } + return 0; +} + void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, @@ -528,15 +539,15 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, } } - int latency = LGTD_LIFX_GATEWAY_LATENCY(gw); + lgtd_time_mono_t latency = lgtd_lifx_gateway_latency(gw); if (latency < LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS) { if (!lgtd_timer_ispending(gw->refresh_timer)) { int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); lgtd_timer_reschedule(gw->refresh_timer, &tv); lgtd_debug( - "[%s]:%hu latency is %dms, scheduling next GET_LIGHT_STATE in %dms", - gw->ip_addr, gw->port, latency, timeout + "[%s]:%hu latency is %jums, scheduling next GET_LIGHT_STATE in %dms", + gw->ip_addr, gw->port, (uintmax_t)latency, timeout ); } return; @@ -544,8 +555,8 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, if (!gw->pending_refresh_req) { lgtd_debug( - "[%s]:%hu latency is %dms, sending GET_LIGHT_STATE now", - gw->ip_addr, gw->port, latency + "[%s]:%hu latency is %jums, sending GET_LIGHT_STATE now", + gw->ip_addr, gw->port, (uintmax_t)latency ); lgtd_lifx_gateway_send_get_all_light_state(gw); } else { diff --git a/lifx/gateway.h b/lifx/gateway.h index 5f510fc..6975e9d 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -67,7 +67,6 @@ struct lgtd_lifx_gateway { lgtd_time_mono_t last_req_at; lgtd_time_mono_t next_req_at; lgtd_time_mono_t last_pkt_at; -#define LGTD_LIFX_GATEWAY_LATENCY(gw) ((gw)->last_pkt_at - (gw)->last_req_at) struct lgtd_lifx_message pkt_ring[LGTD_LIFX_GATEWAY_PACKET_RING_SIZE]; #define LGTD_LIFX_GATEWAY_INC_MESSAGE_RING_INDEX(idx) do { \ (idx) += 1; \ @@ -102,6 +101,7 @@ void lgtd_lifx_gateway_close_all(void); void lgtd_lifx_gateway_remove_and_close_bulb(struct lgtd_lifx_gateway *, struct lgtd_lifx_bulb *); void lgtd_lifx_gateway_force_refresh(struct lgtd_lifx_gateway *); +lgtd_time_mono_t lgtd_lifx_gateway_latency(const struct lgtd_lifx_gateway *); void lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, diff --git a/lifx/watchdog.c b/lifx/watchdog.c index 91181c0..f5d3e9c 100644 --- a/lifx/watchdog.c +++ b/lifx/watchdog.c @@ -107,7 +107,7 @@ lgtd_lifx_watchdog_timeout_event_callback(evutil_socket_t socket, // gateways aren't bulbs themselves: struct lgtd_lifx_gateway *gw, *next_gw; LIST_FOREACH_SAFE(gw, &lgtd_lifx_gateways, link, next_gw) { - int gw_lag = now - gw->last_pkt_at; + int gw_lag = lgtd_lifx_gateway_latency(gw); if (gw_lag >= LGTD_LIFX_WATCHDOG_DEVICE_TIMEOUT_MSECS) { lgtd_info( "closing bulb gateway [%s]:%hu that " diff --git a/tests/lifx/mock_gateway.h b/tests/lifx/mock_gateway.h index 9790982..1103085 100644 --- a/tests/lifx/mock_gateway.h +++ b/tests/lifx/mock_gateway.h @@ -9,6 +9,15 @@ struct lgtd_lifx_tag; struct lgtd_lifx_gateway_list lgtd_lifx_gateways = LIST_HEAD_INITIALIZER(&lgtd_lifx_gateways); +#ifndef MOCKED_LGTD_LIFX_GATEWAY_LATENCY +lgtd_time_mono_t +lgtd_lifx_gateway_latency(const struct lgtd_lifx_gateway *gw) +{ + (void)gw; + return 0; +} +#endif + #ifndef MOCKED_LIFX_GATEWAY_SEND_TO_SITE bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, From f5763d139354f56928c366a8bafd07d943e46e2f Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 31 Aug 2015 02:00:51 -0700 Subject: [PATCH 103/181] Run lightsd in the foreground by default and clarify the README To run lightsd in the background use the new -d flag, the -f and -d flags cancel each others. I like BSD, I like it when daemons are actually daemons but no-one knows what a daemon is anyway, and with newer process managers (upstart, systemd...) forking in the background by default becomes less relevant. --- README.rst | 57 +++++++++++++++++++++++++++++++------------------- core/lightsd.c | 26 ++++++++++++++--------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/README.rst b/README.rst index 8ab523f..6e8c922 100644 --- a/README.rst +++ b/README.rst @@ -33,8 +33,8 @@ following commands through a JSON-RPC_ interface: - set_label; - tag/untag (group/ungroup bulbs together). -The JSON-RPC interface works on top of TCP/IPv4/v6, Unix sockets (coming up) or -over a command pipe (named pipe, see mkfifo(1)). +The JSON-RPC interface works on top of TCP/IPv4/v6 or over a command pipe (named +pipe, see mkfifo(1)). lightsd can target single or multiple bulbs at once: @@ -48,12 +48,6 @@ lightsd works and is developed against LIFX firmwares 1.1, 1.5 and 2.0. .. _JSON-RPC: http://www.jsonrpc.org/specification -Developpers ------------ - -Feel free to reach out via email or irc (kalessin on freenode). As the project -name implies, I'm fairly interested in other smart bulbs. - Requirements ------------ @@ -65,44 +59,59 @@ dependencies: - CMake ≥ 2.8; - libevent ≥ 2.0.19. +Those dependencies can be installed on your system using your package manager or +brew_ on Mac OS X. + lightsd optionally depends on libbsd ≥ 0.5.0 on platforms missing ``setproctitle`` (pretty much any non-BSD system, including Mac OS X). lightsd is actively developed and tested from Arch Linux, Debian and Mac OS X; both for 32/64 bits and little/big endian architectures. +.. _brew: http://brew.sh/ + Build instructions ------------------ -Clone this repository and then: +From a terminal prompt, clone the repository and run those commands: :: - .../lightsd$ mkdir build && cd build - .../lightsd/build$ cmake .. - .../lightsd/build$ make -j5 + …/lightsd$ mkdir build && cd build + …/lightsd/build$ cmake .. + …/lightsd/build$ make -j5 lightsd -Finally, to start lightsd with the jsonrpc interface listening on localhost -port 1234 and a command pipe named lightsd.cmd: +To start lightsd with the jsonrpc interface listening on localhost port 1234 and +a command pipe named lightsd.cmd: :: - .../lightsd/build$ core/lightsd -v info -l ::1:1234 -c lightsd.cmd + …lightsd/build$ core/lightsd -l ::1:1234 -c lightsd.cmd -lightsd forks in the background by default, display running processes and check -how we are doing: +This repository contains a small client that you can use to manipulate your +bulbs through lightsd. The client is written in `Python 3`_, Mac OS X and Linux +usually have it installed by default. From a new terminal prompt, cd to the root +of the repository and run it using: :: - ps aux | grep lightsd + …/lightsd$ examples/lightsc.py -You can stop lightsd with: +You can exit the lightsd.py using ^D (ctrl + d). Use ^C to stop lightsd. + +lightsd can daemonize itself using the ``-d`` option: :: - pkill lightsd + …/lightsd/build$ core/lightsd -d -l ::1:1234 -c lightsd.cmd -Use the ``-f`` option to run lightsd in the foreground. +Check how lightsd is running: + +:: + + ps aux | grep lightsd + +.. _Python 3: https://www.python.org/ Known issues ------------ @@ -127,4 +136,10 @@ to me. .. _Supervisor: http://www.supervisord.org/ +Developpers +----------- + +Feel free to reach out via email or irc (kalessin on freenode, insist if I don't +reply). As the project name implies, I'm fairly interested in other smart bulbs. + .. vim: set tw=80 spelllang=en spell: diff --git a/core/lightsd.c b/core/lightsd.c index cf9d758..2935d44 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -53,7 +53,7 @@ #include "lightsd.h" struct lgtd_opts lgtd_opts = { - .foreground = false, + .foreground = true, .log_timestamps = true, .verbosity = LGTD_INFO }; @@ -137,14 +137,17 @@ static void lgtd_usage(const char *progname) { printf( - "Usage: %s ...\n\n" - " [-l,--listen addr:port [+]]\n" - " [-c,--comand-pipe /command/fifo [+]]\n" - " [-f,--foreground]\n" - " [-t,--no-timestamps]\n" - " [-h,--help]\n" - " [-V,--version]\n" - " [-v,--verbosity debug|info|warning|error]\n", +"Usage: %s ...\n\n" +" [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP " +" at this address (can be repeated).\n" +" [-c,--comand-pipe /command/fifo [+]] Open a JSON-RPC command pipe a this " +" location (can be repeated).\n" +" [-f,--foreground] Stay in the foreground (default).\n" +" [-d,--daemonize] Fork in the background.\n" +" [-t,--no-timestamps] Disable timestamps in logs.\n" +" [-h,--help] Display this.\n" +" [-V,--version] Display version and build information.\n" +" [-v,--verbosity debug|info|warning|error]\n", progname ); lgtd_cleanup(); @@ -163,13 +166,14 @@ main(int argc, char *argv[], char *envp[]) {"listen", required_argument, NULL, 'l'}, {"command-pipe", required_argument, NULL, 'c'}, {"foreground", no_argument, NULL, 'f'}, + {"daemonize", no_argument, NULL, 'd'}, {"no-timestamps", no_argument, NULL, 't'}, {"help", no_argument, NULL, 'h'}, {"verbosity", required_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} }; - const char short_opts[] = "l:c:fthv:V"; + const char short_opts[] = "l:c:fdthv:V"; if (argc == 1) { lgtd_usage(argv[0]); @@ -198,6 +202,8 @@ main(int argc, char *argv[], char *envp[]) case 'f': lgtd_opts.foreground = true; break; + case 'd': + lgtd_opts.foreground = false; case 't': lgtd_opts.log_timestamps = false; break; From 69a590d3b105208d8d2e94f9670483dfcc02ac87 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 3 Sep 2015 02:05:13 -0700 Subject: [PATCH 104/181] Release prep --- CMakeLists.txt | 18 ++++++++++-- README.rst | 15 ++++++---- core/CMakeLists.txt | 2 ++ core/daemon.c | 9 +++++- core/lightsd.c | 21 ++++++++++---- core/lightsd.h | 1 + core/version.h.in | 2 ++ examples/toggle | 5 ++++ {examples => share}/lightsc.sh | 28 +++++++++++++++++++ .../daemon/test_daemon_update_proctitle.c | 2 ++ 10 files changed, 89 insertions(+), 14 deletions(-) create mode 100755 examples/toggle rename {examples => share}/lightsc.sh (60%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ea2f35..8272ec2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,11 +3,12 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) # first version TARGET_INCLUDE_DIRECTORI PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "0") -SET(CPACK_PACKAGE_VERSION_MINOR "0") -SET(CPACK_PACKAGE_VERSION_PATCH "1") +SET(CPACK_PACKAGE_VERSION_MINOR "9") +SET(CPACK_PACKAGE_VERSION_PATCH "0") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") +MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") MESSAGE(STATUS "lightsd version: ${LIGHTSD_VERSION}") MESSAGE(STATUS "Target system: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} ${CMAKE_SYSTEM_PROCESSOR}") MESSAGE(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") @@ -39,6 +40,7 @@ SET(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}} -pipe") SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Wstrict-prototypes -std=c99") +STRING(STRIP "${CMAKE_C_FLAGS}" CMAKE_C_FLAGS) SET(CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE} "") ADD_DEFINITIONS( @@ -74,3 +76,15 @@ ADD_SUBDIRECTORY(compat) ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(lifx) ADD_SUBDIRECTORY(tests) + +INSTALL( + FILES COPYING README.rst docs/protocol.rst + DESTINATION share/doc/lightsd +) +INSTALL( + DIRECTORY examples + DESTINATION share/doc/lightsd + USE_SOURCE_PERMISSIONS + REGEX ".*\\.sw.$" EXCLUDE +) +INSTALL(FILES share/lightsc.sh DESTINATION share/lightsd) diff --git a/README.rst b/README.rst index 6e8c922..ce98219 100644 --- a/README.rst +++ b/README.rst @@ -44,7 +44,7 @@ lightsd can target single or multiple bulbs at once: - broadcast; - composite (list of targets); -lightsd works and is developed against LIFX firmwares 1.1, 1.5 and 2.0. +lightsd works and is developed against LIFX firmwares 1.1, 1.5, 2.0 and 2.1. .. _JSON-RPC: http://www.jsonrpc.org/specification @@ -56,11 +56,9 @@ should be quite easy, but isn't really the focus) and on any kind of hardware including embedded devices. Hence why lightsd is written in C with reasonable dependencies: -- CMake ≥ 2.8; -- libevent ≥ 2.0.19. - -Those dependencies can be installed on your system using your package manager or -brew_ on Mac OS X. +- libevent ≥ 2.0.19 (released May 2012); +- CMake ≥ 2.8.11 (released May 2013): only if you want to build lightsd from its + sources. lightsd optionally depends on libbsd ≥ 0.5.0 on platforms missing ``setproctitle`` (pretty much any non-BSD system, including Mac OS X). @@ -68,6 +66,11 @@ lightsd optionally depends on libbsd ≥ 0.5.0 on platforms missing lightsd is actively developed and tested from Arch Linux, Debian and Mac OS X; both for 32/64 bits and little/big endian architectures. +Installation +------------ + +TBD. + .. _brew: http://brew.sh/ Build instructions diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 6806fa0..6636abc 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -36,3 +36,5 @@ TARGET_LINK_LIBRARIES( IF (HAVE_LIBBSD) TARGET_LINK_LIBRARIES(lightsd ${LIBBSD_LIBRARY}) ENDIF (HAVE_LIBBSD) + +INSTALL(TARGETS lightsd RUNTIME DESTINATION bin) diff --git a/core/daemon.c b/core/daemon.c index 19cb3c2..3503bbe 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -46,6 +46,8 @@ #include "stats.h" #include "lightsd.h" +static bool lgtd_daemon_proctitle_initialized = false; + bool lgtd_daemon_unleash(void) { @@ -67,7 +69,7 @@ lgtd_daemon_unleash(void) } close(null); -#define SUMMON() do { \ +#define SUMMON() do { \ switch (fork()) { \ case 0: \ break; \ @@ -92,6 +94,7 @@ lgtd_daemon_setup_proctitle(int argc, char *argv[], char *envp[]) #if LGTD_HAVE_LIBBSD setproctitle_init(argc, argv, envp); lgtd_daemon_update_proctitle(); + lgtd_daemon_proctitle_initialized = true; #else (void)argc; (void)argv; @@ -102,6 +105,10 @@ lgtd_daemon_setup_proctitle(int argc, char *argv[], char *envp[]) void lgtd_daemon_update_proctitle(void) { + if (!lgtd_daemon_proctitle_initialized) { + return; + } + #if LGTD_HAVE_PROCTITLE char title[LGTD_DAEMON_TITLE_SIZE] = { 0 }; int i = 0; diff --git a/core/lightsd.c b/core/lightsd.c index 2935d44..ea63ca4 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -138,16 +138,19 @@ lgtd_usage(const char *progname) { printf( "Usage: %s ...\n\n" -" [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP " +" [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP \n" " at this address (can be repeated).\n" -" [-c,--comand-pipe /command/fifo [+]] Open a JSON-RPC command pipe a this " -" location (can be repeated).\n" +" [-c,--comand-pipe /command/fifo [+]] Open an unidirectional JSON-RPC \n" +" command pipe at this location (can \n" +" be repeated).\n" " [-f,--foreground] Stay in the foreground (default).\n" " [-d,--daemonize] Fork in the background.\n" " [-t,--no-timestamps] Disable timestamps in logs.\n" " [-h,--help] Display this.\n" " [-V,--version] Display version and build information.\n" -" [-v,--verbosity debug|info|warning|error]\n", +" [-v,--verbosity debug|info|warning|error]\n" +"\nor,\n\n" +" [--install-prefix] Display the install prefix for lightsd.\n", progname ); lgtd_cleanup(); @@ -171,6 +174,7 @@ main(int argc, char *argv[], char *envp[]) {"help", no_argument, NULL, 'h'}, {"verbosity", required_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, + {"install-prefix", no_argument, NULL, 'p'}, {NULL, 0, NULL, 0} }; const char short_opts[] = "l:c:fdthv:V"; @@ -224,7 +228,14 @@ main(int argc, char *argv[], char *envp[]) } break; case 'V': - printf("lightsd v%s\n", LGTD_VERSION); + printf("lightsd %s\n", LGTD_VERSION); + return 0; + case 'p': + printf( + "%s%s\n", LGTD_INSTALL_PREFIX, LGTD_INSTALL_PREFIX[ + LGTD_ARRAY_SIZE(LGTD_INSTALL_PREFIX) - 1 + ] != '/' ? "/" : "" + ); return 0; default: lgtd_usage(argv[0]); diff --git a/core/lightsd.h b/core/lightsd.h index 7ef97c0..17fa456 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -74,6 +74,7 @@ struct lgtd_opts { extern struct lgtd_opts lgtd_opts; extern struct event_base *lgtd_ev_base; +extern const char *lgtd_progname; char *lgtd_iee8023mactoa(const uint8_t *addr, char *buf, int buflen); #define LGTD_IEEE8023MACTOA(addr, buf) \ diff --git a/core/version.h.in b/core/version.h.in index 02d056b..56d0169 100644 --- a/core/version.h.in +++ b/core/version.h.in @@ -35,3 +35,5 @@ const char LGTD_VERSION[] = ( "proctitle_support: @HAVE_PROCTITLE@\n\n" "Copyright (c) 2014, 2015, Louis Opter " ); + +const char LGTD_INSTALL_PREFIX[] = "@CMAKE_INSTALL_PREFIX@"; diff --git a/examples/toggle b/examples/toggle new file mode 100755 index 0000000..fbb3844 --- /dev/null +++ b/examples/toggle @@ -0,0 +1,5 @@ +#!/bin/sh + +. `lightsd --install-prefix`/share/lightsd/lightsc.sh + +lightsc power_toggle ${*:-'"*"'} diff --git a/examples/lightsc.sh b/share/lightsc.sh similarity index 60% rename from examples/lightsc.sh rename to share/lightsc.sh index 922e9c5..1e7eb31 100644 --- a/examples/lightsc.sh +++ b/share/lightsc.sh @@ -1,4 +1,32 @@ #!/bin/sh +# Copyright (c) 2015, Louis Opter +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # Here is an example script that dims bulbs to a warm orange: diff --git a/tests/core/daemon/test_daemon_update_proctitle.c b/tests/core/daemon/test_daemon_update_proctitle.c index c2d2762..b54b26a 100644 --- a/tests/core/daemon/test_daemon_update_proctitle.c +++ b/tests/core/daemon/test_daemon_update_proctitle.c @@ -41,6 +41,8 @@ mock_setproctitle(const char *fmt, ...) int main(void) { + lgtd_daemon_proctitle_initialized = true; + expected = "bulbs(found=0, on=0); clients(connected=0)"; lgtd_daemon_update_proctitle(); if (setproctitle_call_count != 1) { From 14aca2f5d30d511a97496882a44165cccda64a20 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 3 Sep 2015 02:05:34 -0700 Subject: [PATCH 105/181] Added tag 0.9.0 for changeset 38014f13f04d --- .hgtags | 1 + 1 file changed, 1 insertion(+) create mode 100644 .hgtags diff --git a/.hgtags b/.hgtags new file mode 100644 index 0000000..39d4fe2 --- /dev/null +++ b/.hgtags @@ -0,0 +1 @@ +38014f13f04d5871b70278518221a937867fd8b6 0.9.0 From 89cf3f8edac478154502f6c01feea110b933501e Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 3 Sep 2015 18:46:54 -0700 Subject: [PATCH 106/181] Fix in-place builds Make things easier for package managers --- CMakeLists.txt | 2 +- CMakeScripts/AddAllSubdirectories.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8272ec2..b8c422b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "0") SET(CPACK_PACKAGE_VERSION_MINOR "9") -SET(CPACK_PACKAGE_VERSION_PATCH "0") +SET(CPACK_PACKAGE_VERSION_PATCH "1") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") diff --git a/CMakeScripts/AddAllSubdirectories.cmake b/CMakeScripts/AddAllSubdirectories.cmake index f9c7158..8ba2f50 100644 --- a/CMakeScripts/AddAllSubdirectories.cmake +++ b/CMakeScripts/AddAllSubdirectories.cmake @@ -1,7 +1,7 @@ FUNCTION(ADD_ALL_SUBDIRECTORIES) FILE(GLOB SUBDIRECTORIES "*") FOREACH (ENTRY ${SUBDIRECTORIES}) - IF (IS_DIRECTORY ${ENTRY}) + IF (IS_DIRECTORY ${ENTRY} AND EXISTS "${ENTRY}/CMakeLists.txt") ADD_SUBDIRECTORY(${ENTRY}) ENDIF () ENDFOREACH () From 046d73f28a67eee687fd5232ceea05606171c637 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 3 Sep 2015 18:47:03 -0700 Subject: [PATCH 107/181] Added tag 0.9.1 for changeset 21d438d34e21 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 39d4fe2..bc90427 100644 --- a/.hgtags +++ b/.hgtags @@ -1 +1,2 @@ 38014f13f04d5871b70278518221a937867fd8b6 0.9.0 +21d438d34e21479a652ba6aa1ec14646385cae5a 0.9.1 From b1edc2da1004bab26cf5764d53d515107f6f0a17 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 10:57:53 -0700 Subject: [PATCH 108/181] Rename --install-prefix into --prefix Let's keep things short on the command line. --- core/lightsd.c | 4 ++-- examples/toggle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/lightsd.c b/core/lightsd.c index ea63ca4..94bba34 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -150,7 +150,7 @@ lgtd_usage(const char *progname) " [-V,--version] Display version and build information.\n" " [-v,--verbosity debug|info|warning|error]\n" "\nor,\n\n" -" [--install-prefix] Display the install prefix for lightsd.\n", +" --prefix Display the install prefix for lightsd.\n", progname ); lgtd_cleanup(); @@ -174,7 +174,7 @@ main(int argc, char *argv[], char *envp[]) {"help", no_argument, NULL, 'h'}, {"verbosity", required_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, - {"install-prefix", no_argument, NULL, 'p'}, + {"prefix", no_argument, NULL, 'p'}, {NULL, 0, NULL, 0} }; const char short_opts[] = "l:c:fdthv:V"; diff --git a/examples/toggle b/examples/toggle index fbb3844..c38eab2 100755 --- a/examples/toggle +++ b/examples/toggle @@ -1,5 +1,5 @@ #!/bin/sh -. `lightsd --install-prefix`/share/lightsd/lightsc.sh +. `lightsd --prefix`/share/lightsd/lightsc.sh lightsc power_toggle ${*:-'"*"'} From e268b00af86bfd0059c2ba38e3f11c22853ee207 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 11:51:30 -0700 Subject: [PATCH 109/181] Add support for unix sockets with the -s option (--socket) --- CMakeLists.txt | 13 +- README.rst | 5 +- core/client.c | 50 +++-- core/client.h | 5 +- core/daemon.c | 22 ++- core/lightsd.c | 25 +-- core/lightsd.h | 9 +- core/listen.c | 182 +++++++++++++++--- core/listen.h | 5 +- core/log.c | 50 ++++- core/proto.c | 20 +- examples/lightsc.py | 39 ++-- lifx/broadcast.c | 33 ++-- lifx/bulb.c | 5 +- lifx/gateway.c | 139 +++++++------ lifx/gateway.h | 12 +- lifx/tagging.c | 8 +- lifx/watchdog.c | 10 +- lifx/wire_proto.c | 5 +- tests/core/client/CMakeLists.txt | 7 +- tests/core/client/test_client_read_callback.c | 5 +- .../test_client_read_callback_extra_data.c | 5 +- ...t_client_read_callback_multiple_requests.c | 5 +- ...test_client_read_callback_part_too_large.c | 5 +- ...est_client_read_callback_yield_on_eagain.c | 7 +- .../daemon/test_daemon_update_proctitle.c | 8 +- tests/core/proto/test_proto_get_light_state.c | 21 +- ..._proto_get_light_state_empty_device_list.c | 5 +- ...est_proto_get_light_state_label_overflow.c | 10 +- ...t_proto_get_light_state_null_device_list.c | 5 +- ...est_proto_get_light_state_unknown_tag_id.c | 15 +- tests/core/proto/test_proto_power_off.c | 5 +- .../test_proto_power_off_routing_error.c | 5 +- tests/core/proto/test_proto_power_on.c | 5 +- .../proto/test_proto_power_on_routing_error.c | 5 +- tests/core/proto/test_proto_power_toggle.c | 5 +- ...oto_power_toggle_targets_to_device_fails.c | 5 +- tests/core/proto/test_proto_set_label.c | 5 +- .../proto/test_proto_set_label_too_long.c | 5 +- tests/core/proto/test_proto_tag_create.c | 5 +- ...st_proto_tag_create_lifx_gw_tag_ids_full.c | 6 +- tests/core/proto/test_proto_tag_update.c | 5 +- tests/core/proto/test_proto_untag.c | 5 +- .../test_proto_untag_tag_does_not_exist.c | 5 +- tests/core/tests_utils.c | 47 ++++- tests/core/tests_utils.h | 5 +- 46 files changed, 560 insertions(+), 293 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b8c422b..0a3e683 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,11 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) # first version TARGET_INCLUDE_DIRECTORIES +# first version with TARGET_INCLUDE_DIRECTORIES: +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "0") SET(CPACK_PACKAGE_VERSION_MINOR "9") -SET(CPACK_PACKAGE_VERSION_PATCH "1") +SET(CPACK_PACKAGE_VERSION_PATCH "2") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") @@ -14,7 +15,7 @@ MESSAGE(STATUS "Target system: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} ${CM MESSAGE(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") MESSAGE(STATUS "Source directory: ${LIGHTSD_SOURCE_DIR}") -SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${LIGHTSD_SOURCE_DIR}/CMakeScripts) +SET(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${LIGHTSD_SOURCE_DIR}/CMakeScripts") ENABLE_TESTING() @@ -38,9 +39,11 @@ INCLUDE(AddTestFromSources) SET(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) -SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}} -pipe") -SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Wstrict-prototypes -std=c99") +SET(CMAKE_C_FLAGS "-pipe ${CMAKE_C_FLAGS}") +STRING(STRIP "${CMAKE_C_FLAGS}" CMAKE_C_FLAGS) +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE}}") STRING(STRIP "${CMAKE_C_FLAGS}" CMAKE_C_FLAGS) +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra -Wall -Wstrict-prototypes -std=c99") SET(CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE} "") ADD_DEFINITIONS( diff --git a/README.rst b/README.rst index ce98219..a939ce1 100644 --- a/README.rst +++ b/README.rst @@ -33,8 +33,8 @@ following commands through a JSON-RPC_ interface: - set_label; - tag/untag (group/ungroup bulbs together). -The JSON-RPC interface works on top of TCP/IPv4/v6 or over a command pipe (named -pipe, see mkfifo(1)). +The JSON-RPC interface works on top of TCP/IPv4/v6, Unix sockets, or over a +command pipe (named pipe, see `mkfifo(1)`_). lightsd can target single or multiple bulbs at once: @@ -47,6 +47,7 @@ lightsd can target single or multiple bulbs at once: lightsd works and is developed against LIFX firmwares 1.1, 1.5, 2.0 and 2.1. .. _JSON-RPC: http://www.jsonrpc.org/specification +.. _mkfifo(1): http://www.openbsd.org/cgi-bin/man.cgi?query=mkfifo Requirements ------------ diff --git a/core/client.c b/core/client.c index 0355d48..31e1d14 100644 --- a/core/client.c +++ b/core/client.c @@ -50,6 +50,7 @@ lgtd_client_close(struct lgtd_client *client) LIST_REMOVE(client, link); bufferevent_free(client->io); + free(client->addr); free(client); } @@ -69,6 +70,9 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) struct lgtd_client *client = ctx; + char addr[LGTD_SOCKADDR_STRLEN]; + LGTD_SOCKADDRTOA(client->addr, addr); + struct evbuffer *input = bufferevent_get_input(bev); size_t nbytes = evbuffer_get_contiguous_space(input); // Get the actual pointer to the beginning of the evbuf: @@ -86,10 +90,7 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) switch (rv) { case JSMN_ERROR_NOMEM: case JSMN_ERROR_INVAL: - lgtd_warnx( - "client [%s]:%hu: request too big or invalid", - client->ip_addr, client->port - ); + lgtd_warnx("client %s: request too big or invalid", addr); evbuffer_drain(input, nbytes); break; case JSMN_ERROR_PART: @@ -100,10 +101,7 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) (void)0; size_t buflen = evbuffer_get_length(input); if (buflen > LGTD_CLIENT_MAX_REQUEST_BUF_SIZE) { - lgtd_warnx( - "client [%s]:%hu: request too big or invalid", - client->ip_addr, client->port - ); + lgtd_warnx("client %s: request too big or invalid", addr); evbuffer_drain(input, buflen); } else if (nbytes == buflen) { return; // We pulled up everything already, wait for more data @@ -139,10 +137,10 @@ lgtd_client_event_callback(struct bufferevent *bev, short events, void *ctx) struct lgtd_client *client = ctx; if (events & (BEV_EVENT_ERROR|BEV_EVENT_EOF)) { - lgtd_info( - "lost connection with client [%s]:%hu", - client->ip_addr, client->port - ); + char addr[LGTD_SOCKADDR_STRLEN]; + lgtd_info("lost connection with client %s", LGTD_SOCKADDRTOA( + client->addr, addr + )); lgtd_client_close(client); } } @@ -197,21 +195,29 @@ lgtd_client_send_error(struct lgtd_client *client, } struct lgtd_client * -lgtd_client_open(evutil_socket_t peer, const struct sockaddr_storage *peer_addr) +lgtd_client_open(evutil_socket_t peer, const struct sockaddr *addr, int addrlen) { assert(peer != -1); - assert(peer_addr); + assert(addr); struct lgtd_client *client = calloc(1, sizeof(*client)); if (!client) { return NULL; } + client->io = bufferevent_socket_new( lgtd_ev_base, peer, BEV_OPT_CLOSE_ON_FREE ); if (!client->io) { - return NULL; + goto error; + } + + client->addr = calloc(1, addrlen); + if (!client->addr) { + goto error; } + memcpy(client->addr, addr, addrlen); + bufferevent_setcb( client->io, lgtd_client_read_callback, @@ -219,15 +225,23 @@ lgtd_client_open(evutil_socket_t peer, const struct sockaddr_storage *peer_addr) lgtd_client_event_callback, client ); - lgtd_sockaddrtoa(peer_addr, client->ip_addr, sizeof(client->ip_addr)); - client->port = lgtd_sockaddrport(peer_addr); - bufferevent_enable(client->io, EV_READ|EV_WRITE|EV_TIMEOUT); + if (bufferevent_enable(client->io, EV_READ|EV_WRITE|EV_TIMEOUT) == -1) { + goto error; + } LIST_INSERT_HEAD(&lgtd_clients, client, link); LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(clients, 1); return client; + +error: + if (client->io) { + bufferevent_free(client->io); + } + free(client->addr); + free(client); + return NULL; } void diff --git a/core/client.h b/core/client.h index 2fad890..ab5ef66 100644 --- a/core/client.h +++ b/core/client.h @@ -33,8 +33,7 @@ enum lgtd_client_error_code { struct lgtd_client { LIST_ENTRY(lgtd_client) link; struct bufferevent *io; - char ip_addr[INET6_ADDRSTRLEN]; - uint16_t port; + struct sockaddr *addr; jsmn_parser jsmn_ctx; jsmntok_t jsmn_tokens[LGTD_CLIENT_JSMN_TOKENS_NUM]; const char *json; @@ -42,7 +41,7 @@ struct lgtd_client { }; LIST_HEAD(lgtd_client_list, lgtd_client); -struct lgtd_client *lgtd_client_open(evutil_socket_t, const struct sockaddr_storage *); +struct lgtd_client *lgtd_client_open(evutil_socket_t, const struct sockaddr *, int); void lgtd_client_open_from_pipe(struct lgtd_client *); void lgtd_client_close_all(void); diff --git a/core/daemon.c b/core/daemon.c index 3503bbe..2406279 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -102,6 +102,22 @@ lgtd_daemon_setup_proctitle(int argc, char *argv[], char *envp[]) #endif } +static char * +lgtd_daemon_update_proctitle_format_sockaddr(const struct sockaddr *sa, + char *buf, + int buflen) +{ + assert(sa); + assert(buf); + assert(buflen > 0); + + if (sa->sa_family == AF_UNIX) { + return ((struct sockaddr_un *)sa)->sun_path; + } + + return lgtd_sockaddrtoa(sa, buf, buflen); +} + void lgtd_daemon_update_proctitle(void) { @@ -135,9 +151,13 @@ lgtd_daemon_update_proctitle(void) } \ } while (0) + char addr[LGTD_SOCKADDR_STRLEN]; LOOP( SLIST, &lgtd_listeners, struct lgtd_listen, - "listening_on", "%s:[%s]", it->addr, it->port + "listening_on", "%s", + lgtd_daemon_update_proctitle_format_sockaddr( + it->sockaddr, addr, sizeof(addr) + ) ); LOOP( diff --git a/core/lightsd.c b/core/lightsd.c index 94bba34..a4c0106 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -76,20 +76,6 @@ lgtd_cleanup(void) #endif } -short -lgtd_sockaddrport(const struct sockaddr_storage *peer) -{ - assert(peer); - - if (peer->ss_family == AF_INET) { - const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; - return ntohs(in_peer->sin_port); - } else { - const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; - return ntohs(in6_peer->sin6_port); - } -} - static void lgtd_signal_event_callback(int signum, short events, void *ctx) { @@ -143,6 +129,8 @@ lgtd_usage(const char *progname) " [-c,--comand-pipe /command/fifo [+]] Open an unidirectional JSON-RPC \n" " command pipe at this location (can \n" " be repeated).\n" +" [-s,--socket /unix/socket [+]] Open an Unix socket at this location \n" +" (can be repeated).\n" " [-f,--foreground] Stay in the foreground (default).\n" " [-d,--daemonize] Fork in the background.\n" " [-t,--no-timestamps] Disable timestamps in logs.\n" @@ -168,6 +156,7 @@ main(int argc, char *argv[], char *envp[]) static const struct option long_opts[] = { {"listen", required_argument, NULL, 'l'}, {"command-pipe", required_argument, NULL, 'c'}, + {"socket", required_argument, NULL, 's'}, {"foreground", no_argument, NULL, 'f'}, {"daemonize", no_argument, NULL, 'd'}, {"no-timestamps", no_argument, NULL, 't'}, @@ -177,7 +166,7 @@ main(int argc, char *argv[], char *envp[]) {"prefix", no_argument, NULL, 'p'}, {NULL, 0, NULL, 0} }; - const char short_opts[] = "l:c:fdthv:V"; + const char short_opts[] = "l:c:s:fdthv:V"; if (argc == 1) { lgtd_usage(argv[0]); @@ -203,6 +192,11 @@ main(int argc, char *argv[], char *envp[]) exit(1); } break; + case 's': + if (!lgtd_listen_unix_open(optarg)) { + exit(1); + } + break; case 'f': lgtd_opts.foreground = true; break; @@ -229,6 +223,7 @@ main(int argc, char *argv[], char *envp[]) break; case 'V': printf("lightsd %s\n", LGTD_VERSION); + lgtd_cleanup(); return 0; case 'p': printf( diff --git a/core/lightsd.h b/core/lightsd.h index 17fa456..7a41ef7 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -66,6 +66,10 @@ enum lgtd_verbosity { enum { LGTD_ERROR_MSG_BUFSIZE = 2048 }; +// FIXME: introspect sizeof(sockaddr_un.sun_path) with CMake to generate a +// reasonable value for that: +enum { LGTD_SOCKADDR_STRLEN = 128 }; + struct lgtd_opts { bool foreground; bool log_timestamps; @@ -79,8 +83,9 @@ extern const char *lgtd_progname; char *lgtd_iee8023mactoa(const uint8_t *addr, char *buf, int buflen); #define LGTD_IEEE8023MACTOA(addr, buf) \ lgtd_iee8023mactoa((addr), (buf), sizeof(buf)) -void lgtd_sockaddrtoa(const struct sockaddr_storage *, char *buf, int buflen); -short lgtd_sockaddrport(const struct sockaddr_storage *); +char *lgtd_sockaddrtoa(const struct sockaddr *, char *buf, int buflen); +#define LGTD_SOCKADDRTOA(addr, buf) \ + lgtd_sockaddrtoa((addr), (buf), sizeof(buf)) char *lgtd_print_duration(uint64_t, char *, int); #define LGTD_PRINT_DURATION(secs, arr) \ diff --git a/core/listen.c b/core/listen.c index 0e9a9ba..661710b 100644 --- a/core/listen.c +++ b/core/listen.c @@ -16,8 +16,12 @@ // along with lighstd. If not, see . #include +#include +#include +#include #include #include +#include #include #include #include @@ -39,26 +43,38 @@ struct lgtd_listen_list lgtd_listeners = static void lgtd_listen_accept_new_client(struct evconnlistener *evlistener, evutil_socket_t peer, - struct sockaddr *peer_addr, + struct sockaddr *addr, int addrlen, void *ctx) { (void)evlistener; - (void)addrlen; - struct lgtd_listen *listener = ctx; - struct lgtd_client *client = lgtd_client_open( - peer, (struct sockaddr_storage *)peer_addr - ); - if (client) { - lgtd_info( - "accepted new client [%s]:%hu", client->ip_addr, client->port + + char bufserver[LGTD_SOCKADDR_STRLEN]; + LGTD_SOCKADDRTOA(listener->sockaddr, bufserver); + + struct lgtd_client *client = NULL; + if (addr->sa_family == AF_UNIX) { + struct sockaddr_storage sockname; + memset(&sockname, 0, sizeof(sockname)); + ev_socklen_t socklen = sizeof(sockname); + getsockname(peer, (struct sockaddr *)&sockname, &socklen); + client = lgtd_client_open(peer, (struct sockaddr *)&sockname, socklen); + } else { + client = lgtd_client_open(peer, addr, addrlen); + } + + if (!client) { + char bufclient[LGTD_SOCKADDR_STRLEN]; + lgtd_warn( + "can't accept new client %s on %s", + LGTD_SOCKADDRTOA(client->addr, bufclient), + bufserver ); return; } - lgtd_warn( - "can't accept new client on %s:%s", listener->addr, listener->port - ); + + lgtd_info("accepted new client %s", bufserver); } void @@ -67,7 +83,14 @@ lgtd_listen_close_all(void) while (!SLIST_EMPTY(&lgtd_listeners)) { struct lgtd_listen *listener = SLIST_FIRST(&lgtd_listeners); SLIST_REMOVE_HEAD(&lgtd_listeners, link); + if (listener->sockaddr->sa_family == AF_UNIX) { + unlink(((struct sockaddr_un *)listener->sockaddr)->sun_path); + } evconnlistener_free(listener->evlistener); + char addr[LGTD_SOCKADDR_STRLEN]; + LGTD_SOCKADDRTOA(listener->sockaddr, addr); + lgtd_info("closed socket %s", addr); + free(listener->sockaddr); free(listener); } @@ -80,13 +103,6 @@ lgtd_listen_open(const char *addr, const char *port) assert(addr); assert(port); - struct lgtd_listen *listener; - SLIST_FOREACH(listener, &lgtd_listeners, link) { - if (!strcmp(listener->addr, addr) && listener->port == port) { - return true; - } - } - struct evutil_addrinfo *res = NULL, hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, @@ -102,13 +118,25 @@ lgtd_listen_open(const char *addr, const char *port) return false; } + struct lgtd_listen *listener; struct evconnlistener *evlistener; for (struct evutil_addrinfo *it = res; it; it = it->ai_next) { + SLIST_FOREACH(listener, &lgtd_listeners, link) { + if (listener->addrlen == it->ai_addrlen + && memcmp(listener->sockaddr, it->ai_addr, it->ai_addrlen)) { + goto done; + } + } + evlistener = NULL; listener = calloc(1, sizeof(*listener)); if (!listener) { goto error; } + listener->sockaddr = calloc(1, it->ai_addrlen); + if (!listener->sockaddr) { + goto error; + } evlistener = evconnlistener_new_bind( lgtd_ev_base, lgtd_listen_accept_new_client, @@ -121,9 +149,11 @@ lgtd_listen_open(const char *addr, const char *port) if (!evlistener) { goto error; } + listener->evlistener = evlistener; - listener->addr = addr; - listener->port = port; + listener->addrlen = it->ai_addrlen; + memcpy(listener->sockaddr, it->ai_addr, it->ai_addrlen); + SLIST_INSERT_HEAD(&lgtd_listeners, listener, link); lgtd_info( "listening on %s:%s (%s)", @@ -131,18 +161,118 @@ lgtd_listen_open(const char *addr, const char *port) ); } - evutil_freeaddrinfo(res); - lgtd_daemon_update_proctitle(); +done: + evutil_freeaddrinfo(res); return true; error: lgtd_warn("can't listen on %s:%s", addr, port); - if (evlistener) { - evconnlistener_free(evlistener); + if (listener) { + if (listener->evlistener) { + evconnlistener_free(evlistener); + } + free(listener->sockaddr); + free(listener); } - free(listener); evutil_freeaddrinfo(res); return false; } + +bool +lgtd_listen_unix_open(const char *path) +{ + assert(path); + + static const int maxpathlen = + sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path); + int pathlen = strlen(path); + if (pathlen > maxpathlen) { + lgtd_warnx( + "%s (%d bytes) is too long, your system only supports paths up to " + "%d bytes", path, pathlen, maxpathlen + ); + return false; + } + + struct lgtd_listen *listener; + SLIST_FOREACH(listener, &lgtd_listeners, link) { + if (listener->addrlen == sizeof(struct sockaddr_un)) { + struct sockaddr_un *sockaddr; + sockaddr = (struct sockaddr_un *)listener->sockaddr; + if (!strcmp(sockaddr->sun_path, path)) { + return true; + } + } + } + + evutil_socket_t fd = -1; + + listener = calloc(1, sizeof(*listener)); + if (!listener) { + goto error; + } + + struct sockaddr_un *sockpath = calloc(1, sizeof(*sockpath)); + if (!sockpath) { + goto error; + } + sockpath->sun_family = AF_UNIX; + memcpy(sockpath->sun_path, path, pathlen); + listener->sockaddr = (struct sockaddr *)sockpath; + listener->addrlen = sizeof(*sockpath); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + goto error; + } + + if (evutil_make_socket_nonblocking(fd) == -1) { + goto error; + } + + struct stat sb; + if (stat(path, &sb) == -1) { + if (errno != ENOENT) { + goto error; + } + } else if ((sb.st_mode & S_IFMT) == S_IFSOCK) { + lgtd_warnx("removing existing unix socket: %s", path); + if (unlink(path) == -1 && errno != ENOENT) { + goto error; + } + } else { + errno = EEXIST; + goto error; + } + + if (bind(fd, (struct sockaddr *)sockpath, sizeof(*sockpath)) == -1) { + goto error; + } + + listener->evlistener = evconnlistener_new( + lgtd_ev_base, + lgtd_listen_accept_new_client, + listener, + LEV_OPT_CLOSE_ON_FREE, + -1, + fd + ); + if (!listener->evlistener) { + goto error; + } + + SLIST_INSERT_HEAD(&lgtd_listeners, listener, link); + lgtd_info("unix socket ready at %s", path); + + return true; + +error: + lgtd_warn("can't open unix socket at %s", path); + if (fd != -1) { + close(fd); + } + free(listener); + return false; +} diff --git a/core/listen.h b/core/listen.h index adc0961..96be133 100644 --- a/core/listen.h +++ b/core/listen.h @@ -21,8 +21,8 @@ struct evconnlistener; struct lgtd_listen { SLIST_ENTRY(lgtd_listen) link; - const char *addr; - const char *port; + ev_socklen_t addrlen; + struct sockaddr *sockaddr; struct evconnlistener *evlistener; }; SLIST_HEAD(lgtd_listen_list, lgtd_listen); @@ -30,4 +30,5 @@ SLIST_HEAD(lgtd_listen_list, lgtd_listen); extern struct lgtd_listen_list lgtd_listeners; bool lgtd_listen_open(const char *, const char *); +bool lgtd_listen_unix_open(const char *); void lgtd_listen_close_all(void); diff --git a/core/log.c b/core/log.c index a9eb923..2fe3b83 100644 --- a/core/log.c +++ b/core/log.c @@ -15,8 +15,10 @@ // You should have received a copy of the GNU General Public License // along with lighstd. If not, see . +#include #include #include +#include #include #include #include @@ -27,6 +29,7 @@ #include #include #include +#include #include #if LGTD_HAVE_LIBBSD @@ -88,23 +91,50 @@ lgtd_iee8023mactoa(const uint8_t *addr, char *buf, int buflen) return buf; } -void -lgtd_sockaddrtoa(const struct sockaddr_storage *peer, char *buf, int buflen) +char * +lgtd_sockaddrtoa(const struct sockaddr *peer, char *buf, int buflen) { assert(peer); assert(buf); - assert(buflen > 0); + assert(buflen > 1); - const char *printed; - if (peer->ss_family == AF_INET) { + const char *printed = NULL; + int i = 0; + switch (peer->sa_family) { + case AF_INET: + (void)0; const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; - int i = 0; - LGTD_SNPRINTF_APPEND(buf, i, buflen, "::ffff:"); + LGTD_SNPRINTF_APPEND(buf, i, buflen, "[::ffff:"); printed = inet_ntop(AF_INET, &in_peer->sin_addr, &buf[i], buflen - i); - } else { + if (printed) { + i += strlen(printed); + LGTD_SNPRINTF_APPEND( + buf, i, buflen, "]:%hu", ntohs(in_peer->sin_port) + ); + } + break; + case AF_INET6: + (void)0; const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; - printed = inet_ntop(AF_INET6, &in6_peer->sin6_addr, buf, buflen); + LGTD_SNPRINTF_APPEND(buf, i, buflen, "["); + printed = inet_ntop(AF_INET6, &in6_peer->sin6_addr, &buf[i], buflen - i); + if (printed) { + i += strlen(printed); + LGTD_SNPRINTF_APPEND( + buf, i, buflen, "]:%hu", ntohs(in6_peer->sin6_port) + ); + } + break; + case AF_UNIX: + (void)0; + const struct sockaddr_un *un_path = (const struct sockaddr_un *)peer; + LGTD_SNPRINTF_APPEND(buf, i, buflen, "at %s", un_path->sun_path); + printed = buf; + break; + default: + break; } + if (!printed) { buf[0] = 0; lgtd_warnx("not enough space to log an ip address"); @@ -112,6 +142,8 @@ lgtd_sockaddrtoa(const struct sockaddr_storage *peer, char *buf, int buflen) abort(); #endif } + + return buf; } char * diff --git a/core/proto.c b/core/proto.c index 054cb6b..0c9bcfe 100644 --- a/core/proto.c +++ b/core/proto.c @@ -195,6 +195,9 @@ lgtd_proto_get_light_state(struct lgtd_client *client, return; } + char client_ip_addr[LGTD_SOCKADDR_STRLEN]; + LGTD_SOCKADDRTOA(client->addr, client_ip_addr); + lgtd_client_start_send_response(client); lgtd_client_write_string(client, "["); struct lgtd_router_device *device; @@ -216,11 +219,10 @@ lgtd_proto_get_light_state(struct lgtd_client *client, "\"addr\":\"%s\"," "\"gateway\":{" "\"site\":\"%s\"," - "\"url\":\"tcp://[%s]:%hu\"," + "\"url\":\"tcp://%s\"," "\"latency\":%ju" "}", - bulb_addr, site_addr, - bulb->gw->ip_addr, bulb->gw->port, + bulb_addr, site_addr, bulb->gw->peeraddr, (uintmax_t)lgtd_lifx_gateway_latency(bulb->gw) ); @@ -347,8 +349,8 @@ lgtd_proto_get_light_state(struct lgtd_client *client, if (i >= (int)sizeof(buf)) { lgtd_warnx( "can't send state of bulb %s (%s) to client " - "[%s]:%hu: output buffer to small", - bulb->state.label, bulb_addr, client->ip_addr, client->port + "%s: output buffer to small", + bulb->state.label, bulb_addr, client_ip_addr ); continue; } @@ -365,9 +367,9 @@ lgtd_proto_get_light_state(struct lgtd_client *client, } else { lgtd_warnx( "tag_id %d on bulb %.*s (%s) doesn't " - "exist on gw [%s]:%hu (site %s)", + "exist on gw %s (site %s)", tag_id, (int)sizeof(bulb->state.label), bulb->state.label, - bulb_addr, bulb->gw->ip_addr, bulb->gw->port, site_addr + bulb_addr, bulb->gw->peeraddr, site_addr ); } } @@ -439,8 +441,8 @@ lgtd_proto_tag(struct lgtd_client *client, goto error_site_alloc; } lgtd_info( - "created tag [%s] with id %d on gw [%s]:%hu", - tag_label, tag_id, site->gw->ip_addr, site->gw->port + "created tag [%s] with id %d on gw %s", + tag_label, tag_id, site->gw->peeraddr ); } diff --git a/examples/lightsc.py b/examples/lightsc.py index da729e6..99ef9b0 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -33,15 +33,25 @@ import json import socket import sys +import urllib.parse import uuid class LightsClient: - def __init__(self, host, port): - self.host = host - self.port = port - self._socket = socket.create_connection((host, port)) + def __init__(self, url): + self.url = url + + parts = urllib.parse.urlparse(args.url) + + if parts.scheme == "unix": + self._socket = socket.socket(socket.AF_UNIX) + self._socket.connect(parts.path) + elif parts.scheme == "tcp": + self._socket = socket.create_connection((parts.hostname, parts.port)) + else: + raise ValueError("Unsupported url {}".format(url)) + self._pipeline = [] self._batch = False @@ -191,8 +201,8 @@ def _drop_to_shell(lightsc): middle = "d073d502e530" # noqa banner = ( - "Connected to {}:{}, use the variable c to interact with your " - "bulbs:\n\n>>> r = c.get_light_state(\"*\")".format(c.host, c.port) + "Connected to {}, use the variable c to interact with your " + "bulbs:\n\n>>> r = c.get_light_state(\"*\")".format(c.url) ) try: @@ -213,18 +223,19 @@ def _drop_to_shell(lightsc): description="lightsc.py is an interactive lightsd Python client" ) parser.add_argument( - "host", type=str, help="The hostname or ip where lightsd is running" - ) - parser.add_argument( - "port", type=int, help="The port on which lightsd is listening on" + "url", type=str, + help="How to connect to lightsd (e.g: " + "unix:///run/lightsd.sock or tcp://[::1]:1234)" ) args = parser.parse_args() + try: - _drop_to_shell(LightsClient(args.host, args.port)) - except socket.error as ex: + with LightsClient(args.url) as client: + _drop_to_shell(client) + except Exception as ex: print( - "Couldn't connect to lightsd@{}:{}, is it running? " - "({})".format(args.host, args.port, ex.strerror), + "Couldn't connect to {}, is lightsd running? " + "({})".format(args.url, ex), file=sys.stderr ) sys.exit(1) diff --git a/lifx/broadcast.c b/lifx/broadcast.c index 34dc22c..b1f30fa 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -88,29 +88,23 @@ lgtd_lifx_broadcast_handle_read(void) lgtd_time_mono_t received_at = lgtd_time_monotonic_msecs(); char peer_addr[INET6_ADDRSTRLEN]; - lgtd_sockaddrtoa(&peer, peer_addr, sizeof(peer_addr)); - short peer_port = lgtd_sockaddrport(&peer); + LGTD_SOCKADDRTOA((const struct sockaddr *)&peer, peer_addr); if (nbytes < LGTD_LIFX_PACKET_HEADER_SIZE) { - lgtd_warnx( - "broadcast packet too short from [%s]:%hu", peer_addr, peer_port - ); + lgtd_warnx("broadcast packet too short from %s", peer_addr); return false; } lgtd_lifx_wire_decode_header(&read.hdr); if (read.hdr.size != nbytes) { - lgtd_warnx( - "incomplete broadcast packet from [%s]:%hu", - peer_addr, peer_port - ); + lgtd_warnx("incomplete broadcast packet from %s", peer_addr); return false; } int proto_version = read.hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; if (proto_version != LGTD_LIFX_PROTOCOL_V1) { lgtd_warnx( - "unsupported protocol %d from [%s]:%hu", - read.hdr.protocol & 0x0fff, peer_addr, peer_port + "unsupported protocol %d from %s", + read.hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK, peer_addr ); } if (read.hdr.packet_type == LGTD_LIFX_GET_PAN_GATEWAY) { @@ -121,22 +115,23 @@ lgtd_lifx_broadcast_handle_read(void) lgtd_lifx_wire_get_packet_info(read.hdr.packet_type); if (!pkt_info) { lgtd_warnx( - "received unknown packet %#x from [%s]:%hu", - read.hdr.packet_type, peer_addr, peer_port + "received unknown packet %#x from %s", + read.hdr.packet_type, peer_addr ); continue; } if (!(read.hdr.protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE)) { lgtd_warnx( - "received non-addressable packet %s from [%s]:%hu", - pkt_info->name, peer_addr, peer_port + "received non-addressable packet %s from %s", + pkt_info->name, peer_addr ); continue; } - struct lgtd_lifx_gateway *gw = lgtd_lifx_gateway_get(&peer); + struct lgtd_lifx_gateway *gw; + gw = lgtd_lifx_gateway_get((struct sockaddr *)&peer, addrlen); if (!gw && read.hdr.packet_type == LGTD_LIFX_PAN_GATEWAY) { gw = lgtd_lifx_gateway_open( - &peer, addrlen, read.hdr.site, received_at + (struct sockaddr *)&peer, addrlen, read.hdr.site, received_at ); if (!gw) { lgtd_err(1, "can't allocate gateway"); @@ -148,9 +143,7 @@ lgtd_lifx_broadcast_handle_read(void) pkt_info->decode(pkt); pkt_info->handle(gw, &read.hdr, pkt); } else { - lgtd_warnx( - "got packet from unknown gateway [%s]:%hu", peer_addr, peer_port - ); + lgtd_warnx("got packet from unknown gateway %s", peer_addr); } } } diff --git a/lifx/bulb.c b/lifx/bulb.c index 4216e26..9c5ac17 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -197,12 +197,11 @@ lgtd_lifx_bulb_close(struct lgtd_lifx_bulb *bulb) RB_REMOVE(lgtd_lifx_bulb_map, &lgtd_lifx_bulbs_table, bulb); char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( - "closed bulb \"%.*s\" (%s) on [%s]:%hu", + "closed bulb \"%.*s\" (%s) on %s", LGTD_LIFX_LABEL_SIZE, bulb->state.label, LGTD_IEEE8023MACTOA(bulb->addr, addr), - bulb->gw->ip_addr, - bulb->gw->port + bulb->gw->peeraddr ); free(bulb); } diff --git a/lifx/gateway.c b/lifx/gateway.c index 6cfa0a3..b253e06 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -78,9 +78,10 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) char site[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( - "connection with gateway bulb [%s]:%hu (site %s) closed", - gw->ip_addr, gw->port, LGTD_IEEE8023MACTOA(gw->site.as_array, site) + "connection with gateway bulb %s (site %s) closed", + gw->peeraddr, LGTD_IEEE8023MACTOA(gw->site.as_array, site) ); + free(gw->peer); free(gw); } @@ -112,9 +113,7 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, struct lgtd_lifx_gateway *gw = (struct lgtd_lifx_gateway *)ctx; if (events & EV_TIMEOUT) { // Not sure how that could happen in UDP but eh. - lgtd_warn( - "lost connection with gateway bulb [%s]:%hu", gw->ip_addr, gw->port - ); + lgtd_warn("lost connection with gateway bulb %s", gw->peeraddr); lgtd_lifx_gateway_close(gw); if (!lgtd_lifx_broadcast_discovery()) { lgtd_err(1, "can't start auto discovery"); @@ -129,7 +128,7 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, int to_write = gw->pkt_ring[gw->pkt_ring_tail].size; int nbytes = evbuffer_write_atmost(gw->write_buf, gw->socket, to_write); if (nbytes == -1 && errno != EAGAIN) { - lgtd_warn("can't write to [%s]:%hu", gw->ip_addr, gw->port); + lgtd_warn("can't write to %s", gw->peeraddr); lgtd_lifx_gateway_close(gw); if (!lgtd_lifx_broadcast_discovery()) { lgtd_err(1, "can't start auto discovery"); @@ -270,8 +269,8 @@ lgtd_lifx_gateway_get_or_open_bulb(struct lgtd_lifx_gateway *gw, SLIST_INSERT_HEAD(&gw->bulbs, bulb, link_by_gw); char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( - "bulb %s on [%s]:%hu", - LGTD_IEEE8023MACTOA(bulb->addr, addr), gw->ip_addr, gw->port + "bulb %s on %s", + LGTD_IEEE8023MACTOA(bulb->addr, addr), gw->peeraddr ); } } @@ -279,7 +278,7 @@ lgtd_lifx_gateway_get_or_open_bulb(struct lgtd_lifx_gateway *gw, } struct lgtd_lifx_gateway * -lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, +lgtd_lifx_gateway_open(const struct sockaddr *peer, ev_socklen_t addrlen, const uint8_t *site, lgtd_time_mono_t received_at) @@ -292,12 +291,12 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, lgtd_warn("can't allocate a new gateway bulb"); return false; } - gw->socket = socket(peer->ss_family, SOCK_DGRAM, IPPROTO_UDP); + gw->socket = socket(peer->sa_family, SOCK_DGRAM, IPPROTO_UDP); if (gw->socket == -1) { lgtd_warn("can't open a new socket"); goto error_socket; } - if (connect(gw->socket, (const struct sockaddr *)peer, addrlen) == -1 + if (connect(gw->socket, peer, addrlen) == -1 || evutil_make_socket_nonblocking(gw->socket) == -1) { lgtd_warn("can't open a new socket"); goto error_connect; @@ -312,13 +311,16 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, ); gw->write_buf = evbuffer_new(); if (!gw->write_ev || !gw->write_buf) { - lgtd_warn("can't allocate a new gateway bulb"); + goto error_allocate; + } + gw->peer = malloc(addrlen); + if (!gw->peer) { goto error_allocate; } - memcpy(&gw->peer, peer, sizeof(gw->peer)); - lgtd_sockaddrtoa(peer, gw->ip_addr, sizeof(gw->ip_addr)); - gw->port = lgtd_sockaddrport(peer); + memcpy(gw->peer, peer, addrlen); + gw->peerlen = addrlen; + LGTD_SOCKADDRTOA(gw->peer, gw->peeraddr); memcpy(gw->site.as_array, site, sizeof(gw->site.as_array)); gw->last_req_at = received_at; gw->next_req_at = received_at; @@ -338,9 +340,8 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, char site_addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( - "gateway for site %s at [%s]:%hu", - LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr), - gw->ip_addr, gw->port + "gateway for site %s at %s", + LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr), gw->peeraddr ); LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); @@ -353,6 +354,7 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, return gw; error_allocate: + lgtd_warn("can't allocate a new gateway bulb"); if (gw->write_ev) { event_free(gw->write_ev); } @@ -362,19 +364,21 @@ lgtd_lifx_gateway_open(const struct sockaddr_storage *peer, error_connect: evutil_closesocket(gw->socket); error_socket: + free(gw->peer); free(gw); return NULL; } struct lgtd_lifx_gateway * -lgtd_lifx_gateway_get(const struct sockaddr_storage *peer) +lgtd_lifx_gateway_get(const struct sockaddr *peer, ev_socklen_t peerlen) { assert(peer); struct lgtd_lifx_gateway *gw, *next_gw; LIST_FOREACH_SAFE(gw, &lgtd_lifx_gateways, link, next_gw) { - if (peer->ss_family == gw->peer.ss_family - && !memcmp(&gw->peer, peer, sizeof(*peer))) { + if (peer->sa_family == gw->peer->sa_family + && peerlen == gw->peerlen + && !memcmp(gw->peer, peer, peerlen)) { return gw; } } @@ -406,8 +410,8 @@ lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, if (gw->pkt_ring_full) { lgtd_warnx( - "dropping packet type %s: packet queue on [%s]:%hu is full", - pkt_info->name, gw->ip_addr, gw->port + "dropping packet type %s: packet queue on %s is full", + pkt_info->name, gw->peeraddr ); return; } @@ -441,9 +445,9 @@ lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, gw->tag_refcounts[tag_id]++; } else { lgtd_warnx( - "reached refcount limit (%u) for tag [%s] (%d) on gw [%s]:%hu", + "reached refcount limit (%u) for tag [%s] (%d) on gw %s", UINT8_MAX, gw->tags[tag_id] ? gw->tags[tag_id]->label : NULL, - tag_id, gw->ip_addr, gw->port + tag_id, gw->peeraddr ); } } @@ -453,9 +457,9 @@ lgtd_lifx_gateway_update_tag_refcounts(struct lgtd_lifx_gateway *gw, if (--gw->tag_refcounts[tag_id] == 0) { char site[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( - "deleting unused tag [%s] (%d) from gw [%s]:%hu (site %s)", + "deleting unused tag [%s] (%d) from gw %s (site %s)", gw->tags[tag_id] ? gw->tags[tag_id]->label : NULL, - tag_id, gw->ip_addr, gw->port, + tag_id, gw->peeraddr, LGTD_IEEE8023MACTOA(gw->site.as_array, site) ); struct lgtd_lifx_packet_tag_labels pkt = { @@ -487,8 +491,8 @@ lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN], site[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "SET_PAN_GATEWAY <-- [%s]:%hu - %s site=%s, service_type=%d", - gw->ip_addr, gw->port, + "SET_PAN_GATEWAY <-- %s - %s site=%s, service_type=%d", + gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), LGTD_IEEE8023MACTOA(hdr->site, site), pkt->service_type ); @@ -503,11 +507,10 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "SET_LIGHT_STATE <-- [%s]:%hu - %s " + "SET_LIGHT_STATE <-- %s - %s " "hue=%#hx, saturation=%#hx, brightness=%#hx, " "kelvin=%d, dim=%#hx, power=%#hx, label=%.*s, tags=%#jx", - gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), pkt->hue, pkt->saturation, pkt->brightness, pkt->kelvin, pkt->dim, pkt->power, LGTD_LIFX_LABEL_SIZE, pkt->label, (uintmax_t)pkt->tags @@ -546,8 +549,8 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); lgtd_timer_reschedule(gw->refresh_timer, &tv); lgtd_debug( - "[%s]:%hu latency is %jums, scheduling next GET_LIGHT_STATE in %dms", - gw->ip_addr, gw->port, (uintmax_t)latency, timeout + "%s latency is %jums, scheduling next GET_LIGHT_STATE in %dms", + gw->peeraddr, (uintmax_t)latency, timeout ); } return; @@ -555,14 +558,14 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, if (!gw->pending_refresh_req) { lgtd_debug( - "[%s]:%hu latency is %jums, sending GET_LIGHT_STATE now", - gw->ip_addr, gw->port, (uintmax_t)latency + "%s latency is %jums, sending GET_LIGHT_STATE now", + gw->peeraddr, (uintmax_t)latency ); lgtd_lifx_gateway_send_get_all_light_state(gw); } else { lgtd_debug( - "[%s]:%hu GET_LIGHT_STATE for all bulbs on this gw has already " - "been enqueued", gw->ip_addr, gw->port + "%s GET_LIGHT_STATE for all bulbs on this gw has already " + "been enqueued", gw->peeraddr ); } } @@ -576,8 +579,7 @@ lgtd_lifx_gateway_handle_power_state(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "SET_POWER_STATE <-- [%s]:%hu - %s power=%#hx", - gw->ip_addr, gw->port, + "SET_POWER_STATE <-- %s - %s power=%#hx", gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), pkt->power ); @@ -620,8 +622,8 @@ lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, tag_id = lgtd_lifx_wire_bitscan64_forward(~gw->tag_ids); if (tag_id == -1) { lgtd_warnx( - "no tag_id left for new tag [%s] on gw [%s]:%hu (site %s)", - tag_label, gw->ip_addr, gw->port, site + "no tag_id left for new tag [%s] on gw %s (site %s)", + tag_label, gw->peeraddr, site ); return -1; } @@ -638,8 +640,8 @@ lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *gw, return -1; } lgtd_debug( - "tag_id %d allocated for tag [%s] on gw [%s]:%hu (site %s)", - tag_id, tag_label, gw->ip_addr, gw->port, site + "tag_id %d allocated for tag [%s] on gw %s (site %s)", + tag_id, tag_label, gw->peeraddr, site ); gw->tag_ids |= LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); gw->tags[tag_id] = tag; @@ -658,10 +660,9 @@ lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id) if (gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id)) { char site[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "tag_id %d deallocated for tag [%s] on gw [%s]:%hu (site %s)", + "tag_id %d deallocated for tag [%s] on gw %s (site %s)", tag_id, gw->tags[tag_id]->label, - gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(gw->site.as_array, site) + gw->peeraddr, LGTD_IEEE8023MACTOA(gw->site.as_array, site) ); lgtd_lifx_tagging_decref(gw->tags[tag_id], gw); gw->tag_ids &= ~LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id); @@ -678,9 +679,8 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "SET_TAG_LABELS <-- [%s]:%hu - %s label=%.*s, tags=%jx", - gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + "SET_TAG_LABELS <-- %s - %s label=%.*s, tags=%jx", + gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), LGTD_LIFX_LABEL_SIZE, pkt->label, (uintmax_t)pkt->tags ); @@ -703,9 +703,8 @@ lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "SET_TAGS <-- [%s]:%hu - %s tags=%#jx", - gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + "SET_TAGS <-- %s - %s tags=%#jx", + gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), (uintmax_t)pkt->tags ); @@ -718,10 +717,10 @@ lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, if (!(gw->tag_ids & LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id))) { lgtd_warnx( "trying to set unknown tag_id %d (%#jx) " - "on bulb %s (%.*s), gw [%s]:%hu (site %s)", + "on bulb %s (%.*s), gw %s (site %s)", tag_id, LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id), LGTD_IEEE8023MACTOA(b->addr, bulb_addr), - LGTD_LIFX_LABEL_SIZE, b->state.label, gw->ip_addr, gw->port, + LGTD_LIFX_LABEL_SIZE, b->state.label, gw->peeraddr, LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr) ); } @@ -758,10 +757,9 @@ lgtd_lifx_gateway_handle_ip_state(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "%s <-- [%s]:%hu - %s " + "%s <-- %s - %s " "signal_strength=%f, rx_bytes=%u, tx_bytes=%u, temperature=%hu", - type, gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + type, gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), pkt->signal_strength, pkt->rx_bytes, pkt->tx_bytes, pkt->temperature ); @@ -799,10 +797,9 @@ lgtd_lifx_gateway_handle_ip_firmware_info(struct lgtd_lifx_gateway *gw, char built_at[64], installed_at[64], addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "%s <-- [%s]:%hu - %s " + "%s <-- %s - %s " "built_at=%s, installed_at=%s, version=%u", - type, gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + type, gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(pkt->built_at, built_at), LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(pkt->installed_at, installed_at), pkt->version @@ -823,10 +820,9 @@ lgtd_lifx_gateway_handle_product_info(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "PRODUCT_INFO <-- [%s]:%hu - %s " + "PRODUCT_INFO <-- %s - %s " "vendor_id=%#x, product_id=%#x, version=%u", - gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), pkt->vendor_id, pkt->product_id, pkt->version ); @@ -845,9 +841,8 @@ lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *gw, char device_time[64], uptime[64], downtime[64], addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "PRODUCT_INFO <-- [%s]:%hu - %s time=%s, uptime=%s, downtime=%s", - gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + "PRODUCT_INFO <-- %s - %s time=%s, uptime=%s, downtime=%s", + gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(pkt->time, device_time), LGTD_PRINT_DURATION(LGTD_NSECS_TO_SECS(pkt->uptime), uptime), LGTD_PRINT_DURATION(LGTD_NSECS_TO_SECS(pkt->downtime), downtime) @@ -868,9 +863,8 @@ lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "BULB_LABEL <-- [%s]:%hu - %s label=%.*s", - gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + "BULB_LABEL <-- %s - %s label=%.*s", + gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), (int)sizeof(pkt->label), pkt->label ); @@ -888,9 +882,8 @@ lgtd_lifx_gateway_handle_ambient_light(struct lgtd_lifx_gateway *gw, char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "AMBIENT_LIGHT <-- [%s]:%hu - %s ambient_light=%flx", - gw->ip_addr, gw->port, - LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), + "AMBIENT_LIGHT <-- %s - %s ambient_light=%flx", + gw->peeraddr, LGTD_IEEE8023MACTOA(hdr->target.device_addr, addr), pkt->illuminance ); diff --git a/lifx/gateway.h b/lifx/gateway.h index 6975e9d..c0ead01 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -47,10 +47,10 @@ struct lgtd_lifx_gateway { // packet doesn't include the device address in the header (i.e: site and // device_addr have the same value) so we have no choice but to use the // remote ip address to identify a gateway: - struct sockaddr_storage peer; - uint8_t addr[LGTD_LIFX_ADDR_LENGTH]; - char ip_addr[INET6_ADDRSTRLEN]; - uint16_t port; + struct sockaddr *peer; + ev_socklen_t peerlen; + // XXX consider having a constants.h file: + char peeraddr[128]; // TODO: just use an integer and rename it to site_id: union { uint8_t as_array[LGTD_LIFX_ADDR_LENGTH]; @@ -90,8 +90,8 @@ extern struct lgtd_lifx_gateway_list lgtd_lifx_gateways; (bulb_fn)(b, __VA_ARGS__); \ } while (0) -struct lgtd_lifx_gateway *lgtd_lifx_gateway_get(const struct sockaddr_storage *); -struct lgtd_lifx_gateway *lgtd_lifx_gateway_open(const struct sockaddr_storage *, +struct lgtd_lifx_gateway *lgtd_lifx_gateway_get(const struct sockaddr *, ev_socklen_t); +struct lgtd_lifx_gateway *lgtd_lifx_gateway_open(const struct sockaddr *, ev_socklen_t, const uint8_t *, lgtd_time_mono_t); diff --git a/lifx/tagging.c b/lifx/tagging.c index 56a9cae..cdcb14a 100644 --- a/lifx/tagging.c +++ b/lifx/tagging.c @@ -125,8 +125,8 @@ lgtd_lifx_tagging_incref(const char *tag_label, } char site_addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_info( - "tag [%s] added to gw [%s]:%hu (site %s) with tag_id %d", - tag_label, gw->ip_addr, gw->port, + "tag [%s] added to gw %s (site %s) with tag_id %d", + tag_label, gw->peeraddr, LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr), tag_id ); site->gw = gw; @@ -150,8 +150,8 @@ lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, if (site) { char site_addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( - "tag [%s] removed from gw [%s]:%hu (site %s)", - tag->label, gw->ip_addr, gw->port, + "tag [%s] removed from gw %s (site %s)", + tag->label, gw->peeraddr, LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr) ); LIST_REMOVE(site, link); diff --git a/lifx/watchdog.c b/lifx/watchdog.c index f5d3e9c..5e06f95 100644 --- a/lifx/watchdog.c +++ b/lifx/watchdog.c @@ -110,17 +110,15 @@ lgtd_lifx_watchdog_timeout_event_callback(evutil_socket_t socket, int gw_lag = lgtd_lifx_gateway_latency(gw); if (gw_lag >= LGTD_LIFX_WATCHDOG_DEVICE_TIMEOUT_MSECS) { lgtd_info( - "closing bulb gateway [%s]:%hu that " - "hasn't received traffic for %dms", - gw->ip_addr, gw->port, - gw_lag + "closing bulb gateway %s that hasn't received traffic for %dms", + gw->peeraddr, gw_lag ); lgtd_lifx_gateway_close(gw); start_discovery = true; } else if (gw_lag >= LGTD_LIFX_WATCHDOG_DEVICE_FORCE_REFRESH_MSECS) { lgtd_info( - "no update on bulb gateway [%s]:%hu for %dms, forcing refresh", - gw->ip_addr, gw->port, gw_lag + "no update on bulb gateway %s for %dms, forcing refresh", + gw->peeraddr, gw_lag ); lgtd_lifx_gateway_force_refresh(gw); } diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 49f8a55..1953c30 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -89,10 +89,9 @@ lgtd_lifx_wire_enosys_packet_handler(struct lgtd_lifx_gateway *gw, bool tagged = hdr->protocol & LGTD_LIFX_PROTOCOL_TAGGED; unsigned int protocol = hdr->protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; lgtd_info( - "%s <-- [%s]:%hu - (Unimplemented, header info: " + "%s <-- %s - (Unimplemented, header info: " "addressable=%d, tagged=%d, protocol=%d)", - pkt_info->name, gw->ip_addr, gw->port, - addressable, tagged, protocol + pkt_info->name, gw->peeraddr, addressable, tagged, protocol ); } diff --git a/tests/core/client/CMakeLists.txt b/tests/core/client/CMakeLists.txt index 9cf3f5c..7d80725 100644 --- a/tests/core/client/CMakeLists.txt +++ b/tests/core/client/CMakeLists.txt @@ -3,17 +3,18 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) -ADD_LIBRARY( +ADD_CORE_LIBRARY( test_core_client STATIC ${LIGHTSD_SOURCE_DIR}/core/jsmn.c ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c + ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c + ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) -TARGET_LINK_LIBRARIES(test_core_client ${TIME_MONOTONIC_LIBRARY}) - FUNCTION(ADD_CLIENT_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_client) ENDFUNCTION() diff --git a/tests/core/client/test_client_read_callback.c b/tests/core/client/test_client_read_callback.c index ea919ee..7191a0a 100644 --- a/tests/core/client/test_client_read_callback.c +++ b/tests/core/client/test_client_read_callback.c @@ -144,9 +144,10 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + lgtd_client_read_callback(FAKE_BUFFEREVENT, client); if (!evbuffer_get_contiguous_space_call_count) { errx(1, "evbuffer_get_contiguous_space not called"); diff --git a/tests/core/client/test_client_read_callback_extra_data.c b/tests/core/client/test_client_read_callback_extra_data.c index c3b4a66..f7e088f 100644 --- a/tests/core/client/test_client_read_callback_extra_data.c +++ b/tests/core/client/test_client_read_callback_extra_data.c @@ -204,9 +204,10 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + lgtd_client_read_callback(FAKE_BUFFEREVENT, client); if (!evbuffer_get_contiguous_space_call_count) { errx(1, "evbuffer_get_contiguous_space not called"); diff --git a/tests/core/client/test_client_read_callback_multiple_requests.c b/tests/core/client/test_client_read_callback_multiple_requests.c index 7c5f492..b45008b 100644 --- a/tests/core/client/test_client_read_callback_multiple_requests.c +++ b/tests/core/client/test_client_read_callback_multiple_requests.c @@ -189,9 +189,10 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + lgtd_client_read_callback(FAKE_BUFFEREVENT, client); if (!evbuffer_get_contiguous_space_call_count) { errx(1, "evbuffer_get_contiguous_space not called"); diff --git a/tests/core/client/test_client_read_callback_part_too_large.c b/tests/core/client/test_client_read_callback_part_too_large.c index 563f608..dcf4204 100644 --- a/tests/core/client/test_client_read_callback_part_too_large.c +++ b/tests/core/client/test_client_read_callback_part_too_large.c @@ -184,9 +184,10 @@ main(void) ); } - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + lgtd_client_read_callback(FAKE_BUFFEREVENT, client); if (!evbuffer_get_contiguous_space_call_count) { errx(1, "evbuffer_get_contiguous_space not called"); diff --git a/tests/core/client/test_client_read_callback_yield_on_eagain.c b/tests/core/client/test_client_read_callback_yield_on_eagain.c index 194ead3..12350a0 100644 --- a/tests/core/client/test_client_read_callback_yield_on_eagain.c +++ b/tests/core/client/test_client_read_callback_yield_on_eagain.c @@ -185,10 +185,11 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); - lgtd_client_read_callback(FAKE_BUFFEREVENT, &client); + lgtd_client_read_callback(FAKE_BUFFEREVENT, client); + lgtd_client_read_callback(FAKE_BUFFEREVENT, client); if (!evbuffer_get_contiguous_space_call_count) { errx(1, "evbuffer_get_contiguous_space not called"); diff --git a/tests/core/daemon/test_daemon_update_proctitle.c b/tests/core/daemon/test_daemon_update_proctitle.c index b54b26a..7cd72cc 100644 --- a/tests/core/daemon/test_daemon_update_proctitle.c +++ b/tests/core/daemon/test_daemon_update_proctitle.c @@ -76,19 +76,19 @@ main(void) } expected = ( - "listening_on(foobar.com:[1234]); " + "listening_on([::ffff:127.0.0.1]:1234); " "lifx_gateways(found=1); " "bulbs(found=2, on=0); " "clients(connected=0)" ); - lgtd_tests_insert_mock_listener("foobar.com", "1234"); + lgtd_tests_insert_mock_listener("127.0.0.1", 1234); lgtd_daemon_update_proctitle(); if (setproctitle_call_count != 5) { errx(1, "setproctitle should have been called"); } expected = ( - "listening_on(foobar.com:[1234]); " + "listening_on([::ffff:127.0.0.1]:1234); " "lifx_gateways(found=1); " "bulbs(found=2, on=1); " "clients(connected=0)" @@ -99,7 +99,7 @@ main(void) } expected = ( - "listening_on(foobar.com:[1234]); " + "listening_on([::ffff:127.0.0.1]:1234); " "lifx_gateways(found=1); " "bulbs(found=2, on=1); " "clients(connected=1)" diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c index 85cdee4..1e5eb17 100644 --- a/tests/core/proto/test_proto_get_light_state.c +++ b/tests/core/proto/test_proto_get_light_state.c @@ -37,7 +37,8 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) } static struct lgtd_lifx_gateway gw_bulb_1 = { - .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs), + .peeraddr = "[::ffff:127.0.0.1]:1" }; static struct lgtd_lifx_bulb bulb_1 = { .addr = { 1, 2, 3, 4, 5 }, @@ -69,7 +70,8 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~"); static struct lgtd_lifx_gateway gw_bulb_2 = { .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), - .tag_ids = 0x7 + .tag_ids = 0x7, + .peeraddr = "[::ffff:127.0.0.1]:2" }; lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0); lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); @@ -104,10 +106,11 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets = (void *)0x2a; - lgtd_proto_get_light_state(&client, targets); + lgtd_proto_get_light_state(client, targets); const char expected[] = ("[" "{" @@ -115,7 +118,7 @@ main(void) "\"addr\":\"05:04:03:02:01:00\"," "\"gateway\":{" "\"site\":\"00:00:00:00:00:00\"," - "\"url\":\"tcp://[]:0\"," + "\"url\":\"tcp://[::ffff:127.0.0.1]:2\"," "\"latency\":0" "}," "\"mcu\":{" @@ -158,7 +161,7 @@ main(void) "\"addr\":\"01:02:03:04:05:00\"," "\"gateway\":{" "\"site\":\"00:00:00:00:00:00\"," - "\"url\":\"tcp://[]:0\"," + "\"url\":\"tcp://[::ffff:127.0.0.1]:1\"," "\"latency\":0" "}," "\"mcu\":{" @@ -228,7 +231,7 @@ main(void) "\"addr\":\"05:04:03:02:01:00\"," "\"gateway\":{" "\"site\":\"00:00:00:00:00:00\"," - "\"url\":\"tcp://[]:0\"," + "\"url\":\"tcp://[::ffff:127.0.0.1]:2\"," "\"latency\":0" "}," "\"mcu\":{\"firmware_version\":\"2.1\"}," @@ -246,7 +249,7 @@ main(void) "\"addr\":\"01:02:03:04:05:00\"," "\"gateway\":{" "\"site\":\"00:00:00:00:00:00\"," - "\"url\":\"tcp://[]:0\"," + "\"url\":\"tcp://[::ffff:127.0.0.1]:1\"," "\"latency\":0" "}," "\"mcu\":{\"firmware_version\":\"0.0\"}," @@ -263,7 +266,7 @@ main(void) reset_client_write_buf(); - lgtd_proto_get_light_state(&client, targets); + lgtd_proto_get_light_state(client, targets); if (client_write_buf_idx != sizeof(expected_info) - 1) { lgtd_errx( diff --git a/tests/core/proto/test_proto_get_light_state_empty_device_list.c b/tests/core/proto/test_proto_get_light_state_empty_device_list.c index f08e456..5f22e95 100644 --- a/tests/core/proto/test_proto_get_light_state_empty_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_empty_device_list.c @@ -38,10 +38,11 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets = (void *)0x2a; - lgtd_proto_get_light_state(&client, targets); + lgtd_proto_get_light_state(client, targets); const char expected[] = "[]"; diff --git a/tests/core/proto/test_proto_get_light_state_label_overflow.c b/tests/core/proto/test_proto_get_light_state_label_overflow.c index 7548c45..dc968c7 100644 --- a/tests/core/proto/test_proto_get_light_state_label_overflow.c +++ b/tests/core/proto/test_proto_get_light_state_label_overflow.c @@ -37,7 +37,8 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) } static struct lgtd_lifx_gateway gw_bulb_1 = { - .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs), + .peeraddr = "[::ffff:127.0.0.1]:1" }; static struct lgtd_lifx_bulb bulb_1 = { .addr = { 1, 2, 3, 4, 5 }, @@ -70,7 +71,8 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets = (void *)0x2a; lgtd_opts.verbosity = LGTD_INFO; @@ -81,7 +83,7 @@ main(void) "\"addr\":\"01:02:03:04:05:00\"," "\"gateway\":{" "\"site\":\"00:00:00:00:00:00\"," - "\"url\":\"tcp://[]:0\"," + "\"url\":\"tcp://[::ffff:127.0.0.1]:1\"," "\"latency\":0" "}," "\"mcu\":{\"firmware_version\":\"0.0\"}," @@ -98,7 +100,7 @@ main(void) reset_client_write_buf(); - lgtd_proto_get_light_state(&client, targets); + lgtd_proto_get_light_state(client, targets); if (client_write_buf_idx != sizeof(expected_info) - 1) { lgtd_errx( diff --git a/tests/core/proto/test_proto_get_light_state_null_device_list.c b/tests/core/proto/test_proto_get_light_state_null_device_list.c index 604c291..09b4728 100644 --- a/tests/core/proto/test_proto_get_light_state_null_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_null_device_list.c @@ -48,10 +48,11 @@ lgtd_client_send_error(struct lgtd_client *client, int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets = (void *)0x2a; - lgtd_proto_get_light_state(&client, targets); + lgtd_proto_get_light_state(client, targets); if (!send_error_called) { lgtd_errx(1, "lgtd_client_send_error hasn't been called"); diff --git a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c index e493371..f8c5358 100644 --- a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c +++ b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c @@ -34,7 +34,8 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) SLIST_HEAD_INITIALIZER(&devices); static struct lgtd_lifx_gateway gw_bulb_1 = { - .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs) + .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_1.bulbs), + .peeraddr = "[::ffff:127.0.0.1]:1" }; static struct lgtd_lifx_bulb bulb_1 = { .addr = { 1, 2, 3, 4, 5 }, @@ -57,7 +58,8 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) struct lgtd_lifx_tag *gw_2_tag_3 = lgtd_tests_insert_mock_tag("wave~"); static struct lgtd_lifx_gateway gw_bulb_2 = { .bulbs = LIST_HEAD_INITIALIZER(&gw_bulb_2.bulbs), - .tag_ids = 0x7 + .tag_ids = 0x7, + .peeraddr = "[::ffff:127.0.0.1]:2" }; lgtd_tests_add_tag_to_gw(gw_2_tag_1, &gw_bulb_2, 0); lgtd_tests_add_tag_to_gw(gw_2_tag_2, &gw_bulb_2, 1); @@ -84,12 +86,13 @@ lgtd_router_targets_to_devices(const struct lgtd_proto_target_list *targets) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets = (void *)0x2a; lgtd_opts.verbosity = LGTD_INFO; - lgtd_proto_get_light_state(&client, targets); + lgtd_proto_get_light_state(client, targets); const char expected[] = ("[" "{" @@ -97,7 +100,7 @@ main(void) "\"addr\":\"05:04:03:02:01:00\"," "\"gateway\":{" "\"site\":\"00:00:00:00:00:00\"," - "\"url\":\"tcp://[]:0\"," + "\"url\":\"tcp://[::ffff:127.0.0.1]:2\"," "\"latency\":0" "}," "\"mcu\":{\"firmware_version\":\"0.0\"}," @@ -115,7 +118,7 @@ main(void) "\"addr\":\"01:02:03:04:05:00\"," "\"gateway\":{" "\"site\":\"00:00:00:00:00:00\"," - "\"url\":\"tcp://[]:0\"," + "\"url\":\"tcp://[::ffff:127.0.0.1]:1\"," "\"latency\":0" "}," "\"mcu\":{\"firmware_version\":\"0.0\"}," diff --git a/tests/core/proto/test_proto_power_off.c b/tests/core/proto/test_proto_power_off.c index b8a95a5..d04d771 100644 --- a/tests/core/proto/test_proto_power_off.c +++ b/tests/core/proto/test_proto_power_off.c @@ -55,9 +55,10 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_proto_power_off(&client, targets); + lgtd_proto_power_off(client, targets); return 0; } diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c index e3133ba..b56e05b 100644 --- a/tests/core/proto/test_proto_power_off_routing_error.c +++ b/tests/core/proto/test_proto_power_off_routing_error.c @@ -55,9 +55,10 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_proto_power_off(&client, targets); + lgtd_proto_power_off(client, targets); return 0; } diff --git a/tests/core/proto/test_proto_power_on.c b/tests/core/proto/test_proto_power_on.c index 3b7e94b..aa22922 100644 --- a/tests/core/proto/test_proto_power_on.c +++ b/tests/core/proto/test_proto_power_on.c @@ -55,9 +55,10 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_proto_power_on(&client, targets); + lgtd_proto_power_on(client, targets); return 0; } diff --git a/tests/core/proto/test_proto_power_on_routing_error.c b/tests/core/proto/test_proto_power_on_routing_error.c index 85777e6..2327ee9 100644 --- a/tests/core/proto/test_proto_power_on_routing_error.c +++ b/tests/core/proto/test_proto_power_on_routing_error.c @@ -55,9 +55,10 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_proto_power_on(&client, targets); + lgtd_proto_power_on(client, targets); return 0; } diff --git a/tests/core/proto/test_proto_power_toggle.c b/tests/core/proto/test_proto_power_toggle.c index 45ea936..e60ea2c 100644 --- a/tests/core/proto/test_proto_power_toggle.c +++ b/tests/core/proto/test_proto_power_toggle.c @@ -127,10 +127,11 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets = (void *)0x2a; - lgtd_proto_power_toggle(&client, targets); + lgtd_proto_power_toggle(client, targets); const char expected[] = "true"; diff --git a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c index 9a018ed..4443c10 100644 --- a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c +++ b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c @@ -66,10 +66,11 @@ lgtd_client_send_error(struct lgtd_client *client, int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets = (void *)0x2a; - lgtd_proto_power_toggle(&client, targets); + lgtd_proto_power_toggle(client, targets); if (client_send_error_call_count != 1) { errx(1, "lgtd_client_send_error called %d times (expected 1)", diff --git a/tests/core/proto/test_proto_set_label.c b/tests/core/proto/test_proto_set_label.c index bee22dd..741e1f4 100644 --- a/tests/core/proto/test_proto_set_label.c +++ b/tests/core/proto/test_proto_set_label.c @@ -69,9 +69,10 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_proto_set_label(&client, targets, "test"); + lgtd_proto_set_label(client, targets, "test"); if (!router_send_call_count) { errx(1, "lgtd_router_send wasn't called"); diff --git a/tests/core/proto/test_proto_set_label_too_long.c b/tests/core/proto/test_proto_set_label_too_long.c index 10bc2ab..c05c5be 100644 --- a/tests/core/proto/test_proto_set_label_too_long.c +++ b/tests/core/proto/test_proto_set_label_too_long.c @@ -71,9 +71,10 @@ main(void) struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); - lgtd_proto_set_label(&client, targets, test_label); + lgtd_proto_set_label(client, targets, test_label); if (!router_send_call_count) { errx(1, "lgtd_router_send wasn't called"); diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c index cd2951e..329f696 100644 --- a/tests/core/proto/test_proto_tag_create.c +++ b/tests/core/proto/test_proto_tag_create.c @@ -220,11 +220,12 @@ setup_devices(void) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); setup_devices(); - lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub"); + lgtd_proto_tag(client, FAKE_TARGET_LIST, "dub"); const char expected[] = "true"; if (client_write_buf_idx != sizeof(expected) - 1) { diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c index c838ab8..7b0233f 100644 --- a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c +++ b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c @@ -187,12 +187,12 @@ setup_devices(void) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); setup_devices(); - lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub"); - + lgtd_proto_tag(client, FAKE_TARGET_LIST, "dub"); if (gateway_send_to_site_called) { lgtd_errx(1, "SET_TAG_LABELS shouldn't have been sent"); diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c index a7a9fa5..0152d22 100644 --- a/tests/core/proto/test_proto_tag_update.c +++ b/tests/core/proto/test_proto_tag_update.c @@ -247,11 +247,12 @@ setup_devices(void) int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); setup_devices(); - lgtd_proto_tag(&client, FAKE_TARGET_LIST, "dub"); + lgtd_proto_tag(client, FAKE_TARGET_LIST, "dub"); const char expected[] = "true"; if (client_write_buf_idx != sizeof(expected) - 1) { diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c index f1d4868..4374724 100644 --- a/tests/core/proto/test_proto_untag.c +++ b/tests/core/proto/test_proto_untag.c @@ -136,13 +136,14 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets = (void *)0x2a; tag_vapor = lgtd_tests_insert_mock_tag("vapor"); - lgtd_proto_untag(&client, targets, "vapor"); + lgtd_proto_untag(client, targets, "vapor"); const char expected[] = "true"; diff --git a/tests/core/proto/test_proto_untag_tag_does_not_exist.c b/tests/core/proto/test_proto_untag_tag_does_not_exist.c index 918405f..3f2d741 100644 --- a/tests/core/proto/test_proto_untag_tag_does_not_exist.c +++ b/tests/core/proto/test_proto_untag_tag_does_not_exist.c @@ -53,12 +53,13 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, int main(void) { - struct lgtd_client client = { .io = FAKE_BUFFEREVENT }; + struct lgtd_client *client; + client = lgtd_tests_insert_mock_client(FAKE_BUFFEREVENT); struct lgtd_proto_target_list *targets; targets = lgtd_tests_build_target_list("*", NULL); - lgtd_proto_untag(&client, targets, "vapor"); + lgtd_proto_untag(client, targets, "vapor"); const char expected[] = "true"; diff --git a/tests/core/tests_utils.c b/tests/core/tests_utils.c index 4d5836d..396111e 100644 --- a/tests/core/tests_utils.c +++ b/tests/core/tests_utils.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,7 @@ #include "lifx/bulb.h" #include "lifx/gateway.h" #include "tests_utils.h" +#include "core/lightsd.h" struct lgtd_listen_list lgtd_listeners = SLIST_HEAD_INITIALIZER(&lgtd_listeners); @@ -42,6 +44,17 @@ lgtd_tests_insert_mock_gateway(int id) gw->socket = id; gw->site.as_array[0] = id; +#if 0 + struct sockaddr_in in_addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = inet_addr("127.0.0.1"), + .sin_port = htons(id) + }; + gw->peer = calloc(1, sizeof(in_addr)); + memcpy(gw->peeraddr, &in_addr, sizeof(in_addr)); + gw->peerlen = sizeof(in_addr); +#endif + LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, 1); @@ -124,16 +137,44 @@ lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *tag, } struct lgtd_listen * -lgtd_tests_insert_mock_listener(const char *addr, const char *port) +lgtd_tests_insert_mock_listener(const char *ipv4, uint16_t port) { struct lgtd_listen *listener = calloc(1, sizeof(*listener)); - listener->addr = addr; - listener->port = port; + struct sockaddr_in in_addr; + memset(&in_addr, 0, sizeof(in_addr)); + in_addr.sin_family = AF_INET; + in_addr.sin_addr.s_addr = inet_addr(ipv4); + in_addr.sin_port = htons(port); + listener->sockaddr = calloc(1, sizeof(in_addr)); + memcpy(listener->sockaddr, &in_addr, sizeof(in_addr)); + listener->addrlen = sizeof(in_addr); SLIST_INSERT_HEAD(&lgtd_listeners, listener, link); return listener; } +static struct sockaddr * +lgtd_tests_make_sockaddr(int family, const char *addr, size_t addrlen) +{ + struct sockaddr *sa = calloc(1, sizeof(struct sockaddr_storage)); + sa->sa_family = family; + memcpy(sa->sa_data, addr, LGTD_MIN( + sizeof(*sa) - offsetof(struct sockaddr, sa_data), addrlen + )); + return sa; +} + +struct lgtd_client * +lgtd_tests_insert_mock_client(struct bufferevent *io) +{ + struct lgtd_client *client = calloc(1, sizeof(*client)); + client->io = io; + client->addr = lgtd_tests_make_sockaddr( + AF_UNIX, "/toto.sock", sizeof("/toto.sock") + ); + return client; +} + char * lgtd_tests_make_temp_dir(void) { diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index e01a8aa..983fc6a 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -1,5 +1,7 @@ #pragma once +struct bufferevent; + static inline bool lgtd_tests_lifx_header_has_flags(const struct lgtd_lifx_packet_header *hdr, int flags) @@ -40,4 +42,5 @@ struct lgtd_lifx_tag *lgtd_tests_insert_mock_tag(const char *); struct lgtd_lifx_site *lgtd_tests_add_tag_to_gw(struct lgtd_lifx_tag *, struct lgtd_lifx_gateway *, int); -struct lgtd_listen *lgtd_tests_insert_mock_listener(const char *addr, const char *port); +struct lgtd_listen *lgtd_tests_insert_mock_listener(const char *, uint16_t); +struct lgtd_client *lgtd_tests_insert_mock_client(struct bufferevent *); From 2aed6719d8cd71b082eb24b0c45c683342029345 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 11:51:31 -0700 Subject: [PATCH 110/181] Properly handle JSON-RPC notifications JSON-RPC notifications are requests without the id field which means that no reply should be sent. --- core/client.c | 4 +- core/client.h | 2 +- core/jsonrpc.c | 83 +++++++++++++---- core/pipe.c | 2 +- tests/core/jsonrpc/test_jsonrpc_batch.c | 8 +- .../test_jsonrpc_batch_notifications_only.c | 90 ++++++++++++++++++ .../test_jsonrpc_batch_one_invalid_request.c | 11 +-- .../test_jsonrpc_batch_one_notification.c | 91 +++++++++++++++++++ .../test_jsonrpc_dispatch_one_no_params.c | 8 +- 9 files changed, 261 insertions(+), 38 deletions(-) create mode 100644 tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c create mode 100644 tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c diff --git a/core/client.c b/core/client.c index 31e1d14..c06bc14 100644 --- a/core/client.c +++ b/core/client.c @@ -49,7 +49,9 @@ lgtd_client_close(struct lgtd_client *client) LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(clients, -1); LIST_REMOVE(client, link); - bufferevent_free(client->io); + if (client->io) { // XXX: see ugly hack in lgtd_jsonrpc_dispatch_one + bufferevent_free(client->io); + } free(client->addr); free(client); } diff --git a/core/client.h b/core/client.h index ab5ef66..59cf8f7 100644 --- a/core/client.h +++ b/core/client.h @@ -17,7 +17,7 @@ #pragma once -enum { LGTD_CLIENT_JSMN_TOKENS_NUM = 48 }; +enum { LGTD_CLIENT_JSMN_TOKENS_NUM = 96 }; enum { LGTD_CLIENT_MAX_REQUEST_BUF_SIZE = 2048 }; enum lgtd_client_error_code { diff --git a/core/jsonrpc.c b/core/jsonrpc.c index a46642d..609a127 100644 --- a/core/jsonrpc.c +++ b/core/jsonrpc.c @@ -1080,10 +1080,26 @@ lgtd_jsonrpc_check_and_call_set_label(struct lgtd_client *client) ); } +static void +lgtd_jsonrpc_batch_prepare_next_part(struct lgtd_client *client, + const int *batch_sent) +{ + assert(client); + + if (batch_sent) { + if (*batch_sent == 1) { + lgtd_client_write_string(client, "["); + } else if (*batch_sent) { + lgtd_client_write_string(client, ","); + } + } +} + static int lgtd_jsonrpc_dispatch_one(struct lgtd_client *client, const jsmntok_t *tokens, - int ntokens) + int ntokens, + int *batch_sent) { static const struct lgtd_jsonrpc_method methods[] = { LGTD_JSONRPC_METHOD( @@ -1125,6 +1141,13 @@ lgtd_jsonrpc_dispatch_one(struct lgtd_client *client, ) }; + if (batch_sent) { + ++*batch_sent; + } + + enum lgtd_jsonrpc_error_code error_code; + const char *error_msg; + struct lgtd_jsonrpc_request request; memset(&request, 0, sizeof(request)); bool ok = lgtd_jsonrpc_check_and_extract_request( @@ -1132,12 +1155,12 @@ lgtd_jsonrpc_dispatch_one(struct lgtd_client *client, ); client->current_request = &request; if (!ok) { - lgtd_jsonrpc_send_error( - client, LGTD_JSONRPC_INVALID_REQUEST, "Invalid request" - ); - return lgtd_jsonrpc_consume_object_or_array( + error_code = LGTD_JSONRPC_INVALID_REQUEST; + error_msg = "Invalid request"; + request.request_ntokens = lgtd_jsonrpc_consume_object_or_array( tokens, 0, ntokens, client->json ); + goto error; } assert(request.method); @@ -1154,22 +1177,40 @@ lgtd_jsonrpc_dispatch_one(struct lgtd_client *client, if (!diff) { int params_count = request.params ? request.params->size : 0; if (params_count != methods[i].params_count) { - lgtd_jsonrpc_send_error( - client, LGTD_JSONRPC_INVALID_PARAMS, - "Invalid number of parameters" - ); + error_code = LGTD_JSONRPC_INVALID_PARAMS; + error_msg = "Invalid number of parameters"; goto error; } + struct bufferevent *client_io = NULL; // keep compilers happy... + if (!request.id) { + // Ugly hack to behave correctly on jsonrpc notifications, it's + // not worth doing it properly right now. It is especially ugly + // since we can't properly close that client now (but we don't + // do that in lgtd_proto and signals are deferred with the + // event loop). + client_io = client->io; + client->io = NULL; + if (batch_sent) { + --*batch_sent; + } + } else { + lgtd_jsonrpc_batch_prepare_next_part(client, batch_sent); + } methods[i].method(client); + if (!request.id) { + client->io = client_io; + } client->current_request = NULL; return request.request_ntokens; } } - lgtd_jsonrpc_send_error( - client, LGTD_JSONRPC_METHOD_NOT_FOUND, "Method not found" - ); + error_code = LGTD_JSONRPC_METHOD_NOT_FOUND; + error_msg = "Method not found"; + error: + lgtd_jsonrpc_batch_prepare_next_part(client, batch_sent); + lgtd_jsonrpc_send_error(client, error_code, error_msg); client->current_request = NULL; return request.request_ntokens; } @@ -1188,20 +1229,21 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) } if (!lgtd_jsonrpc_type_array(client->jsmn_tokens, client->json)) { - lgtd_jsonrpc_dispatch_one(client, client->jsmn_tokens, parsed); + lgtd_jsonrpc_dispatch_one(client, client->jsmn_tokens, parsed, NULL); return; } - bool comma = false; + int batch_sent = 0; for (int ti = 1; ti < parsed;) { const jsmntok_t *tok = &client->jsmn_tokens[ti]; - lgtd_client_write_string(client, comma ? "," : "["); - comma = true; - if (lgtd_jsonrpc_type_object(tok, client->json)) { - ti += lgtd_jsonrpc_dispatch_one(client, tok, parsed - ti); + ti += lgtd_jsonrpc_dispatch_one( + client, tok, parsed - ti, &batch_sent + ); } else { + batch_sent++; + lgtd_jsonrpc_batch_prepare_next_part(client, &batch_sent); lgtd_jsonrpc_send_error( client, LGTD_JSONRPC_INVALID_REQUEST, "Invalid request" ); @@ -1214,5 +1256,8 @@ lgtd_jsonrpc_dispatch_request(struct lgtd_client *client, int parsed) } } } - lgtd_client_write_string(client, "]"); + + if (batch_sent) { + lgtd_client_write_string(client, "]"); + } } diff --git a/core/pipe.c b/core/pipe.c index 4f747d6..88d0493 100644 --- a/core/pipe.c +++ b/core/pipe.c @@ -107,7 +107,7 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) switch (rv) { case JSMN_ERROR_NOMEM: case JSMN_ERROR_INVAL: - lgtd_warnx("pipe %s: request too big or invalid", pipe->path); + lgtd_warnx("pipe %s: request too big or invalid: %s", pipe->path, buf); // ignore what's left drain = true; break; diff --git a/tests/core/jsonrpc/test_jsonrpc_batch.c b/tests/core/jsonrpc/test_jsonrpc_batch.c index ce57067..c831eb0 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch.c @@ -53,6 +53,7 @@ lgtd_proto_get_light_state(struct lgtd_client *client, int main(void) { + jsmntok_t tokens[32]; const char json[] = ("[" "{" "\"method\": \"power_on\"," @@ -67,12 +68,9 @@ main(void) "\"jsonrpc\": \"2.0\"" "}" "]"); - struct lgtd_client client = { .json = json }; + struct lgtd_client client = { .json = json, .jsmn_tokens = tokens }; int parsed = parse_json( - client.jsmn_tokens, - LGTD_ARRAY_SIZE(client.jsmn_tokens), - json, - sizeof(json) + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) ); lgtd_jsonrpc_dispatch_request(&client, parsed); diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c b/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c new file mode 100644 index 0000000..4d1c9da --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c @@ -0,0 +1,90 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_GET_LIGHT_STATE +#define MOCKED_LGTD_PROTO_POWER_ON +#include "mock_proto.h" +#include "test_jsonrpc_utils.h" + +static int power_on_call_count = 0; + +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (power_on_call_count++) { + errx(1, "proto_power_on should have been called once"); + } +} + +static int get_light_state_call_count = 0; + +void +lgtd_proto_get_light_state(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (get_light_state_call_count++) { + errx(1, "proto_power_on should have been called once"); + } +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("[" + "{" + "\"method\": \"power_on\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}," + "{" + "\"method\": \"get_light_state\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}" + "]"); + struct lgtd_client client = { .json = json, .jsmn_tokens = tokens }; + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + lgtd_jsonrpc_dispatch_request(&client, parsed); + + if (!power_on_call_count) { + errx(1, "power_on was never called"); + } + + if (!get_light_state_call_count) { + errx(1, "get_light_state was never called"); + } + + const char expected[] = ""; + if (strcmp(expected, client_write_buf)) { + errx(1, "got client buf %s (expected %s)", client_write_buf, expected); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c b/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c index 1a9cec1..7d9d005 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c @@ -30,6 +30,7 @@ lgtd_proto_power_on(struct lgtd_client *client, int main(void) { + jsmntok_t tokens[32]; const char json[] = ("[" "{" "\"method\": \"power_on\"," @@ -44,12 +45,9 @@ main(void) "\"jsonrpc\": \"2.0\"" "}" "]"); - struct lgtd_client client = { .json = json }; + struct lgtd_client client = { .json = json, .jsmn_tokens = tokens }; int parsed = parse_json( - client.jsmn_tokens, - LGTD_ARRAY_SIZE(client.jsmn_tokens), - json, - sizeof(json) + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) ); lgtd_jsonrpc_dispatch_request(&client, parsed); @@ -58,7 +56,8 @@ main(void) errx(1, "power_on was never called"); } - const char expected[] = ("[," + const char expected[] = ("[" + "," // we mocked the first function "{" "\"jsonrpc\": \"2.0\", " "\"id\": \"1f7a32c8-6741-4ee7-bec1-8431c7d514dc\", " diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c b/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c new file mode 100644 index 0000000..af63472 --- /dev/null +++ b/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c @@ -0,0 +1,91 @@ +#include "jsonrpc.c" + +#include "mock_client_buf.h" +#define MOCKED_LGTD_PROTO_GET_LIGHT_STATE +#define MOCKED_LGTD_PROTO_POWER_ON +#include "mock_proto.h" +#include "test_jsonrpc_utils.h" + +static int power_on_call_count = 0; + +void +lgtd_proto_power_on(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (power_on_call_count++) { + errx(1, "proto_power_on should have been called once"); + } +} + +static int get_light_state_call_count = 0; + +void +lgtd_proto_get_light_state(struct lgtd_client *client, + const struct lgtd_proto_target_list *targets) +{ + if (!client) { + errx(1, "missing client!"); + } + + if (strcmp(SLIST_FIRST(targets)->target, "*")) { + errx( + 1, "Invalid target [%s] (expected=[*])", + SLIST_FIRST(targets)->target + ); + } + + if (get_light_state_call_count++) { + errx(1, "proto_power_on should have been called once"); + } +} + +int +main(void) +{ + jsmntok_t tokens[32]; + const char json[] = ("[" + "{" + "\"method\": \"power_on\"," + "\"id\": \"004daf12-0561-4fbc-bfdb-bfe69cfbf4b5\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}," + "{" + "\"method\": \"get_light_state\"," + "\"params\": [\"*\"]," + "\"jsonrpc\": \"2.0\"" + "}" + "]"); + struct lgtd_client client = { .json = json, .jsmn_tokens = tokens }; + int parsed = parse_json( + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) + ); + + lgtd_jsonrpc_dispatch_request(&client, parsed); + + if (!power_on_call_count) { + errx(1, "power_on was never called"); + } + + if (!get_light_state_call_count) { + errx(1, "get_light_state was never called"); + } + + const char expected[] = "[]"; // we mocked the functions + if (strcmp(expected, client_write_buf)) { + errx(1, "got client buf %s (expected %s)", client_write_buf, expected); + } + + return 0; +} diff --git a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c index 9f7e986..c1c90ff 100644 --- a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c @@ -19,6 +19,7 @@ lgtd_proto_power_on(struct lgtd_client *client, int main(void) { + jsmntok_t tokens[32]; const char json[] = ("{" "\"jsonrpc\": \"2.0\"," "\"method\": \"power_on\"," @@ -26,13 +27,10 @@ main(void) "}"); struct lgtd_client client = { .json = json }; int parsed = parse_json( - client.jsmn_tokens, - LGTD_ARRAY_SIZE(client.jsmn_tokens), - json, - sizeof(json) + tokens, LGTD_ARRAY_SIZE(tokens), json, sizeof(json) ); - lgtd_jsonrpc_dispatch_one(&client, client.jsmn_tokens, parsed); + lgtd_jsonrpc_dispatch_one(&client, tokens, parsed, NULL); const char expected[] = ("{" "\"jsonrpc\": \"2.0\", " From fcd0f66f48e164677dad7613ef0f6e1722216243 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 11:51:31 -0700 Subject: [PATCH 111/181] Stop using a global struct in watchdog.c It's annoying.. --- lifx/watchdog.c | 49 ++++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/lifx/watchdog.c b/lifx/watchdog.c index 5e06f95..d76419e 100644 --- a/lifx/watchdog.c +++ b/lifx/watchdog.c @@ -36,13 +36,8 @@ #include "watchdog.h" #include "core/lightsd.h" -static struct { - struct event *watchdog_interval_ev; - struct event *discovery_timeout_ev; -} lgtd_lifx_watchdog_context = { - .watchdog_interval_ev = NULL, - .discovery_timeout_ev = NULL -}; +static struct event *lgtd_watchdog_interval_ev = NULL; +static struct event *lgtd_discovery_timeout_ev = NULL; static void lgtd_lifx_watchdog_discovery_timeout_event_callback(evutil_socket_t socket, @@ -134,17 +129,17 @@ lgtd_lifx_watchdog_timeout_event_callback(evutil_socket_t socket, bool lgtd_lifx_watchdog_setup(void) { - assert(!lgtd_lifx_watchdog_context.watchdog_interval_ev); - assert(!lgtd_lifx_watchdog_context.discovery_timeout_ev); + assert(!lgtd_watchdog_interval_ev); + assert(!lgtd_discovery_timeout_ev); - lgtd_lifx_watchdog_context.discovery_timeout_ev = event_new( + lgtd_discovery_timeout_ev = event_new( lgtd_ev_base, -1, 0, lgtd_lifx_watchdog_discovery_timeout_event_callback, NULL ); - lgtd_lifx_watchdog_context.watchdog_interval_ev = event_new( + lgtd_watchdog_interval_ev = event_new( lgtd_ev_base, -1, EV_PERSIST, @@ -152,8 +147,7 @@ lgtd_lifx_watchdog_setup(void) NULL ); - if (lgtd_lifx_watchdog_context.discovery_timeout_ev - && lgtd_lifx_watchdog_context.watchdog_interval_ev) { + if (lgtd_discovery_timeout_ev && lgtd_watchdog_interval_ev) { return true; } @@ -166,15 +160,15 @@ lgtd_lifx_watchdog_setup(void) void lgtd_lifx_watchdog_close(void) { - if (lgtd_lifx_watchdog_context.discovery_timeout_ev) { - event_del(lgtd_lifx_watchdog_context.discovery_timeout_ev); - event_free(lgtd_lifx_watchdog_context.discovery_timeout_ev); - lgtd_lifx_watchdog_context.discovery_timeout_ev = NULL; + if (lgtd_discovery_timeout_ev) { + event_del(lgtd_discovery_timeout_ev); + event_free(lgtd_discovery_timeout_ev); + lgtd_discovery_timeout_ev = NULL; } - if (lgtd_lifx_watchdog_context.watchdog_interval_ev) { - event_del(lgtd_lifx_watchdog_context.watchdog_interval_ev); - event_free(lgtd_lifx_watchdog_context.watchdog_interval_ev); - lgtd_lifx_watchdog_context.watchdog_interval_ev = NULL; + if (lgtd_watchdog_interval_ev) { + event_del(lgtd_watchdog_interval_ev); + event_free(lgtd_watchdog_interval_ev); + lgtd_watchdog_interval_ev = NULL; } } @@ -185,14 +179,12 @@ lgtd_lifx_watchdog_start(void) !RB_EMPTY(&lgtd_lifx_bulbs_table) || !LIST_EMPTY(&lgtd_lifx_gateways) ); - bool pending = evtimer_pending( - lgtd_lifx_watchdog_context.watchdog_interval_ev, NULL - ); + bool pending = evtimer_pending(lgtd_watchdog_interval_ev, NULL); if (!pending) { struct timeval tv = LGTD_MSECS_TO_TIMEVAL( LGTD_LIFX_WATCHDOG_INTERVAL_MSECS ); - if (event_add(lgtd_lifx_watchdog_context.watchdog_interval_ev, &tv)) { + if (event_add(lgtd_watchdog_interval_ev, &tv)) { lgtd_err(1, "can't start watchdog"); } lgtd_debug("starting watchdog timer"); @@ -202,10 +194,9 @@ lgtd_lifx_watchdog_start(void) void lgtd_lifx_watchdog_start_discovery(void) { - assert(!evtimer_pending( - lgtd_lifx_watchdog_context.discovery_timeout_ev, NULL - )); + assert(!evtimer_pending(lgtd_discovery_timeout_ev, NULL)); - lgtd_lifx_watchdog_discovery_timeout_event_callback(-1, 0, NULL); + lgtd_discovery_timeout = LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; + lgtd_lifx_watchdog_lgtd_discovery_timeout_event_callback(-1, 0, NULL); lgtd_debug("starting discovery timer"); } From 01ea64c5f90b7999a61015fa1e24c3f59c4f5fe9 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 11:51:31 -0700 Subject: [PATCH 112/181] Incrementally switch from active discovery to passive discovery --- lifx/watchdog.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lifx/watchdog.c b/lifx/watchdog.c index d76419e..fc58394 100644 --- a/lifx/watchdog.c +++ b/lifx/watchdog.c @@ -38,6 +38,8 @@ static struct event *lgtd_watchdog_interval_ev = NULL; static struct event *lgtd_discovery_timeout_ev = NULL; +static int lgtd_discovery_timeout = + LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; static void lgtd_lifx_watchdog_discovery_timeout_event_callback(evutil_socket_t socket, @@ -48,19 +50,26 @@ lgtd_lifx_watchdog_discovery_timeout_event_callback(evutil_socket_t socket, (void)events; (void)ctx; - int timeout = LGTD_LIFX_WATCHDOG_PASSIVE_DISCOVERY_INTERVAL_MSECS; if (LIST_EMPTY(&lgtd_lifx_gateways)) { + lgtd_discovery_timeout = + LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; lgtd_debug( "discovery didn't returned anything in %dms, restarting it", - LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS + lgtd_discovery_timeout ); - timeout = LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; } else { - lgtd_debug("sending periodic discovery packet"); + lgtd_discovery_timeout = LGTD_MIN( + lgtd_discovery_timeout * 2, + LGTD_LIFX_WATCHDOG_PASSIVE_DISCOVERY_INTERVAL_MSECS + ); + lgtd_debug( + "sending periodic discovery packet, timeout=%d", + lgtd_discovery_timeout + ); } - struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); - if (event_add(lgtd_lifx_watchdog_context.discovery_timeout_ev, &tv) + struct timeval tv = LGTD_MSECS_TO_TIMEVAL(lgtd_discovery_timeout); + if (event_add(lgtd_discovery_timeout_ev, &tv) || !lgtd_lifx_broadcast_discovery()) { lgtd_err(1, "can't start discovery"); } @@ -197,6 +206,6 @@ lgtd_lifx_watchdog_start_discovery(void) assert(!evtimer_pending(lgtd_discovery_timeout_ev, NULL)); lgtd_discovery_timeout = LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; - lgtd_lifx_watchdog_lgtd_discovery_timeout_event_callback(-1, 0, NULL); + lgtd_lifx_watchdog_discovery_timeout_event_callback(-1, 0, NULL); lgtd_debug("starting discovery timer"); } From d6488ee0e0dffaccd897b6de212964a7812f2322 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 11:51:31 -0700 Subject: [PATCH 113/181] Handle arbitrary large JSON-RPC request up to 4KiB Turns out it's pretty easy to run out of jsmn tokens, so this changeset uses reallocarray to allocate them. This means that we have to parse the request twice (one time to check how many tokens we need, one time to do the actual parsing). Since tokens are now allocated dynamically it makes a lot more sense to use jsmn's PARENT_LINKS and this changeset does that. --- CMakeLists.txt | 3 ++ CMakeScripts/CompatReallocArray.cmake | 33 ++++++++++++ core/client.c | 50 +++++++++++-------- core/client.h | 7 +-- core/lightsd.h | 35 +++++++++++++ core/pipe.c | 45 ++++++++++------- ...test_client_read_callback_part_too_large.c | 2 +- tests/core/pipe/test_pipe_close.c | 2 +- tests/core/pipe/test_pipe_open.c | 15 +----- .../pipe/test_pipe_open_fifo_already_exists.c | 15 +----- tests/core/pipe/test_pipe_read_callback.c | 9 +--- .../pipe/test_pipe_read_callback_extra_data.c | 9 +--- ...est_pipe_read_callback_multiple_requests.c | 9 +--- .../test_pipe_read_callback_yield_on_eagain.c | 9 +--- tests/core/pipe/tests_pipe_utils.h | 12 ----- 15 files changed, 137 insertions(+), 118 deletions(-) create mode 100644 CMakeScripts/CompatReallocArray.cmake delete mode 100644 tests/core/pipe/tests_pipe_utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a3e683..cdf5b9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ INCLUDE(CheckFunctionExists) INCLUDE(TestBigEndian) INCLUDE(CompatSetProctitle) INCLUDE(CompatTimeMonotonic) +INCLUDE(CompatReallocArray) TEST_BIG_ENDIAN(BIG_ENDIAN_SYSTEM) @@ -59,8 +60,10 @@ ADD_DEFINITIONS( "-DLGTD_HAVE_LIBBSD=${HAVE_LIBBSD}" "-DLGTD_HAVE_PROCTITLE=${HAVE_PROCTITLE}" + "-DLGTD_HAVE_REALLOCARRAY=${HAVE_REALLOCARRAY}" "-DJSMN_STRICT=1" + "-DJSMN_PARENT_LINKS=1" ) IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") diff --git a/CMakeScripts/CompatReallocArray.cmake b/CMakeScripts/CompatReallocArray.cmake new file mode 100644 index 0000000..cfb3f1b --- /dev/null +++ b/CMakeScripts/CompatReallocArray.cmake @@ -0,0 +1,33 @@ +IF (DEFINED HAVE_REALLOCARRAY) + RETURN() +ENDIF () + +MESSAGE(STATUS "Looking for reallocarray") + +IF (HAVE_LIBBSD) + MESSAGE(STATUS "Looking for reallocarray - found") + SET(HAVE_REALLOCARRAY 1 CACHE INTERNAL "reallocarray found in libbsd") + RETURN() +ENDIF () + +SET(CMAKE_REQUIRED_QUIET TRUE) +CHECK_FUNCTION_EXISTS("reallocarray" HAVE_REALLOCARRAY) +UNSET(CMAKE_REQUIRED_QUIET) +IF (HAVE_REALLOCARRAY) + MESSAGE(STATUS "Looking for reallocarray - found") + SET( + HAVE_REALLOCARRAY 1 + CACHE INTERNAL + "reallocarray found on the system" + ) +ELSE () + MESSAGE( + STATUS + "Looking for reallocarray - not found, using built-in compatibilty file" + ) + SET( + HAVE_REALLOCARRAY 0 + CACHE INTERNAL + "reallocarray not found, using internal implementation" + ) +ENDIF () diff --git a/core/client.c b/core/client.c index c06bc14..3eb2509 100644 --- a/core/client.c +++ b/core/client.c @@ -53,6 +53,7 @@ lgtd_client_close(struct lgtd_client *client) bufferevent_free(client->io); } free(client->addr); + free(client->jsmn_tokens); free(client); } @@ -80,15 +81,13 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) // Get the actual pointer to the beginning of the evbuf: const char *buf = (char *)evbuffer_pullup(input, nbytes); + jsmntok_t *tokens = NULL; + int ntokens = 0; do { - jsmn_init(&client->jsmn_ctx); - jsmnerr_t rv = jsmn_parse( - &client->jsmn_ctx, - buf, - nbytes, - client->jsmn_tokens, - LGTD_ARRAY_SIZE(client->jsmn_tokens) - ); + jsmn_parser jsmn_ctx; + parse_after_realloc: + jsmn_init(&jsmn_ctx); + jsmnerr_t rv = jsmn_parse(&jsmn_ctx, buf, nbytes, tokens, ntokens); switch (rv) { case JSMN_ERROR_NOMEM: case JSMN_ERROR_INVAL: @@ -110,19 +109,30 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) } break; default: - client->json = buf; - lgtd_jsonrpc_dispatch_request(client, rv); - client->json = NULL; - size_t request_size = client->jsmn_tokens[0].end; - evbuffer_drain(input, request_size); - if (request_size < nbytes) { - buf += request_size; - nbytes -= request_size; - // FIXME: instead of calling jsmn_parse again, return the number - // of tokens consumed from jsonrpc and make this case a loop. - continue; + ntokens = rv; + if (tokens) { + client->json = buf; + lgtd_jsonrpc_dispatch_request(client, ntokens); + client->json = NULL; + size_t request_size = tokens[0].end; + tokens = NULL; + evbuffer_drain(input, request_size); + if (request_size < nbytes) { + buf += request_size; + nbytes -= request_size; + // FIXME: instead of calling jsmn_parse again, return the + // number of tokens consumed from jsonrpc and make this + // case a loop. + continue; + } + break; + } else { + client->jsmn_tokens = reallocarray( + client->jsmn_tokens, ntokens, sizeof(*tokens) + ); + tokens = client->jsmn_tokens; + goto parse_after_realloc; } - break; } // pullup and resume parsing: buf = (char *)evbuffer_pullup(input, -1); diff --git a/core/client.h b/core/client.h index 59cf8f7..1ec2c50 100644 --- a/core/client.h +++ b/core/client.h @@ -17,8 +17,7 @@ #pragma once -enum { LGTD_CLIENT_JSMN_TOKENS_NUM = 96 }; -enum { LGTD_CLIENT_MAX_REQUEST_BUF_SIZE = 2048 }; +enum { LGTD_CLIENT_MAX_REQUEST_BUF_SIZE = 4096 }; enum lgtd_client_error_code { LGTD_CLIENT_SUCCESS = LGTD_JSONRPC_SUCCESS, @@ -34,15 +33,13 @@ struct lgtd_client { LIST_ENTRY(lgtd_client) link; struct bufferevent *io; struct sockaddr *addr; - jsmn_parser jsmn_ctx; - jsmntok_t jsmn_tokens[LGTD_CLIENT_JSMN_TOKENS_NUM]; + jsmntok_t *jsmn_tokens; const char *json; struct lgtd_jsonrpc_request *current_request; }; LIST_HEAD(lgtd_client_list, lgtd_client); struct lgtd_client *lgtd_client_open(evutil_socket_t, const struct sockaddr *, int); -void lgtd_client_open_from_pipe(struct lgtd_client *); void lgtd_client_close_all(void); void lgtd_client_write_string(struct lgtd_client *, const char *); diff --git a/core/lightsd.h b/core/lightsd.h index 7a41ef7..32edf53 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -15,6 +15,23 @@ // You should have received a copy of the GNU General Public License // along with lighstd. If not, see . +// reallocarray has been copied from OpenBSD under the following license: +/* + * Copyright (c) 2008 Otto Moerbeek + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + #pragma once #ifndef __attribute__ @@ -70,6 +87,24 @@ enum { LGTD_ERROR_MSG_BUFSIZE = 2048 }; // reasonable value for that: enum { LGTD_SOCKADDR_STRLEN = 128 }; +#if !LGTD_HAVE_REALLOCARRAY +# define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4)) +static inline void * +reallocarray(void *optr, size_t nmemb, size_t size) +{ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { +# ifndef NDEBUG + abort(); +# endif + return NULL; + } + return realloc(optr, size * nmemb); +} +#elif LGTD_HAVE_LIBBSD +# include +#endif + struct lgtd_opts { bool foreground; bool log_timestamps; diff --git a/core/pipe.c b/core/pipe.c index 88d0493..241104e 100644 --- a/core/pipe.c +++ b/core/pipe.c @@ -52,6 +52,7 @@ _lgtd_command_pipe_close(struct lgtd_command_pipe *pipe) SLIST_REMOVE(&lgtd_command_pipes, pipe, lgtd_command_pipe, link); evbuffer_free(pipe->read_buf); event_free(pipe->read_ev); + free(pipe->client.jsmn_tokens); free(pipe); } @@ -93,17 +94,16 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) } if (!drain) { + jsmntok_t *tokens = NULL; + int ntokens = 0; + jsmn_parser jsmn_ctx; next_request: - jsmn_init(&pipe->client.jsmn_ctx); + (void)0; const char *buf = (char *)evbuffer_pullup(pipe->read_buf, -1); ssize_t bufsz = evbuffer_get_length(pipe->read_buf); - jsmnerr_t rv = jsmn_parse( - &pipe->client.jsmn_ctx, - buf, - bufsz, - pipe->client.jsmn_tokens, - LGTD_ARRAY_SIZE(pipe->client.jsmn_tokens) - ); + parse_after_realloc: + jsmn_init(&jsmn_ctx); + jsmnerr_t rv = jsmn_parse(&jsmn_ctx, buf, bufsz, tokens, ntokens); switch (rv) { case JSMN_ERROR_NOMEM: case JSMN_ERROR_INVAL: @@ -122,17 +122,25 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) } break; default: - pipe->client.json = buf; - int ntokens = rv; - lgtd_jsonrpc_dispatch_request(&pipe->client, ntokens); - - pipe->client.json = NULL; - int request_size = pipe->client.jsmn_tokens[0].end; - evbuffer_drain(pipe->read_buf, request_size); - if (request_size < bufsz) { - goto next_request; + ntokens = rv; + if (tokens) { + pipe->client.json = buf; + lgtd_jsonrpc_dispatch_request(&pipe->client, ntokens); + pipe->client.json = NULL; + int request_size = tokens[0].end; + tokens = NULL; + evbuffer_drain(pipe->read_buf, request_size); + if (request_size >= bufsz) { + break; + } + } else { + pipe->client.jsmn_tokens = reallocarray( + pipe->client.jsmn_tokens, ntokens, sizeof(*tokens) + ); + tokens = pipe->client.jsmn_tokens; + goto parse_after_realloc; } - break; + goto next_request; } } @@ -172,7 +180,6 @@ _lgtd_command_pipe_open(const char *path) return false; } - lgtd_client_open_from_pipe(&pipe->client); pipe->path = path; pipe->fd = -1; diff --git a/tests/core/client/test_client_read_callback_part_too_large.c b/tests/core/client/test_client_read_callback_part_too_large.c index dcf4204..0be5a52 100644 --- a/tests/core/client/test_client_read_callback_part_too_large.c +++ b/tests/core/client/test_client_read_callback_part_too_large.c @@ -19,7 +19,7 @@ #include "tests_client_utils.h" static unsigned char request[] = ("{" - "\"id\": \"verylongidyooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\"," + "\"id\": \"verylooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooongid\"," "\"jsonrpc\": \"2.0\"," "\"method\": \"get_light_state\"," "\"params\": [\"*\"]" diff --git a/tests/core/pipe/test_pipe_close.c b/tests/core/pipe/test_pipe_close.c index 5bd771c..bfae00d 100644 --- a/tests/core/pipe/test_pipe_close.c +++ b/tests/core/pipe/test_pipe_close.c @@ -6,6 +6,7 @@ #include "lifx/wire_proto.h" +#include "mock_daemon.h" #define MOCKED_EVENT_NEW #define MOCKED_EVBUFFER_NEW #define MOCKED_EVENT_DEL @@ -18,7 +19,6 @@ #include "mock_timer.h" #include "tests_utils.h" -#include "tests_pipe_utils.h" char *tmpdir = NULL; diff --git a/tests/core/pipe/test_pipe_open.c b/tests/core/pipe/test_pipe_open.c index 19d17a2..72c7ce1 100644 --- a/tests/core/pipe/test_pipe_open.c +++ b/tests/core/pipe/test_pipe_open.c @@ -6,6 +6,7 @@ #include "lifx/wire_proto.h" +#include "mock_daemon.h" #define MOCKED_EVUTIL_MAKE_SOCKET_NONBLOCKING #define MOCKED_EVENT_NEW #define MOCKED_EVBUFFER_NEW @@ -17,8 +18,6 @@ #include "mock_timer.h" #include "tests_utils.h" -#define MOCKED_CLIENT_OPEN_FROM_PIPE -#include "tests_pipe_utils.h" char *tmpdir = NULL; @@ -103,18 +102,6 @@ event_add(struct event *ev, const struct timeval *timeout) return 0; } -static int client_open_from_pipe_call_count = 0; - -void -lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) -{ - if (!pipe_client) { - errx(1, "missing pipe_client"); - } - - client_open_from_pipe_call_count++; -} - int main(void) { diff --git a/tests/core/pipe/test_pipe_open_fifo_already_exists.c b/tests/core/pipe/test_pipe_open_fifo_already_exists.c index b563d83..c2bfe56 100644 --- a/tests/core/pipe/test_pipe_open_fifo_already_exists.c +++ b/tests/core/pipe/test_pipe_open_fifo_already_exists.c @@ -6,6 +6,7 @@ #include "lifx/wire_proto.h" +#include "mock_daemon.h" #define MOCKED_EVUTIL_MAKE_SOCKET_NONBLOCKING #define MOCKED_EVENT_NEW #define MOCKED_EVBUFFER_NEW @@ -17,8 +18,6 @@ #include "mock_timer.h" #include "tests_utils.h" -#define MOCKED_CLIENT_OPEN_FROM_PIPE -#include "tests_pipe_utils.h" char *tmpdir = NULL; @@ -103,18 +102,6 @@ event_add(struct event *ev, const struct timeval *timeout) return 0; } -static int client_open_from_pipe_call_count = 0; - -void -lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) -{ - if (!pipe_client) { - errx(1, "missing pipe_client"); - } - - client_open_from_pipe_call_count++; -} - int main(void) { diff --git a/tests/core/pipe/test_pipe_read_callback.c b/tests/core/pipe/test_pipe_read_callback.c index aebb185..d85a8b5 100644 --- a/tests/core/pipe/test_pipe_read_callback.c +++ b/tests/core/pipe/test_pipe_read_callback.c @@ -6,6 +6,7 @@ #include "lifx/wire_proto.h" +#include "mock_daemon.h" #define MOCKED_EVENT_NEW #define MOCKED_EVENT_DEL #define MOCKED_EVBUFFER_NEW @@ -21,7 +22,6 @@ #include "mock_timer.h" #include "tests_utils.h" -#include "tests_pipe_utils.h" static unsigned char request[] = ("{" "\"jsonrpc\": \"2.0\"," @@ -141,13 +141,6 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) ); } - jsmn_parser jsmn_ctx; - jsmn_init(&jsmn_ctx); - struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); - if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { - errx(1, "the client json parser context wasn't re-initialized"); - } - return &request[evbuffer_pullup_call_count++ ? sizeof(request) - 1 : 0]; } diff --git a/tests/core/pipe/test_pipe_read_callback_extra_data.c b/tests/core/pipe/test_pipe_read_callback_extra_data.c index b90131a..df348fb 100644 --- a/tests/core/pipe/test_pipe_read_callback_extra_data.c +++ b/tests/core/pipe/test_pipe_read_callback_extra_data.c @@ -6,6 +6,7 @@ #include "lifx/wire_proto.h" +#include "mock_daemon.h" #define MOCKED_EVENT_NEW #define MOCKED_EVBUFFER_NEW #define MOCKED_EVBUFFER_READ @@ -20,7 +21,6 @@ #include "mock_timer.h" #include "tests_utils.h" -#include "tests_pipe_utils.h" #define REQUEST_1 "{" \ "\"jsonrpc\": \"2.0\"," \ @@ -144,13 +144,6 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) ); } - jsmn_parser jsmn_ctx; - jsmn_init(&jsmn_ctx); - struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); - if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { - errx(1, "the client json parser context wasn't re-initialized"); - } - return &request[evbuffer_pullup_call_count++ ? sizeof(request) - 1 : 0]; } diff --git a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c index 843de32..19fff7c 100644 --- a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c +++ b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c @@ -6,6 +6,7 @@ #include "lifx/wire_proto.h" +#include "mock_daemon.h" #define MOCKED_EVENT_NEW #define MOCKED_EVBUFFER_NEW #define MOCKED_EVBUFFER_READ @@ -20,7 +21,6 @@ #include "mock_timer.h" #include "tests_utils.h" -#include "tests_pipe_utils.h" #define REQUEST_1 "{" \ "\"jsonrpc\": \"2.0\"," \ @@ -161,13 +161,6 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) ); } - jsmn_parser jsmn_ctx; - jsmn_init(&jsmn_ctx); - struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); - if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { - errx(1, "the client json parser context wasn't re-initialized"); - } - int offset; switch (evbuffer_pullup_call_count) { case 0: diff --git a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c index 6453153..9e3c545 100644 --- a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c +++ b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c @@ -6,6 +6,7 @@ #include "lifx/wire_proto.h" +#include "mock_daemon.h" #define MOCKED_EVENT_NEW #define MOCKED_EVBUFFER_NEW #define MOCKED_EVBUFFER_READ @@ -20,7 +21,6 @@ #include "mock_timer.h" #include "tests_utils.h" -#include "tests_pipe_utils.h" #define REQUEST_1 "{" \ "\"jsonrpc\": \"2.0\"," \ @@ -161,13 +161,6 @@ evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size) ); } - jsmn_parser jsmn_ctx; - jsmn_init(&jsmn_ctx); - struct lgtd_command_pipe *pipe = SLIST_FIRST(&lgtd_command_pipes); - if (memcmp(&pipe->client.jsmn_ctx, &jsmn_ctx, sizeof(jsmn_ctx))) { - errx(1, "the client json parser context wasn't re-initialized"); - } - int offset; switch (evbuffer_pullup_call_count) { case 0: diff --git a/tests/core/pipe/tests_pipe_utils.h b/tests/core/pipe/tests_pipe_utils.h deleted file mode 100644 index 1128108..0000000 --- a/tests/core/pipe/tests_pipe_utils.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include "mock_daemon.h" - -#ifndef MOCKED_CLIENT_OPEN_FROM_PIPE -void -lgtd_client_open_from_pipe(struct lgtd_client *pipe_client) -{ - memset(pipe_client, 0, sizeof(*pipe_client)); - jsmn_init(&pipe_client->jsmn_ctx); -} -#endif From aad31d5cdf0c9d35c857aeb1b095cc2560fbdd1f Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 11:51:31 -0700 Subject: [PATCH 114/181] Import setproctitle from libbsd 0.7.0 Using libbsd was getting annoying: it clashes with my own compatibility functions and it's hard to use on Mac OS X since it clashes with so many things. --- CMakeLists.txt | 7 +- CMakeScripts/CompatReallocArray.cmake | 6 - CMakeScripts/CompatSetProctitle.cmake | 73 +++- CMakeScripts/FindLibBSD.cmake | 10 - README.rst | 3 - core/CMakeLists.txt | 5 +- core/daemon.c | 22 +- core/lightsd.h | 2 - core/log.c | 6 +- core/setproctitle.c | 325 ++++++++++++++++++ core/version.h.in | 3 +- tests/CMakeLists.txt | 3 - tests/core/daemon/CMakeLists.txt | 1 + .../daemon/test_daemon_update_proctitle.c | 1 - tests/lifx/tagging/CMakeLists.txt | 5 +- 15 files changed, 401 insertions(+), 71 deletions(-) delete mode 100644 CMakeScripts/FindLibBSD.cmake create mode 100644 core/setproctitle.c diff --git a/CMakeLists.txt b/CMakeLists.txt index cdf5b9b..3b16f96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,10 +26,12 @@ FIND_PACKAGE(Event2 REQUIRED COMPONENTS core) FIND_PACKAGE(Endian REQUIRED) INCLUDE(CheckFunctionExists) +INCLUDE(CheckVariableExists) INCLUDE(TestBigEndian) + +INCLUDE(CompatReallocArray) INCLUDE(CompatSetProctitle) INCLUDE(CompatTimeMonotonic) -INCLUDE(CompatReallocArray) TEST_BIG_ENDIAN(BIG_ENDIAN_SYSTEM) @@ -58,8 +60,7 @@ ADD_DEFINITIONS( "-DLGTD_BIG_ENDIAN_SYSTEM=${BIG_ENDIAN_SYSTEM}" "-DLGTD_SIZEOF_VOID_P=${CMAKE_SIZEOF_VOID_P}" - "-DLGTD_HAVE_LIBBSD=${HAVE_LIBBSD}" - "-DLGTD_HAVE_PROCTITLE=${HAVE_PROCTITLE}" + "-DLGTD_HAVE_SETPROCTITLE=${HAVE_SETPROCTITLE}" "-DLGTD_HAVE_REALLOCARRAY=${HAVE_REALLOCARRAY}" "-DJSMN_STRICT=1" diff --git a/CMakeScripts/CompatReallocArray.cmake b/CMakeScripts/CompatReallocArray.cmake index cfb3f1b..e7368d8 100644 --- a/CMakeScripts/CompatReallocArray.cmake +++ b/CMakeScripts/CompatReallocArray.cmake @@ -4,12 +4,6 @@ ENDIF () MESSAGE(STATUS "Looking for reallocarray") -IF (HAVE_LIBBSD) - MESSAGE(STATUS "Looking for reallocarray - found") - SET(HAVE_REALLOCARRAY 1 CACHE INTERNAL "reallocarray found in libbsd") - RETURN() -ENDIF () - SET(CMAKE_REQUIRED_QUIET TRUE) CHECK_FUNCTION_EXISTS("reallocarray" HAVE_REALLOCARRAY) UNSET(CMAKE_REQUIRED_QUIET) diff --git a/CMakeScripts/CompatSetProctitle.cmake b/CMakeScripts/CompatSetProctitle.cmake index 1b9c46e..b079b6b 100644 --- a/CMakeScripts/CompatSetProctitle.cmake +++ b/CMakeScripts/CompatSetProctitle.cmake @@ -1,20 +1,59 @@ -IF (NOT HAVE_PROCTITLE) - SET(CMAKE_REQUIRED_QUIET TRUE) - SET(HAVE_PROCTITLE 0 CACHE INTERNAL "setproctitle found in libbsd") - SET(HAVE_LIBBSD 0 CACHE INTERNAL "libbsd found") - MESSAGE(STATUS "Looking for setproctitle") - CHECK_FUNCTION_EXISTS("setproctitle" HAVE_PROCTITLE) - IF (NOT HAVE_PROCTITLE) - MESSAGE(STATUS "Looking for setproctitle - not found, falling back on libbsd") - FIND_PACKAGE(LibBSD) - IF (NOT LibBSD_FOUND) - MESSAGE(STATUS "Couldn't find setproctitle, no fancy report in the process list") - ELSE () - SET(HAVE_PROCTITLE 1 CACHE INTERNAL "setproctitle found in libbsd") - SET(HAVE_LIBBSD 1 CACHE INTERNAL "libbsd found") +IF (DEFINED HAVE_SETPROCTITLE) + IF (HAVE_SETPROCTITLE EQUAL 0) + IF (HAVE_CLEARENV) + ADD_DEFINITIONS("-DHAVE_CLEARENV=1") ENDIF () - ELSE () - SET(HAVE_PROCTITLE 1 CACHE INTERNAL "setproctitle found on the system") + IF (HAVE_GETEXECNAME) + ADD_DEFINITIONS("-DHAVE_GETEXECNAME=1") + ENDIF () + IF (HAVE___PROGNAME) + ADD_DEFINITIONS("-DHAVE___PROGNAME=1") + ENDIF () + IF (HAVE_HAVE_PROGRAM_INVOCATION_SHORT_NAME) + ADD_DEFINITIONS("-DHAVE_HAVE_PROGRAM_INVOCATION_SHORT_NAME=1") + ENDIF () + ENDIF () + RETURN() +ENDIF () + +MESSAGE(STATUS "Looking for setproctitle") + +SET(CMAKE_REQUIRED_QUIET TRUE) +CHECK_FUNCTION_EXISTS("setproctitle" HAVE_SETPROCTITLE) +UNSET(CMAKE_REQUIRED_QUIET) +IF (HAVE_SETPROCTITLE) + MESSAGE( + STATUS + "Looking for setproctitle - found" + ) + SET(HAVE_SETPROCTITLE 1 CACHE INTERNAL "setproctitle found on the system") +ELSE () + MESSAGE( + STATUS + "Looking for setproctitle - not found, using built-in compatibilty file" + ) + SET( + HAVE_SETPROCTITLE 0 + CACHE INTERNAL + "setproctitle not found, using internal implementation" + ) + + CHECK_FUNCTION_EXISTS("clearenv" HAVE_CLEARENV) + CHECK_FUNCTION_EXISTS("getexecname" HAVE_GETEXECNAME) + CHECK_VARIABLE_EXISTS("__progname" HAVE___PROGNAME) + CHECK_VARIABLE_EXISTS( + "program_invocation_short_name" HAVE_PROGRAM_INVOCATION_SHORT_NAME + ) + IF (HAVE_CLEARENV) + ADD_DEFINITIONS("-DHAVE_CLEARENV=1") + ENDIF () + IF (HAVE_GETEXECNAME) + ADD_DEFINITIONS("-DHAVE_GETEXECNAME=1") + ENDIF () + IF (HAVE___PROGNAME) + ADD_DEFINITIONS("-DHAVE___PROGNAME=1") + ENDIF () + IF (HAVE_HAVE_PROGRAM_INVOCATION_SHORT_NAME) + ADD_DEFINITIONS("-DHAVE_HAVE_PROGRAM_INVOCATION_SHORT_NAME=1") ENDIF () - UNSET(CMAKE_REQUIRED_QUIET) ENDIF () diff --git a/CMakeScripts/FindLibBSD.cmake b/CMakeScripts/FindLibBSD.cmake deleted file mode 100644 index 1afeec4..0000000 --- a/CMakeScripts/FindLibBSD.cmake +++ /dev/null @@ -1,10 +0,0 @@ -FIND_PATH(LIBBSD_INCLUDE_DIR bsd.h PATH_SUFFIXES bsd) - -FIND_LIBRARY(LIBBSD_LIBRARY bsd) -IF(LIBBSD_LIBRARY) - SET(LibBSD_FOUND TRUE) -ENDIF() - -INCLUDE(FindPackageHandleStandardArgs) - -FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibBSD DEFAULT_MSG LIBBSD_LIBRARY LIBBSD_INCLUDE_DIR) diff --git a/README.rst b/README.rst index a939ce1..c09df3d 100644 --- a/README.rst +++ b/README.rst @@ -61,9 +61,6 @@ dependencies: - CMake ≥ 2.8.11 (released May 2013): only if you want to build lightsd from its sources. -lightsd optionally depends on libbsd ≥ 0.5.0 on platforms missing -``setproctitle`` (pretty much any non-BSD system, including Mac OS X). - lightsd is actively developed and tested from Arch Linux, Debian and Mac OS X; both for 32/64 bits and little/big endian architectures. diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 6636abc..c02da2b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -22,6 +22,7 @@ ADD_EXECUTABLE( pipe.c proto.c router.c + setproctitle.c stats.c timer.c ) @@ -33,8 +34,4 @@ TARGET_LINK_LIBRARIES( ${TIME_MONOTONIC_LIBRARY} ) -IF (HAVE_LIBBSD) - TARGET_LINK_LIBRARIES(lightsd ${LIBBSD_LIBRARY}) -ENDIF (HAVE_LIBBSD) - INSTALL(TARGETS lightsd RUNTIME DESTINATION bin) diff --git a/core/daemon.c b/core/daemon.c index 2406279..dee7bbb 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -27,10 +27,6 @@ #include #include -#if LGTD_HAVE_LIBBSD -#include -#endif - #include #include "time_monotonic.h" @@ -91,14 +87,16 @@ lgtd_daemon_unleash(void) void lgtd_daemon_setup_proctitle(int argc, char *argv[], char *envp[]) { -#if LGTD_HAVE_LIBBSD - setproctitle_init(argc, argv, envp); - lgtd_daemon_update_proctitle(); - lgtd_daemon_proctitle_initialized = true; -#else +#if LGTD_HAVE_SETPROCTITLE (void)argc; (void)argv; (void)envp; +#else + void setproctitle_init(int argc, char *argv[], char *envp[]); + + setproctitle_init(argc, argv, envp); + lgtd_daemon_update_proctitle(); + lgtd_daemon_proctitle_initialized = true; #endif } @@ -125,7 +123,10 @@ lgtd_daemon_update_proctitle(void) return; } -#if LGTD_HAVE_PROCTITLE +#if !LGTD_HAVE_SETPROCTITLE + void setproctitle(const char *fmt, ...); +#endif + char title[LGTD_DAEMON_TITLE_SIZE] = { 0 }; int i = 0; @@ -177,5 +178,4 @@ lgtd_daemon_update_proctitle(void) PREFIX("clients(connected=%d)", LGTD_STATS_GET(clients)); setproctitle("%s", title); -#endif } diff --git a/core/lightsd.h b/core/lightsd.h index 32edf53..2270090 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -101,8 +101,6 @@ reallocarray(void *optr, size_t nmemb, size_t size) } return realloc(optr, size * nmemb); } -#elif LGTD_HAVE_LIBBSD -# include #endif struct lgtd_opts { diff --git a/core/log.c b/core/log.c index 2fe3b83..970e079 100644 --- a/core/log.c +++ b/core/log.c @@ -32,10 +32,6 @@ #include #include -#if LGTD_HAVE_LIBBSD -#include -#endif - #include #include "lifx/wire_proto.h" @@ -233,6 +229,6 @@ lgtd_libevent_log(int severity, const char *msg) case EVENT_LOG_MSG: lgtd_info("%s", msg); break; case EVENT_LOG_WARN: lgtd_warnx("%s", msg) break; case EVENT_LOG_ERR: lgtd_warnx("%s", msg); break; - default: break; + default: break; } } diff --git a/core/setproctitle.c b/core/setproctitle.c new file mode 100644 index 0000000..e14d622 --- /dev/null +++ b/core/setproctitle.c @@ -0,0 +1,325 @@ +#if !LGTD_HAVE_SETPROCTITLE +/* + * Copyright © 2010 William Ahern + * Copyright © 2012-2013 Guillem Jover + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern char **environ; + +static struct { + /* Original value. */ + const char *arg0; + + /* Title space available. */ + char *base, *end; + + /* Pointer to original nul character within base. */ + char *nul; + + bool warned; + bool reset; + int error; +} SPT; + + +static inline size_t +spt_min(size_t a, size_t b) +{ + return a < b ? a : b; +} + +/* + * For discussion on the portability of the various methods, see + * http://lists.freebsd.org/pipermail/freebsd-stable/2008-June/043136.html + */ +static int +spt_clearenv(void) +{ +#ifdef HAVE_CLEARENV + return clearenv(); +#else + char **tmp; + + tmp = malloc(sizeof(*tmp)); + if (tmp == NULL) + return errno; + + tmp[0] = NULL; + environ = tmp; + + return 0; +#endif +} + +static int +spt_copyenv(int envc, char *envp[]) +{ + char **envcopy; + char *eq; + int envsize; + int i, error; + + if (environ != envp) + return 0; + + /* Make a copy of the old environ array of pointers, in case + * clearenv() or setenv() is implemented to free the internal + * environ array, because we will need to access the old environ + * contents to make the new copy. */ + envsize = (envc + 1) * sizeof(char *); + envcopy = malloc(envsize); + if (envcopy == NULL) + return errno; + memcpy(envcopy, envp, envsize); + + error = spt_clearenv(); + if (error) { + environ = envp; + free(envcopy); + return error; + } + + for (i = 0; envcopy[i]; i++) { + eq = strchr(envcopy[i], '='); + if (eq == NULL) + continue; + + *eq = '\0'; + if (setenv(envcopy[i], eq + 1, 1) < 0) + error = errno; + *eq = '='; + + if (error) { +#ifdef HAVE_CLEARENV + /* Because the old environ might not be available + * anymore we will make do with the shallow copy. */ + environ = envcopy; +#else + environ = envp; + free(envcopy); +#endif + return error; + } + } + + /* Dispose of the shallow copy, now that we've finished transfering + * the old environment. */ + free(envcopy); + + return 0; +} + +static int +spt_copyargs(int argc, char *argv[]) +{ + char *tmp; + int i; + + for (i = 1; i < argc || (i >= argc && argv[i]); i++) { + if (argv[i] == NULL) + continue; + + tmp = strdup(argv[i]); + if (tmp == NULL) + return errno; + + argv[i] = tmp; + } + + return 0; +} + +/* + Rejected in glibc (http://sourceware.org/ml/libc-alpha/2006-03/msg00125.html) +*/ + +#ifdef HAVE___PROGNAME +extern const char *__progname; +#else +static const char *__progname = NULL; +#endif + +const char * +getprogname(void) +{ +#if defined(HAVE_PROGRAM_INVOCATION_SHORT_NAME) + if (__progname == NULL) + __progname = program_invocation_short_name; +#elif defined(HAVE_GETEXECNAME) + /* getexecname(3) returns an absolute pathname, normalize it. */ + if (__progname == NULL) + setprogname(getexecname()); +#endif + + return __progname; +} + +void +setprogname(const char *progname) +{ + const char *last_slash; + + last_slash = strrchr(progname, '/'); + if (last_slash == NULL) + __progname = progname; + else + __progname = last_slash + 1; +} + +void +setproctitle_init(int argc, char *argv[], char *envp[]) +{ + char *base, *end, *nul, *tmp; + int i, envc, error; + + /* Try to make sure we got called with main() arguments. */ + if (argc < 0) + return; + + base = argv[0]; + if (base == NULL) + return; + + nul = &base[strlen(base)]; + end = nul + 1; + + for (i = 0; i < argc || (i >= argc && argv[i]); i++) { + if (argv[i] == NULL || argv[i] < end) + continue; + + end = argv[i] + strlen(argv[i]) + 1; + } + + for (i = 0; envp[i]; i++) { + if (envp[i] < end) + continue; + + end = envp[i] + strlen(envp[i]) + 1; + } + envc = i; + + SPT.arg0 = strdup(argv[0]); + if (SPT.arg0 == NULL) { + SPT.error = errno; + return; + } + + tmp = strdup(getprogname()); + if (tmp == NULL) { + SPT.error = errno; + return; + } + setprogname(tmp); + + error = spt_copyenv(envc, envp); + if (error) { + SPT.error = error; + return; + } + + error = spt_copyargs(argc, argv); + if (error) { + SPT.error = error; + return; + } + + SPT.nul = nul; + SPT.base = base; + SPT.end = end; +} + +#ifndef SPT_MAXTITLE +#define SPT_MAXTITLE 255 +#endif + +void +setproctitle(const char *fmt, ...) +{ + /* Use buffer in case argv[0] is passed. */ + char buf[SPT_MAXTITLE + 1]; + va_list ap; + char *nul; + int len; + + if (SPT.base == NULL) { + if (!SPT.warned) { + warnx("setproctitle not initialized, please either call " + "setproctitle_init() or link against libbsd-ctor."); + SPT.warned = true; + } + return; + } + + if (fmt) { + if (fmt[0] == '-') { + /* Skip program name prefix. */ + fmt++; + len = 0; + } else { + /* Print program name heading for grep. */ + snprintf(buf, sizeof(buf), "%s: ", getprogname()); + len = strlen(buf); + } + + va_start(ap, fmt); + len += vsnprintf(buf + len, sizeof(buf) - len, fmt, ap); + va_end(ap); + } else { + len = snprintf(buf, sizeof(buf), "%s", SPT.arg0); + } + + if (len <= 0) { + SPT.error = errno; + return; + } + + if (!SPT.reset) { + memset(SPT.base, 0, SPT.end - SPT.base); + SPT.reset = true; + } else { + memset(SPT.base, 0, spt_min(sizeof(buf), SPT.end - SPT.base)); + } + + len = spt_min(len, spt_min(sizeof(buf), SPT.end - SPT.base) - 1); + memcpy(SPT.base, buf, len); + nul = &SPT.base[len]; + + if (nul < SPT.nul) { + *SPT.nul = '.'; + } else if (nul == SPT.nul && &nul[1] < SPT.end) { + *SPT.nul = ' '; + *++nul = '\0'; + } +} +#else +typedef int hello_compiler; +#endif diff --git a/core/version.h.in b/core/version.h.in index 56d0169..e54cac5 100644 --- a/core/version.h.in +++ b/core/version.h.in @@ -31,8 +31,7 @@ const char LGTD_VERSION[] = ( "@LIGHTSD_VERSION@\n\n" - "cflags: @CMAKE_C_FLAGS@\n" - "proctitle_support: @HAVE_PROCTITLE@\n\n" + "cflags: @CMAKE_C_FLAGS@\n\n" "Copyright (c) 2014, 2015, Louis Opter " ); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4f4ce80..a1633c4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,9 +6,6 @@ FUNCTION(ADD_CORE_LIBRARY LIBNAME) ${LIGHTSD_SOURCE_DIR}/core/ ${LIGHTSD_BINARY_DIR}/core/ ) - IF (HAVE_LIBBSD) - TARGET_LINK_LIBRARIES(${LIBNAME} ${LIBBSD_LIBRARY}) - ENDIF (HAVE_LIBBSD) ENDFUNCTION() ADD_ALL_SUBDIRECTORIES() diff --git a/tests/core/daemon/CMakeLists.txt b/tests/core/daemon/CMakeLists.txt index b9e0ff7..8fc298f 100644 --- a/tests/core/daemon/CMakeLists.txt +++ b/tests/core/daemon/CMakeLists.txt @@ -6,6 +6,7 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_core_daemon STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c + ${LIGHTSD_SOURCE_DIR}/core/setproctitle.c ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c diff --git a/tests/core/daemon/test_daemon_update_proctitle.c b/tests/core/daemon/test_daemon_update_proctitle.c index 7cd72cc..5dfab6c 100644 --- a/tests/core/daemon/test_daemon_update_proctitle.c +++ b/tests/core/daemon/test_daemon_update_proctitle.c @@ -1,7 +1,6 @@ void mock_setproctitle(const char *fmt, ...) __attribute__((format(printf, 1, 2))); -#undef LGTD_HAVE_LIBBSD #undef LGTD_HAVE_PROCTITLE #define LGTD_HAVE_PROCTITLE 1 #define setproctitle mock_setproctitle diff --git a/tests/lifx/tagging/CMakeLists.txt b/tests/lifx/tagging/CMakeLists.txt index 9ba35b8..d5c1e5f 100644 --- a/tests/lifx/tagging/CMakeLists.txt +++ b/tests/lifx/tagging/CMakeLists.txt @@ -3,15 +3,12 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) -ADD_LIBRARY( +ADD_CORE_LIBRARY( test_lifx_tagging STATIC ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ) -IF (HAVE_LIBBSD) - TARGET_LINK_LIBRARIES(test_lifx_tagging ${LIBBSD_LIBRARY}) -ENDIF (HAVE_LIBBSD) FUNCTION(ADD_TAGGING_TEST TEST_SOURCE) ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_lifx_tagging) From e15f263f11d42cab89e993db38db96049f6d3a7b Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 15:15:05 -0700 Subject: [PATCH 115/181] Add a systemd unit file and -u/-g options to drop privileges This changeset also fix the chmod logic so other users can use the socket or the pipe too. A plist file for Mac OS X is generated from homebrew-lightsd. --- CMakeLists.txt | 1 + core/daemon.c | 85 +++++++++++++++++++ core/daemon.h | 2 + core/lightsd.c | 35 ++++++-- core/lightsd.h | 2 + core/listen.c | 6 ++ core/pipe.c | 15 +--- dist/lightsd.service | 10 +++ tests/core/pipe/test_pipe_open.c | 1 - .../pipe/test_pipe_open_fifo_already_exists.c | 1 - 10 files changed, 136 insertions(+), 22 deletions(-) create mode 100644 dist/lightsd.service diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b16f96..7684601 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,3 +95,4 @@ INSTALL( REGEX ".*\\.sw.$" EXCLUDE ) INSTALL(FILES share/lightsc.sh DESTINATION share/lightsd) +INSTALL(FILES dist/lightsd.service DESTINATION lib/systemd/system) diff --git a/core/daemon.c b/core/daemon.c index dee7bbb..4af570b 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -16,10 +16,16 @@ // along with lighstd. If not, see . #include +#include #include #include +#include +#include #include +#include #include +#include +#include #include #include #include @@ -179,3 +185,82 @@ lgtd_daemon_update_proctitle(void) setproctitle("%s", title); } + +void +lgtd_daemon_die_if_running_as_root_unless_requested(const char *requested_user) +{ + if (requested_user && !strcmp(requested_user, "root")) { + return; + } + + if (geteuid() == 0 || getegid() == 0) { + lgtd_errx( + 1, + "not running as root unless -u root is passed in; if you don't " + "understand why this very basic safety measure is in place and " + "use -u root then you deserve to be thrown under a bus, kthx bye." + ); + } +} + +void +lgtd_daemon_drop_privileges(const char *user, const char *group) +{ + assert(user); + + uid_t uid; + gid_t gid; + + struct passwd *user_info = getpwnam(user); + if (!user_info) { + lgtd_err(1, "can't get user info for %s", user); + } + uid = user_info->pw_uid; + + struct group *group_info; + if (group) { + group_info = getgrnam(group); + } else { + group_info = getgrgid(user_info->pw_gid); + group = group_info->gr_name; + } + if (!group_info) { + lgtd_err(1, "can't get group info for %s", group ? group : user); + } + gid = group_info->gr_gid; + + struct lgtd_command_pipe *pipe; + SLIST_FOREACH(pipe, &lgtd_command_pipes, link) { + if (fchown(pipe->fd, uid, gid) == -1) { + lgtd_err(1, "can't chown %s to %s:%s", pipe->path, user, group); + } + } + + struct lgtd_listen *listener; + SLIST_FOREACH(listener, &lgtd_listeners, link) { + if (listener->sockaddr->sa_family != AF_UNIX) { + continue; + } + + const char *path = ((struct sockaddr_un *)listener->sockaddr)->sun_path; + if (chown(path, uid, gid) == -1) { + char addr[LGTD_SOCKADDR_STRLEN]; + lgtd_err( + 1, "can't chown %s to %s:%s", + LGTD_SOCKADDRTOA(listener->sockaddr, addr), user, group + ); + } + } + + if (setgid(gid) == -1) { + lgtd_err(1, "can't change group to %s", group); + } + + if (setgroups(1, &gid) == -1) { + lgtd_err(1, "can't change group to %s", group); + } + + if (setuid(uid) == -1) { + lgtd_err(1, "can't change user to %s", user); + } +} diff --git a/core/daemon.h b/core/daemon.h index 9a4c772..569679e 100644 --- a/core/daemon.h +++ b/core/daemon.h @@ -22,3 +22,5 @@ enum { LGTD_DAEMON_TITLE_SIZE = 2048 }; bool lgtd_daemon_unleash(void); // \_o< void lgtd_daemon_setup_proctitle(int, char *[], char *[]); void lgtd_daemon_update_proctitle(void); +void lgtd_daemon_die_if_running_as_root_unless_requested(const char *); +void lgtd_daemon_drop_privileges(const char *, const char *); diff --git a/core/lightsd.c b/core/lightsd.c index a4c0106..b942f24 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -55,7 +55,9 @@ struct lgtd_opts lgtd_opts = { .foreground = true, .log_timestamps = true, - .verbosity = LGTD_INFO + .verbosity = LGTD_INFO, + .user = NULL, + .group = NULL }; struct event_base *lgtd_ev_base = NULL; @@ -124,15 +126,18 @@ lgtd_usage(const char *progname) { printf( "Usage: %s ...\n\n" -" [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP \n" -" at this address (can be repeated).\n" -" [-c,--comand-pipe /command/fifo [+]] Open an unidirectional JSON-RPC \n" -" command pipe at this location (can \n" -" be repeated).\n" -" [-s,--socket /unix/socket [+]] Open an Unix socket at this location \n" +" [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP at\n" +" this address (can be repeated).\n" +" [-c,--comand-pipe /command/fifo [+]] Open an unidirectional JSON-RPC\n" +" command pipe at this location (can be\n" +" repeated).\n" +" [-s,--socket /unix/socket [+]] Open an Unix socket at this location\n" " (can be repeated).\n" " [-f,--foreground] Stay in the foreground (default).\n" " [-d,--daemonize] Fork in the background.\n" +" [-u,--user user] Drop privileges to this user (and the \n" +" group of this user if -g is missing)\n" +" [-g,--group group] Drop privileges to this group\n" " [-t,--no-timestamps] Disable timestamps in logs.\n" " [-h,--help] Display this.\n" " [-V,--version] Display version and build information.\n" @@ -159,6 +164,8 @@ main(int argc, char *argv[], char *envp[]) {"socket", required_argument, NULL, 's'}, {"foreground", no_argument, NULL, 'f'}, {"daemonize", no_argument, NULL, 'd'}, + {"user", required_argument, NULL, 'u'}, + {"group", required_argument, NULL, 'g'}, {"no-timestamps", no_argument, NULL, 't'}, {"help", no_argument, NULL, 'h'}, {"verbosity", required_argument, NULL, 'v'}, @@ -166,7 +173,7 @@ main(int argc, char *argv[], char *envp[]) {"prefix", no_argument, NULL, 'p'}, {NULL, 0, NULL, 0} }; - const char short_opts[] = "l:c:s:fdthv:V"; + const char short_opts[] = "l:c:s:fdu:g:thv:V"; if (argc == 1) { lgtd_usage(argv[0]); @@ -202,6 +209,12 @@ main(int argc, char *argv[], char *envp[]) break; case 'd': lgtd_opts.foreground = false; + case 'u': + lgtd_opts.user = optarg; + break; + case 'g': + lgtd_opts.group = optarg; + break; case 't': lgtd_opts.log_timestamps = false; break; @@ -240,6 +253,12 @@ main(int argc, char *argv[], char *envp[]) argc -= optind; argv += optind; + if (lgtd_opts.user) { + lgtd_daemon_drop_privileges(lgtd_opts.user, lgtd_opts.group); + } + + lgtd_daemon_die_if_running_as_root_unless_requested(lgtd_opts.user); + lgtd_lifx_wire_load_packet_info_map(); if (!lgtd_lifx_watchdog_setup() || !lgtd_lifx_broadcast_setup()) { lgtd_err(1, "can't setup lightsd"); diff --git a/core/lightsd.h b/core/lightsd.h index 2270090..2ea1f2c 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -107,6 +107,8 @@ struct lgtd_opts { bool foreground; bool log_timestamps; enum lgtd_verbosity verbosity; + const char *user; + const char *group; }; extern struct lgtd_opts lgtd_opts; diff --git a/core/listen.c b/core/listen.c index 661710b..926db77 100644 --- a/core/listen.c +++ b/core/listen.c @@ -251,6 +251,11 @@ lgtd_listen_unix_open(const char *path) goto error; } + mode_t mode = S_IWUSR|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IWGRP; + if (chmod(path, mode)) { + goto error; + } + listener->evlistener = evconnlistener_new( lgtd_ev_base, lgtd_listen_accept_new_client, @@ -273,6 +278,7 @@ lgtd_listen_unix_open(const char *path) if (fd != -1) { close(fd); } + unlink(path); free(listener); return false; } diff --git a/core/pipe.c b/core/pipe.c index 241104e..cf91c12 100644 --- a/core/pipe.c +++ b/core/pipe.c @@ -154,14 +154,6 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) lgtd_command_pipe_reset(pipe); } -static mode_t -lgtd_command_pipe_get_umask(void) -{ - mode_t mask = umask(0); - umask(mask); - return mask; -} - static bool _lgtd_command_pipe_open(const char *path) { @@ -188,10 +180,9 @@ _lgtd_command_pipe_open(const char *path) if (errno != EEXIST) { goto error; } - mode &= ~lgtd_command_pipe_get_umask(); - if (chmod(path, mode)) { - goto error; - } + } + if (chmod(path, mode)) { + goto error; } pipe->fd = open(path, O_RDONLY|O_NONBLOCK); diff --git a/dist/lightsd.service b/dist/lightsd.service new file mode 100644 index 0000000..bc62223 --- /dev/null +++ b/dist/lightsd.service @@ -0,0 +1,10 @@ +[Unit] +Description=LIFX WiFi smart bulbs control service +After=network.target + +[Service] +ExecStart=/usr/bin/lightsd -v warning -f -u lightsd -s %t/lightsd.socket -c %t/lightsd.cmd +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/tests/core/pipe/test_pipe_open.c b/tests/core/pipe/test_pipe_open.c index 72c7ce1..233a073 100644 --- a/tests/core/pipe/test_pipe_open.c +++ b/tests/core/pipe/test_pipe_open.c @@ -140,7 +140,6 @@ main(void) mode_t expected_mode; expected_mode = S_IFIFO|S_IWUSR|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IWGRP; - expected_mode &= ~lgtd_command_pipe_get_umask(); if (sb.st_mode != expected_mode) { errx( 1, "unexpected mode %o (expected %o)", diff --git a/tests/core/pipe/test_pipe_open_fifo_already_exists.c b/tests/core/pipe/test_pipe_open_fifo_already_exists.c index c2bfe56..f90b9bf 100644 --- a/tests/core/pipe/test_pipe_open_fifo_already_exists.c +++ b/tests/core/pipe/test_pipe_open_fifo_already_exists.c @@ -145,7 +145,6 @@ main(void) mode_t expected_mode; expected_mode = S_IFIFO|S_IWUSR|S_IRUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IWGRP; - expected_mode &= ~lgtd_command_pipe_get_umask(); if (sb.st_mode != expected_mode) { errx( 1, "unexpected mode %o (expected %o)", From 05478a1a7c85e476be28ab9ab7ae9d17c8c66202 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 15:15:15 -0700 Subject: [PATCH 116/181] Handle an existing non-fifo file with -c option more gracefully --- core/pipe.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/pipe.c b/core/pipe.c index cf91c12..673aa98 100644 --- a/core/pipe.c +++ b/core/pipe.c @@ -172,6 +172,16 @@ _lgtd_command_pipe_open(const char *path) return false; } + struct stat sb; + if (stat(path, &sb) == -1) { + if (errno != ENOENT) { + goto error; + } + } else if ((sb.st_mode & S_IFMT) != S_IFIFO) { + errno = EEXIST; + goto error; + } + pipe->path = path; pipe->fd = -1; From 01cc165696faf6d8cad978fe3c70d265f1dd14b9 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 15:15:15 -0700 Subject: [PATCH 117/181] Do a much better job at keeping in-sync with the bulb By always keeping the bulbs refresh timer running. This is so good we might wanna give a shot at reducing the min refresh interval. --- lifx/gateway.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/lifx/gateway.c b/lifx/gateway.c index b253e06..2fd4776 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -245,6 +245,15 @@ lgtd_lifx_gateway_refresh_callback(struct lgtd_timer *timer, (void)timer; struct lgtd_lifx_gateway *gw = ctx.as_ptr; lgtd_lifx_gateway_send_get_all_light_state(gw); + + struct timeval tv = LGTD_MSECS_TO_TIMEVAL( + LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS + ); + lgtd_timer_reschedule(gw->refresh_timer, &tv); + lgtd_debug( + "scheduling next GET_LIGHT_STATE on %s in %dms", + gw->peeraddr, LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS + ); } void @@ -544,15 +553,13 @@ lgtd_lifx_gateway_handle_light_status(struct lgtd_lifx_gateway *gw, lgtd_time_mono_t latency = lgtd_lifx_gateway_latency(gw); if (latency < LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS) { - if (!lgtd_timer_ispending(gw->refresh_timer)) { - int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; - struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); - lgtd_timer_reschedule(gw->refresh_timer, &tv); - lgtd_debug( - "%s latency is %jums, scheduling next GET_LIGHT_STATE in %dms", - gw->peeraddr, (uintmax_t)latency, timeout - ); - } + int timeout = LGTD_LIFX_GATEWAY_MIN_REFRESH_INTERVAL_MSECS - latency; + struct timeval tv = LGTD_MSECS_TO_TIMEVAL(timeout); + lgtd_timer_reschedule(gw->refresh_timer, &tv); + lgtd_debug( + "%s latency is %jums, re-scheduling next GET_LIGHT_STATE in %dms", + gw->peeraddr, (uintmax_t)latency, timeout + ); return; } From 87a0eeb9768fc408001af22adb788863d8fb27cd Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 16:21:04 -0700 Subject: [PATCH 118/181] Create parent directories for pipes and unix sockets This allows us to reduce the clutter in /run. The new "default" path are: /run/lightsd/socket and /run/lightsd/pipe. The directories are created *before* privileges are dropped and with mode 755. Created directories aren't cleaned-up by lightsd at exit. --- core/daemon.c | 64 ++++++++++++++++++++++++++++++++++++++++ core/daemon.h | 1 + core/listen.c | 6 +++- core/pipe.c | 7 ++++- dist/lightsd.service | 2 +- examples/lightsc.py | 5 ++-- share/lightsc.sh | 2 +- tests/core/mock_daemon.h | 9 ++++++ 8 files changed, 90 insertions(+), 6 deletions(-) diff --git a/core/daemon.c b/core/daemon.c index 4af570b..e6d854b 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -19,12 +19,15 @@ #include #include #include +#include #include #include #include #include +#include #include #include +#include #include #include #include @@ -264,3 +267,64 @@ lgtd_daemon_drop_privileges(const char *user, const char *group) lgtd_err(1, "can't change user to %s", user); } } + +static bool +_lgtd_daemon_makedirs(const char *fp) +{ + char *fpsave = NULL, *next = NULL; + + fpsave = strdup(fp); + if (!fpsave) { + goto err; + } + next = strdup(dirname(fpsave)); + if (!next) { + goto err; + } + + if (!strcmp(next, fp)) { + goto done; + } + + bool ok = _lgtd_daemon_makedirs(next); + if (!ok) { + goto err; + } + + struct stat sb; + if (stat(next, &sb) == -1) { + if (errno == ENOENT) { + mode_t mode = S_IWUSR|S_IRUSR|S_IXUSR + |S_IRGRP|S_IXGRP|S_IWGRP + |S_IXOTH|S_IROTH; + if (mkdir(next, mode) == 0) { + goto done; + } + } + goto err; + } else if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + goto err; + } + +done: + free(fpsave); + free(next); + return true; + +err: + free(fpsave); + free(next); + return false; +} + +bool +lgtd_daemon_makedirs(const char *filepath) +{ + if (!_lgtd_daemon_makedirs(filepath)) { + lgtd_warn("can't create parent directories for %s", filepath); + return false; + } + + return true; +} diff --git a/core/daemon.h b/core/daemon.h index 569679e..7aebc6d 100644 --- a/core/daemon.h +++ b/core/daemon.h @@ -24,3 +24,4 @@ void lgtd_daemon_setup_proctitle(int, char *[], char *[]); void lgtd_daemon_update_proctitle(void); void lgtd_daemon_die_if_running_as_root_unless_requested(const char *); void lgtd_daemon_drop_privileges(const char *, const char *); +bool lgtd_daemon_makedirs(const char *); diff --git a/core/listen.c b/core/listen.c index 926db77..439054e 100644 --- a/core/listen.c +++ b/core/listen.c @@ -207,6 +207,10 @@ lgtd_listen_unix_open(const char *path) } } + if (!lgtd_daemon_makedirs(path)) { + return false; + } + evutil_socket_t fd = -1; listener = calloc(1, sizeof(*listener)); @@ -237,7 +241,7 @@ lgtd_listen_unix_open(const char *path) if (errno != ENOENT) { goto error; } - } else if ((sb.st_mode & S_IFMT) == S_IFSOCK) { + } else if (S_ISSOCK(sb.st_mode)) { lgtd_warnx("removing existing unix socket: %s", path); if (unlink(path) == -1 && errno != ENOENT) { goto error; diff --git a/core/pipe.c b/core/pipe.c index 673aa98..6d19a3e 100644 --- a/core/pipe.c +++ b/core/pipe.c @@ -31,6 +31,7 @@ #include #include +#include "daemon.h" #include "jsmn.h" #include "jsonrpc.h" #include "client.h" @@ -166,6 +167,10 @@ _lgtd_command_pipe_open(const char *path) } } + if (!lgtd_daemon_makedirs(path)) { + return false; + } + pipe = calloc(1, sizeof(*pipe)); if (!pipe) { lgtd_warn("can't open command pipe %s", path); @@ -177,7 +182,7 @@ _lgtd_command_pipe_open(const char *path) if (errno != ENOENT) { goto error; } - } else if ((sb.st_mode & S_IFMT) != S_IFIFO) { + } else if (!S_ISFIFO(sb.st_mode)) { errno = EEXIST; goto error; } diff --git a/dist/lightsd.service b/dist/lightsd.service index bc62223..9a51cd4 100644 --- a/dist/lightsd.service +++ b/dist/lightsd.service @@ -3,7 +3,7 @@ Description=LIFX WiFi smart bulbs control service After=network.target [Service] -ExecStart=/usr/bin/lightsd -v warning -f -u lightsd -s %t/lightsd.socket -c %t/lightsd.cmd +ExecStart=/usr/bin/lightsd -t -v warning -f -u lightsd -s %t/lightsd/socket -c %t/lightsd/pipe Restart=on-failure [Install] diff --git a/examples/lightsc.py b/examples/lightsc.py index 99ef9b0..3966b42 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -223,9 +223,10 @@ def _drop_to_shell(lightsc): description="lightsc.py is an interactive lightsd Python client" ) parser.add_argument( - "url", type=str, + "-u", "--url", type=str, help="How to connect to lightsd (e.g: " - "unix:///run/lightsd.sock or tcp://[::1]:1234)" + "unix:///run/lightsd/socket or tcp://[::1]:1234)", + default="unix:///run/lightsd/socket", ) args = parser.parse_args() diff --git a/share/lightsc.sh b/share/lightsc.sh index 1e7eb31..99ba281 100644 --- a/share/lightsc.sh +++ b/share/lightsc.sh @@ -80,7 +80,7 @@ _lightsc_jq() { } _lightsc_get_pipe() { - local pipe=${COMMAND_PIPE:-/run/lightsd.cmd} + local pipe=${COMMAND_PIPE:-/run/lightsd/pipe} if [ ! -p $pipe ] ; then echo >&2 "$pipe cannot be found, is lightsd running?" exit 1 diff --git a/tests/core/mock_daemon.h b/tests/core/mock_daemon.h index 0f2632e..9326337 100644 --- a/tests/core/mock_daemon.h +++ b/tests/core/mock_daemon.h @@ -6,3 +6,12 @@ lgtd_daemon_update_proctitle(void) { } #endif + +#ifndef MOCKED_DAEMON_MAKEDIRS +bool +lgtd_daemon_makedirs(const char *fp) +{ + (void)fp; + return true; +} +#endif From 3088bb7f30102441edd45073ce220fbc42d74217 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 16:21:06 -0700 Subject: [PATCH 119/181] Rename the LIFX watchdog module to discovery --- core/lightsd.c | 8 +++--- lifx/CMakeLists.txt | 2 +- lifx/{watchdog.c => discovery.c} | 47 ++++++++++++++++--------------- lifx/{watchdog.h => discovery.h} | 20 ++++++------- lifx/gateway.c | 4 +-- tests/lifx/gateway/CMakeLists.txt | 2 +- 6 files changed, 42 insertions(+), 41 deletions(-) rename lifx/{watchdog.c => discovery.c} (79%) rename lifx/{watchdog.h => discovery.h} (60%) diff --git a/core/lightsd.c b/core/lightsd.c index b942f24..5682597 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -41,7 +41,7 @@ #include "lifx/bulb.h" #include "lifx/gateway.h" #include "lifx/broadcast.h" -#include "lifx/watchdog.h" +#include "lifx/discovery.h" #include "version.h" #include "jsmn.h" #include "jsonrpc.h" @@ -65,7 +65,7 @@ struct event_base *lgtd_ev_base = NULL; void lgtd_cleanup(void) { - lgtd_lifx_watchdog_close(); + lgtd_lifx_discovery_close(); lgtd_listen_close_all(); lgtd_command_pipe_close_all(); lgtd_client_close_all(); @@ -260,7 +260,7 @@ main(int argc, char *argv[], char *envp[]) lgtd_daemon_die_if_running_as_root_unless_requested(lgtd_opts.user); lgtd_lifx_wire_load_packet_info_map(); - if (!lgtd_lifx_watchdog_setup() || !lgtd_lifx_broadcast_setup()) { + if (!lgtd_lifx_discovery_setup() || !lgtd_lifx_broadcast_setup()) { lgtd_err(1, "can't setup lightsd"); } @@ -271,7 +271,7 @@ main(int argc, char *argv[], char *envp[]) } } - lgtd_lifx_watchdog_start_discovery(); + lgtd_lifx_discovery_start(); event_base_dispatch(lgtd_ev_base); diff --git a/lifx/CMakeLists.txt b/lifx/CMakeLists.txt index 9b904de..b3e118f 100644 --- a/lifx/CMakeLists.txt +++ b/lifx/CMakeLists.txt @@ -9,8 +9,8 @@ ADD_LIBRARY( lifx broadcast.c bulb.c + discovery.c gateway.c tagging.c - watchdog.c wire_proto.c ) diff --git a/lifx/watchdog.c b/lifx/discovery.c similarity index 79% rename from lifx/watchdog.c rename to lifx/discovery.c index fc58394..a956b5d 100644 --- a/lifx/watchdog.c +++ b/lifx/discovery.c @@ -33,18 +33,18 @@ #include "broadcast.h" #include "bulb.h" #include "gateway.h" -#include "watchdog.h" +#include "discovery.h" #include "core/lightsd.h" static struct event *lgtd_watchdog_interval_ev = NULL; static struct event *lgtd_discovery_timeout_ev = NULL; static int lgtd_discovery_timeout = - LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; + LGTD_LIFX_DISCOVERY_ACTIVE_DISCOVERY_INTERVAL_MSECS; static void -lgtd_lifx_watchdog_discovery_timeout_event_callback(evutil_socket_t socket, - short events, - void *ctx) +lgtd_lifx_discovery_timeout_event_callback(evutil_socket_t socket, + short events, + void *ctx) { (void)socket; (void)events; @@ -52,7 +52,7 @@ lgtd_lifx_watchdog_discovery_timeout_event_callback(evutil_socket_t socket, if (LIST_EMPTY(&lgtd_lifx_gateways)) { lgtd_discovery_timeout = - LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; + LGTD_LIFX_DISCOVERY_ACTIVE_DISCOVERY_INTERVAL_MSECS; lgtd_debug( "discovery didn't returned anything in %dms, restarting it", lgtd_discovery_timeout @@ -60,7 +60,7 @@ lgtd_lifx_watchdog_discovery_timeout_event_callback(evutil_socket_t socket, } else { lgtd_discovery_timeout = LGTD_MIN( lgtd_discovery_timeout * 2, - LGTD_LIFX_WATCHDOG_PASSIVE_DISCOVERY_INTERVAL_MSECS + LGTD_LIFX_DISCOVERY_PASSIVE_DISCOVERY_INTERVAL_MSECS ); lgtd_debug( "sending periodic discovery packet, timeout=%d", @@ -76,9 +76,9 @@ lgtd_lifx_watchdog_discovery_timeout_event_callback(evutil_socket_t socket, } static void -lgtd_lifx_watchdog_timeout_event_callback(evutil_socket_t socket, - short events, - void *ctx) +lgtd_lifx_discovery_watchdog_interval_callback(evutil_socket_t socket, + short events, + void *ctx) { (void)socket; (void)events; @@ -95,7 +95,7 @@ lgtd_lifx_watchdog_timeout_event_callback(evutil_socket_t socket, next_bulb ) { int light_state_lag = now - bulb->last_light_state_at; - if (light_state_lag >= LGTD_LIFX_WATCHDOG_DEVICE_TIMEOUT_MSECS) { + if (light_state_lag >= LGTD_LIFX_DISCOVERY_DEVICE_TIMEOUT_MSECS) { lgtd_info( "closing bulb \"%.*s\" that hasn't been updated for %dms", LGTD_LIFX_LABEL_SIZE, bulb->state.label, light_state_lag @@ -112,14 +112,14 @@ lgtd_lifx_watchdog_timeout_event_callback(evutil_socket_t socket, struct lgtd_lifx_gateway *gw, *next_gw; LIST_FOREACH_SAFE(gw, &lgtd_lifx_gateways, link, next_gw) { int gw_lag = lgtd_lifx_gateway_latency(gw); - if (gw_lag >= LGTD_LIFX_WATCHDOG_DEVICE_TIMEOUT_MSECS) { + if (gw_lag >= LGTD_LIFX_DISCOVERY_DEVICE_TIMEOUT_MSECS) { lgtd_info( "closing bulb gateway %s that hasn't received traffic for %dms", gw->peeraddr, gw_lag ); lgtd_lifx_gateway_close(gw); start_discovery = true; - } else if (gw_lag >= LGTD_LIFX_WATCHDOG_DEVICE_FORCE_REFRESH_MSECS) { + } else if (gw_lag >= LGTD_LIFX_DISCOVERY_DEVICE_FORCE_REFRESH_MSECS) { lgtd_info( "no update on bulb gateway %s for %dms, forcing refresh", gw->peeraddr, gw_lag @@ -136,7 +136,7 @@ lgtd_lifx_watchdog_timeout_event_callback(evutil_socket_t socket, } bool -lgtd_lifx_watchdog_setup(void) +lgtd_lifx_discovery_setup(void) { assert(!lgtd_watchdog_interval_ev); assert(!lgtd_discovery_timeout_ev); @@ -145,14 +145,14 @@ lgtd_lifx_watchdog_setup(void) lgtd_ev_base, -1, 0, - lgtd_lifx_watchdog_discovery_timeout_event_callback, + lgtd_lifx_discovery_timeout_event_callback, NULL ); lgtd_watchdog_interval_ev = event_new( lgtd_ev_base, -1, EV_PERSIST, - lgtd_lifx_watchdog_timeout_event_callback, + lgtd_lifx_discovery_watchdog_interval_callback, NULL ); @@ -161,13 +161,13 @@ lgtd_lifx_watchdog_setup(void) } int errsave = errno; - lgtd_lifx_watchdog_close(); + lgtd_lifx_discovery_close(); errno = errsave; return false; } void -lgtd_lifx_watchdog_close(void) +lgtd_lifx_discovery_close(void) { if (lgtd_discovery_timeout_ev) { event_del(lgtd_discovery_timeout_ev); @@ -182,7 +182,7 @@ lgtd_lifx_watchdog_close(void) } void -lgtd_lifx_watchdog_start(void) +lgtd_lifx_discovery_start_watchdog(void) { assert( !RB_EMPTY(&lgtd_lifx_bulbs_table) || !LIST_EMPTY(&lgtd_lifx_gateways) @@ -191,7 +191,7 @@ lgtd_lifx_watchdog_start(void) bool pending = evtimer_pending(lgtd_watchdog_interval_ev, NULL); if (!pending) { struct timeval tv = LGTD_MSECS_TO_TIMEVAL( - LGTD_LIFX_WATCHDOG_INTERVAL_MSECS + LGTD_LIFX_DISCOVERY_WATCHDOG_INTERVAL_MSECS ); if (event_add(lgtd_watchdog_interval_ev, &tv)) { lgtd_err(1, "can't start watchdog"); @@ -201,11 +201,12 @@ lgtd_lifx_watchdog_start(void) } void -lgtd_lifx_watchdog_start_discovery(void) +lgtd_lifx_discovery_start(void) { assert(!evtimer_pending(lgtd_discovery_timeout_ev, NULL)); - lgtd_discovery_timeout = LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS; - lgtd_lifx_watchdog_discovery_timeout_event_callback(-1, 0, NULL); + lgtd_discovery_timeout = + LGTD_LIFX_DISCOVERY_ACTIVE_DISCOVERY_INTERVAL_MSECS; + lgtd_lifx_discovery_timeout_event_callback(-1, 0, NULL); lgtd_debug("starting discovery timer"); } diff --git a/lifx/watchdog.h b/lifx/discovery.h similarity index 60% rename from lifx/watchdog.h rename to lifx/discovery.h index 9c1c9c7..43cac29 100644 --- a/lifx/watchdog.h +++ b/lifx/discovery.h @@ -17,15 +17,15 @@ #pragma once -enum lgtd_lifx_watchdog_constants { - LGTD_LIFX_WATCHDOG_INTERVAL_MSECS = 500, - LGTD_LIFX_WATCHDOG_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000, - LGTD_LIFX_WATCHDOG_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000, - LGTD_LIFX_WATCHDOG_DEVICE_TIMEOUT_MSECS = 3000, - LGTD_LIFX_WATCHDOG_DEVICE_FORCE_REFRESH_MSECS = 2000 +enum lgtd_lifx_discovery_constants { + LGTD_LIFX_DISCOVERY_WATCHDOG_INTERVAL_MSECS = 500, + LGTD_LIFX_DISCOVERY_DEVICE_TIMEOUT_MSECS = 3000, + LGTD_LIFX_DISCOVERY_DEVICE_FORCE_REFRESH_MSECS = 2000, + LGTD_LIFX_DISCOVERY_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000, + LGTD_LIFX_DISCOVERY_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000, }; -bool lgtd_lifx_watchdog_setup(void); -void lgtd_lifx_watchdog_start(void); -void lgtd_lifx_watchdog_close(void); -void lgtd_lifx_watchdog_start_discovery(void); +bool lgtd_lifx_discovery_setup(void); +void lgtd_lifx_discovery_close(void); +void lgtd_lifx_discovery_start_watchdog(void); +void lgtd_lifx_discovery_start(void); diff --git a/lifx/gateway.c b/lifx/gateway.c index 2fd4776..a150de4 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -36,7 +36,7 @@ #include "core/time_monotonic.h" #include "bulb.h" #include "gateway.h" -#include "watchdog.h" +#include "discovery.h" #include "broadcast.h" #include "core/timer.h" #include "tagging.h" @@ -356,7 +356,7 @@ lgtd_lifx_gateway_open(const struct sockaddr *peer, // In case this is the first bulb (re-)discovered, start the watchdog, it // will stop by itself: - lgtd_lifx_watchdog_start(); + lgtd_lifx_discovery_start_watchdog(); LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, 1); diff --git a/tests/lifx/gateway/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt index eb7f4c1..8140926 100644 --- a/tests/lifx/gateway/CMakeLists.txt +++ b/tests/lifx/gateway/CMakeLists.txt @@ -17,7 +17,7 @@ ADD_LIBRARY( test_lifx_gateway STATIC ${LIGHTSD_SOURCE_DIR}/lifx/broadcast.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c - ${LIGHTSD_SOURCE_DIR}/lifx/watchdog.c + ${LIGHTSD_SOURCE_DIR}/lifx/discovery.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ) From f32cdad699c2a625a4026ecc13fa7e6557ffea62 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 16:25:31 -0700 Subject: [PATCH 120/181] Add the --rundir option so we can easily launch lightsc.py --- core/CMakeLists.txt | 8 ++++---- core/lightsd.c | 12 +++++++++++- core/version.h.in | 2 ++ examples/lightsc.py | 17 ++++++++++++++++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index c02da2b..835874e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -8,6 +8,9 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) +IF (NOT LGTD_RUNTIME_DIRECTORY) + SET(LGTD_RUNTIME_DIRECTORY "${LIGHTSD_BINARY_DIR}") +ENDIF () CONFIGURE_FILE(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) ADD_EXECUTABLE( @@ -28,10 +31,7 @@ ADD_EXECUTABLE( ) TARGET_LINK_LIBRARIES( - lightsd - lifx - ${EVENT2_CORE_LIBRARY} - ${TIME_MONOTONIC_LIBRARY} + lightsd lifx ${EVENT2_CORE_LIBRARY} ${TIME_MONOTONIC_LIBRARY} ) INSTALL(TARGETS lightsd RUNTIME DESTINATION bin) diff --git a/core/lightsd.c b/core/lightsd.c index 5682597..9b86d5e 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -143,7 +143,9 @@ lgtd_usage(const char *progname) " [-V,--version] Display version and build information.\n" " [-v,--verbosity debug|info|warning|error]\n" "\nor,\n\n" -" --prefix Display the install prefix for lightsd.\n", +" --prefix Display the install prefix for lightsd.\n" +"\nor,\n\n" +" --rundir Display the runtime directory for lightsd.\n", progname ); lgtd_cleanup(); @@ -171,6 +173,7 @@ main(int argc, char *argv[], char *envp[]) {"verbosity", required_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {"prefix", no_argument, NULL, 'p'}, + {"rundir", no_argument, NULL, 'r'}, {NULL, 0, NULL, 0} }; const char short_opts[] = "l:c:s:fdu:g:thv:V"; @@ -245,6 +248,13 @@ main(int argc, char *argv[], char *envp[]) ] != '/' ? "/" : "" ); return 0; + case 'r': + printf( + "%s%s\n", LGTD_RUNTIME_DIRECTORY, LGTD_RUNTIME_DIRECTORY[ + LGTD_ARRAY_SIZE(LGTD_RUNTIME_DIRECTORY) - 1 + ] != '/' ? "/" : "" + ); + return 0; default: lgtd_usage(argv[0]); } diff --git a/core/version.h.in b/core/version.h.in index e54cac5..2f23696 100644 --- a/core/version.h.in +++ b/core/version.h.in @@ -36,3 +36,5 @@ const char LGTD_VERSION[] = ( ); const char LGTD_INSTALL_PREFIX[] = "@CMAKE_INSTALL_PREFIX@"; + +const char LGTD_RUNTIME_DIRECTORY[] = "@LGTD_RUNTIME_DIRECTORY@"; diff --git a/examples/lightsc.py b/examples/lightsc.py index 3966b42..edd16fc 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -31,7 +31,10 @@ import argparse import contextlib import json +import locale +import os import socket +import subprocess import sys import urllib.parse import uuid @@ -219,6 +222,17 @@ def _drop_to_shell(lightsc): code.interact(banner=banner, local=locals()) if __name__ == "__main__": + try: + lightsdrundir = subprocess.check_output(["lightsd", "--rundir"]) + except Exception as ex: + print( + "Couldn't infer lightsd's runtime directory is lightsd installed? " + "({})".format(ex), + file=sys.stderr + ) + sys.exit(1) + lightsdrundir = lightsdrundir.decode(locale.getpreferredencoding()).strip() + parser = argparse.ArgumentParser( description="lightsc.py is an interactive lightsd Python client" ) @@ -226,11 +240,12 @@ def _drop_to_shell(lightsc): "-u", "--url", type=str, help="How to connect to lightsd (e.g: " "unix:///run/lightsd/socket or tcp://[::1]:1234)", - default="unix:///run/lightsd/socket", + default="unix://" + os.path.join(lightsdrundir, "socket") ) args = parser.parse_args() try: + print("Connecting to lightsd@{}".format(args.url)) with LightsClient(args.url) as client: _drop_to_shell(client) except Exception as ex: From 8237518e2f6340184a78f712a8f43d2ac916235d Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 13 Sep 2015 17:30:51 -0700 Subject: [PATCH 121/181] Added tag 0.9.2 for changeset 3a4befe8819e --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index bc90427..1296365 100644 --- a/.hgtags +++ b/.hgtags @@ -1,2 +1,3 @@ 38014f13f04d5871b70278518221a937867fd8b6 0.9.0 21d438d34e21479a652ba6aa1ec14646385cae5a 0.9.1 +3a4befe8819ea7a81f89a6ed550cc88803108f6a 0.9.2 From 4163e18071e82c9dc95ba0eedfae8e0df5fbbcc4 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 14 Sep 2015 01:03:26 -0700 Subject: [PATCH 122/181] Fix command pipe support & cleanup of pipes/sockets lightsc.sh is now configured at build time, packages will work out of the box. Most importantly the user/group of the parent directory of any socket or command pipe is now properly set when the -u option is used. This means that command pipes work again and that both pipes and sockets can be properly cleaned-up at exit. --- CMakeLists.txt | 14 +++++- core/CMakeLists.txt | 5 +-- core/daemon.c | 68 +++++++++++++++++++++++------ core/daemon.h | 4 +- core/lightsd.c | 15 +++++-- share/{lightsc.sh => lightsc.sh.in} | 2 +- 6 files changed, 83 insertions(+), 25 deletions(-) rename share/{lightsc.sh => lightsc.sh.in} (98%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7684601..822d5a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "0") SET(CPACK_PACKAGE_VERSION_MINOR "9") -SET(CPACK_PACKAGE_VERSION_PATCH "2") +SET(CPACK_PACKAGE_VERSION_PATCH "3") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") @@ -74,6 +74,10 @@ IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") ENDIF () ENDIF () +IF (NOT LGTD_RUNTIME_DIRECTORY) + SET(LGTD_RUNTIME_DIRECTORY "${LIGHTSD_BINARY_DIR}") +ENDIF () + INCLUDE_DIRECTORIES( ${LIGHTSD_BINARY_DIR}/compat ${LIGHTSD_BINARY_DIR}/compat/generic @@ -94,5 +98,11 @@ INSTALL( USE_SOURCE_PERMISSIONS REGEX ".*\\.sw.$" EXCLUDE ) -INSTALL(FILES share/lightsc.sh DESTINATION share/lightsd) +CONFIGURE_FILE( + share/lightsc.sh.in "${LIGHTSD_BINARY_DIR}/share/lightsc.sh" @ONLY +) +INSTALL( + FILES "${LIGHTSD_BINARY_DIR}/share/lightsc.sh" + DESTINATION share/lightsd +) INSTALL(FILES dist/lightsd.service DESTINATION lib/systemd/system) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 835874e..1eb12dc 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -8,10 +8,7 @@ INCLUDE_DIRECTORIES( ${CMAKE_CURRENT_BINARY_DIR} ) -IF (NOT LGTD_RUNTIME_DIRECTORY) - SET(LGTD_RUNTIME_DIRECTORY "${LIGHTSD_BINARY_DIR}") -ENDIF () -CONFIGURE_FILE(version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) +CONFIGURE_FILE(version.h.in "${CMAKE_CURRENT_BINARY_DIR}/version.h") ADD_EXECUTABLE( lightsd diff --git a/core/daemon.c b/core/daemon.c index e6d854b..41e06aa 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -52,6 +52,8 @@ #include "lightsd.h" static bool lgtd_daemon_proctitle_initialized = false; +static struct passwd *lgtd_user_info = NULL; +static struct group *lgtd_group_info = NULL; bool lgtd_daemon_unleash(void) @@ -207,34 +209,75 @@ lgtd_daemon_die_if_running_as_root_unless_requested(const char *requested_user) } void -lgtd_daemon_drop_privileges(const char *user, const char *group) +lgtd_daemon_set_user(const char *user) { assert(user); - uid_t uid; - gid_t gid; + static struct passwd user_info_storage; struct passwd *user_info = getpwnam(user); if (!user_info) { lgtd_err(1, "can't get user info for %s", user); } - uid = user_info->pw_uid; + + lgtd_user_info = memcpy(&user_info_storage, user_info, sizeof(*user_info)); +} + +void +lgtd_daemon_set_group(const char *group) +{ + assert(lgtd_user_info); + + static struct group group_info_storage; struct group *group_info; if (group) { group_info = getgrnam(group); } else { - group_info = getgrgid(user_info->pw_gid); + group_info = getgrgid(lgtd_user_info->pw_gid); group = group_info->gr_name; } if (!group_info) { - lgtd_err(1, "can't get group info for %s", group ? group : user); + lgtd_err( + 1, "can't get group info for %s", + group ? group : lgtd_user_info->pw_name + ); } - gid = group_info->gr_gid; + + lgtd_group_info = memcpy( + &group_info_storage, group_info, sizeof(*group_info) + ); +} + +static int +lgtd_daemon_chown_dir_of(const char *filepath, uid_t uid, gid_t gid) +{ + char *fp = strdup(filepath); + if (!fp) { + return -1; + } + + char *dir = dirname(fp); + int rv = chown(dir, uid, gid); + free(fp); + return rv; +} + +void +lgtd_daemon_drop_privileges(void) +{ + assert(lgtd_user_info); + assert(lgtd_group_info); + + uid_t uid = lgtd_user_info->pw_uid; + const char *user = lgtd_user_info->pw_name; + gid_t gid = lgtd_group_info->gr_gid; + const char *group = lgtd_group_info->gr_name; struct lgtd_command_pipe *pipe; SLIST_FOREACH(pipe, &lgtd_command_pipes, link) { - if (fchown(pipe->fd, uid, gid) == -1) { + if (lgtd_daemon_chown_dir_of(pipe->path, uid, gid) == -1 + || fchown(pipe->fd, uid, gid) == -1) { lgtd_err(1, "can't chown %s to %s:%s", pipe->path, user, group); } } @@ -246,12 +289,9 @@ lgtd_daemon_drop_privileges(const char *user, const char *group) } const char *path = ((struct sockaddr_un *)listener->sockaddr)->sun_path; - if (chown(path, uid, gid) == -1) { - char addr[LGTD_SOCKADDR_STRLEN]; - lgtd_err( - 1, "can't chown %s to %s:%s", - LGTD_SOCKADDRTOA(listener->sockaddr, addr), user, group - ); + if (lgtd_daemon_chown_dir_of(path, uid, gid) == -1 + || chown(path, uid, gid) == -1) { + lgtd_err(1, "can't chown %s to %s:%s", path, user, group); } } diff --git a/core/daemon.h b/core/daemon.h index 7aebc6d..2c06ce4 100644 --- a/core/daemon.h +++ b/core/daemon.h @@ -23,5 +23,7 @@ bool lgtd_daemon_unleash(void); // \_o< void lgtd_daemon_setup_proctitle(int, char *[], char *[]); void lgtd_daemon_update_proctitle(void); void lgtd_daemon_die_if_running_as_root_unless_requested(const char *); -void lgtd_daemon_drop_privileges(const char *, const char *); +void lgtd_daemon_set_user(const char *); +void lgtd_daemon_set_group(const char *); +void lgtd_daemon_drop_privileges(void); bool lgtd_daemon_makedirs(const char *); diff --git a/core/lightsd.c b/core/lightsd.c index 9b86d5e..bad8c31 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -55,7 +55,11 @@ struct lgtd_opts lgtd_opts = { .foreground = true, .log_timestamps = true, +#ifndef NDEBUG .verbosity = LGTD_INFO, +#else + .verbosity = LGTD_WARN, +#endif .user = NULL, .group = NULL }; @@ -136,8 +140,9 @@ lgtd_usage(const char *progname) " [-f,--foreground] Stay in the foreground (default).\n" " [-d,--daemonize] Fork in the background.\n" " [-u,--user user] Drop privileges to this user (and the \n" -" group of this user if -g is missing)\n" -" [-g,--group group] Drop privileges to this group\n" +" group of this user if -g is missing).\n" +" [-g,--group group] Drop privileges to this group (-g requires\n" +" the -u option to be used).\n" " [-t,--no-timestamps] Disable timestamps in logs.\n" " [-h,--help] Display this.\n" " [-V,--version] Display version and build information.\n" @@ -264,7 +269,11 @@ main(int argc, char *argv[], char *envp[]) argv += optind; if (lgtd_opts.user) { - lgtd_daemon_drop_privileges(lgtd_opts.user, lgtd_opts.group); + lgtd_daemon_set_user(lgtd_opts.user); + lgtd_daemon_set_group(lgtd_opts.group); + lgtd_daemon_drop_privileges(); + } else if (lgtd_opts.group) { + lgtd_errx(1, "please, specify an user with the -u option"); } lgtd_daemon_die_if_running_as_root_unless_requested(lgtd_opts.user); diff --git a/share/lightsc.sh b/share/lightsc.sh.in similarity index 98% rename from share/lightsc.sh rename to share/lightsc.sh.in index 99ba281..de2846f 100644 --- a/share/lightsc.sh +++ b/share/lightsc.sh.in @@ -80,7 +80,7 @@ _lightsc_jq() { } _lightsc_get_pipe() { - local pipe=${COMMAND_PIPE:-/run/lightsd/pipe} + local pipe=${LIGHTSD_COMMAND_PIPE:-@LGTD_RUNTIME_DIRECTORY@/pipe} if [ ! -p $pipe ] ; then echo >&2 "$pipe cannot be found, is lightsd running?" exit 1 From 8ebf3aa0f4b20139c2bb6e97464590becb0d7911 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 14 Sep 2015 01:44:15 -0700 Subject: [PATCH 123/181] Added tag 0.9.3 for changeset 8a41f4cfee7e --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 1296365..95e6517 100644 --- a/.hgtags +++ b/.hgtags @@ -1,3 +1,4 @@ 38014f13f04d5871b70278518221a937867fd8b6 0.9.0 21d438d34e21479a652ba6aa1ec14646385cae5a 0.9.1 3a4befe8819ea7a81f89a6ed550cc88803108f6a 0.9.2 +8a41f4cfee7e6ea6d7e161f4e96cfc45bb485cc4 0.9.3 From e5dc5623af47d4d2291a597c26f44fdaea843cc2 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 16 Sep 2015 01:42:56 -0700 Subject: [PATCH 124/181] Fix how the program name is displayed in the help message --- CMakeLists.txt | 4 ++-- core/lightsd.c | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 822d5a1..72d9516 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "0") SET(CPACK_PACKAGE_VERSION_MINOR "9") -SET(CPACK_PACKAGE_VERSION_PATCH "3") +SET(CPACK_PACKAGE_VERSION_PATCH "4") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") @@ -70,7 +70,7 @@ ADD_DEFINITIONS( IF (CMAKE_BUILD_TYPE MATCHES "DEBUG") ADD_DEFINITIONS("-DQUEUE_MACRO_DEBUG=1") IF (CMAKE_COMPILER_IS_GNUCC) - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -ggdb") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3 -ggdb3") ENDIF () ENDIF () diff --git a/core/lightsd.c b/core/lightsd.c index bad8c31..e5a4378 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -139,7 +139,7 @@ lgtd_usage(const char *progname) " (can be repeated).\n" " [-f,--foreground] Stay in the foreground (default).\n" " [-d,--daemonize] Fork in the background.\n" -" [-u,--user user] Drop privileges to this user (and the \n" +" [-u,--user user] Drop privileges to this user (and the\n" " group of this user if -g is missing).\n" " [-g,--group group] Drop privileges to this group (-g requires\n" " the -u option to be used).\n" @@ -160,6 +160,9 @@ lgtd_usage(const char *progname) int main(int argc, char *argv[], char *envp[]) { + char progname[32]; + memcpy(progname, argv[0], LGTD_MIN(sizeof(progname), strlen(argv[0]))); + lgtd_daemon_setup_proctitle(argc, argv, envp); lgtd_configure_libevent(); @@ -184,7 +187,7 @@ main(int argc, char *argv[], char *envp[]) const char short_opts[] = "l:c:s:fdu:g:thv:V"; if (argc == 1) { - lgtd_usage(argv[0]); + lgtd_usage(progname); } for (int rv = getopt_long(argc, argv, short_opts, long_opts, NULL); @@ -195,7 +198,7 @@ main(int argc, char *argv[], char *envp[]) (void)0; char *sep = strrchr(optarg, ':'); if (!sep || !sep[1]) { - lgtd_usage(argv[0]); + lgtd_usage(progname); } *sep = '\0'; if (!lgtd_listen_open(optarg, sep + 1)) { @@ -227,7 +230,7 @@ main(int argc, char *argv[], char *envp[]) lgtd_opts.log_timestamps = false; break; case 'h': - lgtd_usage(argv[0]); + lgtd_usage(progname); case 'v': for (int i = 0;;) { const char *verbose_levels[] = { @@ -243,7 +246,7 @@ main(int argc, char *argv[], char *envp[]) } break; case 'V': - printf("lightsd %s\n", LGTD_VERSION); + printf("%s %s\n", progname, LGTD_VERSION); lgtd_cleanup(); return 0; case 'p': @@ -261,7 +264,7 @@ main(int argc, char *argv[], char *envp[]) ); return 0; default: - lgtd_usage(argv[0]); + lgtd_usage(progname); } } From 98b6d0527975ce1288dafd3b31be3bde0f9facce Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 16 Sep 2015 01:47:10 -0700 Subject: [PATCH 125/181] Fix command pipe handling in lightsc.sh and add an alert example Expose lightsc_get_pipe in lightsc.sh: it's useful for batch requests with lightsc_make_request. Use `lightsd --rundir` instead of doing retarded things with CMake. Add an alert script example: sleep $((x * 60)) ; alert '"#kitchen"' is a favorite of mine. --- CMakeLists.txt | 9 ++------ examples/alert | 5 +++++ share/{lightsc.sh.in => lightsc.sh} | 32 ++++++----------------------- 3 files changed, 13 insertions(+), 33 deletions(-) create mode 100755 examples/alert rename share/{lightsc.sh.in => lightsc.sh} (78%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 72d9516..71eda9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,7 @@ ENDIF () IF (NOT LGTD_RUNTIME_DIRECTORY) SET(LGTD_RUNTIME_DIRECTORY "${LIGHTSD_BINARY_DIR}") ENDIF () +MESSAGE(STATUS "lightsd runtime directory: ${LGTD_RUNTIME_DIRECTORY}") INCLUDE_DIRECTORIES( ${LIGHTSD_BINARY_DIR}/compat @@ -98,11 +99,5 @@ INSTALL( USE_SOURCE_PERMISSIONS REGEX ".*\\.sw.$" EXCLUDE ) -CONFIGURE_FILE( - share/lightsc.sh.in "${LIGHTSD_BINARY_DIR}/share/lightsc.sh" @ONLY -) -INSTALL( - FILES "${LIGHTSD_BINARY_DIR}/share/lightsc.sh" - DESTINATION share/lightsd -) +INSTALL(FILES share/lightsc.sh DESTINATION share/lightsd) INSTALL(FILES dist/lightsd.service DESTINATION lib/systemd/system) diff --git a/examples/alert b/examples/alert new file mode 100755 index 0000000..f495552 --- /dev/null +++ b/examples/alert @@ -0,0 +1,5 @@ +#!/bin/sh + +. `lightsd --prefix`/share/lightsd/lightsc.sh + +lightsc set_waveform ${*:-'"*"'} '"SQUARE"' 0 1.0 0.6 3500 500 15 0.5 true diff --git a/share/lightsc.sh.in b/share/lightsc.sh similarity index 78% rename from share/lightsc.sh.in rename to share/lightsc.sh index de2846f..6ebe690 100644 --- a/share/lightsc.sh.in +++ b/share/lightsc.sh @@ -28,26 +28,6 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# Here is an example script that dims bulbs to a warm orange: - -# #!/bin/sh -# -# # Optional (default value: /run/lightsd.cmd): -# COMMAND_PIPE=/foo/bar/lightsd.cmd -# -# . /usr/lib/lightsd/lightsc.sh -# -# lightsc set_light_from_hsbk ${*:-'"*"'} 37.469443 1.0 0.05 3500 600 - -# Here is how you could use it: -# -# - dim all the bulbs: orange -# - dim the bulb named kitchen: orange '"kitchen"' -# - dim the bulb named kitchen and the bulbs tagged bedroom: -# orange '["kitchen", "#bedroom"]' -# -# You can also load this file directly in your shell rc configuration file. -# # NOTE: Keep in mind that arguments must be JSON, you will have to enclose # tags and labels into double quotes '"likethis"'. Also keep in mind # that the pipe is write-only you cannot read any result back. @@ -79,8 +59,8 @@ _lightsc_jq() { fi } -_lightsc_get_pipe() { - local pipe=${LIGHTSD_COMMAND_PIPE:-@LGTD_RUNTIME_DIRECTORY@/pipe} +lightsc_get_pipe() { + local pipe=${LIGHTSD_COMMAND_PIPE:-`lightsd --rundir`/pipe} if [ ! -p $pipe ] ; then echo >&2 "$pipe cannot be found, is lightsd running?" exit 1 @@ -90,11 +70,11 @@ _lightsc_get_pipe() { # Can be used to build batch request: # -# tee $COMMAND_PIPE < Date: Wed, 16 Sep 2015 01:47:12 -0700 Subject: [PATCH 126/181] Fix the LIFX gateway timeout detection Don't look at the difference on the last RTT (latency) but look at the difference between now and the last time a packet was sent. This should also speed up the re-discovery after a wakeup from sleep from example. --- lifx/discovery.c | 6 +++++- lifx/gateway.h | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lifx/discovery.c b/lifx/discovery.c index a956b5d..e24b8e2 100644 --- a/lifx/discovery.c +++ b/lifx/discovery.c @@ -111,7 +111,11 @@ lgtd_lifx_discovery_watchdog_interval_callback(evutil_socket_t socket, // gateways aren't bulbs themselves: struct lgtd_lifx_gateway *gw, *next_gw; LIST_FOREACH_SAFE(gw, &lgtd_lifx_gateways, link, next_gw) { - int gw_lag = lgtd_lifx_gateway_latency(gw); + // The gateway latency is the difference during the last round-trip-time + // (RTT) and has been a PITA to get right (it's off sometimes). Anyway, + // here we are interested in a timeout: how much time elapsed since the + // last update, this is different than the last RTT. + int gw_lag = lgtd_lifx_gateway_msecs_since_last_update(gw); if (gw_lag >= LGTD_LIFX_DISCOVERY_DEVICE_TIMEOUT_MSECS) { lgtd_info( "closing bulb gateway %s that hasn't received traffic for %dms", diff --git a/lifx/gateway.h b/lifx/gateway.h index c0ead01..5337c1c 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -90,6 +90,14 @@ extern struct lgtd_lifx_gateway_list lgtd_lifx_gateways; (bulb_fn)(b, __VA_ARGS__); \ } while (0) +static inline lgtd_time_mono_t +lgtd_lifx_gateway_msecs_since_last_update(const struct lgtd_lifx_gateway *gw) +{ + assert(gw); + + return lgtd_time_monotonic_msecs() - gw->last_pkt_at; +} + struct lgtd_lifx_gateway *lgtd_lifx_gateway_get(const struct sockaddr *, ev_socklen_t); struct lgtd_lifx_gateway *lgtd_lifx_gateway_open(const struct sockaddr *, ev_socklen_t, From 3936c4e9ba1a5c3d5a46bc4a86c3bf0f41c2e8c0 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 16 Sep 2015 01:49:24 -0700 Subject: [PATCH 127/181] Write installation and usage instructions, rehaul README --- README.rst | 85 +------- docs/conf.py | 9 +- docs/developers.rst | 10 + docs/first-steps.rst | 258 +++++++++++++++++++++++ docs/images/mac_os_x_startup_warning.png | Bin 0 -> 86269 bytes docs/index.rst | 27 ++- docs/installation.rst | 105 +++++++++ docs/internal-apis.rst | 23 -- docs/known-issues.rst | 20 ++ docs/protocol.rst | 10 +- 10 files changed, 434 insertions(+), 113 deletions(-) create mode 100644 docs/developers.rst create mode 100644 docs/first-steps.rst create mode 100644 docs/images/mac_os_x_startup_warning.png create mode 100644 docs/installation.rst delete mode 100644 docs/internal-apis.rst create mode 100644 docs/known-issues.rst diff --git a/README.rst b/README.rst index c09df3d..baa24f0 100644 --- a/README.rst +++ b/README.rst @@ -52,10 +52,10 @@ lightsd works and is developed against LIFX firmwares 1.1, 1.5, 2.0 and 2.1. Requirements ------------ -lightsd aims to be highly portable on any slightly POSIX system (win32 support -should be quite easy, but isn't really the focus) and on any kind of hardware -including embedded devices. Hence why lightsd is written in C with reasonable -dependencies: +lightsd aims to be highly portable on any slightly POSIX system (native Windows +support has been kept in mind should be quite easy, but isn't really the focus) +and on any kind of hardware including embedded devices. Hence why lightsd is +written in C with reasonable dependencies: - libevent ≥ 2.0.19 (released May 2012); - CMake ≥ 2.8.11 (released May 2013): only if you want to build lightsd from its @@ -64,81 +64,8 @@ dependencies: lightsd is actively developed and tested from Arch Linux, Debian and Mac OS X; both for 32/64 bits and little/big endian architectures. -Installation ------------- - -TBD. - -.. _brew: http://brew.sh/ - -Build instructions ------------------- - -From a terminal prompt, clone the repository and run those commands: - -:: - - …/lightsd$ mkdir build && cd build - …/lightsd/build$ cmake .. - …/lightsd/build$ make -j5 lightsd - -To start lightsd with the jsonrpc interface listening on localhost port 1234 and -a command pipe named lightsd.cmd: - -:: - - …lightsd/build$ core/lightsd -l ::1:1234 -c lightsd.cmd - -This repository contains a small client that you can use to manipulate your -bulbs through lightsd. The client is written in `Python 3`_, Mac OS X and Linux -usually have it installed by default. From a new terminal prompt, cd to the root -of the repository and run it using: - -:: - - …/lightsd$ examples/lightsc.py - -You can exit the lightsd.py using ^D (ctrl + d). Use ^C to stop lightsd. - -lightsd can daemonize itself using the ``-d`` option: - -:: - - …/lightsd/build$ core/lightsd -d -l ::1:1234 -c lightsd.cmd - -Check how lightsd is running: - -:: - - ps aux | grep lightsd - -.. _Python 3: https://www.python.org/ - -Known issues ------------- - -The White 800 appears to be less reliable than the LIFX Original or Color 650. -The grouping (tagging) code of the LIFX White 800 in particular appears to be -bugged: after a tagging operation the LIFX White 800 keep saying it has no tags. - -Power ON/OFF are the only commands with auto-retry, i.e: lightsd will keep -sending the command to the bulb until its state changes. This is not implemented -(yet) for ``set_light_from_hsbk``, ``set_waveform``, ``set_label``, ``tag`` and -``untag``. - -In general, crappy WiFi network with latency, jitter or packet loss are gonna be -challenging until lightsd has an auto-retry mechanism, there is also room for -optimizations in how lightsd communicates with the bulbs. - -While lightsd appears to be pretty stable, if you want to run lightsd in the -background, I recommend doing so in a process supervisor (e.g: Supervisor_) that -can restart lightsd if it crashes. Otherwise, please feel free to report crashes -to me. - -.. _Supervisor: http://www.supervisord.org/ - -Developpers ------------ +Developers +---------- Feel free to reach out via email or irc (kalessin on freenode, insist if I don't reply). As the project name implies, I'm fairly interested in other smart bulbs. diff --git a/docs/conf.py b/docs/conf.py index 5688dca..f16626c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,9 +52,9 @@ # built documents. # # The short X.Y version. -version = '0.0.1' +version = '1.0.0' # The full version, including alpha/beta/rc tags. -release = '0.0.1' +release = '1.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -242,9 +242,8 @@ # dir menu entry, description, category) texinfo_documents = [ ('index', 'lightsd', 'lightsd Documentation', - 'Louis Opter', 'lightsd', 'One line description of project.', - 'Miscellaneous'), -] + 'Louis Opter', 'lightsd', 'Daemon to control your WiFi smart bulbs.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] diff --git a/docs/developers.rst b/docs/developers.rst new file mode 100644 index 0000000..7073bfa --- /dev/null +++ b/docs/developers.rst @@ -0,0 +1,10 @@ +Developers +========== + +Repository, stars, 2 minutes hate: https://github.com/lopter/lightsd. + +Feel free to reach out via email or irc (kalessin on freenode, insist if I +don't reply). As the project name implies, I'm fairly interested in other smart +bulbs. + +.. vim: set tw=80 spelllang=en spell: diff --git a/docs/first-steps.rst b/docs/first-steps.rst new file mode 100644 index 0000000..b357ff6 --- /dev/null +++ b/docs/first-steps.rst @@ -0,0 +1,258 @@ +First steps +=========== + +Those instructions assume that you have followed the :doc:`installation +instructions `. + +Starting and stopping lightsd +----------------------------- + +lightsd listens for UDP traffic from the bulbs on ``0.0.0.0`` port +``udp/56700``. + +Mac OS X +~~~~~~~~ + +.. note:: + + This warning will be displayed the first time you start lightsd after an + install or upgrade: + + .. image:: /images/mac_os_x_startup_warning.png + :width: 500px + + Click Allow, lightsd uses the network to communicate with your bulbs. + +Start lightsd with: + +:: + + launchctl load ~/Library/LaunchAgents/homebrew.mxcl.lightsd.plist + +Stop lightsd with: + +:: + + launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.lightsd.plist + +Check how lightsd is running with: + +:: + + ps aux | grep lightsd + +Read the logs with: + +:: + + tail -F `brew --prefix`/var/log/lightsd.log + +Try to :ref:`toggle your lights ` and read on some of the examples +bundled with lightsd. + +Linux (systemd) +~~~~~~~~~~~~~~~ + +Start lightsd with: + +:: + + systemctl start lightsd + +Stop lightsd with: + +:: + + systemctl stop lightsd + +Check how lightsd is running with: + +:: + + ps aux | grep lightsd + +Read the logs with: + +:: + + journalctl -x -f _SYSTEMD_UNIT=lightsd.unit + +Try to :ref:`toggle your lights ` and read on some of the examples +bundled with lightsd. + +Manually (other systems) +~~~~~~~~~~~~~~~~~~~~~~~~ + +Assuming you've just built :ref:`lightsd from the sources +`, lightsd will be in the ``core`` directory [#]_. + +The examples are communicating with lightsd through a pipe or an unix socket, +start lightsd with them: + +:: + + core/lightsd -c pipe -s socket + +From another terminal, check how lightsd is running with: + +:: + + ps aux | grep lightsd + +You can stop lightsd with ^C (ctrl+c). + +Checkout the :ref:`examples `. + +.. [#] ``build/core`` if you start from the root of the repository. + +.. _cli: + +Command line options +~~~~~~~~~~~~~~~~~~~~ + +:: + + Usage: lightsd ... + + [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP at + this address (can be repeated). + [-c,--comand-pipe /command/fifo [+]] Open an unidirectional JSON-RPC + command pipe at this location (can be + repeated). + [-s,--socket /unix/socket [+]] Open an Unix socket at this location + (can be repeated). + [-f,--foreground] Stay in the foreground (default). + [-d,--daemonize] Fork in the background. + [-u,--user user] Drop privileges to this user (and the + group of this user if -g is missing). + [-g,--group group] Drop privileges to this group (-g requires + the -u option to be used). + [-t,--no-timestamps] Disable timestamps in logs. + [-h,--help] Display this. + [-V,--version] Display version and build information. + [-v,--verbosity debug|info|warning|error] + + or, + + --prefix Display the install prefix for lightsd. + + or, + + --rundir Display the runtime directory for lightsd. + +.. _toggle: + +Toggle your lights +------------------ + +:: + + `lightsd --prefix`/share/doc/lightsd/examples/toggle + +Or, from the root of the repository: + +:: + + examples/toggle + +.. _examples: + +Using lightsc.sh +---------------- + +`lightsc.sh`_ is a small shell script that wraps a few things around lightsd' +command pipe. Once you've sourced it with: + +:: + + . `lightsd --prefix`/share/lightsd/lightsc.sh + +Or, from the root of the repository: + +:: + + . share/lightsc.sh + +You can use the following things to send commands to your bulbs from your +current shell or shell script: + +.. data:: LIGHTSD_COMMAND_PIPE + + By default lightsc will use `lightsd --rundir`/pipe but you can set that to + your own value. + +.. function:: lightsc method ... + + Call the given :ref:`method ` with the given arguments. + lightsc display the generated JSON that was sent. + +.. function:: lightsc_get_pipe + + Equivalent to ``${LIGHTSD_COMMAND_PIPE:-`lightsd --rundir`/pipe}`` but also + check if lightsd is running. + +.. function:: lightsc_make_request method .. + + Like lightsc but display the generated json instead of sending it out to + lightsd: with this and lightsc_get_pipe you can do batch requests: + +.. note:: + + Keep in mind that arguments must be JSON, you will have to enclose tags and + labels into double quotes '"likethis"'. The command pipe is write-only: you + cannot read any result back. + +Examples: + +Build a batch request manually: + +:: + + tee `lightsc_get_pipe` <` to see everything you can do. + +.. _lightsc.py: https://github.com/lopter/lightsd/blob/master/examples/lightsc.py + +.. vim: set tw=80 spelllang=en spell: diff --git a/docs/images/mac_os_x_startup_warning.png b/docs/images/mac_os_x_startup_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..a76da039f634378f9298bf45222e99c32b4323cf GIT binary patch literal 86269 zcmZsC1yo$g(l8b*1PSi$?l8E!ySqEVh5*6c-QC@TLvSa!1$PVX_LF_PFZuqroWr>@ zeXGm5tGcSXCPZFV>;o(oEC>k52MKXuMGz41S`ZM>TxiI*k)@#rQ4kO~R0|;?c?lsQ z0(nO}QwwVo5D@W@1T`pir6IITEk$S;T0~IF9CiWsgjKGoZ=&L0{vU-1iWeqD-p$hy zQBbvj<*sXjXebLGhw7icC!!i|QLoOqAcM)CxV`zj#r+a4_Oo)Wr)LfA8th?xy;y^{t*r zOwX7BxVJEW`sk2>nScibh;{}o;w~8fzQ21rXEPY17gS9x;ztN?akMRE$~nX>W6H+( z1z|CFp<{56lwm^{CD3T(0khp2B?BBz(B4E3_$Y*P+2EB03mW*|6-2q=;nWW`+^B|I z+xVOLFIuD9=gBmxha8E!L`by=^kUINskdJex4uZJ7Mfeb=`0jmbbE_Cj91&vV%GWf zVK|Cu-VGUSAW{o>WDdkEgq^puDDaOKKZ9@$V9&uZfIO|>B+4=LRwq&j#UpKThE`|@ zhS1lG^|&?rOy+TDaBw-Y@TLxookw8xSp4XUo=m2`PcblRA_~|;hh|K-*pGRt-{+u| ztLKSA$A+xI>CNE8RQQ&66Lm4&*3)n8pDWI2<{7+J0R2oshZ?`un++oY!)1v@~0X_+zc^K2LZiL!#=Bs=(CZlDPiaql)ae;F`{O-0$ z#J%oF0J5|u)UgAAnnG|G|1Gj{_oDn-79M7?SmnKASjHTcl4v=pE_FIXDdg zM2YwNQ888{Jcn33UJp?pk27>(?P%tod!z?DzTCyHfXW2kF@ey0cDcrN9{FiAXk?baAvXVvlH!c#Y7% zG6JmPedI4X1?XIDE8PU#CeTKaFBGR8Q!?$vcA3EMt-~awt)!^AKzbirogUprqK}uM zw>DjMT&O{0MdVW+bOxrt*B6vP_D?&Hu2#Et8sNRRGOS}5l@ri!Yiz zZ2G$-e)1-mpFPc!P`bVXXLxYL(1L>I(6|ah^+@2oa5bVhNbJ4nFaks|5F{`XB6q>i z8s0QQp)nuCF>BwK2zZG_5*9_j7e{wQKM;6`VIMR@5h^KWDhjPje;)8ycJ(27{N)u;#7N{HCcz@lw$em>yTvb2znadMk)em8F?M)cSZ{h3G z&?LRciZGv`1N=XB>vk)2V>abJK%a&P6Zx47J9%3%`y|Vz%W2E}kS!-~{MZ`Y-b?r~ zBwv(?xH-04f<}?9B)UGKKFcZADPl(KK|GPvDz0M4Yg?EpOPimL@RT@9Az4;m3AyJd+vjk*;ICpmyh+frT?&q()x2h3}qkYQvU zvRZO98E%&IOvqA$Qlt__J)^JvCJ}@(v@&00e0EjGrc=Q+;b;*moGN5A$?G2)4wh_} zxKCu3$Q$$+RFX4CrA;a8GB}I#a*(r$rxpvQbC}Dm^0D)@vbCxn<)d=s_% z$?IwHDSz?ts(9f6#{?JiWAUSf+=JuAz{2dutYole@vzOeL@|%IQ?|P5N;W;VO}Eju z@aXY3-!zXks9njQMqzDWmSLu4Z?_CgT}(S4ZA^2PA(D1X?afl=JZR*c{V~lw(KgpM z;A?C*<4H|gMosymZ{L#I^ALz15O@$G2Xv#%GRZOzG9P*LfEw;F?UC-DXD&Co`zwcj_jivd_fB_I_x*Q?_q-R= z*HYJI*VtEIFSAb9u=t^&;nnf&x#XFnan+GIFt!ov8H#z5v2ake(VjSZS%fjBa6$ti z18m_u5ls>M5v<^#Hhea)d+H4K43yiegk_-V;rTOy-%2oP@dKpP;t0 zJ$QvXgi{pKs`9$HTgw~=abz`pa9BBy+d}Wxla!ZRCuJdx{b+=IO9n4k*tktH%N zISlAm+cY~{HnSSbX@*huva^%eic?sK0#Z> zeY%ytz!|hhHNG>^&OPC@S_+)Fc)s|O7ByiuVW*W{r+?s^8Ibwjl~xdql-^RS{Hxpj zW(VnuRB4i8qA5+gqP|9T&iYDilgZc!YZAT2>SNd|eM5j_KzH{twYKW9dPvoF#j(0c zo#j!fok)6aSYKivX`h~Iotoa$?BV>Bp4eP|`GLAsXQLsm1MZiVitC?8W$YBTgKOVg z8J&!J$aV{JWlq^9mOOHL8;u9hhS4Sye@;bD6;?cG&D%UHCaSvB{U~emO6zY_S?0Cg zv+h*gP*C_P^hxYIa)JxUb$WcUHY;zTu2GxT#ASCo%~SJv{CW3IYfSgLz3+3U>Umj9 z8FAUN6^^Cljm5E+R@hvq<>Rf~+8VHHk6$SRxKimZ>Ul0;FYk2LcPi1j20-yBKB?Z? zXd#9aYs4+Ur?uqA+GT}fD`a(HCuMQQeGizMJD&2mLI~q4)pXJvjw-{^`|RszcpnlI zcTXOq`K|)INKx*(am=#y3*hA{;-=&T*UWb!o$tu?=8ALT6Zu4Ird{*8$Hs!lKv+c5 zF+HnxW2?ti=t@E!?hWn-HQY)zXMi)CtIPqwVeEOS(3vV zUagPtd*hd_DXw~JBdzQnUxvWD{c?j~5v1@exJ#d=we!L9kdw-^;41^#KDQODRdrtW)uVgTzoQR=f_+tdvRbCzQWJZ*xqH0Of6`yG z+Bk|~FIy{ZuW&bethAvp+@E|bzwN;f<@{3FB*jMS<5#~P^YVQ}wc zQV{qJWQRESLRVqW>YmQ3r1(f}hn8#)7HJ3|vXHyismXb=!?H^AFR8xto30yi6L+fM*D9-_Y> z0B@gv4bu}5`~~7<#Y3bnBTpb?=V(H}O2I#)qZvITCnqO80~0+H6YU!W?I)nElYtwp?I+^@Ao&N6u*oMQM+h=_g>`tRR=_%v~|_)W?7)8Asf2}u9z4Lu_r1O0z-zfI--H42co za5J%17q+l5vHkQ$gO`(qjr%Y7|9SJ9;vX|Le$QlK>3kwu z7ckrdsQlT+c=FXwRPvoA&^Ect&bw@8xGj|ChWM8RtDj>(pzKZY$fBe(y47tOL;;Db z?@Jg&N_P!Qy?tXT6_g?zy}{F?Umq*m=XtHF+HdCX#m6~;Ujy6cT34&CwX9C-R@>)v zTdl{_IsN2~2iEl`o3)Lh>srG>h`XQ}z)}dlFD+5^+BR(jO8066BI!e5fQov*J&1_p z@nY;27CGE-Z^-na{vH)XTwg0i^lnQ22XGJx>>cjlg*Sm%#i-@qKz}|c2q9pZqIW|BC~9RMjs1r)h8bu$34#&6<9i#`e)i70T9?06 z^&62l5(N#!1=gUWH)>T4W|f;8Bm4ib6D|#Lfi~zUzk4Dd9)gZs_?O7?lF(r9k1+gWWYi4QTnfLu4^@d{lq(jsa z6J}$>IMeJ#j&?Br(yu%;^qHLqVLjO6uO09Ho#B3m# z;Ekndzf(6n#JhamSz`SESE@@~lpv44QQ5acv1CB}XP36?{~*BtLifTOhwc{$^=3grQjS@1{MU^8{mTg9i(2^w}G4k7wdo9vZF|6AB@9D_o;;W-cJ^dwn9rW{YM$XBHpL3 z3NePf|42^21rhFJo%4!_9{VL=TgpYpr{Evv|CZs`g908x`wa@p~ zjqJdp)jynM4Qmzva81Q!mIx1ftNmt0v(VE@2FJX%Unad!0^mr)R*V!PU0SxbyIIY zI{}8zk+HVL5VI`Ulv$rRdwY zea%RWVd+pQfV3Hd9>9&PlaHzeFGC&2)I!TjKLxp*ue6irQ7&|Nb9#H!Nv4P0S8MU{2xU0GOL*9-B4shjhu%$^Fm@77CWeQ3!$q^r>{5Y*T}k z)+l&M{Nkc_=B2FyvK4a4_=k5i;cBb}OY#Tt@Kn=4ohN}U){4>nvi_~w0wc3MT)hTI z3D-q|XJ6Sp1rr9#843=sO%CqIFf)Vm%|4=^4vRD!(gN>l1$<1^u?GomkhAU}xX;)T zy5cF7)<;vA?RT#*Ik<|gJ93;VYne#>Ai05|+&^em!vs&gOY_vpxUwZ(rUctfy6cOVoT^5*Kmk!IKbHdap1thgPM4(-xW@GUnnvRF9f*7Ot5AO!~*b$606%n zg3K3j=(3Iht>%r6$THVzGI7CiVi~eab0WYvUMe&v+|LqUcdp~|q%ey(S%%6R;zr0t zA{U;AwVk#vFHupOj*7MZ%T~Ff>}%$NZu|=*1l=jbA6|cx-aw#;0%O)U4`}2TIa{F0utwYw9aM|x%X_}zzMx`c=x)T9H z+`MciL8NFYiOz9Ar4-5OlM*1SjSA06a9KFY?m8mx@|}dU4~x^lR*>xbk-Qv!>_%cT zyn{TBq8y1h=hRHOPmZDgcp!aW^#?g1fgHskik;$Z9WkVQqj58sQ^s^mg z@ATfTx$^zEJHRFcGEuU*#%73Rs$gdlsGh_O?mOWX)N!&X3Of~qi)Vt53^fjP9NSaq z)6AnBaF_CiWQ7H4_8BbC&v0rq@LFoN+vDz9BP*MM>rQ=9X)g}zFPoFw`5FEH zW{|%+@)90~+INc38j9>bqEvc1lRA8X=577(G@MUo)U0DpqvVkQP<}12DWc&|`Y!ii zoq>Yjd8m}-TSlh$Zi4$VyfSzDV!X0XNQYSoBrXIYKgiUGT#pr#_i=F|lpyhHFfuR8 z1cdrzWSs6^Jki`z*9jiD#puktoH-)`=*>Bk^u)spnl(gwp>%YqIU;dJQpmUQl1MUB zh`dXNm%|MiDvA`g>&k?btBaR~sPmnC=HF9o<-Q<&^rsiTeB?g~&l7J_!GKY;erGVBRYkn%kr z2Zsq1Ez-i{^26BNT2305vptlXA3fD)sJMcU?@9^LWgxXFHe8{-UJVI%Crg~AOIG{H zKTx$q?8MSoi>D?T!9e0;&FibKLSA4oVgX3+|EeW9vAq2~%|B@3 zd&(6&y~(zQ3rR{6div-{-S#ClIajRMu-kZGSZ~!&5^8G;^ztgtC`Ryaa|sFXeoNNk z{wY~}Ic}0YklH2+$8{wLeX8Amgs$@O(B@looq%oQr(6oP0m1RRpnejhX9eW|vI4Jx z!g?elI1-ZrI)fR!sG(YUF_u7a$CjumR~0qwAJPEvEFF9m=XjRco`5ik!ODUCi^;;N z*xVhSXch2rZGxhsyf)eCli+b`uGm!xXH!!8q<$(A*D1NA`kh@DK<1ud?Y+ssReYKI zaU?x(MPzlH7SP6Ybhtm~;j|wrh%7@XOKzhRb3Yw>5U*3k0;)uoI;_7;O2gfFf*nyZ z8jzufUFx!#Z`0}^oSgK>34-Xu9ZC2WMqp`I)z~R}4R#i?sEakXcVXK77Y$gqtsJqla3A6MkKLF`}ZumG@vtMX#W#3%*zl!VF(%(4xKL zV)jXgrVw(@yizv6gzjE_oTzSTgH@wMZ9$2SZ3sXyw6n6O#|{}H>m+JBL^AFvX51LulVqjVMu*puw;14g&vLAcNp7?3e3AY~ zYB(iGpbxiEYOc&nnIBH*F+d-y#@X6W3(Lb|u{@&A+vTlbrp@j2%(yMX^?G>d7^}84 zs+Kim2R^n{=Q9vcrt>p52ibjfzKV!`P>bLQmZlDZQW`y>6>IeSeg_>z8|KlO3Z6Fq zjj^}M=GAhE%w5UdNvudq8D8!8E_xg6`-1efMfiQ9cWtTgajzXLv8;EMC07%%<+!6Q z$%0Pq3@{t62ZiA_^Fn4|zmq%^b})y2h*ZG427IDE2m~5IeCiRcJ7fVMlAw9+pVWk{v?d3vV$VP@?D73D}-I@B(g)iOx;m z@D1w9r?drw1D|l-=SkuB+-)W*EiYc9<<`<}?JMl$4IjAV4Kw2Z!8#e3!vJDRIDYZ2 zLqDQ|2keDVT|wlzIb7-PrzwhUDU>W$qwc7tTE=_ufgfzLi<>d;@8$Vek0n{tF8SGx z)~7`Qm3Zi#ZR&n>9~-1);-0hHXyVww+yhJE?wxgqj^cxK+@=s#IuKSS`OG=#E=!y0 zZK#kv8s&QTM)IiO#NPwQ=8IY4G-DH9(uR3#ER7|R>ngJHB<2_ezc?6keEUzX`d4%i z;QhpdFIh#j6wYEwsDl|PvJLw!U2R3aGrK0eEE95({}t>`#~nODJ%H*~}@lrLq*gr;YU0YOq-!~O|ztMYh$tLf2ChozL{ z$Br9$*5ERpE*e5<#zoP8Bz)QGNEnHXVG|3ih(erm347X+X7T4b$LDE8*!!I{R~ra8 zmqRVXEpc?HLee9r!=e{!15gLKe+A0wn6*{?BJQgMiXZdeGI}ce#q8J!yh)@Jcyg>L z_taxIwv}_4-JKNJ`1%oqfS-dS5D604f+C1@D7D6efp$e=8B8x zvhnhOBYss#?M1`b3Oq`=4|G|PO#m=wx>lp$as48{r$oTJsO@32XB6gh~zp}zVd|__xsk=EU%8c$!BQ^uZB#oHh|9Xq0{)E zEJsU0n2#!)86|k18;@6i@AoU!Mdlq>r{$NO-N`X9TBE)WMZUMv z(e2ZXnO-#3-4q43!&|0o?=k62yteG=qO(yzSAxrfvI*adIqb&L4zPqZfcg)>T z{>NYmjU?Gw+5olF1v<>(V#mzxCq&=F@_Cug!F;hu_^~g1ElP5z*d^hAlmvdcIbDca zTz1MW7PIi(hRwnSrrxTk_oR7x8pha)F}i$xdP77L9n|b0K35QflDHS;{rIto=Nezj z_Lx<*-F<7oH85FBVMWL;6pVAvptDTOswX1TMU&b#!D3AW63Fm&HeuS&qV-%JDB1)H zGno9c@|RBwHwJeYH%ii?qPhc;P3SP+J#woCsr+eG^6AjMaO2zw%Ka9Z=NaSWXNMwQ z_id3H;7>@-Fcj!7!;WvxtMz93yewHV$=%l+fUv9Mw!8x`Ge%u28=kjBRpV67&!)xC z6ca^-oG<}wA;Q-2l2>PI<5m(f0x0g?9HHcoz*mb$;mHGdm$|(x1rJImaz+_3P+p0p z`p!f;L7cG-cIdn;*tKYMBpKtZJX^f}F#E1S0scs}z?OcN%=0>2WwK{wP6$;nRKQ#d(Xswvk93I&;{nFsKiQz=5ET zlEU!D8QIi5!vhIX-BlOG2mI(Jej~DoD1*5~YqI84+>bg=bnPo>o@&_!^Y}S-1&(I37P2;n%8Znbt zxsTT#(QWZL%oi~ebbLa_*MQ-d&F`o{H5%h(#jzwq086y9&qrjRrdo$PXI-Atqf%@8 zb|2K1Xq6F@l&YysN_&78PSUw=f+HraMWlDu%xM#tQWEP>}kgjiDEh4%8SjeX`H zJc6S7Xh-mDbC8Gf4wXG1vEv$(7fUzzd5mK)NM+^YHK&wXistutTu|TU#VM}r5v*o2 z&1z)GjV)VfsBzk&0t4tBwey!~yPfn2(mp^tm~5Rvohzm+`CPXZMHuHjX7GT)3Jg<_ zq&V@|#0Y2AO=Efv%NWOo7zK;TW>ZJtzfHuI3=D+=%Yonp@C z*v4|4vWMY!_ZZ5ekUjQncc|@pc#cPg)3`z;KqMHDyXYvh$!yfpp>>Yiem`}RP5NlpZ!)iSoGUzwIcRpK z!n`?*#cTiw7wi$wccEs>3PPe|`=eR~3VjzrxjChREMSDn`EoUFcLam;TGB{iGleRu zG*TefqMJgli;p)e#|RH!+A6=5C*D9hJ-k>${0v`1EG)xa3e-xvVs?W9Q!RSz`6L+VHoh!N7!0_Ra8^SBzPNC@TN_IeS2bFiVZG4YN7&!&sgS4;P%CrqN&etx#;HyCZ@RRUhBxSb2@ z?lD#&EB4ABdTcGO&%+Xo=f>Y1bkH#mgWad?@ABBhwI9%WyWm7i& zgyNngQwH!0q-)WCq@2+}^1X-Wa0&ixNruGh?7Iw;q!HtVjKymh{ZY1DN0=ib75-OZ zJM$Lqx;qILLq?^@7yB>3{-j2{Au}3w1A-GtzO&=E<`n93)v0`RseF1oF9~_4_i2)= z29sur&Uma<`58?CD1Ri~XLSxC(x~1Qu`Aq2RR?;oD5K^aJ%4XK2T_0Kkmyt|~9HpAF#!xm>z~W>G z%K%gy&7yEiPu@PFL|nK};nK89v==~{%_%kWD~Z&po*Dr*C&r9@Ffn=pd$=6^{AFs` z0gW@*6Y>Wdu4osR1PO^D;>(L;p(2fSij2WGG?(!bX@zxd<}xM#nT_kZ4Y7F@q{Pe~ z{;=|iyip*EVUCL6>Wt_&3fQQuJZZB( z?s5s>K(VZn`$bE&QScJ7rg)7oI9(J6lv>Qqb#K{iS8wR4@#Ue0Mjreh(m(hiBrX=m z*Lpdh<-O-@=PSM|v#>CRNEb8F8u*^gROrLtS?DMbHsw8t>~iilJS$sXSfhId*1X4$ z7X@!fq;bE8qzIk*p`bU;NvBs~o8?iIk4F^@I`Ht(QEK^cF^RLu`zi7D-KV7j0k)EG zgx?0J4-xp$hr@{Cg8I;nRG2;j_xd%kd`TT@Yhk*|*6RumhiyrOr@w}K@|< zH3Sro`n@}7G)ecKD%3cPFA{-WnKW9CoH~%T z#H;Z>mLr6J4jE#%$x`7e=-gPR@`hw_{UZF@`HGt5Yp}{XgbWq_hRHs|Op3JCHLjaN z(DVjQZtQoPrqE+SKb?RvNh~+8lU!x>=Fo@%UGFu#Rc*v#uZv(qtqZXYk#=}A_-#cW zmSM9$)&B#UV+)FXaX6))fGjmWf*F!<6$e2j-J$j>XuPFUb5-d}g?2F!pNx|Nhw$s& z0{Lr(L#O?85M#EqVfLYeWx$~mc5V3CoQbxLek;*6mm@@>T*@|O!KZ$yFdcnJf{y|8 z;R99a`;|=A9PN|f+MhzNWY#Aq!w zylgdh7yTB$G(O`+mRcG3$Gs5#CRG3N9gARpf68i5%{ zp1yNYRi%KMP5wQ@$zBq^PAK+>4PS&wDlH%0TMd}aAbX0T;>hWODof140!j28YoneD zbIg#k8je;b0oyhaan@T-0ACt`KO;fHm+`^F67jIchX`J#q3AH)qSKMDB|fhXA<9T= zXLbsYX7&QQsbz5}_mQuGJFi}Dg}WtK?ga?T>YK|9msjm5!)*5Zj#}TJfC0tGUmlxr zz%sLt5O{Bf4jK6Y^<7Sfgs#aCQVD@Q3IXfrHk!*jv&W5`JdjClCll=h$~aLtGXy(t zosa0qx`3z*^uyVDTl!+@J=)E-%Sdb9Lqs%xQ>C9VePJ?Ah%NiwSW#|g_6OA#sy=t~ z%MP@)*{yV@B?wXw8OLQ0)||i#Cu1t;^(4p2PZ*t12%^h$eFxz|?4zcd%K6+ElFfQ4 z@6`g<;%463zaq<*Ua+#nqDUy*?(fMob{0*PKE6c=cDy^%rvG&^KHVY&;|m1!XaYuY z*|qY0Sl|bW1Hzg$gZ=e;>+8gO@oClvEOGEA+=aDDh(}mo%K`#f&P{Rd9_~p`7Ip-j zuc?V!tfY(g9VczHBJsw^ao-0;ZAZ^1BzYyRbGi_ef$s%LVCb>!w9xEC;c_Lqh;*7? zw;i)k?BPf)TO$N};MjHK)g-AGP69Be%45nEGp*dpSC6w@VT4FNJCKInz z!!4Lw8XBMuReje(S^@tfJ>t(HL^QER9!6#>gr!y!ILH1}${k-=BZt%8(v~~t5g@w8tWk?jtd5Cfm&RiUG8QYs+1+!pC2GMBT+Bw&+Y zM!V~A%C5fyYL?Ht+*z&8s5N}ZI z*Ufn%G|G~{hr?vTED~cXh%h1^5;ndF@3YpzYpIxsq8qz!Xu#JQ9;y@TT=|LDf+9ao zhfT3JOq>-LHJya=+!r5?Iw*S5WZ)sN<1nne%9n9n)aTFjIQ+Z9_{W(}2o~c?tLoQJ zpk?4;up`hSW70flR{M?RPq*?<8s*b1@YcP;g+d4QuE0Gd6Cd_<0rIyDzfak&Gvc?- zl?W`B!1H$rW}0kIh_z6^N&eAvAr!)&&(9VrB;Z3a6qN9|$S`!k!gE>_cJQyzeNp`8 z1ZiOejM71R%^|kAs9%`vKm5bn9}Oq^5V=H%Sc9Sp@iO)YMYMEaz0yv?2_eI3SYy2nzbpTLkdKDGgRG4u+)uY)z!al{w~MaQ?*F%C zlE;EJMjeC{h?W2;9oYw3$Q2y{hX($S@Bc>;K2t(nzj%eqJ4;@_ht|EozpuPl7xEiY_&21bKgo-xuiGS&^7}Rtm^^;_ zk3Zz1(1qakv2Ij3e@~M)m3LusL_YGbpMEM3#(|3Q7*N7mRTK zbq555c~eJ^&B=6nGf}NX(Q9?8|DQbdAI`I#5C<&?(h$_8zg2`JA}p83{f@!^Q4^?W z_8DUEn@-_uuxjH$GSl*rGJp zTSzWDed#58cqP5##FPGKy!qd*F$!J?QB-$%_5BxAy|}u8f1&zzDU`$f)^?Mxmx8Yo zFx+dDTf!8t3Vf@c4E+0Mni3{pi5^pFV1@y_9o7k*cPaK0Tgl9%m%aPl zK=ubd44)=w2*_F?R2uk%jHY!4zJJpo{|4ckM|fyXlUc_UQQe!o^zvfN_*W^eeznsv z!{EswSR=n(^nbS)LoHG;?qEL*@79?j^Zt_o zaM1~nm4Eqn@9Cdvd9#=+#5Y(|SWu|66rusge)?L;PENyb$AWpTKy<*&vU zT?oZzDE;|UcTmyKBg(_g=pJBXUKdQo6_v_E>822{@ID>jpL5D?{r9KL#zCYXM#mUR zn8^A3-l@%RI27ZzxV*sg*4km2t!o!|0s+eJgO%*{Pw6-u1y>cT>kIJ;%2U+O-`i{-G$e2D0uiSx{i-zDg(Hc1=+VSGe6#y-H}duEriYzw2;n zn9dGY-tuEv^!bV*B4Op6`mcPe- zwGT^9d!Rze69Mw44EW9INP~EwMg(c?nqMI?iY$LOm z?4CHyJb{g!Ti3UDXxAD^KDU)02rOr_|MZCt&L=*|pv_8P;BF*F0LGY}J{vN427%{R zxIzecsORRtVzbsyFb5w+7T#dW91E3BkjRb4$Hv2g^OJ4tB{jmuZqQZg!1K#fNxg_1 z?g=NE>#3ujl!Zm7vcb&q1Xm$5ShFC3>Wn-Es720QOqnPKrMq!!1~>3y><|eY92`Fo z$eI56XS2M50^yW!9@A!y&~P|PxMXf7Zka)U(lQ7->E$O=yp~bK2i+Ea!7MPCx80;_m{}$~lfsDxj1wdoVMj;a3sicnt z!h{WZqaPlKY%qlc9@HRJ5|YscG&(D-V?^x9 zwjLMMJw3mX7AlpX z5^(8d9lxpwy#Bg=J)eLTuO@h`awhp41W0i!8qp|iv0T&{FHMOHxwTeH{N|cgPTDu= zL4C}2T_%*Xg3XNp?!QIJaj514cMza@3Rm{cFx>k+=tR2~BraX0x3spGA}SfOD&0!R zF!}vG_Z9T_DFax3xpWTDy{s>w+ViR3cVaXH@WyU`|s{kR#E0AeT@lQb&FcK&H^+nm_w0H{59`8zy){LW2EjfQtQM{!zAVEc?Fr&25gI1oi}MaY;qLhy%=z znZ2*y8`k>mq)D}b&k+oHtIy-5#cH)UvNvAkGiY&k_&%=gkNSeJ*QxeS4&*ssix5t- z{5y;)!8k6)xxTxeLx>ZNv$Ad{ZBP{P#`YSZcwVO12jq21rfUpg(L%6}^fWQxq zWP&8q2EaTH)x)ix&(@oLM_c%ojZh=L^qikts@j$&i9OWjkA2r>^~iBPe6&%BdEE~( zx{xXH!^w0)s`|CU)rStMMYgK*oa_h5;4*%_rdw@-n+(ZA96XPYXHoa8LI#nzLFeg9 zXH}O~qYeukvQ?FL$rneSnTJ8~1L0Fbx2-O0`=&je$wDLf#@Y++;%=FP%GSbhFSGg0 z$XNu79r-U{F^y3u1=QTPh-EBspVL2}+Lu+GxLRpCr`g={LiXc(`NuCJPw*ZXXO}%^V|-6o9h{$I+Ar|b;^ODO?e;(! ztVL<9&n3)7mDRoT&Y}{Ss9vQ=o3zu*rq_P^g#+_p2A zT&n13#7dq2mRFYMASS^sW%!DgT-dR<=zR+0J@J~D?g5`VEh@_f zVp3gj3Y)Cz0GmqY5fYW}&CVOv)%E*sl-li|vrmF9c@PX};JkO{%XYWn>B?V$rkwA7D|qyL z+r0$8mKIjs;IvYArhh4YZci-BC;j%@YU#`x;q&<)UzyqmfJ=yss~krRG#ZPQ3)18k zm}f0qV3koUWt;^(PmIj(6!PjV__$xi-_vN9t@h-PL#j7gbJtJxD_xlX2>x~rmK6j( zh;p48;b|w{{qrVheQxK_QxTBBOK-CbiLlDJa;aZlbtG{&o=)0tRgeRu3|A{no--|= zDx)%3+tNyZtZLZAOF~QPM4-2Be0OB)Eho}EXE>kc0o}R#-Pw z%?`s>=x2S`EG>2lX51;H2=J`xqck&-<+iOu`X7`kOM9{lkD{GIQrB&upVQM5_B}RK z($N#dU~im%Mq?iRctUNOfj4yJYlPknd{;v<+YV}*u>_`L(PzKZjtx_O{lQS;8)Wk1 z$pF8*F0U#w8QMvcc91l6(8VNTWnY%j&WV=*4W{EGwTx@S8aOkUulnYZxemnq>TG0B z4~H0Rt;PF>&e=)k@bM2F0yMw|_l>c$F6JO@_`>CBcj&1|fn%tHJwiI&F4qmaj!i-i z{guQOlYVX3kCJPIXcMMyL9qFw=$-Szf-jW zST~J_TZC`kuY+F69}zA{62@1pIMP0+YvL08Kr-qiNG$*Qb<}6zW|%sZLdes808 ze87Q%#egqLdxl`U9X`3QZi}Jd#oIPwjW?V^l?_n zb~(aTNXUkBO0nA0rj9L5y4?Bu!i@iA;X@6o?ejPv7ifwu)nz^>x)|_8rdrZ(uNi&K zN%{8Y=OJM1DI%9k7Z+1km(Osgjs1i??c*UF<5tX}=QoYU-N8B5Z>bS!1|x-R<~PcC z_=pq&^&Q%DC#JZ;`ojvAeJ-taML#f%7lZ!jJ48ff- z+?%8+h^k03Hr-ac{H$a19}-eX88SP(jfM-$g-YS2i+3rOMh8Bsw7j_xQjG&>`%+C7 zrgZ-0pADaOFO(BYA%{58n%!M9%c`AT=e*gVb=!lQeEm$D4U)}PBi@=CXW8OvMO>L% zv^5X+`;1!Vgm<<@kW0~`XD!CW(KyMq42$dIf)d3^ma4A<{I#D)wv(V?1iK=^La8qw zcOUwoNn@vA={apTQs)^{PrWtQNHDv+iM9Aa1 zq5}Vzy|}RWjjm{Y20*m>#PiqC|JIfdMUtiuZW_#!6y48wO5Tl0I?p@`>|wMA<3xY{R|HHm>kvYTvf@Z~m2O z52RVBE%g_gy7FL9!$7vxKhyMvk?ff%tBQD|bmhk;q-{Mz-I{4sy2J37kHI}2ajk%g z_=Ktpbw62c>^ouX(&Q&23!HiRdtR$%*qXHJk3o4>Pj4cGsH@kKaQE8!Ap$yynz3zH z*KZ~LZ)z*|OM|#FY9eLT%x zbdns^$-s0FF)aTC<<*75+6mRJ4>&c$1VtJ zNQYKaW;x(p6}2I%TRi#U@IU?I%@@Eq)mul_{W=(RA=J^rOFv$QxL&zqI9v+uy_Uz? zuY-2~!t}yPWA1O($PWg-n(d-L`#t4oF*S>Bi@0vxXS1W&Ww^n~v-6&Y zi&zU>IeLihCVtpz69=5Ggf+Xo)Hy7zx{7vtsz5gG0*L(OSZc#vt(U+ekTB)@E5^MV zem(AGxDRA<>No!_%Ww1(!7qzZ_Pv>Rh2Ij9r?A;-B0h?AXI~l&JSGXu!a3vLg{IJL zN;29Ya6kcfr;WcZv(tdNuU@B|e0^bZC_FOr(G(*ls`vH>KXZhL=+TwmTSEfPwpk%u z5quYa>#6o{h9Q29wmiFB{};pL@K5*LVE+z})m<&;zJ+ZkxCbkX3tZ>bGac_(d*b_5 zWa#I5neectSYJSZwOGqc@VE$QI^a5jLJ^f=JQI=w7I z^UM!(a_xFFievVBz3d*?>mbRl%Z2x&lCibqZD_gj&HA^dudO>Y5KO@3$tCFu-qt<_ zM&Nd<7%E!Umf-8!u0c=pFR;a2U6IY#a_cp==M^Bo5(_7-f0^WwKDBvU(f|G>K7HX9KoNj}W z9vHD}f7jO=_?ogsy>d%|HCWZkvGYe;M4{8Y_>g>g{-sAi14ax-0 zWb;+*Z~Uovx@VY`)l+7jj^}VURO>GQSQ|R)&&NqB>w%F@Q>qI!D9lcS=YiP5-;bN{ z>~a(-I^wVW|9AmBmi7(4>IDM4ANz83I}JwTbZ`DJoX!fv>#aRHwJk*&0LUYmgZWeFgiDE+${p!A^CQS3LEMlA}#cyhrC4XT^v>N0J z0>9g=pRDHnm@A*>Wki({jNKS^4unqVh2@6B+=%QJi!-bw=AwM_xvsrHZ=Fn@iSB~r zb*U*QtR#W+iuLH;>tJ60MiReMV^fA6n)M|gsxDx5r(&jk-f6R>s4Rf7o;zA{&n`!N z>j&&K|1&zF0Fc_H_XXYNaz?CBpoOyic3!zTV}ZU6-cY%072`3cI{AA}(~BV+9k5{d zP93Gq(a-F$5RyYl=$G}$@U!io$^on6>+RrIv&AQm1nT1sCI>r0v-wW9+9E(HiuZeB*my`Le3#7YLS#cxUh2mE1FHrp3 zP{10{0_Oi3hDXY!4+%TeEsrCU!ZXH{Ug|(tu&VLGcfE1{D?xH1P&7g6x??U(jz)io zAG)D7B86PTT8)cne4O8HI*Da}3pBkagyw&dkm53TBo7pCp^bQWC9c`Wr3r%#ObS34 z)Ezxc)5-%#c0DvUGENxrwhlPFRylPh;~G0t*>%3~@f3X% z429GG`I}F04cwYFy{VdJXA$SXV3_{GXa4PXu)JJODB`%pkY1)ml}jFN{ohkX1bv-F zu$u9^6aA0Z>)rJted%fH_boIG)1z3@*yFNBS_LeImA$u4f; zY{~fGAH;VS5YI-%qft!<-K}97zY6InebO}%ke~m?1<`l!&fC~5h9F#~CzGpP81eK47=K!2 z6w|8xYE%*PcTt+d4vZkDI*5NqG&kQK;qR80rV4Jmoe$m9)jU%+$%@|<-|1sn0|dW} ze_xX5@X6-FAEc{xMhdpkQhB+B$yhG2yUMweHT=eaTQ zRHoh9XAKX9-viFARt6V&MPqQL(@TnNPS({4C<#TXkgj@g<6^}^rHku5#EEnEuDvcl zV0Lx7(xeN&kUZ#M;)kZ}Z}M(oyvhA{OQ+Z7Sm<1*MIoA%z}E8l)`4 z$8u2ZUT>Nj^k?nTg}mA7=ic}*3NgY>)8xzrsV4NR*K2-j8H`tQ?^ch%5c4~T z!_E!c@1Tb%KR6Z9o2g9TLxVzXfQO{qZ7$JpIuIqR`J~IKD;_+)nxp{TF8%b%f=JgM zpBQ>~gcq~{1=?_qf9gCZ0QIrM+&D9B3I=1_lD8mp@2!1|pU(u_Ht*j)+&mF)@X$=5 zHIW=05|`}~j=t|6G1XNr<+{5JGdD4_7h9sc=97Q*qIZC*xxB$>>E%0<>*?q9mIk@wYfat0$K#iXLrgJ+AGrqq;4l+ z&dS-LbYJ~WVhF=b?m$!l@uXGYjb=HBQ(77a9#iq6;p`7w!gNiNA|aL3TJaZPpJWWbR7LdTe^jIy{C9lo*@zT`rIW-T|-c9I2@Oq-*j)UStZ!B621`itQ8U) z23lSC@u+}!{-8r;*S6u>&Dg0EVEf_Z3eMQ`<)C+3umD%9ne#a3uaRJKHIO+uIgLJ% z)`ItmU*v2=sTO1^!q+pegGb#7!(!J;RhDo<7!1$Mb>!s=ye~h4HXEbds1jlPN^CLH zrq`a&m(FvNwgvp1bp~J&;r=5+AiJgfNkQ0W0;>}@1nUn^Kb&ZU$S599%yjn!eLdCg zHu(1oaavAy7_HC;tMNw<3Zxl^xnz9S$2^w)(ls@gH!@4ESBzt4=75lCso7aw$|&k+ zz-IwxIMzd0d3y^E+Ruxkn~Axus}w(8d#>;Dr2$g0 z(#ipUF5lZaGznC#`*6!dV~iFCYQ1n(SV_q4j~74+7@~*4vhL9X<`vL z`PkMGxpk#xC7@*9n@SQ-1on6Q!qnWkA^NK7-t;n{qz9%RJ~x%GDST(a*>&B4K74Nn zd3oX8-tL2e0)xp$zeKh3Ug7wAy;&}{yoD;1e;Gn(4;Q!399_}Yn}{LsH?_hf;(C;! zch@()e@ow-i>jL%7ed%k&YQGpcV%8P8XT~kY3U|-xy6?TeiaFn&Hapao0pMe@YK4N zff#S%V7rZW&I}nL`DONches!*9J8m5l#E41RgS}!YuwsFm>c|ob5!^It4lxPtDKq| zQi~^Y9b>l5ckPoiNl=c-wA@p!pFOV|eX;(~MfgvTcx6Y14HqQFr@DWc-Z}L*M}Jvg zYdd!cnyj1Ug)OT+C(l4((smAYnJXP{oljsq7p=qVD!`T(-2r8q88!OH`d#CSqV3X)9?M!_^r zD7|6q85ic}s@~!^eqdYPP{dPu&|aadB!I5&q5ni(e9g zP63AG-vWrBBmX+2UezjSWo^S*9eHb>GlC_?;0!d9p@S+Wwf(8VDx!GcFN_U#b$zy) zTN_=trrYUji=m8Zx-03;c+PY^UZRgRXCi3*pXVRz%c7{c$eJaQ$Zft_OdMP+!}i*} zNCaz}RdkRzel16rLq2#IW#{oVC@{yPk)SGcGhhmYOOhMDiR zys}$XHqTW_e)eq_^Lu1c9ceCuiL1*4pMCCC<~rE{Za+Vt$7QUsf_(Z){08yw9ImI~ zaJ{;nZ`RW=dNtrE5}?3&bAHES7@T!^2}J*BI6);7vK8~^ppe>*A>xGJ4g^Rm(zr2r z)jb&d)TQ)LNPmpwr?5@s$ygjB3#MX3kIf6>Zc^tCRG@e%MYSlLBTJr(1p)bz|J5 zE&FM`rdCkBVoe{%(eJkO+TG)yUj-l$ObRo8k8kbWIK|>n22wRSQU7#O*8^ztLne4W z_&jUO3zX4^%j|ROTmv&A#o~*s$ZUl_)MVNr9&PliHS!*{K@>i>jIW%%4b)EZ$D_x9ht}D%Ud>qT#@^JJHMZ~@>*>5Kn+Xl!+ zB}qI<%<(d>1yz1O9pjaKQhPdm5{CG5i`J_`U;Ww<)GQhB2$wr(`Wj7_keISaxlS^% zw%w6CxebIsCoQgV+HAA23=A^DHiJ=RJA&)J@xblbFkeM@tV!NA!1vefo}da=J~k1w zF`k5ye6lIDH2uX(nsae=Q{EgBNLcU8*b=;z`>7^@)+DE%-2h>;J>G_SRzE^<$c6=N zizez9`CnzVAI6F`<%`U%ZG?J8qxD#>j{`dmWjE1)r4dE*scf{zQ)m8z=xj1EVS&b9 z`5X6hnehWZvZ-wJpa~q4@`JnO2Z`q2&Cltn{{@$!TnEN7Wcnt5<6zc>Sa%$ZIm-C( z@(H~R8fT~jHgaqub3aeTsrF^R=7s^L?I5>fz0UCs7fUZ^ntE1WbA+0?O#9;vw6l2A zE3I_`38pUsusFVv0S4u*WnutHs>s}&HHrAy_s4c!3z86CAWs18Xuegd;aYX$6|%?S zD%W;6!ar-S{!-O7YJQe$nBL)GV=R_x6DU8o0iyH`my8y~P9Jps`fE*%S;}Yp?0>4j z{N*A&vV|#cuv`7zg_455_P}iH@u6+W`>WtV?_8xf+4CkPqnc8*m zo4|><%dFGKqmxOokMFcqjrZG0-=t7xO-@@3F@)6frt=E z?;HBN=%Q3Q2uv?r{!idMk`~po~;BAU?C>Wb|yQ&QXY@?)WwTZ8T2VdR#m9w}9I$Y0h z>X5huL$k(ruZN@f?3ISU6LsY%Q!m>jd;fd@n(@4iH`R}WV8C<-w*y?Y-THdBxF_C8 zn<|Nyh_;~4eO~nEGymOOl$u_7c`@uUPYW|kb)UK>+xWja?a)^lK(dMDU)<+?@{1zx z-5?;!0$DTla%H#VQ%^9vvcE(?e)n11V^%kwu&DU~zdz!L_@LADyed4nm$cMk0 z6#!#}GfPSY1>gz4`S_RDfoOf6ZY95ua(it+wjCtmC%ht>?)&;LW3YP0bwFYoEVcyC zux)ZDx@Jf}g8FAcf+0yOb@k4JAqXsinfaU%l`iz(y2axLU}!A2z)e}=$g0lp=3L;{ z_h5ZIoCtL|1fZ^ICU((%a5|jI^#uxIhrX7_LwDagr)^s*)VmKCEeX@^S6C;@u6Gr# zQ`)?k%yWeV{VYni_;xrujvnNGd^gJIh%Ri}lE$`N+(~61Q&Wq?j?)vv0!W?UOTtx_ z<=6xJ@Ict_{xUMy#!ez)^+u&&eK7&|)z@eE>b?~iy)?2c_{ z`ACye&#>>V%fVwx(81@}NZ-!BCd+dWkZZgijDB`Bq7Z&RXdM78P=c-ZWToQT9Lv2^ z&KQX1-(@wUR4#XxcqilsjWo4_o)TCc2%!bNz{e{;#W0n?WCapfFg4-{xaBTPWIFNg z75iWf0u*)m;GA5eFIPWcWjcMCI5-xB%kBcb5CTga&XH!nX2ErR^5QZBy)ki48<$RV znETbXVlQF8$_cq|rCLQ@ik&V9d#vqccXqjv4^OxuFq)HCi4Iq3BMn-+8sV+VRxDKa zg}8%cc%;A5{DF5_QwOESwfZo{10d6267D3UqNxgjuID3wdaB?%OcE-=MqEnx@Y5?I zab(N5WC$(=x33$TNIQp<_Q~l}F}?ZydguJ%4{mwsPT%80Eomy)^If*3CxVfr|5rNl zUTWw>`%|LcJ3REJH$I!hE7>>ZE!yh~F!XiceItQ%;<8Pd2;i=*ZF>{YVC5wTN4Ni> ze8ZD~Umqk3uOr_RlefMac}oqTC=kLLRDDyPPImjSA*NxO2oaN38|UMHVpn%FVzHzC zGXbBfxY-x*a7wawIDPxeW(?i*R*&~jZxJGoYrrB z;#17qvY93MnWa(O$-QF_=qY&pF`BzrpMxIq-vqnnKI%7$mjnS6glM%Nu)=+r^09iW zz#P5~L}xZ|KUkGT?m_1FO@;oAoGpevG`ld{I>9j%LL&bEa)VJsbx6g&&liN(nWTUi zg4gK~&s#5WS3cEU7GA>vJ|QT$-Ekr^&J*xU4pj#%B8y(`sDxBYBz~*JXj<#9O)kk_ z+I5Vupm?UXduvA8d7}pIRBLR>2v9kAHXyb=JM$wOQ01jNY~mQ6&P@y_x>sJbA=Cv- z`#BQ(SRP@Ti*F~w1Tyd#q0y3Xqh;PWlO5~%FdI35%jxiZe?87B$IPxRS{r>7sW~%xYrVRU{Xe?;1VKnC z-=?xcZLRFyQnP=;uw$gD;OfWDe3(N~`-Ite?|8{M9}q{?U*eV-R5&6Xcv}X6 zU7wh@^$ZR)PXv+D%_=U1LgIhicnPp2AX8vcQcR*TElN!IviUPYzEltqNV$yax0Yw8 z!C)kUG~m*l7s&`gHTg;4(=HVGVJn?6i>RtN8>7hQJNaMKLw58|sw= zJ618mvm+E0u1UsFD@JCcb)CoiBpJ$B>8E_TnNAl7Jv24KPTkrqDeD=H-!JnjmPJ1f z{=f9}QN)3Up1fo@-z=G3Fn!OF6rohA>3E8fa)0?m=B?uu%kL0w$1+6KAmxrx#jt$O zE(S|tdx}5zuEibOV;(i#uMKo&PLC&H%7PCry6LQ=^myd)>p6CR>bRU|^*F0fx5aXy z$ACGdEb&;OY5X;jvHQf(_VcgY+uciA{YCQ4Zy#Aobh-Zo5&Xdu$^XzM|E-bFKS3Lt zh^fYZ!UTv=bb&9Vau7%p@XwanPSFXDzBD62#hZVZcsqeZl$kcDkKn_Z=);<(qQTt$ z;9{gyeA)CgiF}!9GmVJi6EOM2OI$oJCXYFpcrgOsRa(xlqxV-8f1 z;Fw@@+h0`CS`EA`XJAC#s&Tmmg*qs~UJ+^uJ3nCZl#RAm*I~Axtqo5d6F! z34Nxqd-)iGRK4Vvywp=I%nQ)PR9Qw>*UrCdiF%t~=4;HJ|7townEe~BY`@=XninVx zjE$Qwn}*F}m~q!cQSAHwfY|?qAPFf_^aa`|a_jr$q%~DG^Ml^*r$-GEw)BSslH91} zh@ejV(eIm^aUf)m1@9ukrK8E`)%*yB7ISdZ<}R4d?s%u>5LrmCq5R{Hzl*4Ak-)XP zDo-^AZ$9tm?O**3PY|g(sv+%y=k_ZEd7Ju5K>Xj(j6xTz1F3eGZJPQt-AR%B^2}>J zkeA?v&l%cWWmvlEsXgbThDY;-89D~oPUIr`n#6G&ll2oRSsyO=P~Cu$QE+`XqE?!v%va7rSTvwHp8cuzGPYM~dZn?3cmH_Aw5|2*D#?Y(k04&4ibn@IV|dEerzb z76&iXZ`0);NCI?w&r`tAgB1MlUCNnH^n3RDTbuc1`^mbtkpG?|ZCh1T^x~B|nA6pm zAy@yCXg{XTw?5{2I&8tocP6Q=#TfNyvpO^1nLhISW|yHF&f;;~4~q!{&il-pCOoa6 z?%8&M>}Kd}~FgcYs~d6w7BkHBt{XTJFNi8 zHVZ&hm=P+$)Yu`o?DDB1D~w=kOw%D}O}>V$UbOmjP=m zfnbT;erL@X1JGDxk%ija(rs~x%E;O6D z2|nk?tln{Vtoe3Cgj=)xYt3ENMUhcLRsr1ZT-r;%%=cpG$TB>l)!j{o1Z?P zDs~IMTwddk^oD=Jg;GzLJb6CKfSZe7W^BR+pk1#neD@aPww;)hE@jFDt~St|XvAIL zrXqF4r=B?TKXI{4k>}k9S?}uN4J(}jXf0#$k4N8 zch3+naYs?DB0@|a!oQ`3-{3epc8$u2>dgK=^9t$F=>TnDUf6UmqYfe!V+`3i#1|3Bm`&k|VEZU&v8W|&!z-jR5kre} z-(2L-U|PM1uf+QX&e`K=)9C9$Xh~EH@@* zd-dCfD}?E<04c6yAyrB&L0bFK#ZwPARCkq+6JFY?E+~Tw6BCJDzSgBy(`{dnZUYAU zqGz&)Id`KrMdJ5aZ`duK8)(8SPaO0%_2u;MDNnVxo(d0tCw4ll&qTI)Wgb8xpif^Y zJ@0c}os(FYarx?Q@Y0XJ4+`2?k3G6dUG^_h%hor{c%wy51Han#)+!v{lhYU}x-zto>sj{bJv3hard5n@yQ8muhtK`2_a)9GqzB1e7z>BD}JM}A0j zxd>X%$RAt-zOu(YGk2Ajn-?$EV7K7}7LYbyzr1EfRpdgd>ayUM z7ZzWbe}3J<^Ks72g>{h^|8V{no^dR?(tRCq!ccttLSP^CB6&*M)9TIrW;RVgY8Byo zI#*Q}OkiIgO#hBkQwTo(icvl*pp;x&|0-j5D-Cp`xT-UHuZUM7>BKklG5fKgLhVr0 z?pJNh>sE%TqP83~A2P?X?HuQO!3X(zqs8NL0P*@G8*F*w#xgz3meM=Lg(`B{M*qKP z6-LRW=SzwkIeQYBzU5!zw}$4Gy(CH++(BC!y()r0AoZ{JQ!lCA!H_k4kayXCK{=(( zemh*zYwTyne?J6H2G2*%jIk7yt#!TM!#nEZcL9edmTfLaUyk349RT~>+TUA{&vaJN z#HvD^dJ{j~^dxfBP0K*AGzdpF-c zv=uPpV_(du$gUE}z7-9)DapSAp3al^70EAU%j6E3cH?I5HSYDJI7m94#p9~%@p+LO zVHQxFHBGxETSC(ZoWKo3xwP-cS@DME-U_XKV12&^q?2X zPe$-In@Dy>}cZhP+f`G&0FO;uY1LNT3?MuoG2)c6SMGs40jlv z)RJ__LAkUE_daF`m&4)AvD9gSBlg|lk_eXo`+Jqw_opeXECWAvAgbck;1aqiRW-|9 zc%q9A+y8$78nb%m1FJLiZPiHOrQx;4G6P85x@e+iEo3FZiQMXT1Rnh-Znwd^?yNJs`92Khe{;nh*bmZ@B*`ZYb>gBDK-1T6ys8ya#$R!~DXSZ`VsBg`>tRET< zFE@+pD(C^>!W#bG7Z20`$^UR^T_9R9GD76N)0MY{fRG}m)@nr$-PnJfNAvYcn|^MC zg0HErN7r-8mc})jnYa7|X0*+#Qp=sxy$Chvu-WJBRkgUM%Zl&9qW#BBF2QJc*pcha z$(dRDLC3WB>wZ)$!R|>S)m4VV=#0{e+YMQIZcj;MqRF(K$4O^v(Dk8=4$_3ddKeaK zcd5=;ggFa2)u&|u1G6Kui16F~@1n+p=@_9|QdB}&Obbq)@_)XA5;AlN-C*0XCDT_L z4HR3D@?AlB>(88KmccX(Ce20CeDVbpAL6FtdBtcs#Zz>Hc(VsEROh{sw z`6x_6RYke&mP6;Een+4q2sKiskYv?3g1vXJ&ho#}Dk; zthYGZm?2Gtjg%(Eb?u`*WAE1z`txj40;07KSRFnZ+6XH4 zoO-7Z#J2N>Zo74!yp&oJRj>vflP{%dwf7)-TcKH{JgPKdPQAsW{?AtS_bA<)VrbHq zI7D@)Ad)FK!FHcUc~tA?19+oI7nuyQQlGxx7$mQt0l=UCAS6!t5UG>x*stO*QnVxl zTk$A%58pS= zTa)f&gF#R2o|XCN&v?x_oHozJA&$!z~HKtM7frRa`)j4 zXI>;o1O3t_v|U*{f1+oBzE%&c?s_mqvaUC-PJ735Td?mzDq%AiboV?yNTl@5mAkJD zlUR7)A2$&EXFe^&D)?ai$!392oSCtupPWLJoa!LRgQ(yOarW7Cp#EK*;Z4b%wUWyzC@-cQ3&Q;9QyWVe|59@ zI;pSPWaUZ;8@W=!eK_4)%yb_Kw=q#1S&G29v)aP%Gbz_newZfMjB|wPTl7kM5b@NW=eNL=KW zv1<}&(Hl6~0Th2iHOkq#3PcU3CRX}yIuEg7eqeZ>k}5|{R<*NnZ;_BFOS8{t{Z%M0 z{9S3eodEBi5QV8suYyVe8G*V37Jfk4KHVhnxV%b}os<+3VBIfZU1JWIZd&qMTH|}| zT~5>Pmx5HcX_Z7;zdczfanlLCLx@sPIq`EmDo$Y!F0X9W$HjNA8-`rQq^rnh#r>7+ zaR|=ez63F#`sF;VI1)5aXyLi13wV{Z!NDaB;Dd)((9pnD9eoQDsq`j;hrh}n^w@L0 zo0lf+D$H)ty+gRDSN57^X<47Qp(Cr{s;m=nc!4kT`g*iREG3%S=u(ehkF`lxFutJZ zkC@_+co?k}l%CI}5avT;2=ZE;;j0z@E}@MIUi%lJt3CETaUym6-%J4;m(k^CIxe6n z@WrUUysswDD4JRza`SAs)qR9$K^;L6cQNg*!hhaZ=>2W!{B>{ZZx?&l;9^(YyBhoQ z{^sJWZKv}xn)FT*$j>~~G#}D`IQntL)rusE{@o8y1;&`#!-0e!3lz3LaG7R|_NiXK z{ZV>IbCa5Pz+S^{W2jpF)Aw(p8ehfw`;V<-*~aO^IDxvo^NG9K<~+%`7cJqJ&NlK0 zEJfYSE4Y_VY+rFzgFAQsEAT?ScVvbEP=;C)>^jP-{xP!32j$tdQ+=_@n=Uic?T2n{ zUsINfp{IYqg^h{1DH%hw&uH?~CJR%aM4q|lZK35@%b6Yc2smIK91dc&)@S!xY7;R3 z1}IX&AG_6>l@YKDkE=uzN{CzUZ>Rs^WR5btXi*Iv1upsntSHWVKF4M4vaRHlC$@y` z&UV`|LyLu|%&2*$KXDgvLEZw`A+Ps_WV_wqA-DoOe&UV%PjTo*s8UnC*iY<9dU&uW%)Y(*M|##h?B z0UJ%h6|pA*Rx6FfB~>l?cGG5Ot8(g*d0q4qS=_eY9~K{;K<39k@J_6k_MhB)P|EXI zvc%5!!v%LNkMS2SaP(a*ltu*JMwpn4un@MBp+fbe zSFhKjom>2PZJM{``MR*LQlx13WPyV9zWuqiXQk?A=SuP}bq!@gKbMy49CIkVlN3lx z^3WSzPb)e{_QuUfXx|r-{`u z_m$WrEGRKngP(4B*qZz+f@l8|!MPf6%B6AI0m6j(WSRSEk_7HIa(zB3v0$~op|$j1 zoVB#2__Lwj6%?Mx&te!lQfl{++ZR;G8|Bs<{wfWeD%QrvofcI5W8xr}W)n%7qoJS_ za-5RZ&#c|=Ae8I91d6RSpV2lbwbJa2pBuGL>A2g5mlyij!QnlPgmG&8Ja=EJ))xQx z!I)=P=ew)3F-z#PS{r1ii! zVp2w@1V&K`h;^G|X?>KuFlg6_nkgm8+{#+N~QzYF_N&L)pxDq+Gv5Ax9t0 zeHLR}`%jY!X>momKfm_{q$cpfsd5FB8qQpKu0tX~|1z$e)#az!5aQdmb8g4A{n4Mf ze4c6rW9q6$#H%Ua(6*eaznK@<`(GlHNk=cvQu)R-PwTfrXiMQ3jTghEvzemb6}rJC ziE8|CZZThTCgZw#rux(1hb_@F@j?A8b55^4w4L^5ogU%yRZWX?)hnOUE+;+~o6AY; zw2x1xg;hR1pcmLw_9ps5Jg1G@%`I9)Hc?W^%>!~hqs?QKtdHhd>7R;^?J;h)U>*^n zwHOYQOQ~??zguhax-Ai-6Oj$ez4o^3RRKNP-O9f1C zUdb-?&fK0oF37ko4uEj-BqROk=*p9p&5>yiTR8Ta8oYjPu%U>}p556uf^nI-s`q6W z8az|bMoM^}*v38!s8(S6#5s}(@WkZrc+8@w3Q=d$=-JK!N+F zXyyTJVf;9xMC2-qNaya4o=pXT&~&YY{l6>$-{aB88QdYHP?W)Ee~se`C%xw2U?Gf9 z0M!b*pj2u})Yob#B>r6QD<@94ao1PHyyPYgalEOb`Lb4MSl%wwzJ1Qrdk`Xb-4jF~ zA*<|k%a2>A4Zr4EP7mWccjhz@8J$j@8q+ckztMSK?F|ty_&|c5FQwcypDFV!i+k^F ze@p1!RT%``{rxJCo_fz%`m-Cp?L=a~4=h6y#S=&#WGN7!lr2?SMtbV3w%;*iS%_hd zlcbMTJmhm>Sk3mN`OB@NDGqRd%TBi`Mr-*UjzcwX2ggJmax0Sz_<~|b%DZ%;M_F!m z?OT8SmTtZL>JMouy8L9tKB>T#xFEi&_``a5mH-7D1=pj=l(nYuxTk^`m*7$=P~K0m0!1l&8B*T{H*bFbwmaZkuLoU;7xO4|~z@ z?cdkjhuW^22)!kuYba*UAA749Vo7j$2fNUDG*p-82aYZ(fgs<-!PyeUwe)?lcmVA9 z&=^}g7rZYp7bXDgea0!omC((&=H#yx#cTJ1cKaka|H61b{6MqcF0G5pB`#9_P&tzPo^MyFBsTw zeu+#7ULk%uNYVvvLeZ)hce^Ano|Iap-Mu|y{I2RyRDWm~a0LB&pi?JXi&hZ`Jg)p8 zz?L7gjAx){S~b^7TOt45gQ-eEPDz`$;mq59oT7h=Q5JgBWBwxaPyc|Cw5~QQJf0$P zU%;8U^OkknQJaJHp}wuKecmsE^ZIfCL%@1EqoJiV=&@A6-ycCrq1(_fri78qr+!mW zERl(R-B74{VvX8~QzKwx?8maD!$P}OY|B&&SSv;yLbn7(Jp?qOP_N%Rs%3$*G2Z>` z;&>JXXI=rRD_Yb&^FX0(WT0odEOZ33k<9P3KWLcWB*#US=>Sj_HRhI9x1L6^-P9@f zR+h*G>IAXwHwsl_3a4hil?fNoq{pNKQQ-jShA8T16*>x>*|i&zgZ!x-d)jQsaGyRLst?GwB@H#-i@q6g$m zNhgeH5_k}&xw6A2+4=JBeXAJucI-ZuH~cKD_R@Zy#n` za&_(B;d{L{-~U>f?W!||)t(ujycL zbUxo-`f)R);@wX>#3C-01U~*0#A{`F?mGw)iHGIaAB# zbncrtuZkFXs2^o`6MsvLgiL|r>P)8vpZEj86Db@=WXHYMYLU2LNtZ?|UJBCk2mbv= zaOGGTQj(s>T~=F;JKq7#XaUUc!yiRS76Ya`SKmcBUW^#ZH(qh9o_(ezlOHD;rxm3+S2~)Ijj4IX&&gNXJ%1iA za{gw6w(HZ)rHi`fzABm>VhqWd2UX~oQq6JumNAWk`iC@wY{#a#_ZgKkff7p7*;LO5iZ8XUwnF%MhCbn(cwvCBx+qTUod1Bi( zC&t8fax&l9`>S*IyQ_b#s`X>ly;^s7Uwxh1lj=sK@9sMU5g{i0AmH-FIlOAL!xV(O ze>#?HzsjV+1{7@YjE%h#df{XQns;RRpJ2Q0oTA%o28Out$*){ z&{3xvXxpqi@nT#1VPb^tI%ON%;md_{W&38IzeK59PlRtdyg(sUHqxh@Y_)GC^%%hw zwV;aTLZnbk;;_z%?V^KX!wX$Io=;nrYz{_G%{5BfI=nJyy(&Dd`aRa9Bls%E_uM@0 zp=TC$PlIG8%BrUEH{VErLR8hC6AEAOWY^=Lmo1so*NQV*zu|34Q@g`l|DKbF_BE~p z$_f&$eh0TuS_!tKf5y#8H9k(Cba$SOM(SVNF4GoRBq%lo+~Kret5Tq|cmR;PKZ8E* zknLm~weh=vunne@gBz`m45#rLE6S%D^5&?`VA#HDW`o8A!k1@Ko4`H>jK4!`HFw#U z$cmQ&wB@V?fG(e$wyHnCaUoE6>(^k%0Ni49{Q-IGDq$#XnIIsxJzF7ts20zyr`w(% zUQML_Wl1Rm(+~=Os5&}Cp;(-1SCdo+IM-z$^IDksYL&(=ZyJ{?nkmiJ{&Ufo3!IoJ2!1y?t}{>>d5>g?t66Ms}*e z&(U>Mt?R=~+X^cxN>}&KsX+zOenV=zV49f&{s-vgY2!;H(66S%-3g7$dFO(Y#5R|1 z+?vNB92TIenB5sJ7-?|$2XsR%9ho7@e2hk5pxzat%&k?FM56nwq}Wwhc{i_#5mM|3 zqa3Bp$N;BC==tq}VNMP8CxcFr2Fm2LQse=%aV0Dooz0umH9~f*l$DU|LVdu??>v*Lf$2uJ{cd0(u~E}QL1bA(?s!YIu_ZhH z!%~~ANz+4TN4g?9Mq#2RLS=k)xRGYEJ(8MiRD9$ryBjk7Yv;qvMG>jKACNAWTjAf} zUSdkE>$@Y;Aui6Gs*ZaR4OvCEQ)=gyEIw+CcRMfQtAThk!yZhAa{WMj8=ycN<;U0w z9|A#NH!GfH!Zh<8xRzT*k~#D0k%QuNWm<6bY^u@KkxY@}h>pdZs|{%Ot)%aAp8@mI zrO4_Tz$vt4vr59X1qwI17;f-~8Ytxz@#geY^mgp~f&9XfYZsNf1=A#rRI1@@fyiIY zh}2J~#;R{8WuVa;jpm@=jB5u~ZAxXa7P5;i;yY%)7$#N0ep5ItS!(UGuVLvDf|Q}! z@q>v-I0d1SC-En&8Wyi|vR^81B~v(n#hdyty0%RJ_bvd?UFKRVp`ax|3Bu1yhY7pI zF|8z#n+DqW(mV>Dj3HhrD23~O2K&_)^ZmyL8Rmv3qk{P`W`+hD$T*o5)-~Crr&cHb zGEFtZml2$Q$iqnV3Bjq6@|<{9Bm||jB>f?BZk3_=I-D987Sh-*WR6u13|^lSqkxos ziucseYV>17-`AugFDVaiU3$#sgPG-HgMJ3ba-MP(@voZ2%a72t$Aov4VZ}(wN7R;-xFHL&bGK1O1g;@s827S`gVJrgvaB^rKC8@3!aP8Xe7eF$F zUCiVIX)7p;Bck zGxMmKF$g9G`(0!1j|9hPD^lpL^RJZjEs)5KJ4JgN4dn?hRXObYMfetlv#{KWkp^j z+f6&jbqlYh{!{MW>`M!W7^?%MX4^U6b;MI*{-j59H4GbF1VAu1ki!_6)!0SO-|f*o zdlwe--RRaEDJE-$hGW9q>BDF%5K#4q|74wNRAp{rs|}By!GhV^4lA8w)mpDAnR8lC zwP=HyOw4brL?TCeQxg0014rCIMy+LrHC}Sh>0R!M{`;xCTI~V~=Xj$4UayfiW>_ZgyHKf(bG{lWxcU5*5n9g zHX{&RfrV~gUjw41y)`?gK;k~4M zI|U2W05k*=uxNemZY!ZzYz=kJfE86VCz(yj@l1amY)R@Emv7G`ZabH>pEp}MF>@7W z=H6wAfm0RU)JrS#AfM&Bve#44Tx&v=PCnU}U94b|&A$hVvX145+qAoQlmA3MXCI_8 z>cZ>EpfPA}Nk+hH5Bc!JqkWiuPigr>W5d%J$l&WEKTu5pbg?`NTB9+7EaB;fA%VCy zQK`SijB>a)@>uQk_L@*w(0U!iq|tmm-@}^|2$hpUs&Z8OhM{`}Czb(?#uL!a{Tz`L zl=BVu%xJdwx_E#3DL2fcAn6umk5li1+)&Ut+^2-(LHaC@X6;-`fhB@eTa@?zxELx* z8$xf6Hiv`+wmo<2sK*dW%E2y^FJu^;A%)9KC$`oslGb?i58j{C#u$>7A`sO)uIKP{ z#L#uu=)OlU>*f>%O*gz8+0W`;r25n|{WNwW{?&3%0bHJh*h&Ju!JqG;=|aX#YQ8Yk|P&H^WtL`Ee;xty>X+X z=B~LZCV^K%-z*Ocy{PG_K&bjn2pRYr@5*kD9XL$VLdORfDRul-hbS{7-|-Gm5iG0| zVKv!sx<-hG64bzXUHLH6t}i|1RdM#a9WW-h?_%z5nQ6NR*nsXXltM32B}X!vfgPPp1tcoR zfKd{wrd|DeL11L0^>OC*xiTvl(gjyr`9AfNXJ%ox!z(@_D6@ope*=1$uY|el<8_mec_ z?q|AUq&U*X$t7YyA`|KH!9JSrfYn)a9!&)V?SSfbc*FcZ07idp>aFHQdwkS9c{n;x zJ+E&Zg@6Ii+V7uO&FpK`X8YbtJo)1c-N)U7IOWUJ{!@RA&Ru@$5bPWog;h4}=e1N! z4EDw+%Y7okMh*U@#`(AM0=1b$Ys~Xebm_K z1m-o@TIIh9QfsJ*Um~Y)Z?2eWIxMMS}0Th=V zdz-LB8py~?O>K5NU~}{H_V4%0dxmKxR}pZj=FAU?_;a#@EMPMdRiV+5g|u}SU%jj_k$w6dc2oB`zq?0Q zD%x7a6(<}lK`1a!1&M14A5!?H_ou{j20LkiQ(zLsdz>~XkLVI_gaGfDvpAiht*P)@ z4=3w*r_IJi1>v6dS1gMD*nJ};lOy8yY5D_N(^hWylt1M0wZL}tL$K_owhJzF=2t1@Tnz&psOF z$rU+nVWq*4Xg&Q(D)PY{8i9-uQ~{w)GIlY~#S*q$QR5zj{Q-@SAKu&r*(K)QbfE7b zwwwp44cQ0D&?L*E2Fx?=6Q$+RZVO7lSp>^M^`2`+57R-Fjpv`II7(#CCt zHh8O(!A%~_clnJn86o{mnVI%b5&itU5HpOZ@lPbABp7W0Dzmjc?2j;NPT-y`8D<&Ys0I?j>Y`C91 zXF^F;IKGP*AZ(;}N=z=n4e9}9$?FSS9T)-=SH?oWao}7bgC0}hYAHMdolH#!{2_DK zpVztwR>J8UQW2nFN+Cdxo{(&pq4Tu>na68jrnM6|x|k`Bc}HW|N^ITV&XLRKoAadp zcI%BJM?eJA>i$F-f!Nk~XM0x^(F)mw3%}^1|m4FDDgVT}IhiLY!xB#Ty z!6Dh&5X)`Y*ag&GHBNc?n{T33A#TsOaJs2KdJW-D0 z_X^ckerog`Y-CXvx+lj)E7U=ZAp ziozNW+H)BSP_X!pYXGy<#gv>fA+{Qp2axnlK*~xkpzFw&(ka=+EwX5z!hcuu9MtKf z@g+<#yHv$$TSDg|dnyOL`w*g^9OHsjcuZEJ7-_Txk$G?BNhHN%rqSM%_^IHPID&Bu zN+IDO632^nR3|B%LUG2FK7;S*jHT~d6E&gYD-1LtKZ{e`T2bqN2?6z0vMu)oKo~BE zATJ}E1#j=bMSw8YluQe$iK4e}4=;@7D*^qaock z;X=UK@p1anbkhvhZ1>Cv@wuinv4Pali+Ty-N}`nl39NA*IcRMJl{GmJ1QMpSN(+k>DHJE!6#Kf&+Lp6Gd_5K^`ZY__&$GgQ2uv5oIOd1paNDyk z|px7>F*qQ{D~?*~dGLX2qi|0Tx;X!O2{p6Y>4F7|ikgt62&Z7V4$Kl*ty zK&3OWS-b<961}3rGZHHO^fJ?!s6q29xud`#aExR92}TiX6RDp$WI}`s(iW|5i*Jq+ zNqGZ~z`&S!B5-TpOK_rd=_$-Pb<1vaKYu;TT-Cu@D|sJsbS3aJJ;0Z8)|qa@HZk zv+rO0!-~xTF}bUf4HoCkRMBwnlqB@{BtP5-w-1)Nv(EOF&gO`M+$kARmJj#>KfXCA zCkPkiH;CCaJ>=4tJ(dRP9gs6HU71R0=caL1H@2SdS2`r%OeItVEuj^`;)O2ckCWqb z5N72JVHaef6xMsc+&ypDz8R;_fJZ?@L3mN~V~@XZv2&IRI&#yo#u|!O~$aa5Y z-If_B3{Ic0){4(_!~YpA?6+=5;S4AxanRi#w^!X7vQi08nR5u)wM0=pEhz8t_(NJ9 z>TM5bNu#7*2y@W1$QV8tZ?n{xq*@wg?Fwva=q@c^0z#pU9#agRSd7y&EgiNn#vfBVb(<3^Dw#U+|R-YM{~cH#JbN_TSOdAC%X1+|qW--=D~AHwORI zKZu_s1uk`q-@5gKFPTTp2OblHkfM;`1oFaD5sUl9qh&~9CQse$=FSv8fzu}&{HmQR zqSXvvbIGaalPRFlgfURq0{_bdzV@U}FqN>+p;tUE7b~uIs>9NqC^sKedw=3k|GOr3 zDo`+}X4b`u zW-?qDw%dY>rLf;vYtzv?f6_eT;qtTC;dY%rrWfz?_FK#pvmpW6u9DxB$1+{hAbi!g&vGCqME%~2P;|Bp7*9j(i`Inj{j?48S3nTjA z?MWOHRGX;Od;p;B#~%=YMJAbxiRyxaArIGK$(uc-E?;MIZ|V6w~&-3Tvv@xes(fBnd|KVXm}F5Q#u++uhP`;n!{Oi#O$IYEW9D`;!Q zE$4k?D8(ga!eZjF{^{^A`=xurQL;vuX!DGUp)_Pj!MYM({F);PGBUr2CEAgj=j^$# zFdQ)AkzsLZGE`{O%f)JGbFp^le&u!S|;97XY^9viq)}z=h z=C6$HUoP`MWz+Vn0Mh>FP$z>ZkQ&m_YeROZ2h{vGY2FAyTkUin$0W&vq1P0`!PchdIj|erTKlVj~W~XYS|# zAJUjD1Kx6_rQw7{%Bo+w>9wlW%S)0Pw2{%0PNd~>JG}L8M}Ja~@#B^<eH_2jjNP%`@1C^3sX+mz?MMA3oep3mP)HQq=xT8gvWh2Qucm72+MNZ*-tNTBYoD$;J!ot*w|k)1r;D0Dk{N z_#ZWUKvpjrT~qgmdZgqWW%BJrKmDDICrHxOEcJj{9?_!4Rur<>=oARYcO+?r`xq8q z?S-_x7o?jks`-`M<9YiXWHEwde!^UmaiSWhYs~u2Fw$ZhcN((0`rlp3yo^MdYQr3> z&`b>pj&szVyd7xxvHGF#h4=fUySt6`JtQoYnUXNx*-hx-hr|DIm_6!wyQ1I>kggJ5wi~EFg@`fZ9 zq*PKjX;_lX9kB#d7Ax4sf^!nAD7oZ%4ro|1O3j=rhne1+FSq=s;Kb?1c}tZiIg5?f z7_}QdG5~>Zx#j*zR8#MbL5ATl{&xvKj*Zhp)y9*=yl}uX!~bSwivO&v`0(8005;E! zIc3@U;e;r5eJ48t)4TE7o@>}wVE7!TG+<;_2=CcAZ%AIcj{y;NCE6K^v!Tps$U0+a z-kQU3mU|J|t4PmJJp~<{izucZ+4w~w-x(ltbb9QT*I@peQ;4X37+hxD!d^i)Lj9Su zuS)Jrx$3^YcD|h23$5Bkn>NNWp;&HSrQiz3z<>*j<2T0xQ$kAXb-XAhCT8EEwI>f@ z%`Ics|ImYfdcnV}K``T75q)@3=l4a7?RHHF35T`M$<%Z%NE1!mB{?{=4nl=M7H1eW z*O*8Ol1HX9^QKI}`?DR6P=Yi$!ui1*6^m}n@wXDTZ!G9LM%at_ePKL2zO0%>fz#j& z(M+w>Tp?GmXcYFA=-+KyLX#e~@J=Y1RE&uSp|ADiDJwG7PHNx(%N?Tw5iGP07hz$3%oG-5Dh9_ zsm4i;9Z;9T_!ov!qUND_HJ&@eU5I=4j5zTm+m>uKBMu2os=)U;fR^Va^a-JC{v zo0HGkY2Ad>K{=09$TxViS(|5b9w!{ReI(788WPghj}{T!;r^y0R{Fwv_{@yF|2Rs1 zbj_Zq5tn@XcEv_WK-TwfWuQRTuUjODDt7dbIY_zcQeUaz^Rpu53`J__T@YJJ@*(&| z5W+;og&SoKzf=8!yoi(ZMpk{4w#A>PhN6Q0Xx*}|f47}X2|lnHJ&CH>a-CsdkEb^k z#wrXYcx4x$DFnhbIjf8Z`$^-ZnFYtfxd+t~SI`kBsVfJVd!>wN=)6*n`Wy&j$5=n& zS*Ks%6l*`x;`?{GSN)A&6UpQlOU+@nz{jE{pdHijjX?j0_|pRXFyqgW8eBRRA?v-a z5$U;+)+2k6@ePO&)W$imIQI8kG>ru?8aU`wxCIOmlZSHbRQ(L6=*bkd{p5R(pAB@$ zx?>tHM}~KQdC>j!4}^pq7&OSwJpMZ5!Rb{1ZT4Z9*mW6mzF4@0OSRB=-57axh6eGN zQ?C#{oF=c%cV@P@a!&>=8Zd`PoG6kxYJ@-P31h}WFBDZ_#yR5(wZe#ZN{IcOf+s=m1hs;s&a`bx zDTLz!Aw@ELpBy)B)B%wf=8x`#UjtF{Lqk;n}T|6EWvKd@Y=~5|w8G)2B;@CW2frkNGm3f0j7{O$2Y5wv|-|Rt$Fyp95i)F_pE3$`vAu5?{ zhl%X@fo=AgkrZeP1{kq6kQAA#Vv(?~q+4^8=s}N80+YC#zkgfh1zsbEwno35S)S1U zPX+$>><_QiWBssTFt39t02lMrl6_h;?j%id5#2QN>a-zS3<8)SW2L)6QL?oxx;Mw0 zoNkex&nl3WK9Y0=k&{xL1x7{Y5+%)ug0PY_qI=w9M=jj_BCK4Byf<%ki>R}Kkx5tm zqmI%|8;M7;Xq=yU({QsCHHPja#_0%+DaQGoMdsAtPq=aR31N7tP3}fa)2S;+`Xi#< zZzP-9T0OojUT>`Q^mMS144K@Z#);Vf^eCnO!n7=l*)pY3-6)Z_M_{SX``lnnE8%hz z8gQYyc+Z6vk)IfWJU&g@5alq6t8YU8e2%K+;AcY^NfTinlCt`avIOy_VCNxGZeRkr z2GA#8E;{J}&x>sC_q*p^+i&JI#d!!L$#oH6mOJ`RztyalWjD27NSa{z!ckRBAoa}w z_lkx|QOkBd{*GhTw+J~#WSLY^oc*nt;&mcYDv|JT)taK@gCCb!dnNy!&s zJ9Zwk{JPeTkiY_uP^5EY3a=r_Pv(%!9`CkZv{(w_o}41Fm^YjMje6vaFxq!*#&cvj z&gE4R=*O=&4^c2H62%R@Zq@THj?aMgWJi&~=N&2c6UpL@DqDwF1`tPqE_YyZV8viz zZ#r{76Ru55TjtcZ#VH$(lEjFU5F;3ru|D;d&bsUY(fK<=z}f)R(EYZWKAUPhhL2kt zdH7lw3*b7u{xsRtEZ=bXEsIkm8Jio!6^^ba5V^}g99fh=Xkq0V*`nt`uREL*w(r|d zIl(56wV+d%y~cC=g3t4x`g3L0q5OuuA#3Mux88Gj%{d4z-2gcJoQv4+5ob5VDOTwA zVt*CxPyg%zb6-kG3HC_$H`8O+Z|ma^hmFj?CR0DW0bDOyuKPRmHZAbB9Zk4LuNyJp zN}?ivZsC`vt**xIgJU#08ghnl$8p0JE(dFUrCw$3ECfFdM&D(^XoWC5|G2^7XW91U z$u6IdR)=n3tF?Las4^-T`O7x*kG((sk!u+?kg@cn~U(x5q3XA$qflVuCS`o}G}Z^1&$eGoRXrY7CY*kEk8@{+VMIsUnmqkev%Sw26TD9Y;)gB;Bg!= znPQIII5)P<_l=rgCBu+Zmy5a~PP19A3w=naCXK76J%Uzfde);i(!Myrknv#C>}jTR5%Y~q z7Au0!{6b5cH-ksH9osUHl6_3$-*f2=QhCOLF30x#k;UG7u|ryGN9!l0Cak{jY{Qn> z+-qnv^v=f}2QHU%9oUGenUSq}M|h|t_#zjKqX|9}^?#*dRoS)c*_k@AInK7)ClAhA zqXRWT5cT!-HNs7sDMyR1E}J%_Z8w+<>bsHt)N)Fbd2l%y8BpK}R#LzC-8#T*j(GFy z0_?Dy%L@|AFv^YVTa!OfOJLyV%t)awZ{Rm%L9iKh(rLI zcqv8o<0MJpI1Hksp~A+Abx}rYDyRA&VtkZV;g7&Gx=FR;>EBOOq?|O*kUYZM>*y91mJ^?`-XNls*PV(p<@3l;`i2B^2@Y`pTnpp0?rc2~NidHqou2>9Lzy^5GwMBeA=s?AtSbXNJ>-E2 zzw=ov(SDAi<+41CZz| zkqj;3KEmwL#LwxyG z;m@ZP3XCN9OF~8y?9v(6u4%7-p{3J06P_#?Zaw*iduasNhfHYPai4aZb3_)?A~Ec4 z&odTyeAaN?n%ba~n107*eBa~|2_sAG8%{M+nRyW;5IN2%uk=QilS!iy9kLqF?NNI4 z!K^fOd)giCxpInyXe;a<9P7U}D5~`y9#XmF*K>B<$S5<`{>zsu4OlDBteVx$)CC*? zDm>RFAy(>4p;Gt&C`WChc)6w4HBPTSIo39RJxr+eI}>%&&tz&+8~pIX4=e^cEygHg zhjd%);_L9g^(b?K9g1-NK8jiaU70J6M~j{LTPM3v*o5;~Vu4pG_XgIDNVg|lx`(K5 z-$jMkX(7vtNcw!ZfQ?{YsGtGXXv18r{=00SKLW*=#}JkJ8VD>YXkETPo_C2l&N^1g zEsH|gVeH7N)JuoZ8Lyw7vLd6(Mn?S!dYK}(L})ksIVlWQKWT`|WcxN?vmCk>BQ{@K z5}o^(a$JGd@*7IBiV=hViaB~u7zGTV#GE9jEN>xW^2taDVQ(#cG$B4Ya-GRypA}*6 z8u|A?er(M4!l0?*3idbKHq2HIjquKxP$BdEuX?Y;tYp^&>=;#?{|4QbtbGZWc95G8 zKbFl@vMAJjW4rx!gXs&xEcQuYw;$bMQ#Wm6{sl-39v=*?%~vDX-m`||?kI9M zEhdFejhjrP7fI^A`gE)BU@r5FQDPCxJ^x$W1531a{iuOvhLJ7&n7rBIg8~hy? zq?CDx7j2u4J%0TH_F!bB?p-JeQVz)}bBWxtEaCUX%gKcwiC^CNx44_r*1vI;qL^y= zIDii?ddKt9;|6};3^=29)zYbvp?l>vD0mCqn!~h?}lFJVdlj^*~ZGW@h2pMvXTE3z^|i$ zODuJE4gyV|U_1sCv9=ino139Z>MM_&>iW8ycF1gt$EZTm5Wv*xG2b9_74b1dT$pzGtM3sFRm zUjJ}n9ipo!Vv$k-in;`mCgS&Aq^j!gb|k}Rz@r)HEXLBC2&a$^+z~mer13bkHI_6R z-||x9q{{d@KN`ABffsNs(+2}Aw&+3oQf;e;?zJMX2P}Px9a`QDtzQ@CF_I5_v_npt zj8-2@44tC`Qv*dv$j3in4r!{d$_qo;X3du{-flApD09ilMxoDmZ(u<+ULkgSuua^& z4i&EzkCn6afH()!(*iYnxNKg2rP1?Yo(2>ZM~?#61MUhAz4bOv%b0}&FbeMvNAzBQ zq4S%p#c)mcIg+3(aaCclp=LdLoz2O!cc}x&tk=Cq4{b))Zio+)+s||Oe82hJZ(`Ru zya>Bxf(J|0nQe0Kvu73o&`jh|jVyw-VkcTatSWp%ES{|^J-Abua0*C+t>l}1G*P~G ziWjuWroP*4)^JMOV@)X~rp=DuHOrPav;FLdO*hnO)2T?F7mH-2pspVBxD@Z63~6Ia zG1!;ZFcYZJ@t^Hqkqj=Y(27OOIjkus?=NtdDqM2rdW{m|K|0BlZ7s9gIHx8NYW&P! zx<4#59%Z{4n@d)B?;uc*K=;83>P%t}N;Mm6O!nfN9;Q;qT&#tbZ+nISA3hL9Wr*0e zA^ZNt7IHZhu^X;_;vEy0(3jxs=Cgz1mg~`2o09M5a_2qujD}L%Kb<0j*kHJ8F~2fx zm;huqz>ihfhRGqb%J>TWbJXhv;E3BQb!P{~F``#PLiXIPLn_kBq@zC@h z-Pi-qy8F4+ALQaiPdl;6SO#~*Bauo2a~nrzq*t9EBwyvsyg;+c$b1?S9;JtQK-|SHpWrdiENQ1murVp{?eqyU%E-%CMqeRe?LBO zi7+!;wMGw{q$3yhXZfM+zlwY@P#L=aBR*HNB=~2i|K|k=;0s18g-{7@Jeqa->IN=F zN}IvG1qOf!4z8=O(TNV1``EzRF}x7(ckwbS`ht@b%A+?K27g`0kE)606P(>2`2=gd z<~kpH6{K{|rC{inKn-EcZK`X8Yz7IVK14AE*w;leR0gTg^*bN|K;Gqw?|_u)%&^?c zWUp1dd}fl5o7%^f2xW3F{dRBw3LP9TLh=Raii;kna{+{5>%`h(O;$Kps1<_LgL6^7 z0|lN=t5QRPUg%L5%ND31Np}sO3~CSZUanyEG4HCIet3n%fybGpQyF3}f{$LlZXV3| z6~7pNmEx+d?`f9Dd|2d&)cpytw`Exi7nRSI6^TeBK@QiQP4tC>Su9+2ji4!;w-A+d zjjd4|m@CdjWnp!0NTaGwu(X+LIfkxZbo4P}MJ4dq013D-Q-nj4#tlDe;<~IjMzV^i z%w`4{keMpsHw#p?YbR5g?oC0|wk=WKelNa;M;JG8*lJ*@G}|jrg%88owVcP8-rKY? zQ{aV1(30PF!<*N9A0+~uMRV#P-<*4L#+y-in9X$RM8Pfc0ZeZeI(Hxe&q=Ob&P>aO zCldg-a#fyg91?Rnb)_72E|-pJQU_b&x!igB-QK(^LmP-K=L@l4WpHKg8ldC5t0T+! zIh@tr7Dg70TW-Bi@n~T-(}$#!>UmuMDqODWt%2_JBSyo1=KPMLx(m5CQV3lFRnxYm>F}C{{_sLcqbu9!`G+gG7k2-35_?!lKjNxp1Cz>hHczcT1Fr$)AZUeWvMn){KYpb(>j#TBeVGM z*B@|2@6jNK;8q$M7r=}gK9@5MBl7e(1i$MJvWZSB6A3@7t9fJeDropr(PmgvNQ{-n zr}KoK0)qM7o%;o>$!GnJXuOO&*@6|GCzP#-D~yF${$iU0t-j#q4>lq*#hoyOn9Ew& zLcvoMK?-^zdb{H$rpzyGQVRw!6{kOq9Lp&c&K0RQiS_IXrIKUksy%1;@d;b-zPno2 z@t?VWyw}mIHJEH{CcK&{t!K-BL z>Y_ZhJYYe8{6WYipM9a_=jp*K!Qr$I!N*DAZyE-pY>mq@X|PsxZN1q3S60o(iG=D` zc|BJQ?2j!$Fa<;Dk(kpSn|y>wvpMEzw%`Sh;ek#60<8eGuzUnYli zkOtJ+RnvPdTD5XK9=hD<<*J`zTnQ{YOrbyIvyt!)3C}fl-3rUbqQwgOlUNl^9VuwE*b=0Dkn;Fsgyg!82P>TKiGYH92 z!RPp*Hb{+y9!NAd``}!L(EBwCD&J}KQ#akanQe3+8lr9TUyD1l9C`%oEWOa`I1c?$D-9b8#EJ(vO^op>^y{+-YA@?vZ zVaSKrWz1|Dpd}!DrqZA&cNxIbHU>g)uKdL zbRl*fJk)5)Tqd1R8Uq%~@%k9%%C`v9WS!)-h*PK1jzXLXGWT)~-glCBh!YaVrZn4NM-T9Nq< zl$CNqU0kUGO}3t`M{*o5(08+uR?@K)Nl?sO5wLz<_2f(J&kJ^@DWVkSjB9SQ#qN>6 z!LGOVwQ9u$|!mjyn&|bc;7&^jG9%j|lGXo&X3$EN@Mb+B|00)KA6GI`?v~ zNJ>iYAI$MwpltXD<0Zeeq6vx)c*BMRHNALs)uolFzES3IfW@X(#KJIdRF4jS6o9^X zp~zwHq)}#tDEvG{Ji~fBDWi5ICca+Jl{>49C#;m#F`c5O6aF!{+tczcHZteK(`m4gQ+I2F) zH0$Cr=D_azA(}LWVdR@7KHeK;f~`bFhenYP%7LzUWw#XUvo$gKhGtMX7dGj<1F9w6*Fg9U&I8^DUU}i0_@pL-wx$ z8(7fY8HM8XDAs*E*(~d#$%X+>tiN;n82v9gB{KihkIHH(qX3&|)=1x^Z;e$3p=$Fi{MF=~(_7saWX1RjcWhO>;Tf zE-&AmP*)YRGSb5G5&G(ws_;u3{;I?K4D%~+-zAJTM~j4xY$2*DJz;b)GChEL8_saf z>c))iDmHQzWG-;OlP8Hm zoaWy(!9+X4kN^JNISpA^9Z*M>O%@#-<7u*K`G^ZFQ8C+>1M0q@+iY1x$Anr|0yND> z#Aeyn@t<#vgZd8s+(_&E+?IN*Xm0G&@^a}>_3Hx+TKwLbXQtfp3kew<$ya1yUDyM$ zSyQ>U<+f>@Fx9Iaa!x$^RpD+H`|Y&Yyt!I*V!VsB%Kf5IFDQS!Hx=X=kD|ft`9*LKo`|E z*Po<5EfU8gxz!rDe|+44bNg5ILpyvH#P0+=k3LU5G>pIELQB7DLH;ijAMBcWJB5?i_5L$0`P`u*zEQ)2pYaE zvvQJcXz)1aLei?CCj*VN_7>P$)$@V;7tyF6wEZtCrvS?xV(cfYhljV8b%s@5?8{D2 z;;pY_h_yv{DZv!stS(0@`sv)`S*1vSRQZWHy`OQkneMWZ-c!0ss1yndMwdp_43_fZHdlG3nax+=<9*51!lgi0t)nB5Se1By?&atShC^6Lv3~&5=3lK&XyfrZoaVA8Nv{wj{CBsY%>>I3Pp++0I>&5%w@b*4bsW_86$%n<-QM z!D1;z>TibF{rF-^p?&AQadi8uukiUsA-nloRTGskzLp?R^pUJ`c{lk+GI!FVptxa( zS9(54EpE{A+A*VqEnF-Qd#j&v(Z#XbTMj6%ri4`Os+L-c`+j|&zW-WM`2*M7Osfmw z5w=lG#fJ0T)T<#lt6-{w*6WJa6MUH>2>XfzZ?W{l(D%WS0f-OCArfh^3j5<2bYYDp z@o0@B%j9)zG%m&0yOydaU?NlPi+8=n)-|_a`d9w+?HAkdSMm*+#G=^Z@ap+v5)-js zbPCohr+CU(%x&OLm*2sc*IbkOfm)e5SBwcQ-|_a@_r`&YP$RtZA{A1>Jjz$Ewgi^`i@^^G1%b6TVpvc!T0P7o|iME?wEf)PUrTO^N&OB6MM{moJ3o!!YKORZTfi^e;T zIb@+j=z;mQhWOWIq0q^W2e?P^b_(`u!Q}}I`^O2_6Yw{>rswP|v$pYC4`nnwev64b z2pfD!>XGvFn!2rf3z9WhHeF~VGJ#$(lgx1EeZvz4fzwHJ1Nkm6T`5$k8qM`KA!ddF znElB_AEz6#o?#wHrMfHyq>y8hExrfxtj`YNR`{W6Z4EZe->kj&qw?NqP3YE%izjW^ z^W6+PL1sww2;L>PInDSQ^h$bER%ry`cPY=nnN^|XY*Fw1rw z)#U%?SbHLQB6%&|$o`SB#S4BS!F%sG$XV@xB~#Hub@~2-XehfMjRi^h=J=gK&#Tkq zWWufgqOrxX;LKN$ZZ-dDt3Cd)c&BBrWG{`|TfoVMx$U0Z=0#c7s)0(+3m5KA5Mv7< z5}?0^dUxvnkw>N(pFd#YFMFJcYiTC7b0)_RU+}}jCC8CCw;e@mtT#nip3h=WVbc~MEap@)AaxeomQkiU zr=bG$Yf)tTb@w0l@0T6-tYcKD4kcvMe?^>Z==yu_nY_MMC!5WE)h>c~PxY`Q&gFee zj?mNM$<^JnSBK7dAckN&c;myC%oaJKYnk7YWjIKkdA}AB67k49eUW`6OL=F} zaV^jC;jzZF&1vwDj%G0Q?g8N=V@0^{?TXxsa9>oQqca+H*~4_dbyNPZPTXRaSz9gP zaTi9jXg2bQgT#I+E_1+Z!p(@VIDhkh+itF{j5soK<1+K2EFqWsDXh|U585Yige5k* zR@aUr_i;>PyupP#J(D4^ykf0@`H(u5S9=?3=jTW5+ftjR78N?IVvum^l~QKSC7MTZ zHMh7u4g9u%aXBOkj5*gCjx-%+3@35^!IxPcN(#*&8ATtQcb=+u`@`>-Bvzjl-yA^-Ysu<8 zEGhdmGweB<65u^U*!pE4Gip|Wi3{HQyUdsSx_V7S!aBG3qn~@ZFm;-VYiqU&mb~(Q zBpMaL>4wSv&?{RR-!W5tc)w|$#WeOlVQ_JUs9Y(#m=7v%QypZt$*#X>^ z=levCALk;H$#>A6*EHysOnHi8&b2wlRV*G7P?&C4#uAp<}g|tOFfym!CaCZ zP93L-cRB0eUDqw=+{zW>eY5f7!<)Mp+a*I9M1&XOB@mc%28#ZY0?!ZK%_up6 zwG_#?;m{#JU=JMy)EI%v;QYawKb}$m&HqT`bz1@-5^Tt$I2pi5%mf5dY-U{f-^N6 zP}+P9@IU;uvvBicg_^qvpB+ay1%BHyKauF{H}OHdkipsI^ee0Thdz4vO^0)OCmke*k?(QOQ${cAiQHp)l5`u`#89iuY~y0qa=I<}LJZQHhO z+qToOZQHi(j%_=4oOGNo{meVFX4d!ftQ+UlI#s)B?+g1-=l09cjA&ug_Imw#cbe;Z z$cBSTTkNK-eLtFm)rT@S{A~C>NWVX!pp0eY)>1k}4i8#E{T1F8K`H|NV;Q&q*kV<7 zG?Vx2@lRu5>^>xrG?e^9^Q3gK8|B&FUQbhiQs|~XFRqn~u++)ZR7^`F?Mp$fvL=%Z zbF?RfE}EGg63LgXcRv?`91}OC53a9E?^|;7nPz{g(ai6|DYUZc?yT8%FP2E!2~d3)_JHUIb_~l3M-A)L&`s!~thw@EJ+4*~)!<66 z15m5f8?Dfx3F#GL0ls&F9xiY85cpnLIklyYfhGr?D{{_bz#5}SWxtrWsYe&>mFwl^ z_bj{RT+dy&!R1`NuW6w^^sa z^kA+Fo+hzT5$n4N%kj?y2>~fnZa23l@isKjBNPBFL5Uuh?LbP+*`M1Ld z-DARoK8-wpE1e_}*Q~#5x8y!rzGHD;@+L02Fgn{~POe#Sc2!M)GuJyqzAdzYN zXFzJhYOd^4KUfHM^}{lK)jZrRtzPT%y^6gDLQ40I&A&d@M^6OOKPD@*;-_Qjrg$5W ziq#in<97+r;Eb)nZK&J$>*&uIykX<^UYf49?{fledbZYl{vQVQyjb$`?{1bFEs&v@ znBSHNrOj58^eHQ`qpkf7b?8tetSU-7oK?u3}qq#U(CB+s~A$8E>gM)$S(Py@K)` z`z@7m3g)mqGm$==7Mw76v|{xaj8mX>`Qrjqijv^|+Y2DvbAe1Je)j<57Y~SwNyYM< zb~)gY>T^27Owv`L~BTTTUjArcIlu z;mNcnfG(MMxuo#ZGzPQ*UD_SuXagR92tPPxc;oHd3BzIv3X)Jgt#V>bLU$dVp_%_& zlcziF^H_fS+)lP^@5h8%Zxtiu&<%!Fj!`o4_P=n6RY@!@SHfp@u-ASU>m(x*yva;G zA{y>Xa(ej$yYz{@#<4awJw4+0b*9@(4f|bQouuCVbm^TQw)F`3ypz<4@bdE3bK+Q$S$Idc|-cROKA8_@3y;R=Fp~Rzw=eYe= z>lP#OkO0s$SO|-6JoU!nA1gt}?!^=DnbvG8tBHU@{f{kFrnv0C6lDnO^`K0}UNSg)$}u--(hz8w>$ zwFoO|XL1-_Ih|6wY?5@r1E{$H-v<%Jv}pRKgp@7-5>VG@m6dwjRzoWcN(7t$mJ= zMAo#{^gif#6cr1_BC1kn#6V?uWXp+(sLWs;q1}ZSo0&^&#opBe>{&!T#3+4?eI&A% z*%GbHbXqc&?&&jX$i=wod09xImb)~`85A}j!fyHH3~d8Di0Tw7wB}q6Y4gi435GN?VabXZs6~+7PC_A(BlrEUdLZRJ7r3i6nZX$v2%3doU4kr0OE}AkLFk603WF8H+we|# z-P8M|Q09Hhh4#3Ghx%q69##s977@B2doRKyo9&?Jx^UQ5_zW%7R|DU#K!?^09l&U& zp1l9=H#}J90)AcrIrJKZaV(<|!lSj4Hzp=0@Ga55)+;R`^m1zZv!NfwmdIWIJ&RP{ zpI3aA=REGi5#4^N-aSm8lKtfM*m2+3Qm`;O{s6g>u-oN! zmAU#L0Sh$ulA3t^#86XRza_7%hq8J5w^jJU27M$EeCZ5aOG;t)6uK4eVM}UBHD9nD zo-cepFGG{k-0fwvH~cX@=C6#~ZGxrQdZ^D}OLUsvDSd<@o_0e3D;(78G$}<<)0EY5 zC-eZrxfWIh{QPKV)hTnq&=PvXi2g^vCAHg{R(YWbrd11|zd-`dUgpQ4+0upg_3iFm z^r}K}=~hBazb7gcf?THOWG0sd!;?z2ko*p2jOH==^yslpDB^<`0z>W$4*;Q8o3TN3 zs=vqPf%<+tlXqO-cTKgchdSrL&3p}thieT~ij8zJ2Iso*b_Yx1@ubS@#^)j)eMqQq zBNsK!`wfi#S=8m)eVuJ~qvk$ZX#d^);u3+>nmR5BRPn0!%hxmYZ&wmkuw)645ldNy{RZ(+kVK;MiCC^SK{QWs+#6Ty6B=TPe z;KZ6PkzO(hDfK%_RgyCELyuMMvuSZHXuvf0r51#@1ENfJ3LWy<*%`b2?hh~+41C({ z=#G8wk zrw|di%pO5(INws)BpY#E+GzJ1W>rcmoZ>hEF2w~62=x>e?Wkom+S&n6&WwUiowE9X zlE4xIev{~;ZxiXld%#NF2gfFd3#dwUoap7UoA*!+Q#3MMc=k8NiHZVg!Cv5x66GvQ@!L$9J)TDe~Q8VTrl+MDAg2pkUPU)v8^lP6{NfG9z{1JAq#D{M+6Je#P!OF zZotJ7X70hn^ZeyW$xUw0jF``fkhz{f?5n<dm3N?HdB(lQ_albE1@@+@ArY?h zAln-hegQPhsf2O*ewKpv`7D%Wd~M2+OfP>ef9Iv1fD(SWa5g_qX^j)8@J}d{B6d=l z#}SG_Xk6i{wkg5nQDNk;OV-60r*Ib1q3npT0q%5Gw*IWaJ|Z!0`H{bV5_2+qX(RIZ zP|>96T8b%rsp(dLuFwfmSTuw#SXI8fc+=5te_(O8$i&hUGQX)SoQ{r;EiY4w?jKHG z`sHR&80P)qdZQ!HrhS((EmlyAUr$ni?bIAp{1oV0{eJY$7HWnj=(lj@PHhHtE^*tT zTFGBS3568CZf+{9jG@ndI48Cpk-~jMWTR?w2Ult!VMp;fqqvHN-91?$tH01rlX}(y zT+vKYV~v4*(;YEOIunP=a*zsS8XFp(g;41_PQ6FXM!sb?wQHPe8@HExiVS6dzL8si zYM6`bixHnF%SRxI_fy83p%K!nrD{acffZzl#*d@t zOg+-0d3jmmN?Zpu$G0Z}uBdwC95Y=J?fv3VR6OgKvle?4z+|!x03+Y+*!-vY^zHc~ z$Zhkg1voS6e+ziIzjK#mqqrQTBZaOXGFX`G_V~O!PZVHt;<{h|Oh8HPhc0N9-Ux1r zhs`@37&R|i^O|pUnshkD@={t7;=G_lREB|F6ez-x4bSch-{zcSYM3L!6!fxBZ*;uj zJPG@aU4+N)&Don3By(YkblH%~*?4|0{+Sa#jF9I-P(Nbjb}B*4^?CZ$a}!J#_PoSa z%#KLqhwJ_mL+uWl`YVaGe_tlua-!a!HkFE8U}@&~xu)IeR>vg%raVwxr^X*InWxv7 zDB|vrd}&D~nf_C`9@816#u2os=4J1hO=`|?9relu2C=!dViAiwY!+bB3n8^Mm3sr-Xmbsg1?D z1f>y$)BKVO^GIf*I3?<&Y!ZYG;{Py6I_GKjliZOy(5B87-YF?4DCwB)O%g)cNT&V? zRoQSwB$^}?(3CVU#^bQikQ(b4n@`}bR*^khisiq2Y}3Oc>BwyubOd}@m$SUhvoHzH*#wv=5R+{9eU;Uv&ZwxJqj=03pGYj zw?~2y)=S~GGMim&qZ#Jq&gc2Ht*a8{T}1xvyDP6lkR_u&gs9)g#hBWHD4iYXpaY8r z{=7TH^)(+H40edZ%dKo&be`??TUjAr@nFb~jFQ$A+gD}zl#Iqdy`A;9XMmM4Y+Uok z>zCQJ&Td7;{MT2ki?T8^BQrBIJt`NcWuSL3Sh=_m%%tNi?ee%?%)Z?~K7pUnF$}{m z4x{%`KV~{QC8H?Tk|+zy1e3)d2PR437{w^a%8ZMN3Am^aX}xBEjkOc76Kq=CLyayt zuj}TOBaQ3kr=zv+h7>PLIr23@1eM>S5s8V*@kBC`;bn0vap%j$`1kTrx%sxBRly4+ zC^fP5I7KSJb#@vc4Ppm! zis6WTinogXqDDnxk(DHoLgwHEa^qAK6$P+8vG1mqghp$6?ocYq>?(UnKE`|;e(^!o z-ioi9-V_z@a>i0#l!S;uIMp%BYkQ0?0r^ zG0cbRkBAXzvlC5u^F6O}&>~z3cgKWTn4&{-*HYyX?u2{}g6ssTtLMhZl9gF4l;IG0 z6NMs4??n%QZeqXq0NxgD!k46uTH9oZbx}zruK8gF+11g7;AKFBx*|JCoxCQO%Vm(# zvC{Lm&E(ibfwr3=)pR(g3CS`LCm!SfjO0JVqJyj>ydbGthM`5lo%14#0u9d}5lly2 zwiBV-kzw13@?}1`GhK%gxf-S7lQa-!?^a4?$oGOI!=n;=%5hHNMiEg8Qj&{wKkr$D zrW}Y%{GF;NRb=E`dTWk}-u9{ayu(f7cZ(|=Gk$cU~wLY74&j0rrPmX8{`SEMm% zsj8AvNbuO5h2w5{hjdnEvI##m1Gi&oG*?mv3nktm7GEa zkIzYfMH9=F)6LyBZ5a8OfAr{pq>>rS0g_sb&>O?Sqsb_P&AHR8IE1<5AxVrq#n$%5PKt?}IRB{~N5?>OMu;L{$fBMEq$o zDo=o*2GXEfumO3#e0fB&jDwBC?YaqA0qx=|f|dfAphfcWnBrF1TLm!HK%Eir|C!>_ zc^(jTgd5!0THhFggJLpSK7F_@U%hawyx~%=y#A72B=k}Y)9gypCkX!aUg9ip5cSI;S+_5(dkYRAJ599$I=Sbe_&InszJE6DnIkd5R3iCFE z@?La)_o|9G6TGhoZ!>xWQwBDbJSwT~egI}VE z*E)z3E?CilA#rzCHS6t;G773bGgPzL@_OTO-NU>$+@TwUde$bqIxFezlC0JYg@=B! zvXQCzMkyPt<2yp{JCc`{g%bs(I93!j>cHlYKpOIzyy7~9prWLcQ=EL78i8$jikHo1 ze0v((i}`*ze=_u<)FT#5Q8lbsmcLx6dP7@Nh@ zI*7({k|k$2DpEnTHrREuXzDGs{>GvaEP97)O49r21K+t&fblR6o^h0OO?685839*~ zc}7k20-q<7%JHuZVUhq7_d3c;h#vz7}3)!5E zC9o2f=zpuhR9sV~{Jc(LbdA^nE--u%@@|-w$1|x(Us0zNvhb&gCKvR6ygVb(8fp7^ zV->~b3rA|ud*BKS3OL4%x)&`a*hz+8C2z6%LjH0Rgqs+F9s*j`#u-98lwFEk zahPmrXzh7&eiYz|dem1m`??Fp-Gl);hd$SRt&eB;CV|qCSWz31z2~jP;;g!jZRf`8 zrIWNHR}nC*59|s8Ojy@e_ruw(U!QFvQ^y?-9BLRF|~{}8d@*?!V}_Vtg*4eIxWx%TTP1=T1plqeE=GfKB!D?!dAOfSz$NW zPB7Nvex>=n;ksUA77jv3XsK7-o9O+0eKJC4`GkbnOk z++KB43)8r5J3rHevD>^ilnYWGe|eRQ3tCpS+B7aluo9XBl5vp?>1oY7niNei(#R%x zRxucfo^sr#&Ro3}`sT9V55eDXYmFd1R`8p=_}KjgdBjpQCU_Vqr-EB4 zLym896_a-5v=l*Qp*Tg8 zG9hlkw}b8K(i}t34)Mo3M|b}n#YDRzZ!j@EyAEm zofi8t83DNcrBny_NJac*EtUUXyMQP*Xluj-Rmb4chPG`<+h6btQhh%z2tr?ax^E*= ziJ^=kT3B)_qTw+TfzK*DrNw46ENaP)$mJbBGj+8wiO9?rit&B|@L?dl+@bv^3O@CN zM$b@F{r8slK8aERzN6$H+99jfp4j*W=a&>ZLFH9K2Q<*7%Ay=ZwWHcf_1L;J4MIee z5*I;9K}u{Ah9m1iJlt4k_g`g$HJicWl6uGZZ1i+w;5)@7ge#Nnp39gOp(rblaqM;^ zpAQ4VAQw)RB(8KFhGf9Qh8}xdY1Ybm?iY2uEHI6C{#7)=Z6;DsrZi6E<&ms~Gt*n2 z?G4rH>|CHyS?BaKZptq;Rc7WZ93$ z>xhUGY$y08o+V0e2q%V0s{7f&$m2~Bmzv!=9#ady3s8T#8~fG}*4reeWdF=+#PMoU zQwurEzh$mc;Nxq4S=W9PgU0Iad2~cOInwvFEZO=H%yRRg`H6~mw7X_8?P4W8EqIk{ zO1SyL70laN0_Ze0>tDApn8$v&98eTS>5V9QalTDh*bupqzT)&w!i}&{R59DNa;muE zH5|z_5W>Go;>UbSN5>alNB^Q$zmKFhah8XxQ01ZZOS?PoDI{4Q!oh+ko*|N8^Kj|4 z$hK-t-%zmmrR#UgLG9R48}TLsx2=7{u8bw?f@+0;nP_RG&5cJSR15HS$fWOb>qfBG zwSVxW_>~&?9gnO;^W4*A!lPxRi67-0jwa)7-a9p~iw`SdIsrAZB#GRW_2yx^9*l&n zs#2gw9_eA}og=(BEbyT=k77@fRSc|XqBP}>#PG;5YKF+nCr*%Bw)n-E?FV(529(N0frIhu=Vg)e*wwA!# zGz;T0ALyNqxQ$AN(ewL8~|Ug&dGn|9_RS=J5VmX5{yJ(Y|JK|^9!lbunpf>^3#D1!USs@P@CsjID& zU%XWTn6S!GMWN2pFC98hr;(6zc~Ov}%F4uf(Mt8jHNJ1=h^?`csqxN4+Bj{N#v@-Y zA`&ef8ab~iyz6=i^TY?A&=H@^zs=@%z7>3NF$FKXPg*z-wUn&l)tfnj;~(m#kX)3z zP1Z<_c%jN}_+pT>DjQXDaS2r@Q-_uGaE$uyhp_P$lcB!QN(q1H;r2hTA2J6M!|wY= z^Y_Xw!;^{(cL6I3<;tm#1+98Hi%IU^yCPX%MAbDhG(V(EI5*Yp66mHfV zqVj}fqW-@1picM8F}|ev4!T@TDNAyCJH+z4FO)5HP`f9dqjZF3QY#ubrbOrn{obyw zN;QARVQHtvd9xQ#ucXXZx z3{*jIh9DyW8Q6-a>V^4<)dqN$B^C7#Xrd^&-cUP6fpzbde|W9?7o;70;bL zuWX3doX?V`Un1tAGEgl^suI|&irJ~>=h_uQfREKlPq(q*+gZ)-^m9Rv4b~Ea4c=@R zt%l|n#*f%s@qEHMaJ8CX z!}rE2(a8z#GUd{-6eGKKK-SSN?U+wry?6v%^8T~q&K#rFGLGE$F60F8)WU-;tlKYC zvrY+3WLn;nk512tzPs1=h`5U?R1oX+YL%9xoPB2I`6`>5R!Y*z&GCbk8aDfvqiuqH zXVvCFz!dIp@T5VO$nt^oPxTa%ykhoUstfqqCtD=qO3NK!uJu!uun_$Ry&uSmWvOfo z_3*OUj#qPz*?Bj`9vMJD>t}IeqqnB6P#XHO!nv;pj8kH^+qIBVx7|? zp=Z_=yQ;gSMyEIYgA<8d9uc4xB5UXUW!3Uo%_%wf?8~#2g+C6yIdSo5IEJh)M;-2E#A()YxKDExwbjNN5k6I z^B3RB8ksdm{w(WRJ@Sb$l1=>`ie0s1&|5%N+Wn4wWMH)cL@>uZuTYb{zt7lKuvPG% z&~vHYxfru%zxBR7Ywpv%#?Iz5BeZPGc=z=Bl=M>5#de4-k-yWJ6oOFsS0SU7GlUnR zMk?`w)o#g5{z}l6*!h80>?%P-y=m;FHR%0s?$fa^R9LJlv{_6j12>8Qap8(1_!r7T zQs!8~Z$*HvYuU3BS3e&uds#MDU8Oj0Dm&_662rYr=k=<|QqWJjE4FH9fTs=~Y;sP` znaMf2?taPZ(&B;oZRB`20l^U(Xuu^N<`LphZ)=v*I+thrO4TIC1Y7NC;p{ zCS%NhxodzaQCu1eXdw0#ZOATHoHWPboOMrOX}J`wvIn$7d?i6$B`XfCn<}=W8VS)s zJY-k|W(dJg!umK*4<08lkiV)zv_jgejKrnJ`hwmlA-_WP+bjrF&rrB09X8@xB2xla zRg}Y_2NaVJISZ^tnNG{GeKcLM%jfzh;(d*l@K&$Ac;+b)<%n04EI1f`zQz&`v$tX& zy}>DbQFhD6RXFZ07``-FK#6kXv+VLJp$#M6CvrQb6oNPD#+Sp3afm^E%O&Jz8b#E7 z^HWjx29WhcqdoHP^y>?jtw+ieclKA=-r`!>b);rE7&72Chgc7iNHtbsU>(eZP{@gW z8K+DN4g8yW|02-PAmK$Em%FI|S40{X(I;{|h%iVsPX=jkf5a+r5Cd_|Pc2 z9Xt(tuD$-OFHmE4_4w%P`hq2Qy5`>vfXe)i(dpce@>JFiB1%a&hLCzRx3&tR01!OX z``phGLSS(Vd3CVo2ivdazq~CJ3OVO=c*Tu6g75W|JO895g$=)?!1nUs9)LByjIYU6 zH>Sz;uWkafyKTTJ)4+(8SKM%`O6lEBBNed{-N^WkJ*asWow}QLv+FB0qmO&I0iPMV zFPrS>f0OjNS?GSIOQa`_NoYGiVp!k2+UK(68UVv0+WYpfpk&N2{HuR?lkT36ACuPi ziFdUVKoj99pw#Yj8_&zR)Q4N734sOgN7FWeu*5asd_H83y&4juWD<{QrAY9x$Wtpe z9OK)Vc3GPXYu(2?_bgJZBN(wfG_Q7OdnqyzR8vxb*<5Mv-*9Pn8OQbSrG^*lL? zkI1Xp^(1ASdMtDLikufIYfwhqEH|L8CS_jF|A==Jbe}WDMv}#@)Vf%`(x%%a7%^RV zvv*tS)ws}6L!S$D?w+?aHKg*q&F*16{mFjWd8|(#=TO9AXE3}jGe)P1A`qIkj!ppn zg=VA1d!rosHi9{*%>;cK%U;pSU5vh-&pzdTn&IMPyi4QM0c{|eunxP7jRZ5?cyHbm z;%f7jW4gcE$tG^TqMRIN*iz`(4HsV}vAXP$BHGoo}Qk z1JqdflxmDMB+C89?8AD1?agO2rK!|Qcgv1^Zn?{~yByx_hy`P@(}dC1?VkIG^8L`u zs@Q!fVW`)8@UTwH+LnXKgygnUm93Ii#7dpLR;M)aWxA&65=RALB)1_4P#edhVq(;FPM_~pCxWNbw zRg=XrlWYwpW#C?FcdsX2>1f)NZcmN*SVGLZMCcSyfD#RW8LW;$WUZ!wVD7DWPmj7D z(abEsE@nP`l)OkD?NH1;8g&R^Dee92vH6-Vs=@jEu14W%&~S}*}5C8U$uBHw`~gM z+jQ&d+-bUNz7X+gtB~D0c`e^Z=qQ{rG$N~AFBanT9s}vZ?~|E=R>w~Ij)cnu@@iHg z>C0xYFmR&8u%M{Di=b!Xq7+${j{={w_0AI%P%S+ zHb>|_K$Cy=WUnhmdlF#n-Ko=`juT?(Vl?SDPoJNxMlv2w>K~2qNxPphY08cILTk$; ztr?dA9Uu(w3B_{-SVg$IKpU@QFo|y#mu4#4FS)DLmZkGZI{%snG zKz-%)9T)5S=znqSzV|aHfS}e}x5j$BO1IXr!Pnj}Z4S8MV(hC65u(r=7cX5=#etu6 z-Wp_GeW|I~1mt%?epVScl+PWPNDOYUZ&svinFI|3JLZ<@%OCqTMz-dCaw5d$W*KKF z%3zS*dX>LRTb@V^>B~aa%>B^am~L9(J;LF+2sGelL6Junvjy~8rR&;2rB_qSzR}N6 z{V{~lhsFCQfHWBGX2(_brKitU$o;D@LdaGve?Ey9>qROulEu2W>HRD9Zpl^03Mw2E(wK%+l($Z|_tAq(_94u^cn zQf_bURAW@P(iOlm#~bVcxL!FS@Hv(q~?{q(<-g_cu`A^h*%~cal*a@%rrq#N|`0lkP{#R^EXNp6=^7Wba?`|7pLs9$LL%d>+pyOY;BShkl zkm!_YRJ!z-v-vJNBGRZ|)@qC{kXcrqJGcA3M#w!RwK_W&gQU4Obaj43rv40%%~R7e zIvponEGF?td#=uQyA0-1$$!)M7rvpA4d;+7;O_)zN;=PPQJrL%#NDP=*w$T7#foTFsto|0cE7+Oz;ba%IB8T;v&Y*-g=DtRer7S;J2mC$%Zff`T!SUikDo6+qN z1XXt?3vrUve+KH1>*>`{cG*hKBr(U`cWL_tSG-btqEhbSE~=&tMaN%P5LtZEQ_Ma< zd9bEB*W45jN(`oIB*zT3yVenFpfGRKHJMokV_R-H2<9V%KqNPj z;TJ>xE4;y{)6VU)?}Fq?P3>d;Xf*&_#IQtD;-x$-PIkrw6BR$FxOwT0N36RsBZW^& zw6iI#YY;>;=?7ag+VEsJQ@};2Kc{sSizJGxA~zFEil}`TLcGR= z3{E(vAIN%}zlXFvXf&F&W0)0Hzx2jfd43}}@cFRBQO7896lzyai!$KRT0n53s~VKd zJDjDND#a`jEIVQoaHOld&YpJ^bq<&J@)EXmqD>^99;$1@Eu&maKXLsv8(kXvFS$sX*`QKFAo95( zAE^RCPifc+I}LBOV=Cd1NJo3Jyt>Gw{=K1I_7_G>1L?N(mk~CP4zAV(PwvSTB@j<1 zSnf~WNu1x{B_xLNoDqG_bg$)hr6Imy#VnGkG=&mPhe)McL|DfQAdg2qMt%oNsd7@w zr8qO+5!VgCYR84N;=Eph5r%p`;dIt6%}17+6}Te{*Lq(v)8{(s-3OA9_~Y*wE18UJ zB&2pL2+Zl4)*L7IuL|?Vm)(T0%7uXJB{pGHq9g16<=_LG6vdxl1_mw^DW&6sZ$&p51Ib}kOTp?APILfl zno@crLso-ghismW;(iUYZ|*-_HvWNpc|_zU(qq4z$pw@zEe^4b!l zpae;Yv5{84rHy%7hG*f%u6Y%wau_w&TC(~gQ-ik_1zJpNcav~wPjK?R^X4Lqp^Um( z8~9{7=j72}g}LscoaELT%wSq!qSE9z<|;PT8gr#5PHXj?5-EW-5)D;!+<`yhAE^+W zCsN&9r8O_Y)7s$!OB%H4WHVYZ^XH(h5lv|I@xLugB`n-a?Td}c#Ud*&(;Aiy%mO&A zbGW@Kx?3&BRL#H7nApq?1d?orefcj5v-_Mn-$Ese6-|TdkLLCKNNPNz^F5-AM5Q84$2n54h}t96RFOozfyRvVXDde~zlP zgYU3OO!6FU8??MJ$OvJez4^P^&a=N64DSmkUU3=A^xf)G&pZ3@p_yrS4n-~H?AU4E zu0J;OYNsUBoPmPA)iDk(Ii=UkUBZ@WzP8zZHe^Z8_#)oL2w}OUgL^Ro(Lp-5o#c4c zBsl$=O7=7IjXA;Me0s?0v>*7INTzDzM-z6t+B|E3xf};cqA?s3S%f(Z7z#!~D>k5wgaL*2@7DzSqM1hpP|Yn4nNk`6Rl-wA$@FHt)$Q zejSIVQwZ$OZ*S|~t3$LiEel8_g_G~oTHY}w$CNqd3ut-H6_Ufj4)wp4dWSh=7;X4- ziUXd|bSmnPjK#;oh9KCn@CFNlm=!;LHCE-<=}9glbh)O^m1^&`dfjn3ebM}G>QI$n z6_Sv2SJzI{Ve`c*V@5NtFvrw`wrUQ;z%zkdqzmyC-e|w`S67bCMjMvy3`?KMH@ci> z06ZUAHNyl6n1{nBNpt3&$u6=VM+EK;Kklx-+XoRgCtLgxZ5J$veM!N+_1f^H5l{FFbKTu^>eugeIs3Zz2opM~pXtn2-H|Lt z673Ilf2)AXH*@Bfi(Jf$1j}J<411>LYr2jps(y#EKVG(h+Gpp+t;Z^h#%k5+Ay~My z*Pi)^dE=6t9DS60p9M7VH@-}(;T5Lijy-5Mh!1&4cX@ztH7|LXV*39vKyJ8z2}azf zRu>-1X&+l9 z`MdQ$)ruBrd}7>-7GneT1nX={HYOb<-6g}~YHDH2hew`2;xtrtnc+WLD@}pN$F*ZY zrg7q>T&_I7t**i$n}C41#advMks`4mchIPy>*>&J#%m-4BC7(b*={o?geo8$RHbH*5`6Vk_90^F(I5Q}gi0S`w zPX2wB-wIh@q@_9B$@hQ#;J^QUmiQZbW`)EP`-ZIkH}~e>MZhv?U(0}2Ci}@K$T|j) zl?_SDiCsTg@Ye|2TkJ0L_|E-PKE@NDgF7(=V*H+fO_lJ~aCIeZf2aufoXw372HvlI zUVW;p@@m>qzn*6>vsuJ$uSbz*1R_Bn4c<}Bz~AtnB4^CIoGycTodpcsiyIwHd548I z?CvF4S5mmK2)+f(KiZ3BH54=&aJ26J?hEVS{0<@AYacB{oK;+N==kpkeh>OV*Ly&Q z+HXh?|pj1y_2f`$0&RDcw_NCPtIXj-&OM{B%~z=^W!gLtk=(82J(o^BT%q~`CD8@G?n zZRCVL!M_SD4{%C@HWvvi2Msi`D6Fu2snoOOD0o`0uxe8Ty3C-|9#=e%hx`TP$^b{W zu30yiMho2i$;rJt(OX$C z!w8{AnJe*&=F20}*DrEFuQF0>@cWgt8G*m2ehPA}d#B+xACW zRl`jFq{9CqEb(s;>~3{RZcw00O?=OuM2N+`#^N(cC{gO2cGN`hwT_LEq1e%d8RCQb zc%`D8o=uI(xI24Ru;yE?3I9;ELi|_4j=wn8Vl%TE6mNj+ZsnWHgxk~I-I{92He+k9 zCY1QaX4Su!-4M|kvY$unp6kGvMwbejF#$-qQv-^LbNyZve^BxES{WAbeKN+T1ot-U z!~Pm)+4F;-p`-bS8PrvKuoumQ(un3=F~gCEo4s2wm^!AqN9G>~{nxVT`+{h?U6K9z zIf29wa1rQ_DyWyBy>HE3rewHu2O2bTAU7!!mXR!_yR&PJW1~&ZgyzHohsO~xnHmto z<7wEpYY)Qed{SO1aeV@f`oqwO5)klXi#-VObvf#%D{+Wf`w5*{YFAFZn zqhnT|H%(bE=IheD#QyBULc7B$oNzIjbbJ!?sesTJ}x#P}IoYEh?>Px9tiX+)jjejCl#e z5Ydh1C!#4DQ^-*b`W_`MvD=;pFFIDR z^{dx_V_8JlHs`&rDr9{R2adBqDwg)t!-+2lgspEX@k!7{T?*9>0>*LS2_;=vVlyN@ zlI%0m)fm;_=i!58C6)cdkVY8VM)7F%DIfXbi=upcn~8;#-oCsC2HEt)3jfRo7(Vw< zkI#}qw>k~Fbv*6L?~<_uWo0$XK(%R4XIR!oZ^dQh+-B#qvK1{E4@kF#5CMV7>s0h4&P=J7j6p9L(tVzECHwCNHh|r(^6A=|X76g%u0GEL%G>(u7D|7Ns zOI{I-PZK_h!B23*b-g5q`i{mr)m^rH0}OV07(WWN;#PeC^O_x?-||wE0W1HfL`xAd zckdAvh3ku6DG@U>N-RzCLe-}^0Zu4^1Pm2gjW=S!0QpXxUS3p$meKij=rkg zp1c<&A2ae)8{-4a)SK{y`KflNOhGb^#N9;(6riPWquPR2E%z5JiIXM?p6jI7Il`FY zD|3k5`Uz}N`Fh}TS3W8#TugEP+Flr?wFYJ(T+RH{=vi_8@f(g;sY$H^FDqT45D{!U z_K;MfPmgxQX}Fu>2!njuktdRL8a8lZ^8G@HtHAVk`L_Y>Qwd4UN9(o{w0fGDZ;}q>i zvf1XLcai6hy7LYK zBPq<9WUgxw0|Y!&f1<#U!rXcUO?uJEHQ7JbciEQ6nn6S?&mJsT*OxLRS~?*Vchaos z%aQ3!_M>zFRR38$(YuBl-m4NP8HFigWnwd$V!=Q;BkCC+fsIEg1Du*LvO}&(g*PHq zW;8NEk&cY!(`#E-&|WVDnAs2YsceU(sfhYnEk{u*oTF{#XOAV9;5H$(!g`yJ7mesE z5fNac4V-#%rHIHhBU;GXkh-ao)Q-0RcP!vTr4V(XrI6L#uD0Jabw;xLr9u5Zai88Q z-J32y5BVHU^IknfEGo;9_ClAG^a|4}eesXS3V#u45%?sI6$6$2C&mtW@94Q%j` zgFNx#An*f;y^{+2Ypmh-%LXo%O;d$yPgP~K8nGxA>h+ z@srnti+OK}6rg((xBaEy06}%DC&T7~Jn76kC6l(c|Df2ROeOMLYEmw}5$jTqJv5qL zFIQTaGga(%8_2=KkwG>c!p*;+ej=VgS)(q3sfVsq&g`+sV20%Eb|! z^&qsBcoP|*eZ}NTQVwXZ3d=f&B~g@RWdos>R|kCL^a~L>AN(BeS}>J2R`RNVD4Msj zl`g2tTr|Mko0WW`)Rskpw>GB$cYWcW&y8+z7FKBjRD#rva9-#omW>xBX}m$7`vH3B^Cy z$TgxO|6u8BCDcQ0Z)MArX}~wQOCn9p+SU6bzT?F|e~$FsA%{?C`-J^imSNWM)>^FV znJ$Y1cjQJsV@EQh7TOE~BJZUcsI7QM{)O6Q2`nKu4UcM+9{(xaEIDoRW<2%vsz=g# zEUCHW@DdWjA-;#I_Pjv*ott!NPnpp7OR%fTxZLR%j-b0^8Xq67Ln5g`!WFt0PY)6D zx0dNdA~P^eAH5us#P>ijDz@a&IxsNlt`h>C8EJ-e^3%i4&yo$M=}s?~yt{@1!%R8J zxHAjt2i?y0Rkdiv_bLj@UY4HjiKE}BmpxTN5pp0+Y^~4r{Ki7j#g3w z*}+^VPc=!qcR{QDV^1m~j^&#;BIXPGa&=}Y$k_1ufF(fwfB}?btO-{FfPFzf(H-o7Tn!}yCt~069|xl zySoK<*Wm8%L4rFR0tC2+keU0RnY*4(@bHSY&fdMNtIN86)zz&OMOVwApNg$QoJUS&z2-h;y%1AnF0vTn_pl)c zZ<-gdM64t%_;SBU%D)pX?UnHN5}mXgN;&$n@R+J7o16Hsy~A5I6HE~~q&mlNp&+aM zMSwoUwJ7hy!y`OO{5e=p63bUZ6XilHN*A^9K2h_gEG@5k={aR1RwAKBt;irDH|BYk zp}%?oG!?)gU_olDv1FK*^L@6ZjWk?8d6!4qT`WY5woP5fUz;cxmMS%5%76U}yP~iB z;7K)ssXA*#HK%dj8Q@|097g_?hcp`BIc?R4P~ zyW-!_GrfF&xvsr9-&=+*v|(8mk35gB+G2QlBBvEX_o3xYJ2E3Fnk0U>R(T8f3G%T=>XDwI_X-g7Zbb&2$)-=-=se4?wW4W1pc@|+CP7I8_;G}c zhx+AniQ3}D7T+JWw}dz-3Didz>;l2bcGmE?5CP0)KXLbOevaTokHz zP_31&aJk0PkSJC-jZ}44Md&uDQ}uehk-H1hL98wGZQhX;pdq0JC>|4*?QO+(x#Yft z);1=zL_^#NExtL5qeF5-z?QN4<&BdG=8!60sU?uo!8>1iLg!#+_>7LY?_Y<_LG0u)5-k=(r4tWcBz zls*>3ydcIyl6>;scTV<>)kSL_G&*^zr(G5Bdf%s7Cv*$qd4okC}4&p-(*24KQCg;B$eA9~tnLX2^W2{sJ^d`xiX}&4E{x2hPgP z%Sb=vNPI(6ZQe+^E=??U%ML^^?BatcxYPRV=tG3;Q0Y8o)46xU%W9XcptwI^15tHNA54Fwr67QGW!2C z#Uc14drC4EA1DSV?SrrE#~K{L^6B^0kAP01ri&TU~QQ01c`vaeE} z8>Mle?&(J+Szj8_UPyaI#EvOY6xKMdo6bdLeQ944QA#g(c^vNL_Ld~hy;`rega>bz zb7<-7e$jo_>0x!8FE!2no89d&XE$q?cb}Y8wSe(cbwwDX^T0QF-ss}JW_bDO@sZ!B zc*F|y!y96y5>*3QdbMQ~d0D60`2e`i5=xSbaD~p~!@)LBC8jLD!DG=DICa>X#W!W2 zEf1re&_>3Fr!6NKVuk|}qQ^;T%VIf*^$F?>8Yk^B{qQWOGW+LZ{gbXXEt9O+%x;hs zn!b2OE*gm$*fG5^him+q&oB_wtMg^^X1%BO)*;pHeyc(%+F2C^ndOr7CH1Dm3So7$ zpOU>BmhAV04tL~P_`#js`DA^C@m46v!$&c6vMqK7AD@dvI^~`+ZsSO`Wu~T{zKri~ zp_3Gs#e_tU;VbDA7-EQ`2mEE?reh(6cIm?HjgxZer8&b1xalGiEd$N#uIN|ML2b(| zLhMAQr|a8f`C()Gdtv-L^4)QmiyA6c;?>hDg1e2?3hNaZOx2n)`+WK%~G%DQmdQGbd zqo`>nR~}~fZAS%}?dcBBuWy&Q+zv@SRa$Zyd6>U3_bfbU+NWye2B=6ZwUk85IzT1^ z`sqs!s*|0f6L7@dJD$u6E6-u98bjqFz>4Vh+&EgHSw-Q0Yg9Lkj~i zOhKm~$05%@gx3rZvZq8s&{gt#9qP89=S(rWqj5gv2oOrt(2Ai9{Dl<%@w+38EdXVuPu`ofIgP)NdH5^)1m2%pJsV#}CdOo&~`U!;AP_PF?>MYHW z^rPMpb%m(2bNDJx!W-Jfz1TGKCKlQAqP%D#88l3e_nu!A!c&9TTiyrGWP zRE0vMGU5lNs&Jd~RzCpLUvy11klfN}zY3X_*|B z&?!7#QTB&$tTnPmkQu8eS9*~n$w~-u*D*^^7w6rscs0r4@2*ZaPSRIYd{avpmvae< zYN@-IzpXZx%=Zk|gh6l=1NSjbDesZ(gLDPO;5%~y7Ns$L=F0rb-mwzq ztb>*WhhBvwcO0oH1q)RWsGU!SCq7N65jh39D?grBN=5pV%Wsf&2v_BAeDsDMMa9Uf zPrVk-1QvLsfIN>JESK~aMVA7LhL7ge{u15|@4=bm4XIA%KKfT+aWrWLSGFpCRar4= zv(!xLgcfA_2C{jNyPb(KF4U zB>k)Y`I3Q8Who&g^fV%4-a+LM9HZm2R+B<84wOezLpEy|0*z zeWVTgv65)n-)VLYpC~>+=^&SveDM zH9-%VzA`EZJ(RWn7!m!9(3&X9IU~)KHOyzFrnby5GJr)EoMTxcE!rjZun{sjjREJ52K zT2vDm#bfs85GOG}#l|G+3Ig2#dMsXuj*Agp*Nn=JK((Z8o8O2~B2)EF?j$;P)0`t{ zn~T=mCVNfn1+!dw9eL+Cc$tt}>*1fSjB4gZz^neztSw2)&G?m?t+oWVh3UuCCQHHb zH2^Ay0)2`P)dZ;d%+Ky|5SBqF`WBktmEr09BvK5BCG9lh5sn$W`?%ju7X0>R;;A+G z$0<&?YYC1@wE#HS+Xk!e#1ekp{ye^W)$%?K8N$;jaHu`nfwzy{={>u%^C7M4Z$t59?Nk$PEgApQlAD!r|;x zYmiqx++kmMb5W#|20D5>pxrY4ZBhf&s9e)!J{5f}DQst1y2`HgI5(MVi?G39DzC%2O#BfkR>Iq?D$VDFK18gTElw!=f2=&gAI0bnm4P~P%J_RIGQGlMEq#r?7 z?iIdtDb23MxexFSw?C+0BuA zzKSS~30sk2EPs}XlZqbf=xWRU1!u)6=Z63SWS&K6o)Fvzpzo?W1Z@NoCwBOmbpAlo zCV+z|9{z5bNHwEnYkk4QF+&B9SA4@rJu>5{uwLSM<+ykT2;T&MODkB%o1$azLX`o; zqaY}$=KB>o0d(lvpF@Tu%a+Zdy4nsoeR6lKh1q6=K&yh75G?Z*>|n+W*l32G*P7ugLDPGHaQ zY3)|usMyE<;EuIopp~Ke<9G?=YzTRTSL1!|HW08-hd$2WuvmHk^hnc z>SKVl=%RD0&ds*kd-vYsgq0iD2TGz+& z{~(3=nH2jS$I1V4%sLvxG3%;1jSv4N1tgdNE8cQ12A=|f z{`m3#fARj7K9C&!e=i=QwUXJtm)uQw<08JK(Mc@uXhX^lxf==-LVa>yJZmd_#pYL? z6sTQ91$(!{?=t$oKIA61aUa@QguvP9RRuYw-wf`o&a+XSH{g+uDPgt?#S`-e-)>9Q z>F|o|Q;UvRMY+Ic0>d!@fEX(iOH-g25&QH13SB2B1l2g?x|U5GrUn8{We$~^%fVcO z6Svwug`zEV&uQ=-{@#`(ECDYSZEXXzp3teyabjpkTpGw+6u~EejwJryVuMa92vq^6 zPR@@uJvTE5w7hD1-VlS)E9j`i;$w>U=pEBnE!`f_hMe}ijtHe#@*pvP6~&G4zar_M zK)O-dQ!mS(_7+1Cet=lkY=v1PSv6TFl%BSF{!??LyIz|ImU_94{{kS>j*!PG%7=5! zEw}cX**v1}MMjUqIwJ)hzw)VwPR{O1^LBUkUdQ;koubNH^|tgQaq28XGra~K5^TY? z)^%PPQ$=sJ4)I$W6T_YCyY9Dmr}jY=ecomma8ze}sx+3!peyX{&DW#$i$TAgZZ&E) zwYssfr^2{DVsEA>_Udq@siyXt8WOqZ&!+)?ZUt!qzC_N>&Qp%P=aA+tpT2#*PVsZg zR*%;>UqeGh^|M)R7HS0*3u8DM9S379lUHh$P4UJq065CqIji1S8uxgffO=?@xbWZIMR7wMVOdNu&A$*t z0;peTNiEy$AdGG<5Hl46rfj;7>F7{zQi!bh_4Yd4A2;NxSLls_V-edsVs~iUG>IXQ z5mA4r!CUuzYKsd}^b^DV(;jBR2Of?>&Fif7dQKY+9ev72$Zjz8v)|7whNYOFvokZ2 zL#2`$t&X)ptomT`^Iy0DKzZt_O)AUTDY zQ~3y8U0tY{qOSVHX^LLG3SJZsCoQLTn?3OR-`)^0Y=)Bm<@@!adis^ly$mVpy=Ofw z9CJ1wT4)U8ahiTM0XZJkj>7yX-uYxzgTzqBol=j-D0YCqq ztDf}}{M*d(Qk^l%uN&OF$;bmcV|MyUY_U|tlOeS*Rm!p|Q?Yt>YR;J8pAOV<^2|?l zpmuFj717j0>DYkEpYWz?S5tti-YOot#avM?)EARq>keUb_oeZs@si91?(+;XOvFiyVd+xB*ZgL0z;6QvA{*_RV1eqhQl~R_x79iH z01o(xoSGx8a4_Ej3-+0ER&-Fr1VQ)1DKI#gGZnpLT#mZ`jdgVJfXwz_#Eaorf(4&F z`ILk*906ZT4nEn}gGn=6sVuWYF?acH_v|g%n!RXbX_B<_{pbqDvhvZgV>75*jap>NM{prP#8`yvMUE;+vD_E|M<2x;~5GC5M!g_fHuV!8K?MxIlHUVp*0|k8 zG=lgfBn9O8-zrfADd;MoI!ddejonP!=|U-`7aOWn^GbRH(XSEYKZS}d;e zEqoZLFJB}OSP0>uS)y#2BLfM+VO9OG)XH|RosprETc;m*84IEW;x(H4mv>@o^bc88 zv`oY=wy~3)l*9w6M@HQe@urm$dRz7($j{6;r;Ho;p1r|OK<`+PPag;<9T1_$=7Z$j zECAgppP~&TU*V1Z`@0epY7?l|%C&MXWfGu>V$Rq)F_x*B)CTx)cWpVFh;SZ-<_>86 zjCl0o9yac%I*A!7o_K(X==I{-_miW0a+*zz0C}tQ1^tw6XR6?p`ts)YCSr!Gb)$#K zfti>g;e5IQ0L8=wG)MK`D1Fg>0xN#d{uK1ON2>r4LkO}B>OaJ-gQ^*tE=e2ZesEE_` zP+NI@(M0WFqnVA2dW_axP(^rl_^pncKO{6`ymc-cv^kM_wSDsGxsb5!8MnDPf?>G8o`e2O*Avj#64se6_ZXu2+Q57odQ`!z;c32;g4R~m zg(|$>?#(xr%%5j?q^*7ww;-rxcl%*=b?-fWRGKIB6aSCWz2;%b0TL55ma0Ff7v)AI z6pu^utZ`NSZaaYH<^i+gewks$lOPn51^kKB?$N+s{tK4Fao^o$E)v2*RjkWeJ(VXk zP(}Xw5DEqM#N7NG8RN_qVufsZh}{X~HHtXZN_P*6WY#^Pg_W>QBBGErIQC%>Pswgt ziTJ&-I^3Qlj>w?ZaN%W_FUVX*fJbT+Ca8Sf!Ug5JQmh~TB&}sTBjMl0J@D(bwj{FE ziTn6hbt!wkQoRQnJcNc;=|+1Wmr%x~IG#UN`8CcJRh~9k*nX z(SJ8*GdDQnE;LyTfCm{O5Pbku>V#!95%++A0KT!hgAvv3(^yYUPbdgte#hK`9)Kv! z)sdgG!b-^45LKhCMY+)v{J5v-XF6d@J?*)zx8MC!nZy($k)x#huIp?6JMbIuDPt>ad zma9iTJ+Xolf}cdsely!&y}R?kMTFV^erT>otg6#HOT06ourdqI>1rS0j^JGIsNe>R zNf?E-;7wIwv{#lf8jr;YvF>wEA82J%PWQ(VfVNxG8&f+$*ycz6fuL*d=i9$@g-XvK zg~|vBb#0P2rs%mnZYy~?Z=hQEG?HL7SlS67-VZ8Xxfn7PKORYW*^(lJ1_>(X^tNVP zay377{4AcL8#xCfv=#oD1qUiwu%3r3?ad_tJ<{&k=y_b+Ke)BtEnQ`svs;aTCdzPB z?g$|fDlj9CC7b>WQQ2c9a7C`#sO8NQ0{H_=vniZL5g+m6tKhemc0x82a3?qs-$Yaj(Z>v5zeMSjvU@ zVoQFq`V+aGsVC9b68S2kYj6FiynIZ1=tY8rA)0&6LlTAK-^H8Pa!nhvH(e5Wf z5uk!X>ZiDr`5BwmEu?m@GdogC;#}A$!wzJmx7%<-Imvf`o%o0LcH(R>ycVD3wjCSy zY$eG=l%xeJRV*UrMIIWK?e#Z9r5m{y+&2<|A{j;Mx5>;ZwO7>wi;;Pc9?so>blX0I zAmy1Og`qt!R-B#CWs>!^& zVCl)-lK3#xemUbShx!8<(1Aea$A?*a?i}C~7vQ}&AtmA|C{hRta%}}|FH7eKyh2H^ z;W}gwSKTe&@1tzliq3|@>~k*3MIM#ss-IIl1Y$^^j^BBzmpxD0lFsM^pD&{K&!)3e z1!8qVqHabSTOWleHW)eJ2-o($Th#1yBKPC>RRtHLr$@+zr_6M>n>tM`m=+v?FCE9O zMb1NpZ)68X8HMqB`6_);dfCnGtGqCf=V*h|gN>>=Y+X4yj^9}z6rRiy3%2txqC27r z%-DY|cpcR+&+7kVqgRM!JGT9#Y;(D$6#uEBBXG(&eyNN?#dV)> zr3cTL<5wxg^JP@xe0P>!z&Qf?&63k2r<%Y>_}kNQ3AWK-P+-n+mDTu!BU6`A1IcwB za6J1^E~Qe-Hs9U?8F6sF&&WR?L?dvu#C0_%d@0{Ts_2Vqxvq%S8Eki|Tdy`@Y(`g5 zOj7?u*b=$ClyGqT0DQ-P8_M%8DNbWrdHdm!8L{AYJx9v*WD@6`72EjIQfo|ucb8c4 zUJbTyt->_<{&t>6?3(Ve#k<8teo0gNQ!Y<*WWs|C@6)9xtmQrEFqju9NP-w=yh6z0 zL?^cv^JQ>|janj@k#uaWtcJpjJg#p{8Kg$YpsF~2_h&C61Bp=YqTSUs7jq)uT^os)yDQtL_+X~p z4JMmW!Z8Ht_VMtsz__SFxP1GX7=WMO~K<`B=~#_B!Y z$Gat=sXDl;zCqmgYanhzBV19)wN`BX5m)vt?B{d8m}dv*k4>Q%S?o~fDl<9&IOy)5 zfy69-dQy5a_fv_q{HRQ_Ho+6eVYuGFXnY2CpWU9_u$Gr^+^{t_VWc4I))&~@(wp@I zQbXSj_dJq@PieQKkMg=rZzS}{Jn;>2Eds=@(%G6rSzcW_ zuA}C=!$-_I-1eGP2h8qNxg8{%FEqQvvgrfYP1j?D3c=Z$J6`c(LGu{RHB!cBPYOHu zxd?%RVYT8P$a#i<_1;jN$+FZ3kx3l1jHk0#7V^W)z8sB^kNnr-xUHvQ@#v^5MHjbk z5c&=OWEwXFpv;=XfolS<`}jBMAKuCPcnV_J-z@2*@GUpi=s4+|e54b{fuaVwF1Rf}QZ9MIK}#uEz58*8);Kn>IS~r2@k$VECWXpmeSNpgW!d zH2{KoG=kn8wp*l*U!LQE<-q*A8eQ$+EQ|Zl3GYz{Yc>W#lYEuDQ zm$Qh^UL~4q%C)zLMgVBW3WaQ?e_B^+Te|9xm#f3Lvn^#b$FqFqPl7Wa%#U>FZZ4)V zNV`W_O`5zDZ=pi`5pG^V0O5qF~UDIG77x}*VTii8uF9X z4OV{^5g|fypp~%r-z{X9Sp3sZ9~LPe7w(xA4+M{ccqLSBqD(?Qu>JUTK90NN-CsU4eYO~&eH{qIL%XnAhU zE;#h-mQ2HtSvHU*XNm8?`fpFRvjvitp&pK8grs6deyJae}q}U zSsZAHvgHU`CSM?9nECld1}Nc6Mk_RtlX&oct~RUE(8;*zyL=;;y60T;3xBd)VHTZdBIDJ$?GQxWWbHsQBke-_kg8i4Z~kHT8hUYcv$G zjcol#Bo~)t0$~qNYCLa1l=1OeEfRF5Z!was&xL=cTPi_t2Qpk5{ey-!046uHvquSX zqQTcU#g?b@k?NN?xnDua+Y_)gTCT4|ZXFb~s}03f17M5tu1M*nG#$A#OKif~q25o4 z=RVHnS#larYp~SuQv~7P15A-xmX8Hy=akmk&xsd`!{U-yzcUQ-RKJC=P4=q*ZSAk) z(@@R>tKA-3t5JSL-foOh|jKJ>HqzFeEY9a2C5l+5MA7>9FY z82^JZVjsDy0r!V-r685RwuT)ShVvd)5!o3)V)DTS-@oIP?G`L|Z% z3pw<`x$Yj#)9y@VwxwRG*?s&U*BnxJ4C%u!7ZM&maTH?ZvZ8Tzwx7r1!p;1D$QHUaMTi>NL%m6 zSQXa`DLOJHe+pb(v}+$!t%fh&AoZ7yxtT zy~}gp;A5RTu9Rm-w5Gg&#Yvy#ynl zx*JS&u%6c!70x?p-Vcfvj$O^e!X%*`6qzwZx7choh2RI_5OWfC?V^XWE)UPiOxuo! zdMUsWF@mq?zUu|~VvFsm-)@fgX*^an9LNIvNrm{qgEkVYma}^4FnL~P3>W$t;K8bn zd2o{*J_wB7tS|>=gFARNQuKD;3qEXd!aq1uF__&W*Wj=}4JTR|vMKZv4(?5xvE1cZ zz-7=dMGT(VFH_l_ZCzKR(*TTe>Wul6PQE_KW=M;7&kX^#N-SNC4o)$=0)&WN)-)5p zB+=kW|2iH>${w4qQ<#ctTMX~A>KuTTR2r?jrlS&lDzN~Vs+I?Z2Hs%MR}Y?lTO?DRpwKGtA1)O5^FuBth8O7 zQ8ye9wKu|qrIe1zpE`87@F9MbRWUUnK!4Z ztu#?d5EP-dQ7_{}qB;kep9G_xNCc6EVq(9Sj5Y6>kV7C!^1y?W3&N5XbTXkqiey22 z$%B?+_62Om=s37iJrEv5_bwfr9Auni>?bSucOMjcoX(ag%!6tQ9+{u6m_lw7*MAk> zr{>AnU*8{UUFJseJ21AM36~rKPOEi+r#Qf7LfePp>y6nG_0@4b9arCLbWQ@b)l=|>wVVUJCre1 zPHRsFqRG;M^UfcC<-39kPV6Vk%FCN8+V6&R8|}W8RaB@0Hxx3Kw}ABg@9(#^uGh3m z+illc_ale}PPQ@NAyECmph%dsp|DrJ)fq1q4lC(9Jl%Pt9lR|StN9J zO$oUkC7)(Cl-(>?*B~1rLBK=7nP8Bx)8u3}PL{PQ@LkU^CGokPWoc`dxL`lrXGXf< zpVb}Vn?X-q2(_Qp@XDPSS_y&u{+t(t_6bVjW0dZ9EtjW1puV)b zJseOZVEz`M;Y-dfL_J9F4J}tA>~2nUT_1nB-we@KG@TGBpKc$a{p6Krs-dkB|xXPzbm-GJNjLZ$>%S8*4s}H{xBakAPTfuuymhBvw?Y3m^b-2psMz)Gy-t?hAW5Y=$>*(T7_AdaeA| z9^JM*!JP&*m0Ghg18%U#ek2fQ<)>27fK+2Ckf7gjdN_Q8_X zU(|32zQIsBS`vC6Cc81zXTa6ZHs58S=BVr#J3|O==i2X~uHZ2klcA(~2VAyYEzI;e zkWb_KE}!wPNya4(z}WF?O1IU`{wHi6JCr$VKW90YJU!7Kr&+J&ht+C)Zu$|%Bq z8*ME|0UwI{u-wy#`x%019R!@8axGPyEf;mwTy_l{#IPmJU$g5*`}%_JA6$c1mL3UR zoj&8Eq<#<>1~S+lnK)W9r5UGKe*eriDm5yn>s`_lN?SEB!EeT5VW+k-I>MH_=i%cW&wUb=0Np2 zX+N}Z%&pfWw3B=Z28tD+ahflWd>Ql{pFEv6%2&QTwVL2OPWC&YP3TOeO|483ATYw2 zGg!#SG*fh9E!wzCc&ZqfualpwclhG=_*@K9bvL{cxLI-R*7nZ@aK|^R-cIGqob-j` zckdcFV*w|!MY_Q_esvEs3$g!ROIbAVbnPp#f;Ekb+V*z-ky{i9IaEc|lBWE+AP{By z8rW=JZH-$tRszT$>iQYiip^>B=~rkP?LbuH9hM z1iCr^QQ?(GfvP}aZe;uM43-)NA41Nu8D{N)<2~^BJ7>FR0>pt3QXg1wus6%tEjrGZmw*TUQE1h*h$l` zx87M^UhXzH*@_g1h{(2$e;tec`fhIyI-MHx>spJe?N8V|o^VZAa|#5XV{J+3vR6)Z zOiJlKdJ>uD?R+h^_qe{ll|I044Bx}D?Gy6-riy90iqlDz;~|$goiLObZPUQ zxqj_osAGILv`;G8{T-0#riB*aB;`ifMq9$%#`4(d>G6STPHyCq<7VJ$q29Tstj75! zZ*C!F>wB{YeM|k-R5bm~h`-xAcKNpw5d?T2Z^~{)L6{Mm`k|mN7q*|4UGeg_+KEcA zd2GUJD=G{G4QHdtdI#?#l&TaQNs(v6+F6S>@kut6X>~D|k+-gMUILsU^xk}V`8Yzt z5@D2z{^U59T_#beRQzk__c2IsG}hDdbx*Djw;%k2yrxnH^*QTgU=F9nJLeYb%aP~j zN`@D6UkBGyv4o`8d!yl__*;byLCq}sYrq;4hSW_|mQ-rlE#!ze+)?jC8D)Y_b}&3^ zmtSWf+5~r3IW_goIF(C8GE<`c$1XUHN{d4j?TyIcQQ|V6C!Njk)o-Om$ei&a!r8=XII{Tz~-L+KYKBwU&=||$i@|xncT>gEKi+y}OB!7+h?2FOis5`l8 z*#^o}tXoFuck5w1t7&T>6CcQgvRM3^j@a2Q7kv>ttNl1aQ-EXY9R9+gP?Q`|J3}_1 zo`loCo%u6;1^{^K4L4jT)Nk_VHYa9gBiRvx>L-*f%c)R*bK@_@?4kG%`InZ*)yFT@ zt|p8xeExfmFT?rUz`sW}X>pER`|FM0I{$kd1@t{rlHvGeD%Up@e{uQGQ#GPlOi|Wk z2BnrYX_zzW2yd~{(qVJJamX4nYu|oR#-%degX2oe+-KhB=fh@ zKEe1}FNWRnHyd9FyOS}UI3r6j9AW!9Z`BR6RSpe@vnbl_bgM1# zS@aNPP}T32m*!5o82(K}E&4!$q4k>y>n`!+%-Y{N{Xa|5xs8S^L!up+O)W@oL0c__ z|L@mjQT*j07OTN(zqWm~_HcnD^Z%c>pa1oL50MU4T{K1l`%j`klv9%_(njsPHOEt1 z`hSod38DFEx*pXS=eOehy-r|IDdZr{vz{_btv3R9EiIQLu0;Cx&|72^8YIgmOpAGC z=h^um|G^E=hs^>6%}IFoh4O!;rqU9M;~!PR|9b>uk_j!S1u-xyZ|e1h1zbq}=D>eW zj7bv;RGh=EiCniKcPu#fY2bZk@zA|XsM9QmfWRpz!?scKZ@LU9;X&m-J_r4}yiwuN zG_SZsZWTG`f-l@n_xI;E9e2lez9{}n!VEru?ws)UIOtz8BU#2OW7<IVJ7v+_4$B%= zf{A^&u8rkinRL@;=_dg{R*BMSsCoU58t*CbRQMg zki5)RhDxD4k`+_Hw_LdrkGTFNxhZTRCQJZEaiB^}T1?fJ>GqJBj)(R0
f^Or zc3_NW9QG87bz_I8ZUw@ z;9~fHR=S&|Z!@$*FDY4;Ak;3%{rI7-Y{d)F@pAY;71*FM#GKsPOnGQZE1P|4=$Y<- z&vE$E|5^9_`BAy-H${n8dL&-fP4E={9om2vDpyhN+lz7A8ta{UYvWgttQ|+TGVK=J z1+nVt?<+WDvQgBCe-t@n>uJ~w$(tpzJP)NwQrX)24x!s5?2oyu!f<;Nb=;N8REapO z>h(1j|KO*oHLWI-dL7-WVt>VS{USrLdMZWDM^pXvgScRaxdbZPW5bM(RBTKAk!LC_XT;p-&9|~+Z~O36hU~Avn(ly z7Kla4a8A<_>8KyWcgxKB#r6H$-OZH*hw~M*vFe`5VT-Jb@7PWOJvJSbvKMzs+#Snq{IhrmQBQGIeC3-9*P*|3K^Gs>eux# zfR*4N^~;Kc;=%^nSA1vvP0LlZAeFjF3xC!9!|gH+FLfNsuseqrxAncLe&JXC3i=s^ z=+OQ5vzOL?Y|svrQ`(M(_82_;3xhhpLgn~dVNiW9<`BNEc3AbFuhN!~c>L_~!%?qo zAG7YBgFNw#B~QEebXoD~@_!5m7n~^MVsXR_e9*z(PoK|sraJ{rqw))x2gc5n#6L`{ zEf`~&Q&mmm`p4x267C(We!CSxW^mpJY?;ZwTr!A;w|r#0(jR%6T+d5CjPz69MJ!4d z#fB+c9f?_UL|`T>a#RS!mc(d3netSf$L~)dgBW431eZs|T4=oz2K(X|CfJ`N3Hhla zAsH-9q<3+|bw}M+U20}zKWb)aZn%4$D01J=02MdSXZ6+#>Utd(uT+XNDpX8)k17BR z)z9iv@ENSX%X~354D$Dgl<9(G4cPO9Cd*X6l#UAzI7z8M?OBv}y1b1%e29h=|8F?< zTlXelA;O`nX@oQgNwQZf36>C-yQ>+A>pzr{@3_8N}wCx^$Yq zQ^jS!Sk!%}qokB_fASac0)uddd*5EyC{I<8i{Z6MUrxjFLBSpC7IQI4Rs$ztG*hGP zAg;^4-()o6;vJuUUwz3yioBjp{l(JdLHGI2pwhGNFi3%);R}*y#pr{1G1?$sXG2mF zYd^a|UVu?@39CGJGQ8ySRV81CvV>n4?{I$n(xiAsMy)8s5fFB3TCK)Vb37d0OnB(| zK6UST-S+UoBR+dC8~=QXH@qnn59c1iuw2X->H3fQO^lqU=!+xkiWt;R z?+sZ@9oi#qmDM?G#ab*VE3(_SFApvb zF7HAHQy*G6))P#Wx4j6LIaXU&wg-O42U$Gey!C4HqA$w?vVw1+UO_It5pjkrdXp+< zZRl&*B*{64eGqKApErj!*k_gGmCmuR71RDyb*JR8u4m|x!!C!?3Lcj1v9G4LSW5#XB`UT&Ln0r1wDnpJ9?10{Fl+W3<`V zX2@!`TmfP#h+{)<31V}Rf6J^31~qTr$y^r9i%x*~^FT>MYNp3seoAevZ}nLJmLyv` zHb3b?jgg>uzybsVgD8>`6?zZyd@VE_MlNOY?qNzJ*{Wys zbIk>BcawAHzvH!F#k1Ca8^f7Qd7-f`x6u3SxBdCiHZ&Fr+dfykXwgRqsquk1ZsvZc zuI9?LS1e)`5wfyWb@J`~Cf2eR!*dF9 zfx@~&&s2N}7aX#8o=alVdl^~~A1VlBT3U6X=?wJ^H3MXeETt*u^a0k>Dn!Oq_|8i~ z((6wjIWCTVR@WaNYUpBl>q{)V1BIL79KuupL@du^-U(+78h_C53(NdHPcVObRNPyD zRrEAZb^~5(-a_PH-c0_$!U9DJCcfo}JRoCG!^Zn7!D0XQg@e98T#+$jZL~7O1*sS@ z-ZN3L@&+~x`(M(fh+!W8q#!9XI&)cVG^3Pe^k%ThP=|ht|7W1eweq&I$*)J_3yy?V zj*d1-$|H$t;Q8`_Rcd-%YlOGc-*Mo}O0r31&C*soy#J<@s{YFem|8kqlV8=_hZUPx z8nYB<# zE>)*b3>Kk_$UEKDPATOw=VWuXBaumIi-}$t5&Q9jf%Sl+hX4Qo literal 0 HcmV?d00001 diff --git a/docs/index.rst b/docs/index.rst index 441da99..e90aed9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,16 +1,39 @@ The lights daemon documentation =============================== -Contents: +Welcome! This is the documentation for lightsd: a small program that runs in the +background, discovers smart bulbs [#bulbs]_ on your local network and let you +control them easily. + +lightsd exposes a JSON-RPC_ interface over TCP IPv4/IPv6 [#tcp]_ and Unix +sockets. The same interface can be exposed over a `named pipe`_: in that case +responses can't be read back from lightsd but this is useful to control your +lights from very basic shell scripts. + +lightsd works out of the box on Mac OS X and Arch Linux but is very easy to +build thanks to its very limited requirements. Check-out the installation +instructions: .. toctree:: :maxdepth: 2 + installation + first-steps protocol + known-issues + developers + +.. [#bulbs] Currently only LIFX_ WiFi smart bulbs are supported. +.. [#tcp] And not over HTTP like most JSON-RPC implementations. + +.. _LIFX: http://www.lifx.co/ +.. _JSON-RPC: http://www.jsonrpc.org/specification +.. _named pipe: http://www.openbsd.org/cgi-bin/man.cgi?query=mkfifo Indices and tables ================== * :ref:`genindex` -* :ref:`modindex` * :ref:`search` + +.. vim: set tw=80 spelllang=en spell: diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..631cafc --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,105 @@ +Installation instructions +========================= + +Installation (for Mac OS X) +--------------------------- + +Make sure you have brew installed and updated: http://brew.sh/. + +:: + + brew install lopter/lightsd/lightsd + +Or, + +:: + + brew tap lopter/lightsd + brew install lightsd + + +Make sure you execute the ``ln -sfv`` command displayed at the end of the +installation: + +:: + + ln -sfv /usr/local/opt/lightsd/*.plist ~/Library/LaunchAgents + +Please, also install Python 3 and ipython if you want to follow the examples in +the next section: + +:: + + brew install python3 + pip3 install ipython + +Read on :doc:`/first-steps` to see how to use lightsd. + +Installation (for Arch Linux) +----------------------------- + +Make sure you have yaourt installed: https://archlinux.fr/yaourt-en (`wiki +page`_). + +:: + + yaourt -Sy lightsd + +Make sure to follow the post-installation instructions: replace ``$USER`` with +the user you usually use. + + +Please also install ipython if you want to follow the examples in the next +section: + +:: + + yaourt -Sy ipython + +Read on :doc:`/first-steps` to see how to use lightsd. + +.. _Yaourt: +.. _wiki page: https://wiki.archlinux.org/index.php/Yaourt + +.. _build_instructions: + +Build instructions (for other systems) +-------------------------------------- + +lightsd should work on any slightly POSIX system (i.e: not Windows), make sure +you have the following requirements installed: + +- libevent ≥ 2.0.19 (released May 2012); +- CMake ≥ 2.8.11 (released May 2013). + +lightsd is developed and tested from Arch Linux, Debian and Mac OS X; both for +32/64 bits and little/big endian architectures. + +For Debian and Ubuntu you would need to install the following packages to build +lightsd: + +:: + + build-essential cmake libevent-dev + +Please also install ipython if you want to follow the examples in the next +section. On Debian and Ubuntu you would install the ``ipython3`` package. + +From a terminal prompt, clone the repository and move to the root of it: + +:: + + git clone https://github.com/lopter/lightsd.git + cd lightsd + +From the root of the repository: + +:: + + mkdir build && cd build + cmake -DCMAKE_BUILD_TYPE=RELEASE .. + make -j5 lightsd + +Read on :doc:`/first-steps` to see how to use lightsd. + +.. vim: set tw=80 spelllang=en spell: diff --git a/docs/internal-apis.rst b/docs/internal-apis.rst deleted file mode 100644 index b659011..0000000 --- a/docs/internal-apis.rst +++ /dev/null @@ -1,23 +0,0 @@ -lightds internal APIs -===================== - -lgtd_proto_* ------------- - -.. function:: lgtd_proto_power_off(target) - - Turns off the targeted bulbs. - -.. function:: lgtd_proto_power_on(target) - - Turns on the targeted bulbs. - -.. function:: lgtd_proto_set_light_from_hsbk(target, h, s, b, k) - - :param string target: targeted bulbs; - :param int h: Hue from 0 (0°) to 65535 (360°); - :param int s: Saturation from 0 to 65535; - :param int b: Brightness from 0 to 65535; - :param int k: Temperature in Kelvin (max 9000K). - -.. vim: set tw=80 spelllang=en spell: diff --git a/docs/known-issues.rst b/docs/known-issues.rst new file mode 100644 index 0000000..e5e046a --- /dev/null +++ b/docs/known-issues.rst @@ -0,0 +1,20 @@ +Known issues +============ + +The White 800 appears to be less reliable than the LIFX Original or Color 650. +The grouping (tagging) code of the LIFX White 800 in particular appears to be +bugged: after a tagging operation the LIFX White 800 keep saying it has no tags. + +The Color 650 with the firmware 2.1 appears to completely hang sometimes and +you have to restart it, the official LIFX app appears to be affected too. + +Power ON/OFF are the only commands with auto-retry, i.e: lightsd will keep +sending the command to the bulb until its state changes. This is not implemented +(yet) for ``set_light_from_hsbk``, ``set_waveform``, ``set_label``, ``tag`` and +``untag``. + +In general, crappy WiFi network with latency, jitter or packet loss are gonna be +challenging until lightsd has an auto-retry mechanism, there is also room for +optimizations in how lightsd communicates with the bulbs. + +.. vim: set tw=80 spelllang=en spell: diff --git a/docs/protocol.rst b/docs/protocol.rst index b8ff97c..08d61ca 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -9,7 +9,9 @@ Targeting bulbs --------------- Commands that manipulate bulbs will take a *target* argument to define on which -bulb(s) the operation should apply: +bulb(s) the operation should apply. The target argument is either a string +(identifying a target as explained in the following table), or an array of +strings (targets). +-----------------------------+-----------------------------------------------+ | ``*`` | targets all bulbs | @@ -23,12 +25,12 @@ bulb(s) the operation should apply: | ``[#TagName, 123f31a5]`` | composite target (JSON array) | +-----------------------------+-----------------------------------------------+ -A target is either a string, a hexadecimal number (without any prefix like 0x) -or an array of targets. - .. note:: The maximum supported length for labels and tag names by LIFX bulbs is 32. + Anything beyond that will be ignored. + +.. _proto_methods: Available methods ----------------- From 68083fccdd68a8e9a112a20183ca03e8196435d0 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 16 Sep 2015 02:11:11 -0700 Subject: [PATCH 128/181] Added tag 0.9.4 for changeset 4ef909311670 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 95e6517..97c27c7 100644 --- a/.hgtags +++ b/.hgtags @@ -2,3 +2,4 @@ 21d438d34e21479a652ba6aa1ec14646385cae5a 0.9.1 3a4befe8819ea7a81f89a6ed550cc88803108f6a 0.9.2 8a41f4cfee7e6ea6d7e161f4e96cfc45bb485cc4 0.9.3 +4ef909311670c1ab1543793afa327749150bf1e2 0.9.4 From ec85a295dae9ea083dfbdef8e671f9ee4b08fd56 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 16 Sep 2015 02:36:57 -0700 Subject: [PATCH 129/181] Link the documentation from the README --- CMakeLists.txt | 6 +++--- README.rst | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 71eda9a..59a2373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,9 +3,9 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) PROJECT(LIGHTSD C) -SET(CPACK_PACKAGE_VERSION_MAJOR "0") -SET(CPACK_PACKAGE_VERSION_MINOR "9") -SET(CPACK_PACKAGE_VERSION_PATCH "4") +SET(CPACK_PACKAGE_VERSION_MAJOR "1") +SET(CPACK_PACKAGE_VERSION_MINOR "0") +SET(CPACK_PACKAGE_VERSION_PATCH "0") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") diff --git a/README.rst b/README.rst index baa24f0..2f6d81b 100644 --- a/README.rst +++ b/README.rst @@ -49,6 +49,12 @@ lightsd works and is developed against LIFX firmwares 1.1, 1.5, 2.0 and 2.1. .. _JSON-RPC: http://www.jsonrpc.org/specification .. _mkfifo(1): http://www.openbsd.org/cgi-bin/man.cgi?query=mkfifo +Documentation +------------- + +Checkout http://lightsd.readthedocs.org/en/latest/ for installation instructions +and a walk-through some examples. + Requirements ------------ From e0c06a312a19a4c736ca05483f1aaac6e55872b0 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 16 Sep 2015 02:40:39 -0700 Subject: [PATCH 130/181] Link back to the repository from the documentation --- docs/index.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index e90aed9..f276fb5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,9 +1,9 @@ The lights daemon documentation =============================== -Welcome! This is the documentation for lightsd: a small program that runs in the -background, discovers smart bulbs [#bulbs]_ on your local network and let you -control them easily. +Welcome! This is the documentation for lightsd_: a small program that runs in +the background, discovers smart bulbs [#bulbs]_ on your local network and let +you control them easily. lightsd exposes a JSON-RPC_ interface over TCP IPv4/IPv6 [#tcp]_ and Unix sockets. The same interface can be exposed over a `named pipe`_: in that case @@ -26,6 +26,7 @@ instructions: .. [#bulbs] Currently only LIFX_ WiFi smart bulbs are supported. .. [#tcp] And not over HTTP like most JSON-RPC implementations. +.. _lightsd: https://github.com/lopter/lightsd .. _LIFX: http://www.lifx.co/ .. _JSON-RPC: http://www.jsonrpc.org/specification .. _named pipe: http://www.openbsd.org/cgi-bin/man.cgi?query=mkfifo From 84e600c5a3198ba6be1befdb7b45db97faf52c3d Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 17 Sep 2015 01:27:29 -0700 Subject: [PATCH 131/181] Adjust the documentation a little bit --- README.rst | 5 +++-- docs/developers.rst | 24 ++++++++++++++++++++---- docs/first-steps.rst | 6 ++++++ docs/installation.rst | 2 +- docs/protocol.rst | 6 ++++++ 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/README.rst b/README.rst index 2f6d81b..202cbc7 100644 --- a/README.rst +++ b/README.rst @@ -52,8 +52,9 @@ lightsd works and is developed against LIFX firmwares 1.1, 1.5, 2.0 and 2.1. Documentation ------------- -Checkout http://lightsd.readthedocs.org/en/latest/ for installation instructions -and a walk-through some examples. +lightsd is packaged for Mac OS X and Arch Linux with OpenWrt coming out later +this year. Checkout http://lightsd.readthedocs.org/en/latest/ for installation +instructions and a walk-through some interactive examples. Requirements ------------ diff --git a/docs/developers.rst b/docs/developers.rst index 7073bfa..ea9cf92 100644 --- a/docs/developers.rst +++ b/docs/developers.rst @@ -1,10 +1,26 @@ -Developers -========== +Developers & companies +====================== -Repository, stars, 2 minutes hate: https://github.com/lopter/lightsd. +lightsd's development takes place on github: https://github.com/lopter/lightsd. + +lightsd follows semver_. + +If you wanna get a feel of what I'm working on you can watch my patch queue: +https://github.com/lopter/lightsd-mq. You will need to install Mercurial_ and +hg-git_ to apply it over the main repository. Some of the patches are just +snippets I put aside; the series_ file will tell you which ones. Feel free to reach out via email or irc (kalessin on freenode, insist if I don't reply). As the project name implies, I'm fairly interested in other smart -bulbs. +bulbs. If you are a company trying to use lightsd feel free to reach me out as +well. lightsd is free software under the GPLv3_ but has been designed in a way +that make it usable in closed source environments. I'm not looking for a new +job, thanks. + +.. _semver: http://semver.org/ +.. _Mercurial: https://mercurial.selenic.com/ +.. _hg-git: http://hg-git.github.io/ +.. _series: https://github.com/lopter/lightsd-mq +.. _GPLv3: https://github.com/lopter/lightsd/blob/master/COPYING .. vim: set tw=80 spelllang=en spell: diff --git a/docs/first-steps.rst b/docs/first-steps.rst index b357ff6..14651b8 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -65,6 +65,12 @@ Stop lightsd with: systemctl stop lightsd +Enable lightsd at boot: + +:: + + systemctl enable lightsd + Check how lightsd is running with: :: diff --git a/docs/installation.rst b/docs/installation.rst index 631cafc..16a3567 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -80,7 +80,7 @@ lightsd: :: - build-essential cmake libevent-dev + build-essential cmake libevent-dev git ca-certificates Please also install ipython if you want to follow the examples in the next section. On Debian and Ubuntu you would install the ``ipython3`` package. diff --git a/docs/protocol.rst b/docs/protocol.rst index 08d61ca..34e136f 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -131,4 +131,10 @@ Available methods untag("#myexistingtag", "myexistingtag") +Notes +----- + +- You cannot the set the color of the bulb while it's turned off; +- lightsd supports batch JSON-RPC requests, use them! + .. vim: set tw=80 spelllang=en spell: From 44e5aa31a9f2ece2878720062235e484dcd61aff Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 17 Sep 2015 01:35:38 -0700 Subject: [PATCH 132/181] Added tag 1.0.0 for changeset 31b8ef65d3d2 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index 97c27c7..e6e5d41 100644 --- a/.hgtags +++ b/.hgtags @@ -3,3 +3,4 @@ 3a4befe8819ea7a81f89a6ed550cc88803108f6a 0.9.2 8a41f4cfee7e6ea6d7e161f4e96cfc45bb485cc4 0.9.3 4ef909311670c1ab1543793afa327749150bf1e2 0.9.4 +31b8ef65d3d239d7e94417de50b4b7e1c6c0a624 1.0.0 From 5ead6ee37385154175d247de635b98a4ff7e2229 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Thu, 17 Sep 2015 02:12:41 -0700 Subject: [PATCH 133/181] Document the -u option for lightsc.py and fix some formatting --- CMakeLists.txt | 2 +- docs/first-steps.rst | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 59a2373..9643d1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "1") SET(CPACK_PACKAGE_VERSION_MINOR "0") -SET(CPACK_PACKAGE_VERSION_PATCH "0") +SET(CPACK_PACKAGE_VERSION_PATCH "1") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") diff --git a/docs/first-steps.rst b/docs/first-steps.rst index 14651b8..5a1bb62 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -187,17 +187,17 @@ current shell or shell script: By default lightsc will use `lightsd --rundir`/pipe but you can set that to your own value. -.. function:: lightsc method ... +.. describe:: lightsc method [arguments…] Call the given :ref:`method ` with the given arguments. lightsc display the generated JSON that was sent. -.. function:: lightsc_get_pipe +.. describe:: lightsc_get_pipe Equivalent to ``${LIGHTSD_COMMAND_PIPE:-`lightsd --rundir`/pipe}`` but also check if lightsd is running. -.. function:: lightsc_make_request method .. +.. describe:: lightsc_make_request method [arguments…] Like lightsc but display the generated json instead of sending it out to lightsd: with this and lightsc_get_pipe you can do batch requests: @@ -259,6 +259,13 @@ Fetch the state of all your bulbs: Check out :doc:`lightsd's protocol ` to see everything you can do. +lightsc.py also accepts an url which lets you connect to any running lightsd, +e.g: + +:: + + lightsc.py -u tcp://localhost:1234 + .. _lightsc.py: https://github.com/lopter/lightsd/blob/master/examples/lightsc.py .. vim: set tw=80 spelllang=en spell: From aac2c46ed795bdb9191dce5be56e42e5db311853 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Fri, 18 Sep 2015 22:10:06 -0700 Subject: [PATCH 134/181] Cope with versions of CMake older than 2.8.11 --- CMakeLists.txt | 15 ++++++++++++--- README.rst | 4 ++-- docs/installation.rst | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9643d1e..31fa2ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ -# first version with TARGET_INCLUDE_DIRECTORIES: -CMAKE_MINIMUM_REQUIRED(VERSION 2.8.11) +# it probably works with older versions but this the oldest tested one: +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.9) PROJECT(LIGHTSD C) @@ -87,7 +87,16 @@ INCLUDE_DIRECTORIES( ADD_SUBDIRECTORY(compat) ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(lifx) -ADD_SUBDIRECTORY(tests) +# 2.8.11 is the first version with TARGET_INCLUDE_DIRECTORIES: +IF (CMAKE_VERSION VERSION_GREATER 2.8.10) + ADD_SUBDIRECTORY(tests) +ELSE () + MESSAGE( + STATUS + "The tests suite requires CMake >= 2.8.11 " + "but you have ${CMAKE_VERSION}, disabling it" + ) +ENDIF () INSTALL( FILES COPYING README.rst docs/protocol.rst diff --git a/README.rst b/README.rst index 202cbc7..1f77ef0 100644 --- a/README.rst +++ b/README.rst @@ -65,8 +65,8 @@ and on any kind of hardware including embedded devices. Hence why lightsd is written in C with reasonable dependencies: - libevent ≥ 2.0.19 (released May 2012); -- CMake ≥ 2.8.11 (released May 2013): only if you want to build lightsd from its - sources. +- CMake ≥ 2.8.9 (released August 2012): only if you want to build lightsd from + its sources. lightsd is actively developed and tested from Arch Linux, Debian and Mac OS X; both for 32/64 bits and little/big endian architectures. diff --git a/docs/installation.rst b/docs/installation.rst index 16a3567..65590e2 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -70,7 +70,7 @@ lightsd should work on any slightly POSIX system (i.e: not Windows), make sure you have the following requirements installed: - libevent ≥ 2.0.19 (released May 2012); -- CMake ≥ 2.8.11 (released May 2013). +- CMake ≥ 2.8.9 (released August 2012). lightsd is developed and tested from Arch Linux, Debian and Mac OS X; both for 32/64 bits and little/big endian architectures. From f47324182e8f7ebaf8ee7a61c3bfe79821c516bb Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Fri, 18 Sep 2015 22:10:07 -0700 Subject: [PATCH 135/181] Actually fix set_waveform on big endian architectures http://gph.is/1tvJ2WD And test it this time. --- CMakeLists.txt | 3 +++ CTestCustom.cmake.in | 3 +++ lifx/wire_proto.h | 4 ++-- .../test_wire_proto_float_endian_conversion.c | 15 +++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 CTestCustom.cmake.in create mode 100644 tests/lifx/wire_proto/test_wire_proto_float_endian_conversion.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 31fa2ae..31747c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,9 @@ ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(lifx) # 2.8.11 is the first version with TARGET_INCLUDE_DIRECTORIES: IF (CMAKE_VERSION VERSION_GREATER 2.8.10) + CONFIGURE_FILE( + CTestCustom.cmake.in "${LIGHTSD_BINARY_DIR}/CTestCustom.cmake" @ONLY + ) ADD_SUBDIRECTORY(tests) ELSE () MESSAGE( diff --git a/CTestCustom.cmake.in b/CTestCustom.cmake.in new file mode 100644 index 0000000..63871c1 --- /dev/null +++ b/CTestCustom.cmake.in @@ -0,0 +1,3 @@ +IF (NOT @BIG_ENDIAN_SYSTEM@) + SET(CTEST_CUSTOM_TESTS_IGNORE test_wire_proto_float_endian_conversion) +ENDIF () diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 1a03bff..3ad5a87 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -29,7 +29,7 @@ static inline floatle_t lgtd_lifx_wire_htolefloat(float f) { union { float f; uint32_t i; } u = { .f = f }; - htole32(u.i); + u.i = htole32(u.i); return u.f; } @@ -37,7 +37,7 @@ static inline floatle_t lgtd_lifx_wire_lefloattoh(float f) { union { float f; uint32_t i; } u = { .f = f }; - le32toh(u.i); + u.i = le32toh(u.i); return u.f; } diff --git a/tests/lifx/wire_proto/test_wire_proto_float_endian_conversion.c b/tests/lifx/wire_proto/test_wire_proto_float_endian_conversion.c new file mode 100644 index 0000000..26e6097 --- /dev/null +++ b/tests/lifx/wire_proto/test_wire_proto_float_endian_conversion.c @@ -0,0 +1,15 @@ +#include + +#include +#include + +#include "lifx/wire_proto.h" + +int +main(void) +{ + union u { float f; uint32_t i; }; + union u value = { .i = 0x11223344 }; + union u new_value = { .f = lgtd_lifx_wire_htolefloat(value.f) }; + return new_value.i != 0x44332211; +} From e06efc1eee1ea87cdec1df740d06d2d75ecaeaa4 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Fri, 18 Sep 2015 22:10:07 -0700 Subject: [PATCH 136/181] Document the unix:// scheme for lightsc.py's -u option --- docs/first-steps.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/first-steps.rst b/docs/first-steps.rst index 5a1bb62..b3f8e6f 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -259,13 +259,19 @@ Fetch the state of all your bulbs: Check out :doc:`lightsd's protocol ` to see everything you can do. -lightsc.py also accepts an url which lets you connect to any running lightsd, -e.g: +lightsc.py also accepts an url which lets you connect to anything running +lightsd, e.g: :: lightsc.py -u tcp://localhost:1234 +Or, for an Unix socket: + +:: + + lightsc.py -u unix:///path/to/lightsd/socket + .. _lightsc.py: https://github.com/lopter/lightsd/blob/master/examples/lightsc.py .. vim: set tw=80 spelllang=en spell: From d3513522bb42e29a0a1269e3ea9dd8f018952f3a Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Fri, 18 Sep 2015 22:17:52 -0700 Subject: [PATCH 137/181] Properly initialize the process title even when no bulbs are discovered --- core/lightsd.c | 4 ++++ docs/first-steps.rst | 11 +++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/lightsd.c b/core/lightsd.c index e5a4378..41c1a08 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -295,6 +295,10 @@ main(int argc, char *argv[], char *envp[]) lgtd_lifx_discovery_start(); + // update at least once: so that if no bulbs are discovered we still get a + // clear status line. + lgtd_daemon_update_proctitle(); + event_base_dispatch(lgtd_ev_base); lgtd_cleanup(); diff --git a/docs/first-steps.rst b/docs/first-steps.rst index b3f8e6f..fc73092 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -92,7 +92,7 @@ Manually (other systems) Assuming you've just built :ref:`lightsd from the sources `, lightsd will be in the ``core`` directory [#]_. -The examples are communicating with lightsd through a pipe or an unix socket, +The examples are communicating with lightsd through a pipe or an Unix socket, start lightsd with them: :: @@ -179,8 +179,8 @@ Or, from the root of the repository: . share/lightsc.sh -You can use the following things to send commands to your bulbs from your -current shell or shell script: +You can use the following variable and functions to send commands to your bulbs +from your current shell or shell script: .. data:: LIGHTSD_COMMAND_PIPE @@ -241,12 +241,11 @@ Or, from the root of the repository: examples/lightsc.py -From there a ``c`` variable has been initialized for you, this a small object -let you directly execute commands on your bulb: +From there, a ``c`` variable has been initialized for you: this small object +lets you directly execute commands on your bulb: For example toggle your lights again: - .. code-block:: python c.power_toggle("*") From 78f9ecb273a0a7e59cea9a0dbac9f35e6e4df4ab Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 19 Sep 2015 00:53:07 -0700 Subject: [PATCH 138/181] Fix build on OpenBSD 5.7 Yes, someone actually tried and emailed me <3. Well supporting GCC 4.2 is not really ideal but on the bright side everything that was done on the system introspection side pretty much worked out of the box. --- CMakeLists.txt | 1 + CMakeScripts/FindEvent2.cmake | 13 ++++++++++--- README.rst | 4 ++-- core/client.c | 6 +++--- core/pipe.c | 6 +++--- docs/installation.rst | 4 ++-- lifx/gateway.c | 2 +- tests/core/tests_shims.c | 14 -------------- tests/lifx/bulb/test_bulb_fetch_hardware_info.c | 6 +++--- .../gateway/test_gateway_handle_tag_labels.c | 4 ++-- tests/lifx/tests_shims.c | 16 ---------------- 11 files changed, 27 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 31747c3..7eac2e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ ENDIF () MESSAGE(STATUS "lightsd runtime directory: ${LGTD_RUNTIME_DIRECTORY}") INCLUDE_DIRECTORIES( + ${EVENT2_INCLUDE_DIR} ${LIGHTSD_BINARY_DIR}/compat ${LIGHTSD_BINARY_DIR}/compat/generic ) diff --git a/CMakeScripts/FindEvent2.cmake b/CMakeScripts/FindEvent2.cmake index 116c147..15aa8a3 100644 --- a/CMakeScripts/FindEvent2.cmake +++ b/CMakeScripts/FindEvent2.cmake @@ -1,13 +1,20 @@ +FIND_PATH( + EVENT2_INCLUDE_DIR + event2/event.h + # OpenBSD has libevent1 in /usr/lib, always try /usr/local first: + HINTS /usr/local/ +) + FOREACH (COMPONENT ${Event2_FIND_COMPONENTS}) STRING(TOUPPER ${COMPONENT} UPPER_COMPONENT) - FIND_LIBRARY(EVENT2_${UPPER_COMPONENT}_LIBRARY event_${COMPONENT}) + FIND_LIBRARY( + EVENT2_${UPPER_COMPONENT}_LIBRARY event_${COMPONENT} HINTS /usr/local/ + ) IF (EVENT2_${UPPER_COMPONENT}_LIBRARY) SET(Event2_${COMPONENT}_FOUND TRUE) ENDIF () ENDFOREACH () -FIND_PATH(EVENT2_INCLUDE_DIR event2/event.h) - INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( diff --git a/README.rst b/README.rst index 1f77ef0..bec7030 100644 --- a/README.rst +++ b/README.rst @@ -68,8 +68,8 @@ written in C with reasonable dependencies: - CMake ≥ 2.8.9 (released August 2012): only if you want to build lightsd from its sources. -lightsd is actively developed and tested from Arch Linux, Debian and Mac OS X; -both for 32/64 bits and little/big endian architectures. +lightsd is actively developed and tested from Arch Linux, Debian, Mac OS X and +OpenBSD; both for 32/64 bits and little/big endian architectures. Developers ---------- diff --git a/core/client.c b/core/client.c index 3eb2509..38ce7dd 100644 --- a/core/client.c +++ b/core/client.c @@ -66,6 +66,8 @@ lgtd_client_close_all(void) } } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" static void lgtd_client_read_callback(struct bufferevent *bev, void *ctx) { @@ -95,10 +97,7 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) evbuffer_drain(input, nbytes); break; case JSMN_ERROR_PART: -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wswitch" case 0: -#pragma GCC diagnostic pop (void)0; size_t buflen = evbuffer_get_length(input); if (buflen > LGTD_CLIENT_MAX_REQUEST_BUF_SIZE) { @@ -139,6 +138,7 @@ lgtd_client_read_callback(struct bufferevent *bev, void *ctx) nbytes = evbuffer_get_contiguous_space(input); } while (nbytes); } +#pragma GCC diagnostic pop static void lgtd_client_event_callback(struct bufferevent *bev, short events, void *ctx) diff --git a/core/pipe.c b/core/pipe.c index 6d19a3e..dbb6532 100644 --- a/core/pipe.c +++ b/core/pipe.c @@ -68,6 +68,8 @@ lgtd_command_pipe_close(struct lgtd_command_pipe *pipe) static void lgtd_command_pipe_reset(struct lgtd_command_pipe *); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" static void lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) { @@ -113,10 +115,7 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) drain = true; break; case JSMN_ERROR_PART: -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wswitch" case 0: -#pragma GCC diagnostic pop if (bufsz >= LGTD_CLIENT_MAX_REQUEST_BUF_SIZE) { lgtd_warnx("pipe %s: request too big", pipe->path); drain = true; @@ -154,6 +153,7 @@ lgtd_command_pipe_read_callback(evutil_socket_t socket, short events, void *ctx) lgtd_command_pipe_reset(pipe); } +#pragma GCC diagnostic pop static bool _lgtd_command_pipe_open(const char *path) diff --git a/docs/installation.rst b/docs/installation.rst index 65590e2..94c9038 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -72,8 +72,8 @@ you have the following requirements installed: - libevent ≥ 2.0.19 (released May 2012); - CMake ≥ 2.8.9 (released August 2012). -lightsd is developed and tested from Arch Linux, Debian and Mac OS X; both for -32/64 bits and little/big endian architectures. +lightsd is developed and tested from Arch Linux, Debian, OpenBSD and Mac OS X; +both for 32/64 bits and little/big endian architectures. For Debian and Ubuntu you would need to install the following packages to build lightsd: diff --git a/lifx/gateway.c b/lifx/gateway.c index a150de4..a25198c 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -725,7 +725,7 @@ lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, lgtd_warnx( "trying to set unknown tag_id %d (%#jx) " "on bulb %s (%.*s), gw %s (site %s)", - tag_id, LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id), + tag_id, (uintmax_t)LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id), LGTD_IEEE8023MACTOA(b->addr, bulb_addr), LGTD_LIFX_LABEL_SIZE, b->state.label, gw->peeraddr, LGTD_IEEE8023MACTOA(gw->site.as_array, site_addr) diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c index 60835f7..b4e7d1c 100644 --- a/tests/core/tests_shims.c +++ b/tests/core/tests_shims.c @@ -33,17 +33,3 @@ void lgtd_cleanup(void) { } - -short -lgtd_sockaddrport(const struct sockaddr_storage *peer) -{ - assert(peer); - - if (peer->ss_family == AF_INET) { - const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; - return ntohs(in_peer->sin_port); - } else { - const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; - return ntohs(in6_peer->sin6_port); - } -} diff --git a/tests/lifx/bulb/test_bulb_fetch_hardware_info.c b/tests/lifx/bulb/test_bulb_fetch_hardware_info.c index 246a85f..a88e216 100644 --- a/tests/lifx/bulb/test_bulb_fetch_hardware_info.c +++ b/tests/lifx/bulb/test_bulb_fetch_hardware_info.c @@ -28,6 +28,8 @@ static int get_version_sent = 0; static int get_mesh_firmware_state_sent = 0; static int get_wifi_firmware_state_sent = 0; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" // we don't test the whole enum void lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, enum lgtd_lifx_packet_type pkt_type, @@ -44,8 +46,6 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, errx(1, "got unexpected pkt"); } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wswitch" // we don't test the whole enum switch (pkt_type) { case LGTD_LIFX_GET_VERSION: get_version_sent++; @@ -57,8 +57,8 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, get_wifi_firmware_state_sent++; break; } -#pragma GCC diagnostic pop } +#pragma GCC diagnostic pop static void test_counters(int get_version, int mcu_fw_info, int wifi_fw_info) diff --git a/tests/lifx/gateway/test_gateway_handle_tag_labels.c b/tests/lifx/gateway/test_gateway_handle_tag_labels.c index dc330d1..64a35c2 100644 --- a/tests/lifx/gateway/test_gateway_handle_tag_labels.c +++ b/tests/lifx/gateway/test_gateway_handle_tag_labels.c @@ -30,7 +30,7 @@ main(void) if (gw.tag_ids != LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42)) { errx( 1, "expected gw.tag_ids == %jx but got %jx", - LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tag_ids + (uintmax_t)LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tag_ids ); } if (!gw.tags[42]) { @@ -55,7 +55,7 @@ main(void) if (gw.tag_ids != expected) { errx( 1, "expected gw.tag_ids == %jx but got %jx", - LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tag_ids + (uintmax_t)LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(42), (uintmax_t)gw.tag_ids ); } if (strcmp(gw.tags[2]->label, "toto")) { diff --git a/tests/lifx/tests_shims.c b/tests/lifx/tests_shims.c index 610d615..bfbcf6f 100644 --- a/tests/lifx/tests_shims.c +++ b/tests/lifx/tests_shims.c @@ -20,22 +20,6 @@ lgtd_cleanup(void) { } -short -lgtd_sockaddrport(const struct sockaddr_storage *peer) -{ - if (!peer) { - return -1; - } - - if (peer->ss_family == AF_INET) { - const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; - return ntohs(in_peer->sin_port); - } else { - const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; - return ntohs(in6_peer->sin6_port); - } -} - void lgtd_daemon_update_proctitle(void) { From 0e7327dade7c3ef8bfbe01779c2470af8a36fe75 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 19 Sep 2015 01:05:21 -0700 Subject: [PATCH 139/181] Add product id for the 230V version of the LIFX White 800 --- lifx/bulb.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lifx/bulb.c b/lifx/bulb.c index 9c5ac17..24c7d32 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -58,10 +58,11 @@ lgtd_lifx_bulb_get_model_name(uint32_t vendor_id, uint32_t product_id) switch (product_id) { case 0x1: case 0x2: - return "A21 (Original)"; + return "A21 (Original 1000)"; case 0x3: return "GU10 (Color 650)"; case 0xa: + case 0xb: return "A19 (White 800)"; default: return "Unknown"; From c27d0367cc4d57dca58a2354f0df760cb98ce455 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 19 Sep 2015 01:06:34 -0700 Subject: [PATCH 140/181] Add a changelog section in the documentation --- docs/changelog.rst | 21 +++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 22 insertions(+) create mode 100644 docs/changelog.rst diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..1493ee5 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,21 @@ +Changelog +========= + +1.0.1 (2015-09-18) +------------------ + +- Fix set_waveform on big endian architectures; +- Fix build under Debian oldstable; +- Fix build under OpenBSD [#]_; +- Fix process title even when no bulbs are discovered; +- Add product id for the 230V version of the LIFX White 800. + +.. [#] Using GCC 4.2, so you just need to do ``pkg_add cmake libevent`` to + build a release. + +1.0.0 (2015-09-17) +------------------ + +- First announced release. + +.. vim: set tw=80 spelllang=en spell: diff --git a/docs/index.rst b/docs/index.rst index f276fb5..f73a701 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,6 +22,7 @@ instructions: protocol known-issues developers + changelog .. [#bulbs] Currently only LIFX_ WiFi smart bulbs are supported. .. [#tcp] And not over HTTP like most JSON-RPC implementations. From 50e923dcfe7f7f5ed8fc0133653836258146291d Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 19 Sep 2015 01:10:40 -0700 Subject: [PATCH 141/181] Added tag 1.0.1 for changeset 1f7c28381493 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index e6e5d41..ae5bd30 100644 --- a/.hgtags +++ b/.hgtags @@ -4,3 +4,4 @@ 8a41f4cfee7e6ea6d7e161f4e96cfc45bb485cc4 0.9.3 4ef909311670c1ab1543793afa327749150bf1e2 0.9.4 31b8ef65d3d239d7e94417de50b4b7e1c6c0a624 1.0.0 +1f7c28381493a9e0a24bf5b918d3772241d15587 1.0.1 From ada9b0c75627ca2714df09aa9619e88180c184ef Mon Sep 17 00:00:00 2001 From: Stephane Sezer Date: Sun, 18 Oct 2015 16:06:16 -0700 Subject: [PATCH 142/181] Fix a small typo in README.rst. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index bec7030..3063fa3 100644 --- a/README.rst +++ b/README.rst @@ -9,7 +9,7 @@ Having to run a daemon to control your LIFX bulbs may seem a little bit backward but has some advantages: - no discovery delay ever, you get all the bulbs and their state right away; -- lightsd is always in sync with the bulbs and always know their state; +- lightsd is always in sync with the bulbs and always knows their state; - lightsd act as an abstraction layer and can expose new discovery mechanisms and an unified API across different kind of smart bulbs; - For those of you with a high paranoia factor, lightsd let you place your bulbs From 1c80bc9cb876d89567116d366a82beef83923c43 Mon Sep 17 00:00:00 2001 From: Sylvain Laurent Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 143/181] Fix missing null byte for progname --- core/lightsd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/lightsd.c b/core/lightsd.c index 41c1a08..1870bb9 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -160,8 +160,8 @@ lgtd_usage(const char *progname) int main(int argc, char *argv[], char *envp[]) { - char progname[32]; - memcpy(progname, argv[0], LGTD_MIN(sizeof(progname), strlen(argv[0]))); + char progname[32] = { 0 }; + memcpy(progname, argv[0], LGTD_MIN(sizeof(progname) - 1, strlen(argv[0]))); lgtd_daemon_setup_proctitle(argc, argv, envp); From 8ad700764060a3570481add160467206eac3376b Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 144/181] Add the --syslog (-S), --syslog-facility (-F), --syslog-ident (-I) options -S diverts logging to syslog, supported facilities are: - daemon; - user; - local0 through 7. -I lets you tune the process identifier and defaults to lightsd (independently of progname). Closes GH-1. --- CMakeLists.txt | 4 +- core/CMakeLists.txt | 2 + core/console.c | 140 ++++++++++ core/console.h | 31 +++ core/daemon.c | 138 ++++++++++ core/daemon.h | 17 ++ core/lightsd.c | 65 ++++- core/lightsd.h | 22 +- core/log.c | 240 ++++-------------- core/utils.c | 119 +++++++++ docs/conf.py | 4 +- docs/first-steps.rst | 12 +- tests/core/client/CMakeLists.txt | 2 +- tests/core/client/test_client_read_callback.c | 1 + .../test_client_read_callback_extra_data.c | 1 + ...t_client_read_callback_multiple_requests.c | 1 + ...test_client_read_callback_part_too_large.c | 1 + ...est_client_read_callback_yield_on_eagain.c | 1 + tests/core/daemon/CMakeLists.txt | 2 +- .../daemon/test_daemon_update_proctitle.c | 3 +- tests/core/jsonrpc/CMakeLists.txt | 2 +- tests/core/jsonrpc/test_jsonrpc_batch.c | 1 + .../test_jsonrpc_batch_notifications_only.c | 1 + .../test_jsonrpc_batch_one_invalid_request.c | 1 + .../test_jsonrpc_batch_one_notification.c | 1 + .../jsonrpc/test_jsonrpc_build_target_list.c | 1 + .../test_jsonrpc_check_and_call_power_off.c | 1 + ..._check_and_call_power_off_missing_target.c | 1 + .../test_jsonrpc_check_and_call_power_on.c | 1 + ...c_check_and_call_power_on_missing_target.c | 1 + ...test_jsonrpc_check_and_call_power_toggle.c | 1 + .../test_jsonrpc_check_and_call_set_label.c | 1 + ...onrpc_check_and_call_set_light_from_hsbk.c | 1 + ..._and_call_set_light_from_hsbk_from_array.c | 1 + ..._call_set_light_from_hsbk_invalid_params.c | 1 + ...test_jsonrpc_check_and_call_set_waveform.c | 1 + ...eck_and_call_set_waveform_invalid_params.c | 1 + .../jsonrpc/test_jsonrpc_check_and_call_tag.c | 1 + ...sonrpc_check_and_call_tag_missing_params.c | 1 + .../test_jsonrpc_check_and_call_untag.c | 1 + ...nrpc_check_and_call_untag_invalid_params.c | 1 + .../test_jsonrpc_consume_object_or_array.c | 1 + .../test_jsonrpc_dispatch_one_no_params.c | 1 + .../test_jsonrpc_extract_request_no_params.c | 1 + ...c_extract_request_notification_no_params.c | 1 + ...est_jsonrpc_extract_request_params_array.c | 1 + .../test_jsonrpc_extract_request_params_obj.c | 1 + ...onrpc_extract_request_valid_notification.c | 1 + ...ues_from_schema_and_array_honors_objsize.c | 1 + tests/core/jsonrpc/test_jsonrpc_send_error.c | 1 + .../core/jsonrpc/test_jsonrpc_send_response.c | 1 + ...onrpc_type_float_between_0_and_1_invalid.c | 1 + ...jsonrpc_type_float_between_0_and_1_valid.c | 1 + ...rpc_type_float_between_0_and_360_invalid.c | 1 + ...onrpc_type_float_between_0_and_360_valid.c | 1 + .../core/jsonrpc/test_jsonrpc_type_integer.c | 1 + ..._jsonrpc_type_integer_invalid_characters.c | 1 + .../test_jsonrpc_type_integer_too_big.c | 1 + .../test_jsonrpc_type_integer_too_small.c | 1 + ...est_jsonrpc_uint16_range_to_float_string.c | 1 + tests/core/mock_log.h | 90 +++++++ tests/core/pipe/CMakeLists.txt | 2 +- tests/core/pipe/test_pipe_close.c | 1 + tests/core/pipe/test_pipe_open.c | 1 + .../pipe/test_pipe_open_fifo_already_exists.c | 1 + tests/core/pipe/test_pipe_read_callback.c | 1 + .../pipe/test_pipe_read_callback_extra_data.c | 1 + ...est_pipe_read_callback_multiple_requests.c | 1 + .../test_pipe_read_callback_yield_on_eagain.c | 1 + tests/core/proto/CMakeLists.txt | 2 +- tests/core/proto/test_proto_get_light_state.c | 1 + ..._proto_get_light_state_empty_device_list.c | 1 + ...est_proto_get_light_state_label_overflow.c | 1 + ...t_proto_get_light_state_null_device_list.c | 1 + ...est_proto_get_light_state_unknown_tag_id.c | 1 + tests/core/proto/test_proto_power_off.c | 1 + .../test_proto_power_off_routing_error.c | 1 + tests/core/proto/test_proto_power_on.c | 1 + .../proto/test_proto_power_on_routing_error.c | 1 + tests/core/proto/test_proto_power_toggle.c | 1 + ...oto_power_toggle_targets_to_device_fails.c | 1 + tests/core/proto/test_proto_set_label.c | 1 + .../proto/test_proto_set_label_too_long.c | 1 + .../proto/test_proto_set_light_from_hsbk.c | 1 + ...oto_set_light_from_hsbk_on_routing_error.c | 1 + tests/core/proto/test_proto_set_waveform.c | 1 + ...test_proto_set_waveform_on_routing_error.c | 1 + tests/core/proto/test_proto_tag_create.c | 1 + ...st_proto_tag_create_lifx_gw_tag_ids_full.c | 1 + tests/core/proto/test_proto_tag_update.c | 1 + tests/core/proto/test_proto_untag.c | 1 + .../test_proto_untag_tag_does_not_exist.c | 1 + tests/core/router/CMakeLists.txt | 2 +- .../router/test_router_send_to_broadcast.c | 1 + .../core/router/test_router_send_to_device.c | 1 + .../test_router_send_to_invalid_targets.c | 1 + tests/core/router/test_router_send_to_label.c | 1 + tests/core/router/test_router_send_to_tag.c | 1 + .../router/test_router_targets_to_devices.c | 1 + tests/lifx/bulb/CMakeLists.txt | 2 +- tests/lifx/bulb/test_bulb_close.c | 1 + .../lifx/bulb/test_bulb_fetch_hardware_info.c | 1 + tests/lifx/bulb/test_bulb_has_label.c | 1 + tests/lifx/bulb/test_bulb_open.c | 1 + tests/lifx/bulb/test_bulb_set_light_state.c | 1 + tests/lifx/bulb/test_bulb_set_power_state.c | 1 + tests/lifx/bulb/test_bulb_set_tags.c | 1 + tests/lifx/gateway/CMakeLists.txt | 2 +- .../gateway/test_gateway_allocate_tag_id.c | 1 + ...ateway_allocate_tag_id_from_lifx_network.c | 1 + ...t_gateway_allocate_tag_id_no_tag_id_left.c | 1 + ...eway_deallocate_tag_id_from_lifx_network.c | 1 + .../gateway/test_gateway_enqueue_packet.c | 1 + .../test_gateway_enqueue_packet_ring_full.c | 1 + ...t_gateway_enqueue_packet_ring_wraparound.c | 1 + .../test_gateway_handle_ambient_light.c | 1 + .../gateway/test_gateway_handle_bulb_label.c | 1 + .../test_gateway_handle_ip_firmware_info.c | 1 + .../gateway/test_gateway_handle_ip_state.c | 1 + .../test_gateway_handle_product_info.c | 1 + .../test_gateway_handle_runtime_info.c | 1 + .../gateway/test_gateway_handle_tag_labels.c | 1 + tests/lifx/gateway/test_gateway_handle_tags.c | 1 + .../test_gateway_update_tag_refcounts.c | 1 + .../gateway/test_gateway_write_callback.c | 1 + ...way_write_callback_clears_ring_full_flag.c | 1 + ...teway_write_callback_last_packet_on_ring.c | 1 + ...est_gateway_write_callback_partial_write.c | 1 + ...t_gateway_write_callback_ring_wraparound.c | 1 + tests/lifx/tagging/CMakeLists.txt | 2 +- tests/lifx/tagging/test_tagging_decref.c | 2 + tests/lifx/tagging/test_tagging_incref.c | 2 + tests/lifx/wire_proto/CMakeLists.txt | 2 +- .../test_wire_proto_encode_decode_header.c | 1 + .../test_wire_proto_waveform_table.c | 2 + 135 files changed, 787 insertions(+), 234 deletions(-) create mode 100644 core/console.c create mode 100644 core/console.h create mode 100644 core/utils.c create mode 100644 tests/core/mock_log.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7eac2e5..518289d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,8 +4,8 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8.9) PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "1") -SET(CPACK_PACKAGE_VERSION_MINOR "0") -SET(CPACK_PACKAGE_VERSION_PATCH "1") +SET(CPACK_PACKAGE_VERSION_MINOR "1") +SET(CPACK_PACKAGE_VERSION_PATCH "0") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 1eb12dc..fc4b0fb 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -13,6 +13,7 @@ CONFIGURE_FILE(version.h.in "${CMAKE_CURRENT_BINARY_DIR}/version.h") ADD_EXECUTABLE( lightsd client.c + console.c daemon.c jsmn.c jsonrpc.c @@ -25,6 +26,7 @@ ADD_EXECUTABLE( setproctitle.c stats.c timer.c + utils.c ) TARGET_LINK_LIBRARIES( diff --git a/core/console.c b/core/console.c new file mode 100644 index 0000000..7c0cdb0 --- /dev/null +++ b/core/console.c @@ -0,0 +1,140 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "console.h" +#include "lightsd.h" + +static void +lgtd_console_isotime_now(char *strbuf, int bufsz) +{ + assert(strbuf); + assert(bufsz > 0); + + struct timeval now; + if (gettimeofday(&now, NULL) == -1) { + goto error; + } + struct tm tm_now; + if (!localtime_r(&now.tv_sec, &tm_now)) { + goto error; + } + LGTD_TM_TO_ISOTIME(&tm_now, strbuf, bufsz, now.tv_usec); + return; +error: + strbuf[0] = '\0'; +} + +static void +lgtd_console_log_header(const char *loglvl, bool showprogname) +{ + if (lgtd_opts.log_timestamps) { + char timestr[64]; + lgtd_console_isotime_now(timestr, sizeof(timestr)); + fprintf( + stderr, "[%s] [%s] %s", + timestr, loglvl, showprogname ? "lightsd: " : "" + ); + return; + } + fprintf(stderr, "[%s] %s", loglvl, showprogname ? "lightsd: " : ""); +} + +void +lgtd_console_err(int eval, const char *fmt, va_list ap) +{ + int errsave = errno; + va_list aq; + va_copy(aq, ap); + // lgtd_cleanup is probably going to free some of the arguments we got, so + // let's print to a buffer before we call err. + char errmsg[LGTD_ERROR_MSG_BUFSIZE]; + vsnprintf(errmsg, sizeof(errmsg), fmt, aq); + va_end(aq); + lgtd_cleanup(); + lgtd_console_log_header("ERR", false); + errno = errsave; + err(eval, "%s", errmsg); +} + +void +lgtd_console_errx(int eval, const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + // lgtd_cleanup is probably going to free some of the arguments we got, so + // let's print to a buffer before we call err. + char errmsg[LGTD_ERROR_MSG_BUFSIZE]; + vsnprintf(errmsg, sizeof(errmsg), fmt, aq); + va_end(aq); + lgtd_cleanup(); + lgtd_console_log_header("ERR", false); + errx(eval, "%s", errmsg); +} + +void +lgtd_console_warn(const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + lgtd_console_log_header("WARN", false); + vwarn(fmt, aq); + va_end(aq); +} + +void +lgtd_console_warnx(const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + lgtd_console_log_header("WARN", false); + vwarnx(fmt, aq); + va_end(aq); +} + +void +lgtd_console_info(const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + lgtd_console_log_header("INFO", true); + vfprintf(stderr, fmt, aq); + va_end(aq); + fprintf(stderr, "\n"); +} + +void +lgtd_console_debug(const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + lgtd_console_log_header("DEBUG", true); + vfprintf(stderr, fmt, aq); + va_end(aq); + fprintf(stderr, "\n"); +} diff --git a/core/console.h b/core/console.h new file mode 100644 index 0000000..41dc4ae --- /dev/null +++ b/core/console.h @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#pragma once + +#ifndef __attribute__ +# define __atttribute__(e) +#endif + +void lgtd_console_err(int, const char *, va_list) + __attribute__((noreturn)); +void lgtd_console_errx(int, const char *, va_list) + __attribute__((noreturn)); +void lgtd_console_warn(const char *, va_list); +void lgtd_console_warnx(const char *, va_list); +void lgtd_console_info(const char *, va_list); +void lgtd_console_debug(const char *, va_list); diff --git a/core/daemon.c b/core/daemon.c index 41e06aa..55b97ec 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -29,11 +29,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -368,3 +370,139 @@ lgtd_daemon_makedirs(const char *filepath) return true; } + +int +lgtd_daemon_syslog_facilitytoi(const char *facility) +{ + struct { + const char *name; + int value; + } syslog_facilities[] = { + { "daemon", LOG_DAEMON }, + { "user", LOG_USER }, + { "local0", LOG_LOCAL0 }, + { "local1", LOG_LOCAL1 }, + { "local2", LOG_LOCAL2 }, + { "local3", LOG_LOCAL3 }, + { "local4", LOG_LOCAL4 }, + { "local5", LOG_LOCAL5 }, + { "local6", LOG_LOCAL6 }, + { "local7", LOG_LOCAL7 } + }; + + for (int i = 0; i != LGTD_ARRAY_SIZE(syslog_facilities); i++) { + if (!strcmp(facility, syslog_facilities[i].name)) { + return syslog_facilities[i].value; + } + } + + lgtd_errx( + 1, + "invalid syslog facility %s (possible values: daemon, " + "user, local0 through 7)", + facility + ); +} + +static void +lgtd_daemon_setup_errfmt(const char *fmt, char *errfmt, int sz) +{ + int n = LGTD_MIN(sz - 1, (int)strlen(fmt) + 1); + memcpy(errfmt, fmt, n); + if (n - 1 + (int)sizeof(": %m") <= sz) { + memcpy(errfmt + n - 1, ": %m", sizeof(": %m")); + } else { + errfmt[n] = '\0'; +#ifndef NDEBUG + abort(); +#endif + } +} + +void +lgtd_daemon_syslog_err(int eval, const char *fmt, va_list ap) +{ + char errfmt[LGTD_DAEMON_ERRFMT_SIZE]; + lgtd_daemon_setup_errfmt(fmt, errfmt, sizeof(errfmt)); + + va_list aq; + va_copy(aq, ap); + vsyslog(LOG_ERR, errfmt, aq); + va_end(aq); + + lgtd_cleanup(); + exit(eval); +} + +// -Wtautological-constant-out-of-range-compare appears to be a clang thing. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic push // yes, I know, the assert below is stupid. +#pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +void +lgtd_daemon_syslog_open(const char *ident, + enum lgtd_verbosity level, + int facility) +{ + static const int syslog_priorities[] = { + LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR + }; + + assert(level < LGTD_ARRAY_SIZE(syslog_priorities)); + + openlog(ident, LOG_PID, facility); + setlogmask(LOG_UPTO(syslog_priorities[level])); +} +#pragma GCC diagnostic pop +#pragma GCC diagnostic pop + +void +lgtd_daemon_syslog_errx(int eval, const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + vsyslog(LOG_ERR, fmt, aq); + va_end(aq); + + lgtd_cleanup(); + exit(eval); +} + +void +lgtd_daemon_syslog_warn(const char *fmt, va_list ap) +{ + char errfmt[LGTD_DAEMON_ERRFMT_SIZE]; + lgtd_daemon_setup_errfmt(fmt, errfmt, sizeof(errfmt)); + + va_list aq; + va_copy(aq, ap); + vsyslog(LOG_WARNING, errfmt, aq); + va_end(aq); +} + +void +lgtd_daemon_syslog_warnx(const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + vsyslog(LOG_WARNING, fmt, aq); + va_end(aq); +} + +void +lgtd_daemon_syslog_info(const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + vsyslog(LOG_INFO, fmt, aq); + va_end(aq); +} + +void +lgtd_daemon_syslog_debug(const char *fmt, va_list ap) +{ + va_list aq; + va_copy(aq, ap); + vsyslog(LOG_DEBUG, fmt, aq); + va_end(aq); +} diff --git a/core/daemon.h b/core/daemon.h index 2c06ce4..a62782f 100644 --- a/core/daemon.h +++ b/core/daemon.h @@ -17,8 +17,16 @@ #pragma once +#ifndef __attribute__ +# define __atttribute__(e) +#endif + +enum lgtd_verbosity; + enum { LGTD_DAEMON_TITLE_SIZE = 2048 }; +enum { LGTD_DAEMON_ERRFMT_SIZE = 4096 }; + bool lgtd_daemon_unleash(void); // \_o< void lgtd_daemon_setup_proctitle(int, char *[], char *[]); void lgtd_daemon_update_proctitle(void); @@ -27,3 +35,12 @@ void lgtd_daemon_set_user(const char *); void lgtd_daemon_set_group(const char *); void lgtd_daemon_drop_privileges(void); bool lgtd_daemon_makedirs(const char *); + +int lgtd_daemon_syslog_facilitytoi(const char *); +void lgtd_daemon_syslog_open(const char *, enum lgtd_verbosity, int); +void lgtd_daemon_syslog_err(int, const char *, va_list) __attribute__((noreturn)); +void lgtd_daemon_syslog_errx(int, const char *, va_list) __attribute__((noreturn)); +void lgtd_daemon_syslog_warn(const char *, va_list); +void lgtd_daemon_syslog_warnx(const char *, va_list); +void lgtd_daemon_syslog_info(const char *, va_list); +void lgtd_daemon_syslog_debug(const char *, va_list); diff --git a/core/lightsd.c b/core/lightsd.c index 1870bb9..e82de51 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -61,11 +62,16 @@ struct lgtd_opts lgtd_opts = { .verbosity = LGTD_WARN, #endif .user = NULL, - .group = NULL + .group = NULL, + .syslog = false, + .syslog_facility = LOG_DAEMON, + .syslog_ident = "lightsd" }; struct event_base *lgtd_ev_base = NULL; +static int lgtd_last_signal_received = 0; + void lgtd_cleanup(void) { @@ -87,14 +93,26 @@ lgtd_signal_event_callback(int signum, short events, void *ctx) { assert(ctx); - lgtd_info( - "received signal %d (%s), exiting...", signum, strsignal(signum) - ); + // NOTE: syslog isn't signal safe, don't log anything in this function. + + lgtd_last_signal_received = signum; event_del((struct event *)ctx); // restore default behavior event_base_loopbreak(lgtd_ev_base); (void)events; } +static void +lgtd_libevent_log(int severity, const char *msg) +{ + switch (severity) { + case EVENT_LOG_DEBUG: lgtd_debug("%s", msg); break; + case EVENT_LOG_MSG: lgtd_info("%s", msg); break; + case EVENT_LOG_WARN: lgtd_warnx("%s", msg); break; + case EVENT_LOG_ERR: lgtd_warnx("%s", msg); break; + default: break; + } +} + static void lgtd_configure_libevent(void) { @@ -130,12 +148,12 @@ lgtd_usage(const char *progname) { printf( "Usage: %s ...\n\n" -" [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP at\n" +" [-l,--listen addr:port] Listen for JSON-RPC commands over TCP at\n" " this address (can be repeated).\n" -" [-c,--comand-pipe /command/fifo [+]] Open an unidirectional JSON-RPC\n" +" [-c,--comand-pipe /command/fifo] Open an unidirectional JSON-RPC\n" " command pipe at this location (can be\n" " repeated).\n" -" [-s,--socket /unix/socket [+]] Open an Unix socket at this location\n" +" [-s,--socket /unix/socket] Open an Unix socket at this location\n" " (can be repeated).\n" " [-f,--foreground] Stay in the foreground (default).\n" " [-d,--daemonize] Fork in the background.\n" @@ -143,7 +161,13 @@ lgtd_usage(const char *progname) " group of this user if -g is missing).\n" " [-g,--group group] Drop privileges to this group (-g requires\n" " the -u option to be used).\n" -" [-t,--no-timestamps] Disable timestamps in logs.\n" +" [-S,--syslog] Divert logging from the console to syslog.\n" +" [-F,--syslog-facility] Facility to use with syslog (defaults to\n" +" daemon, other possible values are user and\n" +" local0-7, see syslog(3)).\n" +" [-I,--syslog-ident] Identifier to use with syslog (defaults to\n" +" lightsd).\n" +" [-t,--no-timestamps] Disable timestamps in the console logs.\n" " [-h,--help] Display this.\n" " [-V,--version] Display version and build information.\n" " [-v,--verbosity debug|info|warning|error]\n" @@ -176,6 +200,9 @@ main(int argc, char *argv[], char *envp[]) {"daemonize", no_argument, NULL, 'd'}, {"user", required_argument, NULL, 'u'}, {"group", required_argument, NULL, 'g'}, + {"syslog", no_argument, NULL, 'S'}, + {"syslog-facility", required_argument, NULL, 'F'}, + {"syslog-ident", required_argument, NULL, 'I'}, {"no-timestamps", no_argument, NULL, 't'}, {"help", no_argument, NULL, 'h'}, {"verbosity", required_argument, NULL, 'v'}, @@ -184,7 +211,7 @@ main(int argc, char *argv[], char *envp[]) {"rundir", no_argument, NULL, 'r'}, {NULL, 0, NULL, 0} }; - const char short_opts[] = "l:c:s:fdu:g:thv:V"; + const char short_opts[] = "l:c:s:fdu:g:SF:I:thv:V"; if (argc == 1) { lgtd_usage(progname); @@ -226,6 +253,15 @@ main(int argc, char *argv[], char *envp[]) case 'g': lgtd_opts.group = optarg; break; + case 'S': + lgtd_opts.syslog = true; + break; + case 'F': + lgtd_opts.syslog_facility = lgtd_daemon_syslog_facilitytoi(optarg); + break; + case 'I': + lgtd_opts.syslog_ident = optarg; + break; case 't': lgtd_opts.log_timestamps = false; break; @@ -271,6 +307,10 @@ main(int argc, char *argv[], char *envp[]) argc -= optind; argv += optind; + // Ideally we should parse the syslog relation options first and call that + // before anything can be logged: + lgtd_log_setup(); + if (lgtd_opts.user) { lgtd_daemon_set_user(lgtd_opts.user); lgtd_daemon_set_group(lgtd_opts.group); @@ -301,6 +341,13 @@ main(int argc, char *argv[], char *envp[]) event_base_dispatch(lgtd_ev_base); + if (lgtd_last_signal_received) { + lgtd_info( + "received signal %d (%s), exiting...", + lgtd_last_signal_received, strsignal(lgtd_last_signal_received) + ); + } + lgtd_cleanup(); return 0; diff --git a/core/lightsd.h b/core/lightsd.h index 2ea1f2c..0e401f1 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -103,12 +103,17 @@ reallocarray(void *optr, size_t nmemb, size_t size) } #endif +struct sockaddr; + struct lgtd_opts { bool foreground; bool log_timestamps; enum lgtd_verbosity verbosity; const char *user; const char *group; + bool syslog; + int syslog_facility; + const char *syslog_ident; }; extern struct lgtd_opts lgtd_opts; @@ -126,16 +131,15 @@ char *lgtd_print_duration(uint64_t, char *, int); #define LGTD_PRINT_DURATION(secs, arr) \ lgtd_print_duration((secs), (arr), sizeof((arr))) -void _lgtd_err(void (*)(int, const char *, ...), int, const char *, ...) - __attribute__((format(printf, 3, 4))); -#define lgtd_err(eval, fmt, ...) _lgtd_err(err, (eval), (fmt), ##__VA_ARGS__); -#define lgtd_errx(eval, fmt, ...) _lgtd_err(errx, (eval), (fmt), ##__VA_ARGS__); -void _lgtd_warn(void (*)(const char *, va_list), const char *, ...) - __attribute__((format(printf, 2, 3))); -#define lgtd_warn(fmt, ...) _lgtd_warn(vwarn, (fmt), ##__VA_ARGS__); -#define lgtd_warnx(fmt, ...) _lgtd_warn(vwarnx, (fmt), ##__VA_ARGS__); +void lgtd_log_setup(void); + +void lgtd_err(int, const char *, ...) + __attribute__((format(printf, 2, 3), noreturn)); +void lgtd_errx(int, const char *, ...) + __attribute__((format(printf, 2, 3), noreturn)); +void lgtd_warn(const char *, ...) __attribute__((format(printf, 1, 2))); +void lgtd_warnx(const char *, ...) __attribute__((format(printf, 1, 2))); void lgtd_info(const char *, ...) __attribute__((format(printf, 1, 2))); void lgtd_debug(const char *, ...) __attribute__((format(printf, 1, 2))); -void lgtd_libevent_log(int, const char *); void lgtd_cleanup(void); diff --git a/core/log.c b/core/log.c index 970e079..a0010c6 100644 --- a/core/log.c +++ b/core/log.c @@ -15,220 +15,68 @@ // You should have received a copy of the GNU General Public License // along with lighstd. If not, see . -#include #include -#include -#include -#include -#include #include -#include -#include #include #include #include #include #include -#include -#include - -#include #include "lifx/wire_proto.h" #include "stats.h" +#include "console.h" +#include "daemon.h" #include "lightsd.h" -static void -lgtd_isotime_now(char *strbuf, int bufsz) -{ - assert(strbuf); - assert(bufsz > 0); - - struct timeval now; - if (gettimeofday(&now, NULL) == -1) { - goto error; - } - struct tm tm_now; - if (!localtime_r(&now.tv_sec, &tm_now)) { - goto error; - } - LGTD_TM_TO_ISOTIME(&tm_now, strbuf, bufsz, now.tv_usec); - return; -error: - strbuf[0] = '\0'; -} - -static void -lgtd_log_header(const char *loglvl, bool showprogname) -{ - if (lgtd_opts.log_timestamps) { - char timestr[64]; - lgtd_isotime_now(timestr, sizeof(timestr)); - fprintf( - stderr, "[%s] [%s] %s", - timestr, loglvl, showprogname ? "lightsd: " : "" - ); - return; - } - fprintf(stderr, "[%s] %s", loglvl, showprogname ? "lightsd: " : ""); -} - -char * -lgtd_iee8023mactoa(const uint8_t *addr, char *buf, int buflen) -{ - assert(addr); - assert(buf); - assert(buflen >= 2 * 6 + 5 + 1); - - snprintf( - buf, buflen, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", - addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] - ); - return buf; -} - -char * -lgtd_sockaddrtoa(const struct sockaddr *peer, char *buf, int buflen) -{ - assert(peer); - assert(buf); - assert(buflen > 1); - - const char *printed = NULL; - int i = 0; - switch (peer->sa_family) { - case AF_INET: - (void)0; - const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; - LGTD_SNPRINTF_APPEND(buf, i, buflen, "[::ffff:"); - printed = inet_ntop(AF_INET, &in_peer->sin_addr, &buf[i], buflen - i); - if (printed) { - i += strlen(printed); - LGTD_SNPRINTF_APPEND( - buf, i, buflen, "]:%hu", ntohs(in_peer->sin_port) - ); - } - break; - case AF_INET6: - (void)0; - const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; - LGTD_SNPRINTF_APPEND(buf, i, buflen, "["); - printed = inet_ntop(AF_INET6, &in6_peer->sin6_addr, &buf[i], buflen - i); - if (printed) { - i += strlen(printed); - LGTD_SNPRINTF_APPEND( - buf, i, buflen, "]:%hu", ntohs(in6_peer->sin6_port) - ); - } - break; - case AF_UNIX: - (void)0; - const struct sockaddr_un *un_path = (const struct sockaddr_un *)peer; - LGTD_SNPRINTF_APPEND(buf, i, buflen, "at %s", un_path->sun_path); - printed = buf; - break; - default: - break; - } - - if (!printed) { - buf[0] = 0; - lgtd_warnx("not enough space to log an ip address"); -#ifndef NDEBUG - abort(); -#endif - } - - return buf; -} - -char * -lgtd_print_duration(uint64_t secs, char *buf, int bufsz) -{ - assert(buf); - assert(bufsz > 0); - - int days = secs / (60 * 60 * 24); - int minutes = secs / 60; - int hours = minutes / 60; - hours = hours % 24; - minutes = minutes % 60; - - int i = 0; - if (days) { - int n = snprintf(buf, bufsz, "%d days ", days); - i = LGTD_MIN(i + n, bufsz); - } - snprintf(&buf[i], bufsz - i, "%02d:%02d", hours, minutes); - return buf; -} - -void -_lgtd_err(void (*errfn)(int, const char *, ...), - int eval, - const char *fmt, - ...) -{ - int errsave = errno; - va_list ap; - va_start(ap, fmt); - // lgtd_cleanup is probably going to free some of the arguments we got, so - // let's print to a buffer before we call err. - char errmsg[LGTD_ERROR_MSG_BUFSIZE]; - vsnprintf(errmsg, sizeof(errmsg), fmt, ap); - va_end(ap); - lgtd_cleanup(); - lgtd_log_header("ERR", false); - errno = errsave; - errfn(eval, errmsg); -} - void -_lgtd_warn(void (*warnfn)(const char *, va_list), const char *fmt, ...) +lgtd_log_setup(void) { - if (lgtd_opts.verbosity <= LGTD_WARN) { - va_list ap; - va_start(ap, fmt); - lgtd_log_header("WARN", false); - warnfn(fmt, ap); - va_end(ap); + if (lgtd_opts.syslog) { + lgtd_daemon_syslog_open( + lgtd_opts.syslog_ident, + lgtd_opts.verbosity, + lgtd_opts.syslog_facility + ); } } -void -lgtd_info(const char *fmt, ...) -{ - if (lgtd_opts.verbosity <= LGTD_INFO) { - va_list ap; - va_start(ap, fmt); - lgtd_log_header("INFO", true); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); - } +#define ERRFN(fn) \ +void \ +lgtd_##fn(int eval, const char *fmt, ...) \ +{ \ + va_list ap; \ + va_start(ap, fmt); \ + if (lgtd_opts.syslog) { \ + lgtd_daemon_syslog_##fn(eval, fmt, ap); \ + } else { \ + lgtd_console_##fn(eval, fmt, ap); \ + } \ + /* not reached */ \ } -void -lgtd_debug(const char *fmt, ...) -{ - if (lgtd_opts.verbosity <= LGTD_DEBUG) { - va_list ap; - va_start(ap, fmt); - lgtd_log_header("DEBUG", true); - vfprintf(stderr, fmt, ap); - va_end(ap); - fprintf(stderr, "\n"); - } +ERRFN(err); +ERRFN(errx); + +#define LOGFN(level ,fn) \ +void \ +lgtd_##fn(const char *fmt, ...) \ +{ \ + if (lgtd_opts.verbosity > (level)) { \ + return; \ + } \ + \ + va_list ap; \ + va_start(ap, fmt); \ + if (lgtd_opts.syslog) { \ + lgtd_daemon_syslog_##fn(fmt, ap); \ + } else { \ + lgtd_console_##fn(fmt, ap); \ + } \ + va_end(ap); \ } -void -lgtd_libevent_log(int severity, const char *msg) -{ - switch (severity) { - case EVENT_LOG_DEBUG: lgtd_debug("%s", msg); break; - case EVENT_LOG_MSG: lgtd_info("%s", msg); break; - case EVENT_LOG_WARN: lgtd_warnx("%s", msg) break; - case EVENT_LOG_ERR: lgtd_warnx("%s", msg); break; - default: break; - } -} +LOGFN(LGTD_WARN, warn); +LOGFN(LGTD_WARN, warnx); +LOGFN(LGTD_INFO, info); +LOGFN(LGTD_DEBUG, debug); diff --git a/core/utils.c b/core/utils.c new file mode 100644 index 0000000..b3640d6 --- /dev/null +++ b/core/utils.c @@ -0,0 +1,119 @@ +// Copyright (c) 2015, Louis Opter +// +// This file is part of lighstd. +// +// lighstd is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// lighstd is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with lighstd. If not, see . + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lightsd.h" + +char * +lgtd_iee8023mactoa(const uint8_t *addr, char *buf, int buflen) +{ + assert(addr); + assert(buf); + assert(buflen >= 2 * 6 + 5 + 1); + + snprintf( + buf, buflen, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5] + ); + return buf; +} + +char * +lgtd_sockaddrtoa(const struct sockaddr *peer, char *buf, int buflen) +{ + assert(peer); + assert(buf); + assert(buflen > 1); + + const char *printed = NULL; + int i = 0; + switch (peer->sa_family) { + case AF_INET: + (void)0; + const struct sockaddr_in *in_peer = (const struct sockaddr_in *)peer; + LGTD_SNPRINTF_APPEND(buf, i, buflen, "[::ffff:"); + printed = inet_ntop(AF_INET, &in_peer->sin_addr, &buf[i], buflen - i); + if (printed) { + i += strlen(printed); + LGTD_SNPRINTF_APPEND( + buf, i, buflen, "]:%hu", ntohs(in_peer->sin_port) + ); + } + break; + case AF_INET6: + (void)0; + const struct sockaddr_in6 *in6_peer = (const struct sockaddr_in6 *)peer; + LGTD_SNPRINTF_APPEND(buf, i, buflen, "["); + printed = inet_ntop(AF_INET6, &in6_peer->sin6_addr, &buf[i], buflen - i); + if (printed) { + i += strlen(printed); + LGTD_SNPRINTF_APPEND( + buf, i, buflen, "]:%hu", ntohs(in6_peer->sin6_port) + ); + } + break; + case AF_UNIX: + (void)0; + const struct sockaddr_un *un_path = (const struct sockaddr_un *)peer; + LGTD_SNPRINTF_APPEND(buf, i, buflen, "at %s", un_path->sun_path); + printed = buf; + break; + default: + break; + } + + if (!printed) { + buf[0] = 0; + lgtd_warnx("not enough space to log an ip address"); +#ifndef NDEBUG + abort(); +#endif + } + + return buf; +} + +char * +lgtd_print_duration(uint64_t secs, char *buf, int bufsz) +{ + assert(buf); + assert(bufsz > 0); + + int days = secs / (60 * 60 * 24); + int minutes = secs / 60; + int hours = minutes / 60; + hours = hours % 24; + minutes = minutes % 60; + + int i = 0; + if (days) { + int n = snprintf(buf, bufsz, "%d days ", days); + i = LGTD_MIN(i + n, bufsz); + } + snprintf(&buf[i], bufsz - i, "%02d:%02d", hours, minutes); + return buf; +} diff --git a/docs/conf.py b/docs/conf.py index f16626c..6e39d09 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,9 +52,9 @@ # built documents. # # The short X.Y version. -version = '1.0.0' +version = '1.1.0' # The full version, including alpha/beta/rc tags. -release = '1.0.0' +release = '1.1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/first-steps.rst b/docs/first-steps.rst index fc73092..27a361a 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -133,6 +133,10 @@ Command line options group of this user if -g is missing). [-g,--group group] Drop privileges to this group (-g requires the -u option to be used). + [-S,--syslog] Divert logging from the console to syslog. + [-F,--syslog-facility] Facility to use with syslog (defaults to + daemon, other possible values are user and + local0-7, see syslog(3)). [-t,--no-timestamps] Disable timestamps in logs. [-h,--help] Display this. [-V,--version] Display version and build information. @@ -184,8 +188,8 @@ from your current shell or shell script: .. data:: LIGHTSD_COMMAND_PIPE - By default lightsc will use `lightsd --rundir`/pipe but you can set that to - your own value. + By default lightsc will use ```lightsd --rundir`/pipe`` but you can set that + to your own value. .. describe:: lightsc method [arguments…] @@ -205,8 +209,8 @@ from your current shell or shell script: .. note:: Keep in mind that arguments must be JSON, you will have to enclose tags and - labels into double quotes '"likethis"'. The command pipe is write-only: you - cannot read any result back. + labels into double quotes ``'"likethis"'``. The command pipe is write-only: + you cannot read any result back. Examples: diff --git a/tests/core/client/CMakeLists.txt b/tests/core/client/CMakeLists.txt index 7d80725..02558b2 100644 --- a/tests/core/client/CMakeLists.txt +++ b/tests/core/client/CMakeLists.txt @@ -6,8 +6,8 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_core_client STATIC ${LIGHTSD_SOURCE_DIR}/core/jsmn.c - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c diff --git a/tests/core/client/test_client_read_callback.c b/tests/core/client/test_client_read_callback.c index 7191a0a..28c332e 100644 --- a/tests/core/client/test_client_read_callback.c +++ b/tests/core/client/test_client_read_callback.c @@ -11,6 +11,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/client/test_client_read_callback_extra_data.c b/tests/core/client/test_client_read_callback_extra_data.c index f7e088f..e1ce8d1 100644 --- a/tests/core/client/test_client_read_callback_extra_data.c +++ b/tests/core/client/test_client_read_callback_extra_data.c @@ -11,6 +11,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/client/test_client_read_callback_multiple_requests.c b/tests/core/client/test_client_read_callback_multiple_requests.c index b45008b..a0b7954 100644 --- a/tests/core/client/test_client_read_callback_multiple_requests.c +++ b/tests/core/client/test_client_read_callback_multiple_requests.c @@ -11,6 +11,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/client/test_client_read_callback_part_too_large.c b/tests/core/client/test_client_read_callback_part_too_large.c index 0be5a52..87e279e 100644 --- a/tests/core/client/test_client_read_callback_part_too_large.c +++ b/tests/core/client/test_client_read_callback_part_too_large.c @@ -12,6 +12,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/client/test_client_read_callback_yield_on_eagain.c b/tests/core/client/test_client_read_callback_yield_on_eagain.c index 12350a0..d8be9bc 100644 --- a/tests/core/client/test_client_read_callback_yield_on_eagain.c +++ b/tests/core/client/test_client_read_callback_yield_on_eagain.c @@ -12,6 +12,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/daemon/CMakeLists.txt b/tests/core/daemon/CMakeLists.txt index 8fc298f..6e4712a 100644 --- a/tests/core/daemon/CMakeLists.txt +++ b/tests/core/daemon/CMakeLists.txt @@ -5,9 +5,9 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_core_daemon STATIC - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/setproctitle.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c diff --git a/tests/core/daemon/test_daemon_update_proctitle.c b/tests/core/daemon/test_daemon_update_proctitle.c index 5dfab6c..b6c1ddf 100644 --- a/tests/core/daemon/test_daemon_update_proctitle.c +++ b/tests/core/daemon/test_daemon_update_proctitle.c @@ -6,11 +6,10 @@ void mock_setproctitle(const char *fmt, ...) #define setproctitle mock_setproctitle #include "daemon.c" -#include - #include "mock_gateway.h" #include "mock_pipe.h" #include "mock_router.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt index ee70592..7423be8 100644 --- a/tests/core/jsonrpc/CMakeLists.txt +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -6,8 +6,8 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_core_jsonrpc STATIC ${LIGHTSD_SOURCE_DIR}/core/jsmn.c - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c diff --git a/tests/core/jsonrpc/test_jsonrpc_batch.c b/tests/core/jsonrpc/test_jsonrpc_batch.c index c831eb0..cb599aa 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_GET_LIGHT_STATE #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c b/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c index 4d1c9da..9ad474c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_GET_LIGHT_STATE #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c b/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c index 7d9d005..133a1d9 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c b/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c index af63472..fff0372 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_GET_LIGHT_STATE #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c index d870cab..21f6136 100644 --- a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c +++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c index aa75be5..2a5608a 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_OFF #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c index 0d0a4af..15ed9a5 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_OFF #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c index 78b82e6..0824445 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c index 79f8def..e878f1d 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c index 7646d5b..c77d05e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_TOGGLE #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c index a7bc3f9..f229d0b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_LABEL #include "mock_proto.h" #include "mock_gateway.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c index e0eafaf..96d600f 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c index 981572c..4b8df1c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c index 01c053c..5564d99 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c index 0cc2f40..3a228d1 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_WAVEFORM #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c index 9dc39ba..de73b82 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_WAVEFORM #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c index e3f0835..5417d64 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_TAG #include "mock_proto.h" #include "mock_gateway.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c index f8aa11b..0fbd5dc 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_TAG #include "mock_proto.h" #include "mock_gateway.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c index 370374f..66de4b6 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_UNTAG #include "mock_proto.h" #include "mock_gateway.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c index eb42787..4f3c816 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_UNTAG #include "mock_proto.h" #include "mock_gateway.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c b/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c index 1d9d6e7..b453144 100644 --- a/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c index c1c90ff..cb5886c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c index c4d2f28..c5895ef 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c index 8ae62d1..235f1ad 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c index fd9d466..276edec 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c index 14e35e6..e076aa7 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c index 102c6aa..05b9736 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c b/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c index 38e6acb..cfa0fda 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c @@ -3,6 +3,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c index e0c8759..344efe5 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_error.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_send_response.c b/tests/core/jsonrpc/test_jsonrpc_send_response.c index c54e382..8eddd9b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_response.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_response.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c index e04c4f4..3d78048 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c index 2fe6e5a..18e90ab 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c index 625da59..abbc05d 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c index 5eafeb6..f0a4e50 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer.c b/tests/core/jsonrpc/test_jsonrpc_type_integer.c index 3de2c1c..492e3d9 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c index 9d4da65..a033eea 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c index a0a4999..ae99797 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c index ef39326..33c436f 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c @@ -1,6 +1,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c index 1098d00..50e949b 100644 --- a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c +++ b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c @@ -3,6 +3,7 @@ #include "jsonrpc.c" #include "mock_client_buf.h" +#include "mock_log.h" #include "mock_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/mock_log.h b/tests/core/mock_log.h new file mode 100644 index 0000000..8468887 --- /dev/null +++ b/tests/core/mock_log.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include + +void +lgtd_err(int eval, const char *fmt, ...) +{ + fprintf(stderr, "ERR: "); + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errno)); + exit(eval); +} + +void +lgtd_errx(int eval, const char *fmt, ...) +{ + fprintf(stderr, "ERR: "); + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); + exit(eval); +} + +void +lgtd_warn(const char *fmt, ...) +{ + if (lgtd_opts.verbosity > LGTD_WARN) { + return; + } + + fprintf(stderr, "WARN: "); + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errno)); +} + +void +lgtd_warnx(const char *fmt, ...) +{ + if (lgtd_opts.verbosity > LGTD_WARN) { + return; + } + + fprintf(stderr, "WARN: "); + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); +} + +void +lgtd_info(const char *fmt, ...) +{ + if (lgtd_opts.verbosity > LGTD_INFO) { + return; + } + + fprintf(stderr, "INFO: "); + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); +} + +void +lgtd_debug(const char *fmt, ...) +{ + if (lgtd_opts.verbosity > LGTD_DEBUG) { + return; + } + + fprintf(stderr, "DEBUG: "); + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputc('\n', stderr); +} diff --git a/tests/core/pipe/CMakeLists.txt b/tests/core/pipe/CMakeLists.txt index 0ea7a09..3adc65c 100644 --- a/tests/core/pipe/CMakeLists.txt +++ b/tests/core/pipe/CMakeLists.txt @@ -6,8 +6,8 @@ INCLUDE_DIRECTORIES( ADD_LIBRARY( test_core_pipe STATIC ${LIGHTSD_SOURCE_DIR}/core/jsmn.c - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c diff --git a/tests/core/pipe/test_pipe_close.c b/tests/core/pipe/test_pipe_close.c index bfae00d..2e996e7 100644 --- a/tests/core/pipe/test_pipe_close.c +++ b/tests/core/pipe/test_pipe_close.c @@ -15,6 +15,7 @@ #include "mock_event2.h" #include "mock_gateway.h" #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_open.c b/tests/core/pipe/test_pipe_open.c index 233a073..d818b2f 100644 --- a/tests/core/pipe/test_pipe_open.c +++ b/tests/core/pipe/test_pipe_open.c @@ -14,6 +14,7 @@ #include "mock_event2.h" #include "mock_gateway.h" #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_open_fifo_already_exists.c b/tests/core/pipe/test_pipe_open_fifo_already_exists.c index f90b9bf..29a07bb 100644 --- a/tests/core/pipe/test_pipe_open_fifo_already_exists.c +++ b/tests/core/pipe/test_pipe_open_fifo_already_exists.c @@ -14,6 +14,7 @@ #include "mock_event2.h" #include "mock_gateway.h" #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_read_callback.c b/tests/core/pipe/test_pipe_read_callback.c index d85a8b5..d18cd61 100644 --- a/tests/core/pipe/test_pipe_read_callback.c +++ b/tests/core/pipe/test_pipe_read_callback.c @@ -18,6 +18,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_read_callback_extra_data.c b/tests/core/pipe/test_pipe_read_callback_extra_data.c index df348fb..d7399df 100644 --- a/tests/core/pipe/test_pipe_read_callback_extra_data.c +++ b/tests/core/pipe/test_pipe_read_callback_extra_data.c @@ -17,6 +17,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c index 19fff7c..0f983a9 100644 --- a/tests/core/pipe/test_pipe_read_callback_multiple_requests.c +++ b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c @@ -17,6 +17,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c index 9e3c545..93b2bc8 100644 --- a/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c +++ b/tests/core/pipe/test_pipe_read_callback_yield_on_eagain.c @@ -17,6 +17,7 @@ #include "mock_gateway.h" #define MOCKED_JSONRPC_DISPATCH_REQUEST #include "mock_jsonrpc.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt index 1c4600b..ab681f7 100644 --- a/tests/core/proto/CMakeLists.txt +++ b/tests/core/proto/CMakeLists.txt @@ -5,9 +5,9 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_core_proto STATIC - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/jsonrpc.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c index 1e5eb17..ea61123 100644 --- a/tests/core/proto/test_proto_get_light_state.c +++ b/tests/core/proto/test_proto_get_light_state.c @@ -4,6 +4,7 @@ #include "mock_daemon.h" #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_get_light_state_empty_device_list.c b/tests/core/proto/test_proto_get_light_state_empty_device_list.c index 5f22e95..59b760a 100644 --- a/tests/core/proto/test_proto_get_light_state_empty_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_empty_device_list.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_get_light_state_label_overflow.c b/tests/core/proto/test_proto_get_light_state_label_overflow.c index dc968c7..087e906 100644 --- a/tests/core/proto/test_proto_get_light_state_label_overflow.c +++ b/tests/core/proto/test_proto_get_light_state_label_overflow.c @@ -4,6 +4,7 @@ #include "mock_daemon.h" #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_get_light_state_null_device_list.c b/tests/core/proto/test_proto_get_light_state_null_device_list.c index 09b4728..5e8f9f3 100644 --- a/tests/core/proto/test_proto_get_light_state_null_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_null_device_list.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c index f8c5358..521543f 100644 --- a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c +++ b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c @@ -4,6 +4,7 @@ #include "mock_daemon.h" #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_power_off.c b/tests/core/proto/test_proto_power_off.c index d04d771..1426dd1 100644 --- a/tests/core/proto/test_proto_power_off.c +++ b/tests/core/proto/test_proto_power_off.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c index b56e05b..2ec259b 100644 --- a/tests/core/proto/test_proto_power_off_routing_error.c +++ b/tests/core/proto/test_proto_power_off_routing_error.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_power_on.c b/tests/core/proto/test_proto_power_on.c index aa22922..1f11bef 100644 --- a/tests/core/proto/test_proto_power_on.c +++ b/tests/core/proto/test_proto_power_on.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_power_on_routing_error.c b/tests/core/proto/test_proto_power_on_routing_error.c index 2327ee9..87c570d 100644 --- a/tests/core/proto/test_proto_power_on_routing_error.c +++ b/tests/core/proto/test_proto_power_on_routing_error.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_power_toggle.c b/tests/core/proto/test_proto_power_toggle.c index e60ea2c..34e74a8 100644 --- a/tests/core/proto/test_proto_power_toggle.c +++ b/tests/core/proto/test_proto_power_toggle.c @@ -4,6 +4,7 @@ #include "mock_daemon.h" #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c index 4443c10..b22b2df 100644 --- a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c +++ b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c @@ -4,6 +4,7 @@ #include "mock_daemon.h" #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_set_label.c b/tests/core/proto/test_proto_set_label.c index 741e1f4..c041d77 100644 --- a/tests/core/proto/test_proto_set_label.c +++ b/tests/core/proto/test_proto_set_label.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_set_label_too_long.c b/tests/core/proto/test_proto_set_label_too_long.c index c05c5be..2c6371a 100644 --- a/tests/core/proto/test_proto_set_label_too_long.c +++ b/tests/core/proto/test_proto_set_label_too_long.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_set_light_from_hsbk.c b/tests/core/proto/test_proto_set_light_from_hsbk.c index 9a915f2..2092c8c 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk.c @@ -5,6 +5,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c index cd641db..c6ce1bc 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c @@ -5,6 +5,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index f997a97..ebe823d 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -5,6 +5,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index 6b1e259..f7916ab 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -5,6 +5,7 @@ #include "mock_client_buf.h" #include "mock_daemon.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c index 329f696..9613feb 100644 --- a/tests/core/proto/test_proto_tag_create.c +++ b/tests/core/proto/test_proto_tag_create.c @@ -6,6 +6,7 @@ #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c index 7b0233f..3f24c16 100644 --- a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c +++ b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c @@ -6,6 +6,7 @@ #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c index 0152d22..8bafb43 100644 --- a/tests/core/proto/test_proto_tag_update.c +++ b/tests/core/proto/test_proto_tag_update.c @@ -6,6 +6,7 @@ #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c index 4374724..ef828c6 100644 --- a/tests/core/proto/test_proto_untag.c +++ b/tests/core/proto/test_proto_untag.c @@ -4,6 +4,7 @@ #include "mock_daemon.h" #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/proto/test_proto_untag_tag_does_not_exist.c b/tests/core/proto/test_proto_untag_tag_does_not_exist.c index 3f2d741..ca2d7f1 100644 --- a/tests/core/proto/test_proto_untag_tag_does_not_exist.c +++ b/tests/core/proto/test_proto_untag_tag_does_not_exist.c @@ -4,6 +4,7 @@ #include "mock_daemon.h" #include "mock_gateway.h" #include "mock_event2.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/router/CMakeLists.txt b/tests/core/router/CMakeLists.txt index 3f75838..1381327 100644 --- a/tests/core/router/CMakeLists.txt +++ b/tests/core/router/CMakeLists.txt @@ -5,9 +5,9 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_core_router STATIC - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/proto.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c diff --git a/tests/core/router/test_router_send_to_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c index 5d5c058..54b7322 100644 --- a/tests/core/router/test_router_send_to_broadcast.c +++ b/tests/core/router/test_router_send_to_broadcast.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" diff --git a/tests/core/router/test_router_send_to_device.c b/tests/core/router/test_router_send_to_device.c index 7000770..5e9f50c 100644 --- a/tests/core/router/test_router_send_to_device.c +++ b/tests/core/router/test_router_send_to_device.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_invalid_targets.c b/tests/core/router/test_router_send_to_invalid_targets.c index c9ae736..b99fe39 100644 --- a/tests/core/router/test_router_send_to_invalid_targets.c +++ b/tests/core/router/test_router_send_to_invalid_targets.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index dc54cdd..55760fd 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index 077e066..d77ed0e 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/core/router/test_router_targets_to_devices.c b/tests/core/router/test_router_targets_to_devices.c index 5359177..ee8f1bf 100644 --- a/tests/core/router/test_router_targets_to_devices.c +++ b/tests/core/router/test_router_targets_to_devices.c @@ -1,6 +1,7 @@ #include "router.c" #include "mock_daemon.h" +#include "mock_log.h" #include "mock_timer.h" #include "tests_utils.h" #include "tests_router_utils.h" diff --git a/tests/lifx/bulb/CMakeLists.txt b/tests/lifx/bulb/CMakeLists.txt index 3e06214..7abd17c 100644 --- a/tests/lifx/bulb/CMakeLists.txt +++ b/tests/lifx/bulb/CMakeLists.txt @@ -5,8 +5,8 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_lifx_bulb_core STATIC - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../../core/tests_utils.c ) diff --git a/tests/lifx/bulb/test_bulb_close.c b/tests/lifx/bulb/test_bulb_close.c index b599301..1b6c180 100644 --- a/tests/lifx/bulb/test_bulb_close.c +++ b/tests/lifx/bulb/test_bulb_close.c @@ -1,6 +1,7 @@ #include "bulb.c" #include "mock_gateway.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/lifx/bulb/test_bulb_fetch_hardware_info.c b/tests/lifx/bulb/test_bulb_fetch_hardware_info.c index a88e216..5a54e40 100644 --- a/tests/lifx/bulb/test_bulb_fetch_hardware_info.c +++ b/tests/lifx/bulb/test_bulb_fetch_hardware_info.c @@ -1,6 +1,7 @@ #include "bulb.c" #include "mock_gateway.h" +#include "mock_log.h" #define MOCKED_LGTD_ROUTER_SEND_TO_DEVICE #include "mock_router.h" #define MOCKED_LGTD_TIMER_STOP diff --git a/tests/lifx/bulb/test_bulb_has_label.c b/tests/lifx/bulb/test_bulb_has_label.c index bead14a..1a69da1 100644 --- a/tests/lifx/bulb/test_bulb_has_label.c +++ b/tests/lifx/bulb/test_bulb_has_label.c @@ -1,6 +1,7 @@ #include "bulb.c" #include "mock_gateway.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/lifx/bulb/test_bulb_open.c b/tests/lifx/bulb/test_bulb_open.c index c06008e..aa4ccf7 100644 --- a/tests/lifx/bulb/test_bulb_open.c +++ b/tests/lifx/bulb/test_bulb_open.c @@ -1,6 +1,7 @@ #include "bulb.c" #include "mock_gateway.h" +#include "mock_log.h" #include "mock_router.h" #define MOCKED_LGTD_TIMER_START #include "mock_timer.h" diff --git a/tests/lifx/bulb/test_bulb_set_light_state.c b/tests/lifx/bulb/test_bulb_set_light_state.c index c5cfcf3..581c7d5 100644 --- a/tests/lifx/bulb/test_bulb_set_light_state.c +++ b/tests/lifx/bulb/test_bulb_set_light_state.c @@ -2,6 +2,7 @@ #define MOCKED_LGTD_LIFX_GATEWAY_UPDATE_TAG_REFCOUNTS #include "mock_gateway.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/lifx/bulb/test_bulb_set_power_state.c b/tests/lifx/bulb/test_bulb_set_power_state.c index 6129df4..a37e55e 100644 --- a/tests/lifx/bulb/test_bulb_set_power_state.c +++ b/tests/lifx/bulb/test_bulb_set_power_state.c @@ -1,6 +1,7 @@ #include "bulb.c" #include "mock_gateway.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/lifx/bulb/test_bulb_set_tags.c b/tests/lifx/bulb/test_bulb_set_tags.c index 53bb5cd..6dcd0fd 100644 --- a/tests/lifx/bulb/test_bulb_set_tags.c +++ b/tests/lifx/bulb/test_bulb_set_tags.c @@ -2,6 +2,7 @@ #define MOCKED_LGTD_LIFX_GATEWAY_UPDATE_TAG_REFCOUNTS #include "mock_gateway.h" +#include "mock_log.h" #include "mock_router.h" #include "mock_timer.h" diff --git a/tests/lifx/gateway/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt index 8140926..a9411f2 100644 --- a/tests/lifx/gateway/CMakeLists.txt +++ b/tests/lifx/gateway/CMakeLists.txt @@ -5,10 +5,10 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_lifx_gateway_core STATIC - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/proto.c ${LIGHTSD_SOURCE_DIR}/core/router.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../../core/tests_utils.c ) diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id.c b/tests/lifx/gateway/test_gateway_allocate_tag_id.c index 88604dd..4ee310c 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id.c @@ -4,6 +4,7 @@ #define MOCKED_LIFX_TAGGING_INCREF #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" static bool tagging_incref_called = false; diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c index f1d676d..d6a73a5 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c @@ -4,6 +4,7 @@ #define MOCKED_LIFX_TAGGING_INCREF #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" static bool tagging_incref_called = false; diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c index b1df7d6..dbe0e10 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c @@ -5,6 +5,7 @@ #define MOCKED_LIFX_TAGGING_INCREF #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" static bool tagging_incref_called = false; diff --git a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c index 2d1c4f6..a3caab0 100644 --- a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c @@ -4,6 +4,7 @@ #define MOCKED_LIFX_TAGGING_DECREF #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" static bool tagging_decref_called = false; diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet.c b/tests/lifx/gateway/test_gateway_enqueue_packet.c index 117bc1c..eef15b3 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet.c @@ -1,6 +1,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" int diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c index d3ea07b..3a8148b 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c @@ -1,6 +1,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" int diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c index 57324c8..41506e4 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c @@ -1,6 +1,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" int diff --git a/tests/lifx/gateway/test_gateway_handle_ambient_light.c b/tests/lifx/gateway/test_gateway_handle_ambient_light.c index 423ca6d..4d2b41e 100644 --- a/tests/lifx/gateway/test_gateway_handle_ambient_light.c +++ b/tests/lifx/gateway/test_gateway_handle_ambient_light.c @@ -2,6 +2,7 @@ #include "gateway.c" +#include "mock_log.h" #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" diff --git a/tests/lifx/gateway/test_gateway_handle_bulb_label.c b/tests/lifx/gateway/test_gateway_handle_bulb_label.c index 8e970d0..1f6d1c3 100644 --- a/tests/lifx/gateway/test_gateway_handle_bulb_label.c +++ b/tests/lifx/gateway/test_gateway_handle_bulb_label.c @@ -2,6 +2,7 @@ #include "gateway.c" +#include "mock_log.h" #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" diff --git a/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c b/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c index e5530fa..c346a3e 100644 --- a/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c +++ b/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c @@ -2,6 +2,7 @@ #include "gateway.c" +#include "mock_log.h" #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" diff --git a/tests/lifx/gateway/test_gateway_handle_ip_state.c b/tests/lifx/gateway/test_gateway_handle_ip_state.c index e26fd17..d60866a 100644 --- a/tests/lifx/gateway/test_gateway_handle_ip_state.c +++ b/tests/lifx/gateway/test_gateway_handle_ip_state.c @@ -2,6 +2,7 @@ #include "gateway.c" +#include "mock_log.h" #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" diff --git a/tests/lifx/gateway/test_gateway_handle_product_info.c b/tests/lifx/gateway/test_gateway_handle_product_info.c index a2e3bea..176ff40 100644 --- a/tests/lifx/gateway/test_gateway_handle_product_info.c +++ b/tests/lifx/gateway/test_gateway_handle_product_info.c @@ -2,6 +2,7 @@ #include "gateway.c" +#include "mock_log.h" #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" diff --git a/tests/lifx/gateway/test_gateway_handle_runtime_info.c b/tests/lifx/gateway/test_gateway_handle_runtime_info.c index a17680d..ffb00d1 100644 --- a/tests/lifx/gateway/test_gateway_handle_runtime_info.c +++ b/tests/lifx/gateway/test_gateway_handle_runtime_info.c @@ -2,6 +2,7 @@ #include "gateway.c" +#include "mock_log.h" #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" diff --git a/tests/lifx/gateway/test_gateway_handle_tag_labels.c b/tests/lifx/gateway/test_gateway_handle_tag_labels.c index 64a35c2..a3ef33f 100644 --- a/tests/lifx/gateway/test_gateway_handle_tag_labels.c +++ b/tests/lifx/gateway/test_gateway_handle_tag_labels.c @@ -3,6 +3,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" int diff --git a/tests/lifx/gateway/test_gateway_handle_tags.c b/tests/lifx/gateway/test_gateway_handle_tags.c index 443a8b4..c45dafa 100644 --- a/tests/lifx/gateway/test_gateway_handle_tags.c +++ b/tests/lifx/gateway/test_gateway_handle_tags.c @@ -2,6 +2,7 @@ #include "gateway.c" +#include "mock_log.h" #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" diff --git a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c index 575849f..681d422 100644 --- a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c +++ b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c @@ -1,6 +1,7 @@ #include "gateway.c" #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" int diff --git a/tests/lifx/gateway/test_gateway_write_callback.c b/tests/lifx/gateway/test_gateway_write_callback.c index 007dfa0..9adfb5a 100644 --- a/tests/lifx/gateway/test_gateway_write_callback.c +++ b/tests/lifx/gateway/test_gateway_write_callback.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" size_t diff --git a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c index e94a2cb..5b5e32f 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c +++ b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" size_t diff --git a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c index 322cf70..9fb07fe 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c +++ b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" size_t diff --git a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c index e95f9cc..c673584 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c +++ b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" size_t diff --git a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c index f9cbfa0..dfdfda7 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c @@ -3,6 +3,7 @@ #define MOCKED_EVBUFFER_WRITE_ATMOST #define MOCKED_EVBUFFER_GET_LENGTH #include "test_gateway_utils.h" +#include "mock_log.h" #include "mock_timer.h" size_t diff --git a/tests/lifx/tagging/CMakeLists.txt b/tests/lifx/tagging/CMakeLists.txt index d5c1e5f..08fa1c2 100644 --- a/tests/lifx/tagging/CMakeLists.txt +++ b/tests/lifx/tagging/CMakeLists.txt @@ -5,8 +5,8 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_lifx_tagging STATIC - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ) diff --git a/tests/lifx/tagging/test_tagging_decref.c b/tests/lifx/tagging/test_tagging_decref.c index e7a41d6..edcd3bc 100644 --- a/tests/lifx/tagging/test_tagging_decref.c +++ b/tests/lifx/tagging/test_tagging_decref.c @@ -1,5 +1,7 @@ #include "tagging.c" +#include "mock_log.h" + static int count_tag(const char *tag_label) { diff --git a/tests/lifx/tagging/test_tagging_incref.c b/tests/lifx/tagging/test_tagging_incref.c index 0e15bb0..889a480 100644 --- a/tests/lifx/tagging/test_tagging_incref.c +++ b/tests/lifx/tagging/test_tagging_incref.c @@ -1,5 +1,7 @@ #include "tagging.c" +#include "mock_log.h" + static int count_tag(const char *tag_label) { diff --git a/tests/lifx/wire_proto/CMakeLists.txt b/tests/lifx/wire_proto/CMakeLists.txt index ba09548..c4c35f2 100644 --- a/tests/lifx/wire_proto/CMakeLists.txt +++ b/tests/lifx/wire_proto/CMakeLists.txt @@ -5,8 +5,8 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_lifx_wire_proto_core STATIC - ${LIGHTSD_SOURCE_DIR}/core/log.c ${LIGHTSD_SOURCE_DIR}/core/stats.c + ${LIGHTSD_SOURCE_DIR}/core/utils.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ) diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c index 062c213..1470493 100644 --- a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c +++ b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c @@ -3,6 +3,7 @@ #include "wire_proto.c" #include "mock_gateway.h" +#include "mock_log.h" int main(void) diff --git a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c index 41f0ba5..60cc173 100644 --- a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c +++ b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c @@ -2,6 +2,8 @@ #include "mock_gateway.h" +#include "mock_log.h" + int main(void) { From 3097bde2898fb001fb4a70937c33aff4c6b77bd2 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 145/181] Lookup for a socket in a build directory in lightsc.py Hopefully it will help people trying to build lightsd manually a little bit. This also fixes relative path support for unix sockets. --- examples/lightsc.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/lightsc.py b/examples/lightsc.py index edd16fc..e686858 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -49,7 +49,8 @@ def __init__(self, url): if parts.scheme == "unix": self._socket = socket.socket(socket.AF_UNIX) - self._socket.connect(parts.path) + path = os.path.join(parts.netloc, parts.path).rstrip(os.path.sep) + self._socket.connect(path) elif parts.scheme == "tcp": self._socket = socket.create_connection((parts.hostname, parts.port)) else: @@ -227,11 +228,14 @@ def _drop_to_shell(lightsc): except Exception as ex: print( "Couldn't infer lightsd's runtime directory is lightsd installed? " - "({})".format(ex), + "({})\nTrying build/socket...".format(ex), file=sys.stderr ) - sys.exit(1) - lightsdrundir = lightsdrundir.decode(locale.getpreferredencoding()).strip() + lightscdir = os.path.realpath(__file__).split(os.path.sep)[:-2] + lightsdrundir = os.path.join(*[os.path.sep] + lightscdir + ["build"]) + else: + encoding = locale.getpreferredencoding() + lightsdrundir = lightsdrundir.decode(encoding).strip() parser = argparse.ArgumentParser( description="lightsc.py is an interactive lightsd Python client" From 17cc4d3e2e6afc15f2b7ce24d5973b5d991a2d44 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 146/181] Stop documenting the -f option We might want to deprecate it at some point. --- core/lightsd.c | 1 - dist/lightsd.service | 2 +- docs/first-steps.rst | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/lightsd.c b/core/lightsd.c index e82de51..371828e 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -155,7 +155,6 @@ lgtd_usage(const char *progname) " repeated).\n" " [-s,--socket /unix/socket] Open an Unix socket at this location\n" " (can be repeated).\n" -" [-f,--foreground] Stay in the foreground (default).\n" " [-d,--daemonize] Fork in the background.\n" " [-u,--user user] Drop privileges to this user (and the\n" " group of this user if -g is missing).\n" diff --git a/dist/lightsd.service b/dist/lightsd.service index 9a51cd4..e80f64e 100644 --- a/dist/lightsd.service +++ b/dist/lightsd.service @@ -3,7 +3,7 @@ Description=LIFX WiFi smart bulbs control service After=network.target [Service] -ExecStart=/usr/bin/lightsd -t -v warning -f -u lightsd -s %t/lightsd/socket -c %t/lightsd/pipe +ExecStart=/usr/bin/lightsd -t -v warning -u lightsd -s %t/lightsd/socket -c %t/lightsd/pipe Restart=on-failure [Install] diff --git a/docs/first-steps.rst b/docs/first-steps.rst index 27a361a..b59d2e2 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -127,7 +127,6 @@ Command line options repeated). [-s,--socket /unix/socket [+]] Open an Unix socket at this location (can be repeated). - [-f,--foreground] Stay in the foreground (default). [-d,--daemonize] Fork in the background. [-u,--user user] Drop privileges to this user (and the group of this user if -g is missing). From 950b53a92b85607387b84483c365dbdd3e143ae1 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 147/181] Fix lintian warning with lightsc.sh https://twitter.com/1opter/status/661834262547718144 --- share/lightsc.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/share/lightsc.sh b/share/lightsc.sh index 6ebe690..fb62311 100644 --- a/share/lightsc.sh +++ b/share/lightsc.sh @@ -1,4 +1,3 @@ -#!/bin/sh # Copyright (c) 2015, Louis Opter # All rights reserved. # @@ -107,3 +106,5 @@ lightsc() { lightsc_make_request $* | tee `lightsc_get_pipe` | _lightsc_jq } + +# vim: set expandtab ft=sh sts=4 sw=4: From d601bfa22ba9effdd5f7a1a34b8f6992d6bf94ac Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 148/181] Replace `b64encode' by `openssl base64' in lightsc.sh Fixes things on OpenBSD, probably among other places. --- share/lightsc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/lightsc.sh b/share/lightsc.sh index fb62311..7632061 100644 --- a/share/lightsc.sh +++ b/share/lightsc.sh @@ -34,8 +34,8 @@ _lightsc_b64e() { if type base64 >/dev/null 2>&1 ; then base64 - elif type b64encode >/dev/null 2>&1 ; then - b64encode + elif type openssl >/dev/null 2>&1 ; then + openssl base64 else cat >/dev/null echo null From 720a661c9adfab16f7cce05f436cf47a34c65e3f Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 149/181] Increase lightsc.py's receive buffer size to 64KiB This really need a proper read loop. --- examples/lightsc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lightsc.py b/examples/lightsc.py index e686858..dcd83e8 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -77,7 +77,7 @@ def _make_payload(cls, method, params): def _execute_payload(self, payload): self._socket.send(json.dumps(payload).encode("utf-8")) # FIXME: proper read loop - response = self._socket.recv(8192).decode("utf-8") + response = self._socket.recv(64 * 1024).decode("utf-8") try: response = json.loads(response) except Exception: From 84fd03557d80a53d42a8754ac2ae8f2beb2e4f34 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 150/181] Update LIFX product ids and models --- lifx/bulb.c | 10 +++++++--- tests/lifx/gateway/test_gateway_handle_product_info.c | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lifx/bulb.c b/lifx/bulb.c index 24c7d32..02ad5e3 100644 --- a/lifx/bulb.c +++ b/lifx/bulb.c @@ -58,12 +58,16 @@ lgtd_lifx_bulb_get_model_name(uint32_t vendor_id, uint32_t product_id) switch (product_id) { case 0x1: case 0x2: - return "A21 (Original 1000)"; + return "Original 1000"; case 0x3: - return "GU10 (Color 650)"; + return "Color 650"; case 0xa: case 0xb: - return "A19 (White 800)"; + return "White 800"; + case 0x12: + return "White 900 BR30"; + case 0x16: + return "Color 1000"; default: return "Unknown"; } diff --git a/tests/lifx/gateway/test_gateway_handle_product_info.c b/tests/lifx/gateway/test_gateway_handle_product_info.c index 176ff40..022c4a8 100644 --- a/tests/lifx/gateway/test_gateway_handle_product_info.c +++ b/tests/lifx/gateway/test_gateway_handle_product_info.c @@ -30,7 +30,7 @@ main(void) errx(1, "the product info weren't set correctly on the bulb"); } - const char *expected_model = "GU10 (Color 650)"; + const char *expected_model = "Color 650"; if (strcmp(b->model, expected_model)) { errx(1, "model %s (expected %s)", b->model, expected_model); } From 5f33cb47d8b64061b5dc939d3ee93544f7925abf Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:24:51 -0800 Subject: [PATCH 151/181] Add build instructions for Debian like systems --- docs/first-steps.rst | 25 ++++++++++++++++ docs/installation.rst | 70 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/docs/first-steps.rst b/docs/first-steps.rst index b59d2e2..061f26c 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -86,6 +86,31 @@ Read the logs with: Try to :ref:`toggle your lights ` and read on some of the examples bundled with lightsd. +Linux (System V style) +~~~~~~~~~~~~~~~~~~~~~~ + +Start lightsd with: + +:: + + /etc/init.d/lightsd start + +Stop lightsd with: + +:: + + /etc/init.d/lightsd stop + +Check how lightsd is running with: + +:: + + ps aux | grep lightsd + +The logs will be logged to `syslogd(8)`_. + +.. _syslogd(8): http://manpages.debian.org/cgi-bin/man.cgi?query=syslogd&sektion=8 + Manually (other systems) ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/installation.rst b/docs/installation.rst index 94c9038..33c28d1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -38,12 +38,12 @@ Read on :doc:`/first-steps` to see how to use lightsd. Installation (for Arch Linux) ----------------------------- -Make sure you have yaourt installed: https://archlinux.fr/yaourt-en (`wiki +Make sure you have Yaourt installed: https://archlinux.fr/yaourt-en (`wiki page`_). :: - yaourt -Sy lightsd + yaourt -Sya lightsd Make sure to follow the post-installation instructions: replace ``$USER`` with the user you usually use. @@ -54,15 +54,66 @@ section: :: - yaourt -Sy ipython + yaourt -Sya ipython Read on :doc:`/first-steps` to see how to use lightsd. -.. _Yaourt: .. _wiki page: https://wiki.archlinux.org/index.php/Yaourt .. _build_instructions: +Build instructions for Debian based systems (Ubuntu/Raspbian) +------------------------------------------------------------- + +.. note:: Those instructions have been tested on Debian Wheezy & Jessie. + +Install the following requirements: + +:: + + build-essential cmake libevent-dev git ca-certificates ipython3 fakeroot wget devscripts debhelper + +Download and extract the release: + +.. parsed-literal:: + + wget -O lightsd\_\ |release|.orig.tar.gz \https://github.com/lopter/lightsd/archive/|release|.tar.gz + tar -xzf lightsd\_\ |release|.orig.tar.gz + cd lightsd-|release| + wget -O - \https://github.com/lopter/lightsd/releases/download/|release|/dpkg-|release|.tar.gz | tar -xzf - + +Build the package: + +:: + + debuild + +Install the package: + +.. note:: + + You will need to run this command as root with `sudo(8)`_ or be logged in as + root already. + +.. parsed-literal:: + + dpkg -i ../lightsd\_\ |release|\_`dpkg --print-architecture`.tar.gz + +Still as root, run the command the package asks you to run: + +.. note:: + + If you are not using sudo replace ``$USER`` by your regular non-root + username: + +:: + + gpasswd -a $USER lightsd + +Log out and back in as ``$USER`` for the change to take effect. + +.. _sudo(8): http://manpages.debian.org/cgi-bin/man.cgi?query=sudo&sektion=8 + Build instructions (for other systems) -------------------------------------- @@ -75,15 +126,8 @@ you have the following requirements installed: lightsd is developed and tested from Arch Linux, Debian, OpenBSD and Mac OS X; both for 32/64 bits and little/big endian architectures. -For Debian and Ubuntu you would need to install the following packages to build -lightsd: - -:: - - build-essential cmake libevent-dev git ca-certificates - -Please also install ipython if you want to follow the examples in the next -section. On Debian and Ubuntu you would install the ``ipython3`` package. +Please also install ipython with Python 3 if you want to follow the examples in +the next section. From a terminal prompt, clone the repository and move to the root of it: From bf0768e0992d1534d92741c41085833ed7197d56 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:26:45 -0800 Subject: [PATCH 152/181] Add the -p (--pidfile) option Needed to properly support old school startup scripts. --- core/daemon.c | 29 ++++ core/daemon.h | 1 + core/lightsd.c | 31 +++- core/lightsd.h | 1 + docs/first-steps.rst | 41 ++--- tests/core/daemon/test_daemon_writepidfile.c | 166 +++++++++++++++++++ 6 files changed, 245 insertions(+), 24 deletions(-) create mode 100644 tests/core/daemon/test_daemon_writepidfile.c diff --git a/core/daemon.c b/core/daemon.c index 55b97ec..06dc5f8 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -371,6 +371,35 @@ lgtd_daemon_makedirs(const char *filepath) return true; } +bool +lgtd_daemon_write_pidfile(const char *filepath) +{ + assert(filepath); + + char pidstr[32]; + int pidlen = snprintf(pidstr, sizeof(pidstr), "%ju", (uintmax_t)getpid()); + int written = 0; + + mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH; + int fd = open(filepath, O_CREAT|O_WRONLY|O_TRUNC, mode); + if (fd == -1) { + return false; + } + + if (lgtd_user_info && lgtd_group_info + && fchown(fd, lgtd_user_info->pw_uid, lgtd_group_info->gr_gid) == -1) { + lgtd_warn( + "can't chown %s to %s:%s", + filepath, lgtd_user_info->pw_name, lgtd_group_info->gr_name + ); + } + + written = write(fd, pidstr, (size_t)pidlen); + + close(fd); + return written == pidlen; +} + int lgtd_daemon_syslog_facilitytoi(const char *facility) { diff --git a/core/daemon.h b/core/daemon.h index a62782f..5fa7d74 100644 --- a/core/daemon.h +++ b/core/daemon.h @@ -33,6 +33,7 @@ void lgtd_daemon_update_proctitle(void); void lgtd_daemon_die_if_running_as_root_unless_requested(const char *); void lgtd_daemon_set_user(const char *); void lgtd_daemon_set_group(const char *); +bool lgtd_daemon_write_pidfile(const char *); void lgtd_daemon_drop_privileges(void); bool lgtd_daemon_makedirs(const char *); diff --git a/core/lightsd.c b/core/lightsd.c index 371828e..d4fae00 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -65,7 +66,8 @@ struct lgtd_opts lgtd_opts = { .group = NULL, .syslog = false, .syslog_facility = LOG_DAEMON, - .syslog_ident = "lightsd" + .syslog_ident = "lightsd", + .pidfile = NULL }; struct event_base *lgtd_ev_base = NULL; @@ -86,6 +88,9 @@ lgtd_cleanup(void) #if LIBEVENT_VERSION_NUMBER >= 0x02010100 libevent_global_shutdown(); #endif + if (lgtd_opts.pidfile) { + unlink(lgtd_opts.pidfile); + } } static void @@ -150,12 +155,13 @@ lgtd_usage(const char *progname) "Usage: %s ...\n\n" " [-l,--listen addr:port] Listen for JSON-RPC commands over TCP at\n" " this address (can be repeated).\n" -" [-c,--comand-pipe /command/fifo] Open an unidirectional JSON-RPC\n" +" [-c,--command-pipe /command/fifo] Open an unidirectional JSON-RPC\n" " command pipe at this location (can be\n" " repeated).\n" " [-s,--socket /unix/socket] Open an Unix socket at this location\n" " (can be repeated).\n" " [-d,--daemonize] Fork in the background.\n" +" [-p,--pidfile /path/to/pid.file] Write lightsd's pid in the given file.\n" " [-u,--user user] Drop privileges to this user (and the\n" " group of this user if -g is missing).\n" " [-g,--group group] Drop privileges to this group (-g requires\n" @@ -197,6 +203,7 @@ main(int argc, char *argv[], char *envp[]) {"socket", required_argument, NULL, 's'}, {"foreground", no_argument, NULL, 'f'}, {"daemonize", no_argument, NULL, 'd'}, + {"pidfile", required_argument, NULL, 'p'}, {"user", required_argument, NULL, 'u'}, {"group", required_argument, NULL, 'g'}, {"syslog", no_argument, NULL, 'S'}, @@ -206,11 +213,11 @@ main(int argc, char *argv[], char *envp[]) {"help", no_argument, NULL, 'h'}, {"verbosity", required_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, - {"prefix", no_argument, NULL, 'p'}, + {"prefix", no_argument, NULL, 'P'}, {"rundir", no_argument, NULL, 'r'}, {NULL, 0, NULL, 0} }; - const char short_opts[] = "l:c:s:fdu:g:SF:I:thv:V"; + const char short_opts[] = "l:c:s:fdp:u:g:SF:I:thv:V"; if (argc == 1) { lgtd_usage(progname); @@ -246,6 +253,10 @@ main(int argc, char *argv[], char *envp[]) break; case 'd': lgtd_opts.foreground = false; + break; + case 'p': + lgtd_opts.pidfile = optarg; + break; case 'u': lgtd_opts.user = optarg; break; @@ -284,7 +295,7 @@ main(int argc, char *argv[], char *envp[]) printf("%s %s\n", progname, LGTD_VERSION); lgtd_cleanup(); return 0; - case 'p': + case 'P': printf( "%s%s\n", LGTD_INSTALL_PREFIX, LGTD_INSTALL_PREFIX[ LGTD_ARRAY_SIZE(LGTD_INSTALL_PREFIX) - 1 @@ -313,6 +324,11 @@ main(int argc, char *argv[], char *envp[]) if (lgtd_opts.user) { lgtd_daemon_set_user(lgtd_opts.user); lgtd_daemon_set_group(lgtd_opts.group); + // create the pidfile before we drop privileges: + if (lgtd_opts.pidfile + && !lgtd_daemon_write_pidfile(lgtd_opts.pidfile)) { + lgtd_warn("couldn't write pidfile at %s", lgtd_opts.pidfile); + } lgtd_daemon_drop_privileges(); } else if (lgtd_opts.group) { lgtd_errx(1, "please, specify an user with the -u option"); @@ -332,6 +348,11 @@ main(int argc, char *argv[], char *envp[]) } } + // update the pidfile after we've forked: + if (lgtd_opts.pidfile && !lgtd_daemon_write_pidfile(lgtd_opts.pidfile)) { + lgtd_warn("couldn't write pidfile at %s", lgtd_opts.pidfile); + } + lgtd_lifx_discovery_start(); // update at least once: so that if no bulbs are discovered we still get a diff --git a/core/lightsd.h b/core/lightsd.h index 0e401f1..5e2b5ed 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -114,6 +114,7 @@ struct lgtd_opts { bool syslog; int syslog_facility; const char *syslog_ident; + const char *pidfile; }; extern struct lgtd_opts lgtd_opts; diff --git a/docs/first-steps.rst b/docs/first-steps.rst index 061f26c..832d0f2 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -145,25 +145,28 @@ Command line options Usage: lightsd ... - [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP at - this address (can be repeated). - [-c,--comand-pipe /command/fifo [+]] Open an unidirectional JSON-RPC - command pipe at this location (can be - repeated). - [-s,--socket /unix/socket [+]] Open an Unix socket at this location - (can be repeated). - [-d,--daemonize] Fork in the background. - [-u,--user user] Drop privileges to this user (and the - group of this user if -g is missing). - [-g,--group group] Drop privileges to this group (-g requires - the -u option to be used). - [-S,--syslog] Divert logging from the console to syslog. - [-F,--syslog-facility] Facility to use with syslog (defaults to - daemon, other possible values are user and - local0-7, see syslog(3)). - [-t,--no-timestamps] Disable timestamps in logs. - [-h,--help] Display this. - [-V,--version] Display version and build information. + [-l,--listen addr:port [+]] Listen for JSON-RPC commands over TCP at + this address (can be repeated). + [-c,--command-pipe /command/fifo [+]] Open an unidirectional JSON-RPC + command pipe at this location (can be + repeated). + [-s,--socket /unix/socket [+]] Open an Unix socket at this location + (can be repeated). + [-d,--daemonize] Fork in the background. + [-p,--pidfile /path/to/pid.file] Write lightsd's pid in the given file. + [-u,--user user] Drop privileges to this user (and the + group of this user if -g is missing). + [-g,--group group] Drop privileges to this group (-g requires + the -u option to be used). + [-S,--syslog] Divert logging from the console to syslog. + [-F,--syslog-facility] Facility to use with syslog (defaults to + daemon, other possible values are user and + local0-7, see syslog(3)). + [-I,--syslog-ident] Identifier to use with syslog (defaults to + lightsd). + [-t,--no-timestamps] Disable timestamps in logs. + [-h,--help] Display this. + [-V,--version] Display version and build information. [-v,--verbosity debug|info|warning|error] or, diff --git a/tests/core/daemon/test_daemon_writepidfile.c b/tests/core/daemon/test_daemon_writepidfile.c new file mode 100644 index 0000000..a6d5977 --- /dev/null +++ b/tests/core/daemon/test_daemon_writepidfile.c @@ -0,0 +1,166 @@ +#include +#include +#include + +int mock_open(const char *, int, ...); +int mock_fchown(int, uid_t, gid_t); +int mock_write(int, const void *, size_t); +int mock_close(int); +pid_t mock_getpid(void); + +#define open(fp, flags, ...) mock_open(fp, flags, ##__VA_ARGS__) +#define fchown(fd, uid, gid) mock_fchown(fd, uid, gid) +#define write(fd, buf, sz) mock_write(fd, buf, sz) +#define close(fd) mock_close(fd) +#define getpid() mock_getpid() +#include "daemon.c" + +#include "mock_gateway.h" +#include "mock_pipe.h" +#include "mock_router.h" +#include "mock_log.h" +#include "mock_timer.h" + +#include "tests_utils.h" + +int mock_getpid_call_count = 0; + +pid_t +mock_getpid(void) +{ + mock_getpid_call_count++; + + return 1234; +} + +int mock_open_call_count = 0; + +int +mock_open(const char *fp, int flags, ...) +{ + mock_open_call_count++; + + va_list ap; + va_start(ap, flags); + mode_t mode = va_arg(ap, int); + va_end(ap); + + if (strcmp(fp, "/test.pid")) { + lgtd_errx(1, "got fp %s (expected test.pid)", fp); + } + + int expected_flags = O_CREAT|O_WRONLY|O_TRUNC; + if (flags != expected_flags) { + lgtd_errx(1, "got flags %#x (expected %#x)", flags, expected_flags); + } + + mode_t expected_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH; + if (mode != expected_mode) { + lgtd_errx(1, "got mode %#x (expected %#x)", mode, expected_mode); + } + + return 42; +} + +int mock_fchown_call_count = 0; + +int +mock_fchown(int fd, uid_t uid, gid_t gid) +{ + mock_fchown_call_count++; + + if (fd != 42) { + lgtd_errx(1, "god fd %d (expected 42)", fd); + } + + if (uid != 1000) { + lgtd_errx(1, "god uid %d (expected 1000)", uid); + } + + if (gid != 500) { + lgtd_errx(1, "god gid %d (expected 500)", gid); + } + + return 0; +} + +int mock_write_call_count = 0; + +int +mock_write(int fd, const void *buf, size_t nbytes) +{ + mock_write_call_count++; + + if (fd != 42) { + lgtd_errx(1, "got fd %d (expected 42)", fd); + } + + if (!buf || strcmp((const char *)buf, "1234")) { + lgtd_errx(1, "writing pid %s (expected 1234)", (const char *)buf); + } + + if (nbytes != 4) { + lgtd_errx(1, "got nbytes %ju (expected 4)", (uintmax_t)nbytes); + } + + return 0; +} + +int mock_close_call_count = 0; + +int +mock_close(int fd) +{ + mock_close_call_count++; + + if (fd != 42) { + lgtd_errx(1, "got fd %d (expected 42)", fd); + } + + return 0; +} + +int +main(void) +{ + struct passwd user_info = { .pw_uid = 1000, .pw_gid = 500 }; + lgtd_user_info = &user_info; + struct group group_info = { .gr_gid = 500 }; + lgtd_group_info = &group_info; + + lgtd_daemon_write_pidfile("/test.pid"); + if (!mock_open_call_count) { + lgtd_errx(1, "open wasn't called"); + } + if (!mock_fchown_call_count) { + lgtd_errx(1, "fchown wasn't called"); + } + if (!mock_getpid_call_count) { + lgtd_errx(1, "getpid wasn't called"); + } + if (!mock_write_call_count) { + lgtd_errx(1, "write wasn't called"); + } + if (!mock_close_call_count) { + lgtd_errx(1, "close wasn't called"); + } + + lgtd_daemon_write_pidfile("/test.pid"); + if (mock_open_call_count != 2) { + lgtd_errx(1, "open wasn't called"); + } + if (mock_fchown_call_count != 2) { + lgtd_errx(1, "fchown wasn't called"); + } + if (mock_getpid_call_count != 2) { + lgtd_errx(1, "getpid wasn't called"); + } + if (mock_write_call_count != 2) { + lgtd_errx(1, "write wasn't called"); + } + if (mock_close_call_count != 2) { + lgtd_errx(1, "close wasn't called"); + } + + return 0; +} From b8954f0bc809a2d2d6d4dd744030d74e57971c29 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:26:49 -0800 Subject: [PATCH 153/181] Install examples in share/lightsd insted of share/doc/lightsd Because Debian thinks it's smart to compress files in /usr/share/doc, which creates a poor user experience for lightsc.py --- CMakeLists.txt | 2 +- docs/first-steps.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 518289d..3308bc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,7 @@ INSTALL( ) INSTALL( DIRECTORY examples - DESTINATION share/doc/lightsd + DESTINATION share/lightsd USE_SOURCE_PERMISSIONS REGEX ".*\\.sw.$" EXCLUDE ) diff --git a/docs/first-steps.rst b/docs/first-steps.rst index 832d0f2..92dffb7 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -184,7 +184,7 @@ Toggle your lights :: - `lightsd --prefix`/share/doc/lightsd/examples/toggle + `lightsd --prefix`/share/lightsd/examples/toggle Or, from the root of the repository: @@ -264,7 +264,7 @@ bulbs. Start lightsc.py with: :: - `lightsd --prefix`/share/doc/lightsd/examples/lightsc.py + `lightsd --prefix`/share/lightsd/examples/lightsc.py Or, from the root of the repository: From e02c0da62d75e4f5c56df9db67044dd1500ba8a3 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:26:49 -0800 Subject: [PATCH 154/181] Add a docs CMake target to easily call sphinx-build --- CMakeLists.txt | 8 +++++++- CMakeScripts/FindSphinx.cmake | 7 +++++++ docs/CMakeLists.txt | 9 +++++++++ docs/_static/.dummy | 0 docs/{conf.py => conf.py.in} | 12 +++++++----- 5 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 CMakeScripts/FindSphinx.cmake create mode 100644 docs/CMakeLists.txt create mode 100644 docs/_static/.dummy rename docs/{conf.py => conf.py.in} (96%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3308bc8..d4afae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,9 +8,9 @@ SET(CPACK_PACKAGE_VERSION_MINOR "1") SET(CPACK_PACKAGE_VERSION_PATCH "0") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +MESSAGE(STATUS "lightsd version: ${LIGHTSD_VERSION}") MESSAGE(STATUS "CMake version: ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}.${CMAKE_PATCH_VERSION}") MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -MESSAGE(STATUS "lightsd version: ${LIGHTSD_VERSION}") MESSAGE(STATUS "Target system: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} ${CMAKE_SYSTEM_PROCESSOR}") MESSAGE(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") MESSAGE(STATUS "Source directory: ${LIGHTSD_SOURCE_DIR}") @@ -24,6 +24,7 @@ ENABLE_TESTING() # TODO: we need at least 2.0.19-stable because of the logging defines FIND_PACKAGE(Event2 REQUIRED COMPONENTS core) FIND_PACKAGE(Endian REQUIRED) +FIND_PACKAGE(Sphinx) INCLUDE(CheckFunctionExists) INCLUDE(CheckVariableExists) @@ -88,6 +89,7 @@ INCLUDE_DIRECTORIES( ADD_SUBDIRECTORY(compat) ADD_SUBDIRECTORY(core) ADD_SUBDIRECTORY(lifx) + # 2.8.11 is the first version with TARGET_INCLUDE_DIRECTORIES: IF (CMAKE_VERSION VERSION_GREATER 2.8.10) CONFIGURE_FILE( @@ -102,6 +104,10 @@ ELSE () ) ENDIF () +IF (SPHINX_FOUND) + ADD_SUBDIRECTORY(docs) +ENDIF () + INSTALL( FILES COPYING README.rst docs/protocol.rst DESTINATION share/doc/lightsd diff --git a/CMakeScripts/FindSphinx.cmake b/CMakeScripts/FindSphinx.cmake new file mode 100644 index 0000000..373b2d1 --- /dev/null +++ b/CMakeScripts/FindSphinx.cmake @@ -0,0 +1,7 @@ +FIND_PROGRAM( + SPHINX_EXECUTABLE + NAMES sphinx-build sphinx-build2 + DOC "Path to sphinx-build executable" +) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Sphinx DEFAULT_MSG SPHINX_EXECUTABLE) diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 0000000..efa6ae7 --- /dev/null +++ b/docs/CMakeLists.txt @@ -0,0 +1,9 @@ +CONFIGURE_FILE(conf.py.in "${CMAKE_CURRENT_BINARY_DIR}/conf.py") + +ADD_CUSTOM_TARGET( + docs + COMMAND "${SPHINX_EXECUTABLE}" -v -W -b html -c "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" _build + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMENT "Building the documentation in ${CMAKE_CURRENT_BINARY_DIR}/_build with sphinx-build" + VERBATIM +) diff --git a/docs/_static/.dummy b/docs/_static/.dummy new file mode 100644 index 0000000..e69de29 diff --git a/docs/conf.py b/docs/conf.py.in similarity index 96% rename from docs/conf.py rename to docs/conf.py.in index 6e39d09..4242198 100644 --- a/docs/conf.py +++ b/docs/conf.py.in @@ -24,12 +24,14 @@ # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +needs_sphinx = '1.2.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = ['sphinx.ext.extlinks'] + +extlinks = {'gh': ('https://github.com/lopter/lightsd/issues/%s', 'GH-')} # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -52,9 +54,9 @@ # built documents. # # The short X.Y version. -version = '1.1.0' +version = '@LIGHTSD_VERSION@' # The full version, including alpha/beta/rc tags. -release = '1.1.0' +release = '@LIGHTSD_VERSION@' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -128,7 +130,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = [os.path.join('@CMAKE_CURRENT_SOURCE_DIR@', '_static')] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied From 55b26c2c7d082252623a62cd44af025ff8da5b36 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:26:49 -0800 Subject: [PATCH 155/181] Update the changelog for 1.1.0 --- docs/changelog.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 1493ee5..a92346d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,31 @@ Changelog ========= +1.1.0 (2015-11-07) +------------------ + +.. note:: + + The ``-f`` (``--foreground``) option is being deprecated with this release + and isn't documented anymore, lightsd starts in the foreground by default and + this option is not necessary, please stop using it. + +New features +~~~~~~~~~~~~ + +- Add syslog support via the ``--syslog`` and ``--syslog-facility`` options + (closes :gh:`1`); +- Debian & OpenWRT packaging and installation instructions. + +Fixes +~~~~~ + +- lightsc.sh: support OSes with openssl but without a base64 utility (closes + :gh:`3`); +- lightsc.py: unix url support fixes and bump the receive buffer size to + accommodate people with many bulbs; +- Add missing product ids/models. + 1.0.1 (2015-09-18) ------------------ From 85288170716690c63b55e73ccebe864dc133eb24 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 7 Nov 2015 23:56:45 -0800 Subject: [PATCH 156/181] Added tag 1.1.0 for changeset 39e9e2070a25 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index ae5bd30..dbd43f4 100644 --- a/.hgtags +++ b/.hgtags @@ -5,3 +5,4 @@ 4ef909311670c1ab1543793afa327749150bf1e2 0.9.4 31b8ef65d3d239d7e94417de50b4b7e1c6c0a624 1.0.0 1f7c28381493a9e0a24bf5b918d3772241d15587 1.0.1 +39e9e2070a25b17fac56ebc61338024b20538cc4 1.1.0 From b5c1f8b2929ec429cf7280610174dae2a382e6d6 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 8 Nov 2015 01:43:57 -0800 Subject: [PATCH 157/181] Fix dpkg -i command in the documentation --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index 33c28d1..a0c50bc 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -97,7 +97,7 @@ Install the package: .. parsed-literal:: - dpkg -i ../lightsd\_\ |release|\_`dpkg --print-architecture`.tar.gz + dpkg -i ../lightsd\_\ |release|-1\_`dpkg --print-architecture`.deb Still as root, run the command the package asks you to run: From b351aa24ae1470f3269cdf2145ee39aee70a0383 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 8 Nov 2015 16:23:29 -0800 Subject: [PATCH 158/181] Document how to run lightsd on OpenWRT trunk --- docs/first-steps.rst | 38 +++++++++++++++++++++++++++++++++++++- docs/installation.rst | 27 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/docs/first-steps.rst b/docs/first-steps.rst index 92dffb7..6e95c8f 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -111,6 +111,42 @@ The logs will be logged to `syslogd(8)`_. .. _syslogd(8): http://manpages.debian.org/cgi-bin/man.cgi?query=syslogd&sektion=8 +OpenWRT (procd) +~~~~~~~~~~~~~~~ + +Start lightsd with: + +:: + + /etc/init.d/lightsd start + +Stop lightsd with: + +:: + + /etc/init.d/lightsd stop + +Enable lightsd at boot: + +:: + + /etc/init.d/lightsd enable + +Check how lightsd is running with: + +:: + + pgrep -l lightsd + +Read the logs with: + +:: + + logread -e lightsd -f + +Try to :ref:`toggle your lights ` and read on some of the examples +bundled with lightsd. + Manually (other systems) ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -253,7 +289,7 @@ Build a batch request manually: ] EOF -.. _lightsc.sh: https://github.com/lopter/lightsd/blob/master/share/lightsc.sh.in +.. _lightsc.sh: https://github.com/lopter/lightsd/blob/master/share/lightsc.sh Using lightsc.py ---------------- diff --git a/docs/installation.rst b/docs/installation.rst index a0c50bc..d5be7e5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -60,6 +60,33 @@ Read on :doc:`/first-steps` to see how to use lightsd. .. _wiki page: https://wiki.archlinux.org/index.php/Yaourt + +Installation (OpenWRT trunk) +---------------------------- + +If you're running `OpenWRT trunk`_ then, from your build root, just add +lightsd's feed: + +:: + + cat >>feeds.conf`[ -f feeds.conf ] || echo .default` < Date: Sun, 8 Nov 2015 16:28:19 -0800 Subject: [PATCH 159/181] Update supported OSes in the README --- README.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 3063fa3..69a257d 100644 --- a/README.rst +++ b/README.rst @@ -52,9 +52,9 @@ lightsd works and is developed against LIFX firmwares 1.1, 1.5, 2.0 and 2.1. Documentation ------------- -lightsd is packaged for Mac OS X and Arch Linux with OpenWrt coming out later -this year. Checkout http://lightsd.readthedocs.org/en/latest/ for installation -instructions and a walk-through some interactive examples. +lightsd is packaged for Mac OS X, Arch Linux, Debian based systems and OpenWRT. +Checkout http://lightsd.readthedocs.org/en/latest/ for installation instructions +and a walk-through some interactive examples. Requirements ------------ @@ -68,8 +68,8 @@ written in C with reasonable dependencies: - CMake ≥ 2.8.9 (released August 2012): only if you want to build lightsd from its sources. -lightsd is actively developed and tested from Arch Linux, Debian, Mac OS X and -OpenBSD; both for 32/64 bits and little/big endian architectures. +lightsd is actively developed and tested from Arch Linux, Debian, Mac OS X, +OpenWRT and OpenBSD; both for 32/64 bits and little/big endian architectures. Developers ---------- From 3982f6633c4a7f53ea01c826b61e1c193b3a65d6 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 8 Nov 2015 23:34:20 -0800 Subject: [PATCH 160/181] Fix journalctl command in the documentation --- docs/first-steps.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/first-steps.rst b/docs/first-steps.rst index 6e95c8f..f69b1ae 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -81,7 +81,7 @@ Read the logs with: :: - journalctl -x -f _SYSTEMD_UNIT=lightsd.unit + journalctl -x -f _SYSTEMD_UNIT=lightsd.service Try to :ref:`toggle your lights ` and read on some of the examples bundled with lightsd. From 849999521c3b88ef8946d45eb28fa99c5f667766 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sat, 14 Nov 2015 23:59:49 -0800 Subject: [PATCH 161/181] Setup the LIFX client source to get unicast responses. This should significantly improve both latency and throughput. --- CMakeLists.txt | 2 +- core/daemon.c | 22 + core/daemon.h | 1 + core/lightsd.c | 2 +- core/lightsd.h | 3 + core/utils.c | 20 + lifx/wire_proto.c | 38 +- lifx/wire_proto.h | 18 +- tests/core/client/CMakeLists.txt | 1 - tests/core/daemon/CMakeLists.txt | 1 - tests/core/daemon/test_daemon_randuint32.c | 94 ++++ tests/core/daemon/test_daemon_writepidfile.c | 2 +- tests/core/jsonrpc/CMakeLists.txt | 1 - tests/core/jsonrpc/test_jsonrpc_batch.c | 1 + .../test_jsonrpc_batch_notifications_only.c | 1 + .../test_jsonrpc_batch_one_invalid_request.c | 1 + .../test_jsonrpc_batch_one_notification.c | 1 + .../jsonrpc/test_jsonrpc_build_target_list.c | 1 + .../test_jsonrpc_check_and_call_power_off.c | 1 + ..._check_and_call_power_off_missing_target.c | 1 + .../test_jsonrpc_check_and_call_power_on.c | 1 + ...c_check_and_call_power_on_missing_target.c | 1 + ...test_jsonrpc_check_and_call_power_toggle.c | 1 + .../test_jsonrpc_check_and_call_set_label.c | 1 + ...onrpc_check_and_call_set_light_from_hsbk.c | 1 + ..._and_call_set_light_from_hsbk_from_array.c | 1 + ..._call_set_light_from_hsbk_invalid_params.c | 1 + ...test_jsonrpc_check_and_call_set_waveform.c | 16 + ...eck_and_call_set_waveform_invalid_params.c | 9 + .../jsonrpc/test_jsonrpc_check_and_call_tag.c | 1 + ...sonrpc_check_and_call_tag_missing_params.c | 1 + .../test_jsonrpc_check_and_call_untag.c | 1 + ...nrpc_check_and_call_untag_invalid_params.c | 1 + .../test_jsonrpc_consume_object_or_array.c | 1 + .../test_jsonrpc_dispatch_one_no_params.c | 1 + .../test_jsonrpc_extract_request_no_params.c | 1 + ...c_extract_request_notification_no_params.c | 1 + ...est_jsonrpc_extract_request_params_array.c | 1 + .../test_jsonrpc_extract_request_params_obj.c | 1 + ...onrpc_extract_request_valid_notification.c | 1 + ...ues_from_schema_and_array_honors_objsize.c | 1 + tests/core/jsonrpc/test_jsonrpc_send_error.c | 1 + .../core/jsonrpc/test_jsonrpc_send_response.c | 1 + ...onrpc_type_float_between_0_and_1_invalid.c | 1 + ...jsonrpc_type_float_between_0_and_1_valid.c | 1 + ...rpc_type_float_between_0_and_360_invalid.c | 1 + ...onrpc_type_float_between_0_and_360_valid.c | 1 + .../core/jsonrpc/test_jsonrpc_type_integer.c | 1 + ..._jsonrpc_type_integer_invalid_characters.c | 1 + .../test_jsonrpc_type_integer_too_big.c | 1 + .../test_jsonrpc_type_integer_too_small.c | 1 + ...est_jsonrpc_uint16_range_to_float_string.c | 1 + tests/core/mock_daemon.h | 8 + tests/core/mock_log.h | 16 - tests/core/pipe/CMakeLists.txt | 1 - tests/core/proto/CMakeLists.txt | 1 - tests/core/proto/test_proto_get_light_state.c | 1 + ..._proto_get_light_state_empty_device_list.c | 1 + ...est_proto_get_light_state_label_overflow.c | 1 + ...t_proto_get_light_state_null_device_list.c | 1 + ...est_proto_get_light_state_unknown_tag_id.c | 1 + tests/core/proto/test_proto_power_off.c | 1 + .../test_proto_power_off_routing_error.c | 1 + tests/core/proto/test_proto_power_on.c | 1 + .../proto/test_proto_power_on_routing_error.c | 1 + tests/core/proto/test_proto_power_toggle.c | 1 + ...oto_power_toggle_targets_to_device_fails.c | 1 + tests/core/proto/test_proto_set_label.c | 1 + .../proto/test_proto_set_label_too_long.c | 1 + .../proto/test_proto_set_light_from_hsbk.c | 1 + ...oto_set_light_from_hsbk_on_routing_error.c | 1 + tests/core/proto/test_proto_set_waveform.c | 1 + ...test_proto_set_waveform_on_routing_error.c | 1 + tests/core/proto/test_proto_tag_create.c | 1 + ...st_proto_tag_create_lifx_gw_tag_ids_full.c | 1 + tests/core/proto/test_proto_tag_update.c | 1 + tests/core/proto/test_proto_untag.c | 1 + .../test_proto_untag_tag_does_not_exist.c | 1 + .../router/test_router_send_to_broadcast.c | 2 +- .../core/router/test_router_send_to_device.c | 2 +- .../test_router_send_to_invalid_targets.c | 2 +- tests/core/router/test_router_send_to_label.c | 2 +- tests/core/router/test_router_send_to_tag.c | 2 +- .../router/test_router_targets_to_devices.c | 2 +- tests/core/tests_shims.c | 11 + tests/lifx/gateway/CMakeLists.txt | 1 - .../gateway/test_gateway_allocate_tag_id.c | 3 +- ...ateway_allocate_tag_id_from_lifx_network.c | 3 +- ...t_gateway_allocate_tag_id_no_tag_id_left.c | 4 +- ...eway_deallocate_tag_id_from_lifx_network.c | 3 +- .../gateway/test_gateway_enqueue_packet.c | 3 +- .../test_gateway_enqueue_packet_ring_full.c | 3 +- ...t_gateway_enqueue_packet_ring_wraparound.c | 3 +- .../test_gateway_handle_ambient_light.c | 3 +- .../gateway/test_gateway_handle_bulb_label.c | 3 +- .../test_gateway_handle_ip_firmware_info.c | 3 +- .../gateway/test_gateway_handle_ip_state.c | 3 +- .../test_gateway_handle_product_info.c | 3 +- .../test_gateway_handle_runtime_info.c | 3 +- .../gateway/test_gateway_handle_tag_labels.c | 3 +- tests/lifx/gateway/test_gateway_handle_tags.c | 3 +- .../test_gateway_update_tag_refcounts.c | 3 +- .../gateway/test_gateway_write_callback.c | 3 +- ...way_write_callback_clears_ring_full_flag.c | 3 +- ...teway_write_callback_last_packet_on_ring.c | 3 +- ...est_gateway_write_callback_partial_write.c | 3 +- ...t_gateway_write_callback_ring_wraparound.c | 3 +- tests/lifx/mock_wire_proto.h | 402 ++++++++++++++++++ tests/lifx/tests_shims.c | 11 + tests/lifx/wire_proto/CMakeLists.txt | 2 - .../test_wire_proto_encode_decode_header.c | 1 + .../test_wire_proto_waveform_table.c | 2 +- 112 files changed, 737 insertions(+), 84 deletions(-) create mode 100644 tests/core/daemon/test_daemon_randuint32.c create mode 100644 tests/lifx/mock_wire_proto.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d4afae3..048dd0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "1") SET(CPACK_PACKAGE_VERSION_MINOR "1") -SET(CPACK_PACKAGE_VERSION_PATCH "0") +SET(CPACK_PACKAGE_VERSION_PATCH "1") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "lightsd version: ${LIGHTSD_VERSION}") diff --git a/core/daemon.c b/core/daemon.c index 06dc5f8..ad9bb9d 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -400,6 +400,28 @@ lgtd_daemon_write_pidfile(const char *filepath) return written == pidlen; } +uint32_t +lgtd_daemon_randuint32(void) +{ + int fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) { + lgtd_err(1, "couldn't open /dev/urandom"); + } + + uint32_t rv; + int nbytes = read(fd, &rv, sizeof(rv)); + if (nbytes != sizeof(rv)) { + close(fd); + lgtd_err( + 1, "couln't fetch %ju bytes from /dev/urandom", + sizeof((uintmax_t)rv) + ); + } + + close(fd); + return rv; +} + int lgtd_daemon_syslog_facilitytoi(const char *facility) { diff --git a/core/daemon.h b/core/daemon.h index 5fa7d74..dbaa30a 100644 --- a/core/daemon.h +++ b/core/daemon.h @@ -36,6 +36,7 @@ void lgtd_daemon_set_group(const char *); bool lgtd_daemon_write_pidfile(const char *); void lgtd_daemon_drop_privileges(void); bool lgtd_daemon_makedirs(const char *); +uint32_t lgtd_daemon_randuint32(void); int lgtd_daemon_syslog_facilitytoi(const char *); void lgtd_daemon_syslog_open(const char *, enum lgtd_verbosity, int); diff --git a/core/lightsd.c b/core/lightsd.c index d4fae00..af61b70 100644 --- a/core/lightsd.c +++ b/core/lightsd.c @@ -336,7 +336,7 @@ main(int argc, char *argv[], char *envp[]) lgtd_daemon_die_if_running_as_root_unless_requested(lgtd_opts.user); - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); if (!lgtd_lifx_discovery_setup() || !lgtd_lifx_broadcast_setup()) { lgtd_err(1, "can't setup lightsd"); } diff --git a/core/lightsd.h b/core/lightsd.h index 5e2b5ed..616e002 100644 --- a/core/lightsd.h +++ b/core/lightsd.h @@ -131,6 +131,9 @@ char *lgtd_sockaddrtoa(const struct sockaddr *, char *buf, int buflen); char *lgtd_print_duration(uint64_t, char *, int); #define LGTD_PRINT_DURATION(secs, arr) \ lgtd_print_duration((secs), (arr), sizeof((arr))) +char* lgtd_print_nsec_timestamp(uint64_t, char *, int); +#define LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(ts, arr) \ + lgtd_print_nsec_timestamp((ts), (arr), sizeof((arr))) void lgtd_log_setup(void); diff --git a/core/utils.c b/core/utils.c index b3640d6..ded5adb 100644 --- a/core/utils.c +++ b/core/utils.c @@ -25,6 +25,7 @@ #include #include #include +#include #include "lightsd.h" @@ -117,3 +118,22 @@ lgtd_print_duration(uint64_t secs, char *buf, int bufsz) snprintf(&buf[i], bufsz - i, "%02d:%02d", hours, minutes); return buf; } + +char * +lgtd_print_nsec_timestamp(uint64_t nsec_ts, char *buf, int bufsz) +{ + assert(buf); + assert(bufsz > 0); + + time_t ts = LGTD_NSECS_TO_SECS(nsec_ts); + + struct tm tm_utc; + if (gmtime_r(&ts, &tm_utc)) { + int64_t usecs = LGTD_NSECS_TO_USECS(nsec_ts - LGTD_SECS_TO_NSECS(ts)); + LGTD_TM_TO_ISOTIME(&tm_utc, buf, bufsz, usecs); + } else { + buf[0] = '\0'; + } + + return buf; +} diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 1953c30..40b2d86 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -27,7 +27,6 @@ #include #include #include -#include #include @@ -35,6 +34,7 @@ #include "core/time_monotonic.h" #include "bulb.h" #include "gateway.h" +#include "core/daemon.h" #include "core/lightsd.h" const union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET = { .tags = 0 }; @@ -50,6 +50,8 @@ const int LGTD_LIFX_DEBRUIJN_SEQUENCE[64] = { 13, 18, 8, 12, 7, 6, 5, 63 }; +static uint32_t lgtd_lifx_client_id = 0; + static struct lgtd_lifx_packet_info_map lgtd_lifx_packet_info = RB_INITIALIZER(&lgtd_lifx_packets_infos); @@ -95,7 +97,7 @@ lgtd_lifx_wire_enosys_packet_handler(struct lgtd_lifx_gateway *gw, ); } -void +static void lgtd_lifx_wire_load_packet_info_map(void) { #define DECODER(x) ((void (*)(void *))(x)) @@ -465,6 +467,15 @@ lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type packet_type) return RB_FIND(lgtd_lifx_packet_info_map, &lgtd_lifx_packet_info, &pkt_info); } +void +lgtd_lifx_wire_setup(void) +{ + lgtd_lifx_wire_load_packet_info_map(); + do { + lgtd_lifx_client_id = lgtd_daemon_randuint32(); + } while (!lgtd_lifx_client_id); +} + #define WAVEFORM_ENTRY(e) { .str = e, .len = sizeof(e) - 1 } const struct lgtd_lifx_waveform_string_id lgtd_lifx_waveform_table[] = { @@ -493,25 +504,6 @@ lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) return LGTD_LIFX_WAVEFORM_INVALID; } -char * -lgtd_lifx_wire_print_nsec_timestamp(uint64_t nsec_ts, char *buf, int bufsz) -{ - assert(buf); - assert(bufsz > 0); - - time_t ts = LGTD_NSECS_TO_SECS(nsec_ts); - - struct tm tm_utc; - if (gmtime_r(&ts, &tm_utc)) { - int64_t usecs = LGTD_NSECS_TO_USECS(nsec_ts - LGTD_SECS_TO_NSECS(ts)); - LGTD_TM_TO_ISOTIME(&tm_utc, buf, bufsz, usecs); - } else { - buf[0] = '\0'; - } - - return buf; -} - static void lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr, int flags) { @@ -534,6 +526,7 @@ lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr, int flags) } hdr->at_time = htole64(hdr->at_time); hdr->packet_type = htole16(hdr->packet_type); + hdr->source = htole32(hdr->source); // not strictly necessary but eh. } // Convert all the fields in the header to the host endianness. @@ -554,6 +547,7 @@ lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *hdr) } hdr->at_time = le64toh(hdr->at_time); hdr->packet_type = le16toh(hdr->packet_type); + hdr->source = le32toh(hdr->source); } const struct lgtd_lifx_packet_info * @@ -564,6 +558,7 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, enum lgtd_lifx_packet_type packet_type) { assert(hdr); + assert(lgtd_lifx_client_id); const struct lgtd_lifx_packet_info *pkt_info = lgtd_lifx_wire_get_packet_info(packet_type); @@ -571,6 +566,7 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, assert(pkt_info); memset(hdr, 0, sizeof(*hdr)); + hdr->source = lgtd_lifx_client_id; hdr->size = pkt_info->size + sizeof(*hdr); hdr->packet_type = packet_type; if (site) { diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index 3ad5a87..cf87740 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -61,8 +61,16 @@ struct lgtd_lifx_packet_header { //! - tagged: true when the target field holds tags; //! - origin: LIFX internal use, should be 0. uint16le_t protocol; - //! Here is what LIFXKit says about it, maybe it's related to zigbee: - //! Message source identifier from NAT table (Internal LIFX use) + //! http://lan.developer.lifx.com/v2.0/docs/header-description + //! The source identifier allows each client to provide an unique value, + //! which will be included by the LIFX device in any message that is sent + //! in response to a message sent by the client. If the source identifier + //! is a non-zero value, then the LIFX device will send a unicast message + //! to the source IP address and port that the client used to send the + //! originating message. If the source identifier is a zero value, then the + //! LIFX device may send a broadcast message that can be received by all + //! clients on the same sub-net. See _ack_required_ and _res_required_ + //! fields in the Frame Address. uint32le_t source; union { //! All targeted tags ORed together. @@ -387,12 +395,10 @@ lgtd_lifx_wire_next_tag_id(int current_tag_id, uint64_t tags) (tag_id_varname) = lgtd_lifx_wire_next_tag_id((tag_id_varname), (tags))) enum lgtd_lifx_waveform_type lgtd_lifx_wire_waveform_string_id_to_type(const char *, int); -char* lgtd_lifx_wire_print_nsec_timestamp(uint64_t, char *, int); -#define LGTD_LIFX_WIRE_PRINT_NSEC_TIMESTAMP(ts, arr) \ - lgtd_lifx_wire_print_nsec_timestamp((ts), (arr), sizeof((arr))) const struct lgtd_lifx_packet_info *lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type); -void lgtd_lifx_wire_load_packet_info_map(void); + +void lgtd_lifx_wire_setup(void); const struct lgtd_lifx_packet_info *lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *, enum lgtd_lifx_target_type, diff --git a/tests/core/client/CMakeLists.txt b/tests/core/client/CMakeLists.txt index 02558b2..7120998 100644 --- a/tests/core/client/CMakeLists.txt +++ b/tests/core/client/CMakeLists.txt @@ -10,7 +10,6 @@ ADD_CORE_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c - ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) diff --git a/tests/core/daemon/CMakeLists.txt b/tests/core/daemon/CMakeLists.txt index 6e4712a..7ecd6a8 100644 --- a/tests/core/daemon/CMakeLists.txt +++ b/tests/core/daemon/CMakeLists.txt @@ -10,7 +10,6 @@ ADD_CORE_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c - ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) diff --git a/tests/core/daemon/test_daemon_randuint32.c b/tests/core/daemon/test_daemon_randuint32.c new file mode 100644 index 0000000..ea639e3 --- /dev/null +++ b/tests/core/daemon/test_daemon_randuint32.c @@ -0,0 +1,94 @@ +#include + +int mock_open(const char *, int, ...); +ssize_t mock_read(int, void *, size_t); +int mock_close(int); + +#define open(fp, flags, ...) mock_open(fp, flags, ##__VA_ARGS__) +#define read(fd, buf, sz) mock_read(fd, buf, sz) +#define close(fd) mock_close(fd) + +#include "daemon.c" + +#include "mock_gateway.h" +#include "mock_pipe.h" +#include "mock_router.h" +#include "mock_log.h" +#include "mock_timer.h" + +static const int MOCK_RANDOM_NUMBER = 0x72616e64; + +int mock_open_call_count = 0; + +int +mock_open(const char *fp, int flags, ...) +{ + mock_open_call_count++; + + if (strcmp(fp, "/dev/urandom")) { + errx(1, "got fp %s (expected /dev/urandom)", fp); + } + + if (flags != O_RDONLY) { + errx(1, "got flags %#x (expected %#x)", flags, O_RDONLY); + } + + return 42; +} + +int mock_read_call_count = 0; + +ssize_t +mock_read(int fd, void *buf, size_t nbytes) +{ + mock_read_call_count++; + + if (fd != 42) { + errx(1, "got fd %d (expected 42)", fd); + } + + if (!buf) { + errx(1, "missing buf"); + } + + if (nbytes != 4) { + errx(1, "got nbytes %ju (expected 4)", (uintmax_t)nbytes); + } + + memcpy(buf, &MOCK_RANDOM_NUMBER, sizeof(MOCK_RANDOM_NUMBER)); + + return nbytes; +} + +int mock_close_call_count = 0; + +int +mock_close(int fd) +{ + mock_close_call_count++; + + if (fd != 42) { + errx(1, "got fd %d (expected 42)", fd); + } + + return 0; +} + +int +main(void) +{ + if (lgtd_daemon_randuint32() != MOCK_RANDOM_NUMBER) { + errx(1, "got unexpected value from randuint32"); + } + if (mock_open_call_count != 1) { + errx(1, "open wasn't once"); + } + if (mock_read_call_count != 1) { + errx(1, "read wasn't once"); + } + if (mock_close_call_count != 1) { + errx(1, "close wasn't once"); + } + + return 0; +} diff --git a/tests/core/daemon/test_daemon_writepidfile.c b/tests/core/daemon/test_daemon_writepidfile.c index a6d5977..55e6e51 100644 --- a/tests/core/daemon/test_daemon_writepidfile.c +++ b/tests/core/daemon/test_daemon_writepidfile.c @@ -46,7 +46,7 @@ mock_open(const char *fp, int flags, ...) va_end(ap); if (strcmp(fp, "/test.pid")) { - lgtd_errx(1, "got fp %s (expected test.pid)", fp); + lgtd_errx(1, "got fp %s (expected /test.pid)", fp); } int expected_flags = O_CREAT|O_WRONLY|O_TRUNC; diff --git a/tests/core/jsonrpc/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt index 7423be8..dcb9453 100644 --- a/tests/core/jsonrpc/CMakeLists.txt +++ b/tests/core/jsonrpc/CMakeLists.txt @@ -8,7 +8,6 @@ ADD_CORE_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/jsmn.c ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/core/utils.c - ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c diff --git a/tests/core/jsonrpc/test_jsonrpc_batch.c b/tests/core/jsonrpc/test_jsonrpc_batch.c index cb599aa..5b3079d 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch.c @@ -5,6 +5,7 @@ #define MOCKED_LGTD_PROTO_GET_LIGHT_STATE #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static int power_on_call_count = 0; diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c b/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c index 9ad474c..1e713de 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch_notifications_only.c @@ -5,6 +5,7 @@ #define MOCKED_LGTD_PROTO_GET_LIGHT_STATE #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static int power_on_call_count = 0; diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c b/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c index 133a1d9..358fd8c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch_one_invalid_request.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static int power_on_call_count = 0; diff --git a/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c b/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c index fff0372..f1c0ba4 100644 --- a/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c +++ b/tests/core/jsonrpc/test_jsonrpc_batch_one_notification.c @@ -5,6 +5,7 @@ #define MOCKED_LGTD_PROTO_GET_LIGHT_STATE #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static int power_on_call_count = 0; diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c index 21f6136..3a24d20 100644 --- a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c +++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c index 2a5608a..3631b61 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_OFF #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c index 15ed9a5..82ec628 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_OFF #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c index 0824445..dcf6e75 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c index e878f1d..e78ff21 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c index c77d05e..86e6e46 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_toggle.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_TOGGLE #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c index f229d0b..444dabe 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_label.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_LABEL #include "mock_proto.h" +#include "mock_wire_proto.h" #include "mock_gateway.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c index 96d600f..bb97880 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c index 4b8df1c..dd62450 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c index 5564d99..c4256f1 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_LIGHT_FROM_HSBK #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c index 3a228d1..d15695a 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c @@ -4,9 +4,25 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_WAVEFORM #include "mock_proto.h" +#define MOCKED_LGTD_LIFX_WIRE_WAVEFORM_STRING_ID_TO_TYPE +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" +enum lgtd_lifx_waveform_type +lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) +{ + if (len != 3) { + errx(1, "err = %d (expected 3)", len); + } + + if (strncmp(s, "SAW", 3)) { + errx(1, "s = %.3s (expected SAW)", s); + } + + return LGTD_LIFX_WAVEFORM_SAW; +} + static bool set_waveform_called = false; void diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c index de73b82..bd675ca 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c @@ -4,11 +4,20 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_SET_WAVEFORM #include "mock_proto.h" +#define MOCKED_LGTD_LIFX_WIRE_WAVEFORM_STRING_ID_TO_TYPE +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static bool set_waveform_called = false; +enum lgtd_lifx_waveform_type +lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) +{ + return len == 3 && !strncmp(s, "SAW", 3) ? + LGTD_LIFX_WAVEFORM_SAW : LGTD_LIFX_WAVEFORM_INVALID; +} + void lgtd_proto_set_waveform(struct lgtd_client *client, const struct lgtd_proto_target_list *targets, diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c index 5417d64..80af1e5 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_TAG #include "mock_proto.h" +#include "mock_wire_proto.h" #include "mock_gateway.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c index 0fbd5dc..184358c 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_TAG #include "mock_proto.h" +#include "mock_wire_proto.h" #include "mock_gateway.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c index 66de4b6..584a538 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_UNTAG #include "mock_proto.h" +#include "mock_wire_proto.h" #include "mock_gateway.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c index 4f3c816..65c40cd 100644 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_UNTAG #include "mock_proto.h" +#include "mock_wire_proto.h" #include "mock_gateway.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c b/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c index b453144..db39cdd 100644 --- a/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_consume_object_or_array.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c index cb5886c..5c001e7 100644 --- a/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_dispatch_one_no_params.c @@ -4,6 +4,7 @@ #include "mock_log.h" #define MOCKED_LGTD_PROTO_POWER_ON #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c index c5895ef..464877e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c index 235f1ad..3ca27a6 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c index 276edec..9a10344 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c index e076aa7..87782f9 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c index 05b9736..4e77f55 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c b/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c index cfa0fda..aac46f4 100644 --- a/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c +++ b/tests/core/jsonrpc/test_jsonrpc_extract_values_from_schema_and_array_honors_objsize.c @@ -5,6 +5,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c index 344efe5..4d724fc 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_error.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_send_response.c b/tests/core/jsonrpc/test_jsonrpc_send_response.c index 8eddd9b..c980afe 100644 --- a/tests/core/jsonrpc/test_jsonrpc_send_response.c +++ b/tests/core/jsonrpc/test_jsonrpc_send_response.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c index 3d78048..cf686ab 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c index 18e90ab..ac59cd6 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c index abbc05d..5620378 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c index f0a4e50..0c14cba 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" static void diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer.c b/tests/core/jsonrpc/test_jsonrpc_type_integer.c index 492e3d9..82fe85e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c index a033eea..4f7e3ba 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c index ae99797..215480e 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c index 33c436f..e3026e3 100644 --- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c +++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c @@ -3,6 +3,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c index 50e949b..83150b2 100644 --- a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c +++ b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c @@ -5,6 +5,7 @@ #include "mock_client_buf.h" #include "mock_log.h" #include "mock_proto.h" +#include "mock_wire_proto.h" #include "test_jsonrpc_utils.h" int diff --git a/tests/core/mock_daemon.h b/tests/core/mock_daemon.h index 9326337..ce0fdf5 100644 --- a/tests/core/mock_daemon.h +++ b/tests/core/mock_daemon.h @@ -15,3 +15,11 @@ lgtd_daemon_makedirs(const char *fp) return true; } #endif + +#ifndef MOCKED_DAEMON_RANDUINT32 +uint32_t +lgtd_daemon_randuint32(void) +{ + return 0x72616e64; +} +#endif diff --git a/tests/core/mock_log.h b/tests/core/mock_log.h index 8468887..40599d5 100644 --- a/tests/core/mock_log.h +++ b/tests/core/mock_log.h @@ -32,10 +32,6 @@ lgtd_errx(int eval, const char *fmt, ...) void lgtd_warn(const char *fmt, ...) { - if (lgtd_opts.verbosity > LGTD_WARN) { - return; - } - fprintf(stderr, "WARN: "); va_list ap; va_start(ap, fmt); @@ -47,10 +43,6 @@ lgtd_warn(const char *fmt, ...) void lgtd_warnx(const char *fmt, ...) { - if (lgtd_opts.verbosity > LGTD_WARN) { - return; - } - fprintf(stderr, "WARN: "); va_list ap; va_start(ap, fmt); @@ -62,10 +54,6 @@ lgtd_warnx(const char *fmt, ...) void lgtd_info(const char *fmt, ...) { - if (lgtd_opts.verbosity > LGTD_INFO) { - return; - } - fprintf(stderr, "INFO: "); va_list ap; va_start(ap, fmt); @@ -77,10 +65,6 @@ lgtd_info(const char *fmt, ...) void lgtd_debug(const char *fmt, ...) { - if (lgtd_opts.verbosity > LGTD_DEBUG) { - return; - } - fprintf(stderr, "DEBUG: "); va_list ap; va_start(ap, fmt); diff --git a/tests/core/pipe/CMakeLists.txt b/tests/core/pipe/CMakeLists.txt index 3adc65c..81dd391 100644 --- a/tests/core/pipe/CMakeLists.txt +++ b/tests/core/pipe/CMakeLists.txt @@ -10,7 +10,6 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c - ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) diff --git a/tests/core/proto/CMakeLists.txt b/tests/core/proto/CMakeLists.txt index ab681f7..d0a3d51 100644 --- a/tests/core/proto/CMakeLists.txt +++ b/tests/core/proto/CMakeLists.txt @@ -10,7 +10,6 @@ ADD_CORE_LIBRARY( ${LIGHTSD_SOURCE_DIR}/core/utils.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/tagging.c - ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c ) diff --git a/tests/core/proto/test_proto_get_light_state.c b/tests/core/proto/test_proto_get_light_state.c index ea61123..b1fdced 100644 --- a/tests/core/proto/test_proto_get_light_state.c +++ b/tests/core/proto/test_proto_get_light_state.c @@ -6,6 +6,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_empty_device_list.c b/tests/core/proto/test_proto_get_light_state_empty_device_list.c index 59b760a..01c20a0 100644 --- a/tests/core/proto/test_proto_get_light_state_empty_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_empty_device_list.c @@ -5,6 +5,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_label_overflow.c b/tests/core/proto/test_proto_get_light_state_label_overflow.c index 087e906..7e0e13c 100644 --- a/tests/core/proto/test_proto_get_light_state_label_overflow.c +++ b/tests/core/proto/test_proto_get_light_state_label_overflow.c @@ -6,6 +6,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_null_device_list.c b/tests/core/proto/test_proto_get_light_state_null_device_list.c index 5e8f9f3..a5a434d 100644 --- a/tests/core/proto/test_proto_get_light_state_null_device_list.c +++ b/tests/core/proto/test_proto_get_light_state_null_device_list.c @@ -5,6 +5,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c index 521543f..45f5073 100644 --- a/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c +++ b/tests/core/proto/test_proto_get_light_state_unknown_tag_id.c @@ -6,6 +6,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_power_off.c b/tests/core/proto/test_proto_power_off.c index 1426dd1..a881002 100644 --- a/tests/core/proto/test_proto_power_off.c +++ b/tests/core/proto/test_proto_power_off.c @@ -5,6 +5,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c index 2ec259b..19f9077 100644 --- a/tests/core/proto/test_proto_power_off_routing_error.c +++ b/tests/core/proto/test_proto_power_off_routing_error.c @@ -5,6 +5,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_on.c b/tests/core/proto/test_proto_power_on.c index 1f11bef..e1bb5b9 100644 --- a/tests/core/proto/test_proto_power_on.c +++ b/tests/core/proto/test_proto_power_on.c @@ -5,6 +5,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_on_routing_error.c b/tests/core/proto/test_proto_power_on_routing_error.c index 87c570d..2cb64d6 100644 --- a/tests/core/proto/test_proto_power_on_routing_error.c +++ b/tests/core/proto/test_proto_power_on_routing_error.c @@ -5,6 +5,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_power_toggle.c b/tests/core/proto/test_proto_power_toggle.c index 34e74a8..b4212e6 100644 --- a/tests/core/proto/test_proto_power_toggle.c +++ b/tests/core/proto/test_proto_power_toggle.c @@ -6,6 +6,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_SEND_TO_DEVICE diff --git a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c index b22b2df..1be2edb 100644 --- a/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c +++ b/tests/core/proto/test_proto_power_toggle_targets_to_device_fails.c @@ -6,6 +6,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_SEND_TO_DEVICE diff --git a/tests/core/proto/test_proto_set_label.c b/tests/core/proto/test_proto_set_label.c index c041d77..e4c7fa3 100644 --- a/tests/core/proto/test_proto_set_label.c +++ b/tests/core/proto/test_proto_set_label.c @@ -5,6 +5,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_label_too_long.c b/tests/core/proto/test_proto_set_label_too_long.c index 2c6371a..17d13c5 100644 --- a/tests/core/proto/test_proto_set_label_too_long.c +++ b/tests/core/proto/test_proto_set_label_too_long.c @@ -5,6 +5,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_light_from_hsbk.c b/tests/core/proto/test_proto_set_light_from_hsbk.c index 2092c8c..0b39393 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk.c @@ -7,6 +7,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c index c6ce1bc..05538ef 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c @@ -7,6 +7,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index ebe823d..bca4879 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -7,6 +7,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index f7916ab..9d4350f 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -7,6 +7,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_RESPONSE diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c index 9613feb..fc8ca88 100644 --- a/tests/core/proto/test_proto_tag_create.c +++ b/tests/core/proto/test_proto_tag_create.c @@ -8,6 +8,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c index 3f24c16..db9f7ae 100644 --- a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c +++ b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c @@ -8,6 +8,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_CLIENT_SEND_ERROR diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c index 8bafb43..4de4ef9 100644 --- a/tests/core/proto/test_proto_tag_update.c +++ b/tests/core/proto/test_proto_tag_update.c @@ -8,6 +8,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c index ef828c6..38e2df5 100644 --- a/tests/core/proto/test_proto_untag.c +++ b/tests/core/proto/test_proto_untag.c @@ -6,6 +6,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/proto/test_proto_untag_tag_does_not_exist.c b/tests/core/proto/test_proto_untag_tag_does_not_exist.c index ca2d7f1..c84b334 100644 --- a/tests/core/proto/test_proto_untag_tag_does_not_exist.c +++ b/tests/core/proto/test_proto_untag_tag_does_not_exist.c @@ -6,6 +6,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" #include "tests_utils.h" #define MOCKED_ROUTER_TARGETS_TO_DEVICES diff --git a/tests/core/router/test_router_send_to_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c index 54b7322..02b19d6 100644 --- a/tests/core/router/test_router_send_to_broadcast.c +++ b/tests/core/router/test_router_send_to_broadcast.c @@ -10,7 +10,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); lgtd_tests_insert_mock_gateway(2); lgtd_tests_insert_mock_gateway(1); diff --git a/tests/core/router/test_router_send_to_device.c b/tests/core/router/test_router_send_to_device.c index 5e9f50c..e14b6ee 100644 --- a/tests/core/router/test_router_send_to_device.c +++ b/tests/core/router/test_router_send_to_device.c @@ -9,7 +9,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); struct lgtd_lifx_bulb *bulb_1 = lgtd_tests_insert_mock_bulb(gw_1, 1); diff --git a/tests/core/router/test_router_send_to_invalid_targets.c b/tests/core/router/test_router_send_to_invalid_targets.c index b99fe39..970de4d 100644 --- a/tests/core/router/test_router_send_to_invalid_targets.c +++ b/tests/core/router/test_router_send_to_invalid_targets.c @@ -33,7 +33,7 @@ test_target(const char *target) int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); lgtd_tests_insert_mock_bulb(gw_1, 1); diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index 55760fd..afea40c 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -9,7 +9,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); struct lgtd_lifx_bulb *bulb_1 = lgtd_tests_insert_mock_bulb(gw_1, 1); diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index d77ed0e..476b969 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -9,7 +9,7 @@ int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); diff --git a/tests/core/router/test_router_targets_to_devices.c b/tests/core/router/test_router_targets_to_devices.c index ee8f1bf..01c4f2a 100644 --- a/tests/core/router/test_router_targets_to_devices.c +++ b/tests/core/router/test_router_targets_to_devices.c @@ -43,7 +43,7 @@ len(const struct lgtd_router_device_list *devices) int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway *gw_1 = lgtd_tests_insert_mock_gateway(1); struct lgtd_lifx_gateway *gw_2 = lgtd_tests_insert_mock_gateway(2); diff --git a/tests/core/tests_shims.c b/tests/core/tests_shims.c index b4e7d1c..a37874a 100644 --- a/tests/core/tests_shims.c +++ b/tests/core/tests_shims.c @@ -29,6 +29,17 @@ struct lgtd_opts lgtd_opts = { struct event_base *lgtd_ev_base = MOCK_LGTD_EV_BASE; +const int LGTD_LIFX_DEBRUIJN_SEQUENCE[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, + 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, + 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, + 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, + 13, 18, 8, 12, 7, 6, 5, 63 +}; + void lgtd_cleanup(void) { diff --git a/tests/lifx/gateway/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt index a9411f2..34df4e4 100644 --- a/tests/lifx/gateway/CMakeLists.txt +++ b/tests/lifx/gateway/CMakeLists.txt @@ -18,7 +18,6 @@ ADD_LIBRARY( ${LIGHTSD_SOURCE_DIR}/lifx/broadcast.c ${LIGHTSD_SOURCE_DIR}/lifx/bulb.c ${LIGHTSD_SOURCE_DIR}/lifx/discovery.c - ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c ) FUNCTION(ADD_GATEWAY_TEST TEST_SOURCE) diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id.c b/tests/lifx/gateway/test_gateway_allocate_tag_id.c index 4ee310c..0fc9495 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id.c @@ -6,6 +6,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" static bool tagging_incref_called = false; @@ -42,8 +43,6 @@ lgtd_lifx_tagging_incref(const char *label, int main(void) { - lgtd_lifx_wire_load_packet_info_map(); - struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c index d6a73a5..791eb14 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c @@ -6,6 +6,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" static bool tagging_incref_called = false; @@ -43,7 +44,7 @@ lgtd_lifx_tagging_incref(const char *label, int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c index dbe0e10..3d57a0c 100644 --- a/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c +++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c @@ -1,4 +1,3 @@ - #include #include "gateway.c" @@ -7,6 +6,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" static bool tagging_incref_called = false; @@ -43,7 +43,7 @@ lgtd_lifx_tagging_incref(const char *label, int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c index a3caab0..b0b97e8 100644 --- a/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c +++ b/tests/lifx/gateway/test_gateway_deallocate_tag_id_from_lifx_network.c @@ -6,6 +6,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" static bool tagging_decref_called = false; @@ -25,7 +26,7 @@ lgtd_lifx_tagging_decref(struct lgtd_lifx_tag *tag, struct lgtd_lifx_gateway *gw int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet.c b/tests/lifx/gateway/test_gateway_enqueue_packet.c index eef15b3..d4a0f2f 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet.c @@ -3,11 +3,12 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c index 3a8148b..702b095 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c @@ -3,11 +3,12 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c index 41506e4..0a5f69a 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c @@ -3,11 +3,12 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_handle_ambient_light.c b/tests/lifx/gateway/test_gateway_handle_ambient_light.c index 4d2b41e..4b10a65 100644 --- a/tests/lifx/gateway/test_gateway_handle_ambient_light.c +++ b/tests/lifx/gateway/test_gateway_handle_ambient_light.c @@ -6,11 +6,12 @@ #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_handle_bulb_label.c b/tests/lifx/gateway/test_gateway_handle_bulb_label.c index 1f6d1c3..b3b212a 100644 --- a/tests/lifx/gateway/test_gateway_handle_bulb_label.c +++ b/tests/lifx/gateway/test_gateway_handle_bulb_label.c @@ -6,11 +6,12 @@ #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; diff --git a/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c b/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c index c346a3e..7cec092 100644 --- a/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c +++ b/tests/lifx/gateway/test_gateway_handle_ip_firmware_info.c @@ -6,11 +6,12 @@ #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; diff --git a/tests/lifx/gateway/test_gateway_handle_ip_state.c b/tests/lifx/gateway/test_gateway_handle_ip_state.c index d60866a..d7f66a2 100644 --- a/tests/lifx/gateway/test_gateway_handle_ip_state.c +++ b/tests/lifx/gateway/test_gateway_handle_ip_state.c @@ -6,11 +6,12 @@ #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; diff --git a/tests/lifx/gateway/test_gateway_handle_product_info.c b/tests/lifx/gateway/test_gateway_handle_product_info.c index 022c4a8..11ebff8 100644 --- a/tests/lifx/gateway/test_gateway_handle_product_info.c +++ b/tests/lifx/gateway/test_gateway_handle_product_info.c @@ -6,11 +6,12 @@ #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; diff --git a/tests/lifx/gateway/test_gateway_handle_runtime_info.c b/tests/lifx/gateway/test_gateway_handle_runtime_info.c index ffb00d1..1cc9254 100644 --- a/tests/lifx/gateway/test_gateway_handle_runtime_info.c +++ b/tests/lifx/gateway/test_gateway_handle_runtime_info.c @@ -6,11 +6,12 @@ #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw = { .last_pkt_at = 42 }; diff --git a/tests/lifx/gateway/test_gateway_handle_tag_labels.c b/tests/lifx/gateway/test_gateway_handle_tag_labels.c index a3ef33f..6dfdc86 100644 --- a/tests/lifx/gateway/test_gateway_handle_tag_labels.c +++ b/tests/lifx/gateway/test_gateway_handle_tag_labels.c @@ -5,11 +5,12 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_handle_tags.c b/tests/lifx/gateway/test_gateway_handle_tags.c index c45dafa..cfe8fbf 100644 --- a/tests/lifx/gateway/test_gateway_handle_tags.c +++ b/tests/lifx/gateway/test_gateway_handle_tags.c @@ -6,11 +6,12 @@ #include "mock_timer.h" #include "test_gateway_utils.h" #include "tests_utils.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c index 681d422..3929a2a 100644 --- a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c +++ b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c @@ -3,11 +3,12 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback.c b/tests/lifx/gateway/test_gateway_write_callback.c index 9adfb5a..95f2138 100644 --- a/tests/lifx/gateway/test_gateway_write_callback.c +++ b/tests/lifx/gateway/test_gateway_write_callback.c @@ -5,6 +5,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" size_t evbuffer_get_length(const struct evbuffer *buf) @@ -45,7 +46,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c index 5b5e32f..d9af5f9 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c +++ b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c @@ -5,6 +5,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" size_t evbuffer_get_length(const struct evbuffer *buf) @@ -45,7 +46,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c index 9fb07fe..ee122a3 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c +++ b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c @@ -5,6 +5,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" size_t evbuffer_get_length(const struct evbuffer *buf) @@ -44,7 +45,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c index c673584..6ca6206 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c +++ b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c @@ -5,6 +5,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" size_t evbuffer_get_length(const struct evbuffer *buf) @@ -52,7 +53,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c index dfdfda7..8a8e9e2 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c @@ -5,6 +5,7 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#include "mock_wire_proto.h" size_t evbuffer_get_length(const struct evbuffer *buf) @@ -45,7 +46,7 @@ evbuffer_write_atmost(struct evbuffer *buf, int main(void) { - lgtd_lifx_wire_load_packet_info_map(); + lgtd_lifx_wire_setup(); struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); diff --git a/tests/lifx/mock_wire_proto.h b/tests/lifx/mock_wire_proto.h new file mode 100644 index 0000000..91213a1 --- /dev/null +++ b/tests/lifx/mock_wire_proto.h @@ -0,0 +1,402 @@ +#pragma once + +const union lgtd_lifx_target LGTD_LIFX_UNSPEC_TARGET = { .tags = 0 }; + +#ifndef MOCKED_LGTD_WIRE_SETUP +void +lgtd_lifx_wire_setup(void) +{ +} +#endif + +#ifndef MOCKED_LGTD_LIFX_WIRE_WAVEFORM_STRING_ID_TO_TYPE +enum lgtd_lifx_waveform_type +lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) +{ + (void)s; + (void)len; + return LGTD_LIFX_WAVEFORM_INVALID; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_WIRE_NULL_PACKET_ENCODER_DECODER +static void +lgtd_lifx_wire_null_packet_encoder_decoder(void *pkt) +{ + (void)pkt; +} + +static void +lgtd_lifx_wire_null_packet_handler(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const void *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} + +const struct lgtd_lifx_packet_info * +lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type packet_type) +{ +#define UNIMPLEMENTED \ + .decode = lgtd_lifx_wire_null_packet_encoder_decoder, \ + .encode = lgtd_lifx_wire_null_packet_encoder_decoder, \ + .handle = lgtd_lifx_wire_null_packet_handler + + static const struct lgtd_lifx_packet_info packet_table[] = { + // Gateway packets: + { + UNIMPLEMENTED, + .name = "GET_PAN_GATEWAY", + .type = LGTD_LIFX_GET_PAN_GATEWAY + }, + { + UNIMPLEMENTED, + .name = "PAN_GATEWAY", + .type = LGTD_LIFX_PAN_GATEWAY, + .size = sizeof(struct lgtd_lifx_packet_pan_gateway) + }, + { + UNIMPLEMENTED, + .name = "SET_TAG_LABELS", + .type = LGTD_LIFX_SET_TAG_LABELS, + .size = sizeof(struct lgtd_lifx_packet_tag_labels), + }, + { + UNIMPLEMENTED, + .name = "GET_TAG_LABELS", + .type = LGTD_LIFX_GET_TAG_LABELS, + .size = sizeof(struct lgtd_lifx_packet_tags), + }, + { + UNIMPLEMENTED, + .name = "TAG_LABELS", + .type = LGTD_LIFX_TAG_LABELS, + .size = sizeof(struct lgtd_lifx_packet_tag_labels) + }, + // Bulb packets: + { + UNIMPLEMENTED, + .name = "SET_LIGHT_COLOR", + .type = LGTD_LIFX_SET_LIGHT_COLOR, + .size = sizeof(struct lgtd_lifx_packet_light_color) + }, + { + UNIMPLEMENTED, + .name = "SET_WAVEFORM", + .type = LGTD_LIFX_SET_WAVEFORM, + .size = sizeof(struct lgtd_lifx_packet_waveform), + }, + { + UNIMPLEMENTED, + .name = "GET_LIGHT_STATUS", + .type = LGTD_LIFX_GET_LIGHT_STATE + }, + { + UNIMPLEMENTED, + .name = "LIGHT_STATUS", + .type = LGTD_LIFX_LIGHT_STATUS, + .size = sizeof(struct lgtd_lifx_packet_light_status), + }, + { + UNIMPLEMENTED, + .size = sizeof(struct lgtd_lifx_packet_power_state), + .name = "SET_POWER_STATE", + .type = LGTD_LIFX_SET_POWER_STATE, + }, + { + UNIMPLEMENTED, + .name = "POWER_STATE", + .type = LGTD_LIFX_POWER_STATE, + .size = sizeof(struct lgtd_lifx_packet_power_state), + }, + { + UNIMPLEMENTED, + .name = "SET_TAGS", + .type = LGTD_LIFX_SET_TAGS, + .size = sizeof(struct lgtd_lifx_packet_tags), + }, + { + UNIMPLEMENTED, + .name = "TAGS", + .type = LGTD_LIFX_TAGS, + .size = sizeof(struct lgtd_lifx_packet_tags), + }, + { + UNIMPLEMENTED, + .name = "GET_MESH_INFO", + .type = LGTD_LIFX_GET_MESH_INFO + }, + { + UNIMPLEMENTED, + .name = "MESH_INFO", + .type = LGTD_LIFX_MESH_INFO, + .size = sizeof(struct lgtd_lifx_packet_ip_state), + }, + { + UNIMPLEMENTED, + .name = "GET_MESH_FIRMWARE", + .type = LGTD_LIFX_GET_MESH_FIRMWARE + }, + { + UNIMPLEMENTED, + .name = "MESH_FIRMWARE", + .type = LGTD_LIFX_MESH_FIRMWARE, + .size = sizeof(struct lgtd_lifx_packet_ip_firmware_info), + }, + { + UNIMPLEMENTED, + .name = "GET_WIFI_INFO", + .type = LGTD_LIFX_GET_WIFI_INFO, + }, + { + UNIMPLEMENTED, + .name = "WIFI_INFO", + .type = LGTD_LIFX_WIFI_INFO, + .size = sizeof(struct lgtd_lifx_packet_ip_state), + }, + { + UNIMPLEMENTED, + .name = "GET_WIFI_FIRMWARE_STATE", + .type = LGTD_LIFX_GET_WIFI_FIRMWARE_STATE + }, + { + UNIMPLEMENTED, + .name = "WIFI_FIRMWARE_STATE", + .type = LGTD_LIFX_WIFI_FIRMWARE_STATE, + .size = sizeof(struct lgtd_lifx_packet_ip_firmware_info), + }, + { + UNIMPLEMENTED, + .name = "GET_VERSION", + .type = LGTD_LIFX_GET_VERSION + }, + { + UNIMPLEMENTED, + .name = "VERSION_STATE", + .type = LGTD_LIFX_VERSION_STATE, + .size = sizeof(struct lgtd_lifx_packet_product_info), + }, + { + UNIMPLEMENTED, + .name = "GET_INFO", + .type = LGTD_LIFX_GET_INFO + }, + { + UNIMPLEMENTED, + .name = "INFO_STATE", + .type = LGTD_LIFX_INFO_STATE, + .size = sizeof(struct lgtd_lifx_packet_runtime_info), + }, + { + UNIMPLEMENTED, + .name = "SET_BULB_LABEL", + .type = LGTD_LIFX_SET_BULB_LABEL, + .size = sizeof(struct lgtd_lifx_packet_label) + }, + { + UNIMPLEMENTED, + .name = "BULB_LABEL", + .type = LGTD_LIFX_BULB_LABEL, + .size = sizeof(struct lgtd_lifx_packet_label), + }, + { + UNIMPLEMENTED, + .name = "GET_AMBIENT_LIGHT", + .type = LGTD_LIFX_GET_AMBIENT_LIGHT + }, + { + UNIMPLEMENTED, + .name = "STATE_AMBIENT_LIGHT", + .type = LGTD_LIFX_STATE_AMBIENT_LIGHT, + .size = sizeof(struct lgtd_lifx_packet_ambient_light), + }, + // Unimplemented but "known" packets + { + UNIMPLEMENTED, + .name = "GET_TIME", + .type = LGTD_LIFX_GET_TIME + }, + { + UNIMPLEMENTED, + .name = "SET_TIME", + .type = LGTD_LIFX_SET_TIME + }, + { + UNIMPLEMENTED, + .name = "TIME_STATE", + .type = LGTD_LIFX_TIME_STATE + }, + { + UNIMPLEMENTED, + .name = "GET_RESET_SWITCH_STATE", + .type = LGTD_LIFX_GET_RESET_SWITCH_STATE + }, + { + UNIMPLEMENTED, + .name = "RESET_SWITCH_STATE", + .type = LGTD_LIFX_RESET_SWITCH_STATE + }, + { + UNIMPLEMENTED, + .name = "GET_BULB_LABEL", + .type = LGTD_LIFX_GET_BULB_LABEL + }, + { + UNIMPLEMENTED, + .name = "GET_MCU_RAIL_VOLTAGE", + .type = LGTD_LIFX_GET_MCU_RAIL_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "MCU_RAIL_VOLTAGE", + .type = LGTD_LIFX_MCU_RAIL_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "REBOOT", + .type = LGTD_LIFX_REBOOT + }, + { + UNIMPLEMENTED, + .name = "SET_FACTORY_TEST_MODE", + .type = LGTD_LIFX_SET_FACTORY_TEST_MODE + }, + { + UNIMPLEMENTED, + .name = "DISABLE_FACTORY_TEST_MODE", + .type = LGTD_LIFX_DISABLE_FACTORY_TEST_MODE + }, + { + UNIMPLEMENTED, + .name = "ACK", + .type = LGTD_LIFX_ACK + }, + { + UNIMPLEMENTED, + .name = "ECHO_REQUEST", + .type = LGTD_LIFX_ECHO_REQUEST + }, + { + UNIMPLEMENTED, + .name = "ECHO_RESPONSE", + .type = LGTD_LIFX_ECHO_RESPONSE + }, + { + UNIMPLEMENTED, + .name = "SET_DIM_ABSOLUTE", + .type = LGTD_LIFX_SET_DIM_ABSOLUTE + }, + { + UNIMPLEMENTED, + .name = "SET_DIM_RELATIVE", + .type = LGTD_LIFX_SET_DIM_RELATIVE + }, + { + UNIMPLEMENTED, + .name = "GET_WIFI_STATE", + .type = LGTD_LIFX_GET_WIFI_STATE + }, + { + UNIMPLEMENTED, + .name = "SET_WIFI_STATE", + .type = LGTD_LIFX_SET_WIFI_STATE + }, + { + UNIMPLEMENTED, + .name = "WIFI_STATE", + .type = LGTD_LIFX_WIFI_STATE + }, + { + UNIMPLEMENTED, + .name = "GET_ACCESS_POINTS", + .type = LGTD_LIFX_GET_ACCESS_POINTS + }, + { + UNIMPLEMENTED, + .name = "SET_ACCESS_POINTS", + .type = LGTD_LIFX_SET_ACCESS_POINTS + }, + { + UNIMPLEMENTED, + .name = "ACCESS_POINT", + .type = LGTD_LIFX_ACCESS_POINT + }, + { + UNIMPLEMENTED, + .name = "GET_DIMMER_VOLTAGE", + .type = LGTD_LIFX_GET_DIMMER_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "STATE_DIMMER_VOLTAGE", + .type = LGTD_LIFX_STATE_DIMMER_VOLTAGE + } + }; + + for (int i = 0; i != sizeof(packet_table) / sizeof(packet_table[0]); i++) { + if (packet_table[i].type == packet_type) { + return &packet_table[i]; + } + } + + return NULL; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_WIRE_SETUP_HEADER +const struct lgtd_lifx_packet_info * +lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, + enum lgtd_lifx_target_type target_type, + union lgtd_lifx_target target, + const uint8_t *site, + enum lgtd_lifx_packet_type packet_type) +{ + (void)hdr; + (void)target_type; + (void)target; + (void)site; + return lgtd_lifx_wire_get_packet_info(packet_type); +} +#endif + +#ifndef MOCKED_LGTD_LIFX_WIRE_DECODE_HEADER +void +lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *hdr) +{ + (void)hdr; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_WIRE_ENCODE_LIGHT_COLOR +void +lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *pkt) +{ + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_WIRE_ENCODE_TAG_LABELS +void +lgtd_lifx_wire_encode_tag_labels(struct lgtd_lifx_packet_tag_labels *pkt) +{ + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_WIRE_ENCODE_TAGS +void +lgtd_lifx_wire_encode_tags(struct lgtd_lifx_packet_tags *pkt) +{ + (void)pkt; +} +#endif + +#ifndef MOCKED_LGTD_LIFX_WIRE_ENCODE_WAVEFORM +void +lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) +{ + (void)pkt; +} +#endif diff --git a/tests/lifx/tests_shims.c b/tests/lifx/tests_shims.c index bfbcf6f..49e5d91 100644 --- a/tests/lifx/tests_shims.c +++ b/tests/lifx/tests_shims.c @@ -15,6 +15,17 @@ struct lgtd_opts lgtd_opts = { struct event_base *lgtd_ev_base = NULL; +const int LGTD_LIFX_DEBRUIJN_SEQUENCE[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, + 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, + 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, + 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, + 13, 18, 8, 12, 7, 6, 5, 63 +}; + void lgtd_cleanup(void) { diff --git a/tests/lifx/wire_proto/CMakeLists.txt b/tests/lifx/wire_proto/CMakeLists.txt index c4c35f2..456874f 100644 --- a/tests/lifx/wire_proto/CMakeLists.txt +++ b/tests/lifx/wire_proto/CMakeLists.txt @@ -5,9 +5,7 @@ INCLUDE_DIRECTORIES( ADD_CORE_LIBRARY( test_lifx_wire_proto_core STATIC - ${LIGHTSD_SOURCE_DIR}/core/stats.c ${LIGHTSD_SOURCE_DIR}/core/utils.c - ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c ) FUNCTION(ADD_WIRE_PROTO_TEST TEST_SOURCE) diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c index 1470493..3babdd2 100644 --- a/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c +++ b/tests/lifx/wire_proto/test_wire_proto_encode_decode_header.c @@ -2,6 +2,7 @@ #include "wire_proto.c" +#include "mock_daemon.h" #include "mock_gateway.h" #include "mock_log.h" diff --git a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c index 60cc173..a04dc1e 100644 --- a/tests/lifx/wire_proto/test_wire_proto_waveform_table.c +++ b/tests/lifx/wire_proto/test_wire_proto_waveform_table.c @@ -1,7 +1,7 @@ #include "wire_proto.c" +#include "mock_daemon.h" #include "mock_gateway.h" - #include "mock_log.h" int From 02341a42e32bbe29e8b04e48c1fd52b9c48cce51 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Tue, 17 Nov 2015 02:03:16 -0800 Subject: [PATCH 162/181] Finish refactoring tests around wire_proto.c This finishes what started the previous changeset: mock the wire_proto module and properly do unit testing. --- core/daemon.c | 2 +- docs/changelog.rst | 9 +++ lifx/gateway.c | 9 ++- tests/core/daemon/test_daemon_randuint32.c | 2 +- .../proto/test_proto_set_light_from_hsbk.c | 64 +++++++++++---- ...oto_set_light_from_hsbk_on_routing_error.c | 64 +++++++++++---- tests/core/proto/test_proto_set_waveform.c | 79 +++++++++++++------ ...test_proto_set_waveform_on_routing_error.c | 73 +++++++++++------ tests/core/proto/test_proto_tag_create.c | 48 +++++++++-- tests/core/proto/test_proto_tag_update.c | 62 ++++++++++++--- tests/core/proto/test_proto_untag.c | 21 ++++- .../test_gateway_update_tag_refcounts.c | 22 +++++- tests/lifx/mock_wire_proto.h | 12 ++- .../test_wire_proto_encode_light_color.c | 43 ++++++++++ .../test_wire_proto_encode_tag_labels.c | 29 +++++++ .../wire_proto/test_wire_proto_encode_tags.c | 23 ++++++ .../test_wire_proto_encode_waveform.c | 53 +++++++++++++ 17 files changed, 507 insertions(+), 108 deletions(-) create mode 100644 tests/lifx/wire_proto/test_wire_proto_encode_light_color.c create mode 100644 tests/lifx/wire_proto/test_wire_proto_encode_tag_labels.c create mode 100644 tests/lifx/wire_proto/test_wire_proto_encode_tags.c create mode 100644 tests/lifx/wire_proto/test_wire_proto_encode_waveform.c diff --git a/core/daemon.c b/core/daemon.c index ad9bb9d..3300536 100644 --- a/core/daemon.c +++ b/core/daemon.c @@ -414,7 +414,7 @@ lgtd_daemon_randuint32(void) close(fd); lgtd_err( 1, "couln't fetch %ju bytes from /dev/urandom", - sizeof((uintmax_t)rv) + (uintmax_t)sizeof(rv) ); } diff --git a/docs/changelog.rst b/docs/changelog.rst index a92346d..424b40e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,15 @@ Changelog ========= +1.1.1 (2015-11-17) +------------------ + +- Greatly improve responsiveness by setting the LIFX source identifier [#]_. +- Fix parallel builds in the Debian package & fix the homebrew formulae for OS X + 10.11 (El Capitan). + +.. [#] http://lan.developer.lifx.com/docs/header-description#frame + 1.1.0 (2015-11-07) ------------------ diff --git a/lifx/gateway.c b/lifx/gateway.c index a25198c..f1a139b 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -427,7 +427,14 @@ lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, evbuffer_add(gw->write_buf, hdr, sizeof(*hdr)); if (pkt) { - assert(pkt_info->size == le16toh(hdr->size) - sizeof(*hdr)); +#ifndef NDEBUG + // actually decode the header instead of just calling + // le16toh(hdr->size) so it's easier to mock things in the tests: + struct lgtd_lifx_packet_header decoded_hdr; + memcpy(&decoded_hdr, hdr, sizeof(decoded_hdr)); + lgtd_lifx_wire_decode_header(&decoded_hdr); + assert(pkt_info->size == decoded_hdr.size - sizeof(*hdr)); +#endif evbuffer_add(gw->write_buf, pkt, pkt_info->size); } gw->pkt_ring[gw->pkt_ring_head].size = sizeof(*hdr) + pkt_info->size; diff --git a/tests/core/daemon/test_daemon_randuint32.c b/tests/core/daemon/test_daemon_randuint32.c index ea639e3..49c57ab 100644 --- a/tests/core/daemon/test_daemon_randuint32.c +++ b/tests/core/daemon/test_daemon_randuint32.c @@ -16,7 +16,7 @@ int mock_close(int); #include "mock_log.h" #include "mock_timer.h" -static const int MOCK_RANDOM_NUMBER = 0x72616e64; +static const uint32_t MOCK_RANDOM_NUMBER = 0x72616e64; int mock_open_call_count = 0; diff --git a/tests/core/proto/test_proto_set_light_from_hsbk.c b/tests/core/proto/test_proto_set_light_from_hsbk.c index 0b39393..8af1992 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk.c @@ -7,6 +7,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_LIGHT_COLOR #include "mock_wire_proto.h" #include "tests_utils.h" @@ -14,6 +15,18 @@ #define MOCKED_ROUTER_SEND #include "tests_proto_utils.h" +int lifx_wire_encode_light_color_call_count = 0; + +void +lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *pkt) +{ + (void)pkt; + + lifx_wire_encode_light_color_call_count++; +} + +int router_send_call_count = 0; + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -34,31 +47,35 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } struct lgtd_lifx_packet_light_color *light_color = pkt; - int hue = le16toh(light_color->hue); - int saturation = le16toh(light_color->saturation); - int brightness = le16toh(light_color->brightness); - int kelvin = le16toh(light_color->kelvin); - int transition = htole32(light_color->transition); - - if (hue != 42) { - errx(1, "got hue = %d (expected 42)", hue); + if (lifx_wire_encode_light_color_call_count != 1) { + errx( + 1, "lifx_wire_encode_light_color_call_count = %d (expected 1)", + lifx_wire_encode_light_color_call_count + ); + } + if (light_color->hue != 42) { + errx(1, "got hue = %d (expected 42)", light_color->hue); } - if (saturation != 10000) { - errx(1, "got saturation = %d (expected 10000)", saturation); + if (light_color->saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", light_color->saturation); } - if (brightness != 20000) { - errx(1, "got brightness = %d (expected 20000)", brightness); + if (light_color->brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", light_color->brightness); } - if (kelvin != 4500) { - errx(1, "got kelvin = %d (expected 4500)", kelvin); + if (light_color->kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", light_color->kelvin); } - if (transition != 150) { - errx(1, "got transition = %d (expected 150)", transition); + if (light_color->transition != 150) { + errx(1, "got transition = %d (expected 150)", light_color->transition); } + router_send_call_count++; + return true; } +int client_send_response_call_count = 0; + void lgtd_client_send_response(struct lgtd_client *client, const char *msg) { @@ -69,6 +86,8 @@ lgtd_client_send_response(struct lgtd_client *client, const char *msg) if (strcmp(msg, "true")) { errx(1, "unexpected response [%s] (expected [true])", msg); } + + client_send_response_call_count++; } int @@ -83,5 +102,18 @@ main(void) &client, targets, 42, 10000, 20000, 4500, 150 ); + if (router_send_call_count != 1) { + errx( + 1, "router_send_call_count = %d (expected 1)", + router_send_call_count + ); + } + if (client_send_response_call_count != 1) { + errx( + 1, "client_send_response_call_count = %d (expected 1)", + client_send_response_call_count + ); + } + return 0; } diff --git a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c index 05538ef..1d7d230 100644 --- a/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c +++ b/tests/core/proto/test_proto_set_light_from_hsbk_on_routing_error.c @@ -7,6 +7,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_LIGHT_COLOR #include "mock_wire_proto.h" #include "tests_utils.h" @@ -14,6 +15,18 @@ #define MOCKED_ROUTER_SEND #include "tests_proto_utils.h" +int lifx_wire_encode_light_color_call_count = 0; + +void +lgtd_lifx_wire_encode_light_color(struct lgtd_lifx_packet_light_color *pkt) +{ + (void)pkt; + + lifx_wire_encode_light_color_call_count++; +} + +int router_send_call_count = 0; + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -34,31 +47,35 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } struct lgtd_lifx_packet_light_color *light_color = pkt; - int hue = le16toh(light_color->hue); - int saturation = le16toh(light_color->saturation); - int brightness = le16toh(light_color->brightness); - int kelvin = le16toh(light_color->kelvin); - int transition = htole32(light_color->transition); - - if (hue != 42) { - errx(1, "got hue = %d (expected 42)", hue); + if (lifx_wire_encode_light_color_call_count != 1) { + errx( + 1, "lifx_wire_encode_light_color_call_count = %d (expected 1)", + lifx_wire_encode_light_color_call_count + ); + } + if (light_color->hue != 42) { + errx(1, "got hue = %d (expected 42)", light_color->hue); } - if (saturation != 10000) { - errx(1, "got saturation = %d (expected 10000)", saturation); + if (light_color->saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", light_color->saturation); } - if (brightness != 20000) { - errx(1, "got brightness = %d (expected 20000)", brightness); + if (light_color->brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", light_color->brightness); } - if (kelvin != 4500) { - errx(1, "got kelvin = %d (expected 4500)", kelvin); + if (light_color->kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", light_color->kelvin); } - if (transition != 150) { - errx(1, "got transition = %d (expected 150)", transition); + if (light_color->transition != 150) { + errx(1, "got transition = %d (expected 150)", light_color->transition); } + router_send_call_count++; + return false; } +int client_send_response_call_count = 0; + void lgtd_client_send_response(struct lgtd_client *client, const char *msg) { @@ -69,6 +86,8 @@ lgtd_client_send_response(struct lgtd_client *client, const char *msg) if (strcmp(msg, "false")) { errx(1, "unexpected response [%s] (expected [false])", msg); } + + client_send_response_call_count++; } int @@ -83,5 +102,18 @@ main(void) &client, targets, 42, 10000, 20000, 4500, 150 ); + if (router_send_call_count != 1) { + errx( + 1, "router_send_call_count = %d (expected 1)", + router_send_call_count + ); + } + if (client_send_response_call_count != 1) { + errx( + 1, "client_send_response_call_count = %d (expected 1)", + client_send_response_call_count + ); + } + return 0; } diff --git a/tests/core/proto/test_proto_set_waveform.c b/tests/core/proto/test_proto_set_waveform.c index bca4879..9132245 100644 --- a/tests/core/proto/test_proto_set_waveform.c +++ b/tests/core/proto/test_proto_set_waveform.c @@ -7,6 +7,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_WAVEFORM #include "mock_wire_proto.h" #include "tests_utils.h" @@ -14,6 +15,18 @@ #define MOCKED_ROUTER_SEND #include "tests_proto_utils.h" +int lifx_wire_encode_waveform_call_count = 0; + +void +lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) +{ + (void)pkt; + + lifx_wire_encode_waveform_call_count++; +} + +int router_send_call_count = 0; + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -34,46 +47,47 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } struct lgtd_lifx_packet_waveform *waveform = pkt; - enum lgtd_lifx_waveform_type waveform_type = waveform->waveform; - int hue = le16toh(waveform->hue); - int saturation = le16toh(waveform->saturation); - int brightness = le16toh(waveform->brightness); - int kelvin = le16toh(waveform->kelvin); - int period = le32toh(waveform->period); - float cycles = lgtd_lifx_wire_lefloattoh(waveform->cycles); - int skew_ratio = le16toh(waveform->skew_ratio); - - if (waveform_type != LGTD_LIFX_WAVEFORM_SAW) { + if (lifx_wire_encode_waveform_call_count != 1) { + errx( + 1, "lifx_wire_encode_waveform_call_count = %d (expected 1)", + lifx_wire_encode_waveform_call_count + ); + } + if (waveform->waveform != LGTD_LIFX_WAVEFORM_SAW) { errx( 1, "got waveform = %d (expected %d)", - waveform_type, LGTD_LIFX_WAVEFORM_SAW + waveform->waveform, LGTD_LIFX_WAVEFORM_SAW ); } - if (hue != 42) { - errx(1, "got hue = %d (expected 42)", hue); + if (waveform->hue != 42) { + errx(1, "got hue = %d (expected 42)", waveform->hue); } - if (saturation != 10000) { - errx(1, "got saturation = %d (expected 10000)", saturation); + if (waveform->saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", waveform->saturation); } - if (brightness != 20000) { - errx(1, "got brightness = %d (expected 20000)", brightness); + if (waveform->brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", waveform->brightness); } - if (kelvin != 4500) { - errx(1, "got kelvin = %d (expected 4500)", kelvin); + if (waveform->kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", waveform->kelvin); } - if (period != 200) { - errx(1, "got period = %d (expected 200)", period); + if (waveform->period != 200) { + errx(1, "got period = %d (expected 200)", waveform->period); } - if (cycles != 10.) { - errx(1, "got cycles = %f (expected 10)", cycles); + if (waveform->cycles != 10.) { + errx(1, "got cycles = %f (expected 10)", waveform->cycles); } - if (skew_ratio != 0) { - errx(1, "got skew_ratio = %d (expected 0)", skew_ratio); + if (waveform->skew_ratio != 0) { + errx(1, "got skew_ratio = %d (expected 0)", waveform->skew_ratio); } + router_send_call_count++; + return true; } +int client_send_response_call_count = 0; + void lgtd_client_send_response(struct lgtd_client *client, const char *msg) { @@ -84,6 +98,8 @@ lgtd_client_send_response(struct lgtd_client *client, const char *msg) if (strcmp(msg, "true")) { errx(1, "unexpected response [%s] (expected [true])", msg); } + + client_send_response_call_count++; } int @@ -99,5 +115,18 @@ main(void) 42, 10000, 20000, 4500, 200, 10., 0, false ); + if (router_send_call_count != 1) { + errx( + 1, "router_send_call_count = %d (expected 1)", + router_send_call_count + ); + } + if (client_send_response_call_count != 1) { + errx( + 1, "client_send_response_call_count = %d (expected 1)", + client_send_response_call_count + ); + } + return 0; } diff --git a/tests/core/proto/test_proto_set_waveform_on_routing_error.c b/tests/core/proto/test_proto_set_waveform_on_routing_error.c index 9d4350f..ddfc91a 100644 --- a/tests/core/proto/test_proto_set_waveform_on_routing_error.c +++ b/tests/core/proto/test_proto_set_waveform_on_routing_error.c @@ -7,6 +7,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_WAVEFORM #include "mock_wire_proto.h" #include "tests_utils.h" @@ -14,6 +15,18 @@ #define MOCKED_ROUTER_SEND #include "tests_proto_utils.h" +int lifx_wire_encode_waveform_call_count = 0; + +void +lgtd_lifx_wire_encode_waveform(struct lgtd_lifx_packet_waveform *pkt) +{ + (void)pkt; + + lifx_wire_encode_waveform_call_count++; +} + +int router_send_call_count = 0; + bool lgtd_router_send(const struct lgtd_proto_target_list *targets, enum lgtd_lifx_packet_type pkt_type, @@ -34,46 +47,41 @@ lgtd_router_send(const struct lgtd_proto_target_list *targets, } struct lgtd_lifx_packet_waveform *waveform = pkt; - enum lgtd_lifx_waveform_type waveform_type = waveform->waveform; - int hue = le16toh(waveform->hue); - int saturation = le16toh(waveform->saturation); - int brightness = le16toh(waveform->brightness); - int kelvin = le16toh(waveform->kelvin); - int period = le32toh(waveform->period); - float cycles = lgtd_lifx_wire_lefloattoh(waveform->cycles); - int skew_ratio = le16toh(waveform->skew_ratio); - - if (waveform_type != LGTD_LIFX_WAVEFORM_SAW) { + if (waveform->waveform != LGTD_LIFX_WAVEFORM_SAW) { errx( 1, "got waveform = %d (expected %d)", - waveform_type, LGTD_LIFX_WAVEFORM_SAW + waveform->waveform, LGTD_LIFX_WAVEFORM_SAW ); } - if (hue != 42) { - errx(1, "got hue = %d (expected 42)", hue); + if (waveform->hue != 42) { + errx(1, "got hue = %d (expected 42)", waveform->hue); } - if (saturation != 10000) { - errx(1, "got saturation = %d (expected 10000)", saturation); + if (waveform->saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", waveform->saturation); } - if (brightness != 20000) { - errx(1, "got brightness = %d (expected 20000)", brightness); + if (waveform->brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", waveform->brightness); } - if (kelvin != 4500) { - errx(1, "got kelvin = %d (expected 4500)", kelvin); + if (waveform->kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", waveform->kelvin); } - if (period != 200) { - errx(1, "got period = %d (expected 200)", period); + if (waveform->period != 200) { + errx(1, "got period = %d (expected 200)", waveform->period); } - if (cycles != 10.) { - errx(1, "got cycles = %f (expected 10)", cycles); + if (waveform->cycles != 10.) { + errx(1, "got cycles = %f (expected 10)", waveform->cycles); } - if (skew_ratio != 0) { - errx(1, "got skew_ratio = %d (expected 0)", skew_ratio); + if (waveform->skew_ratio != 0) { + errx(1, "got skew_ratio = %d (expected 0)", waveform->skew_ratio); } + router_send_call_count++; + return false; } +int client_send_response_call_count = 0; + void lgtd_client_send_response(struct lgtd_client *client, const char *msg) { @@ -84,6 +92,8 @@ lgtd_client_send_response(struct lgtd_client *client, const char *msg) if (strcmp(msg, "false")) { errx(1, "unexpected response [%s] (expected [false])", msg); } + + client_send_response_call_count++; } int @@ -99,5 +109,18 @@ main(void) 42, 10000, 20000, 4500, 200, 10., 0, false ); + if (router_send_call_count != 1) { + errx( + 1, "router_send_call_count = %d (expected 1)", + router_send_call_count + ); + } + if (client_send_response_call_count != 1) { + errx( + 1, "client_send_response_call_count = %d (expected 1)", + client_send_response_call_count + ); + } + return 0; } diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c index fc8ca88..34c0d9d 100644 --- a/tests/core/proto/test_proto_tag_create.c +++ b/tests/core/proto/test_proto_tag_create.c @@ -8,6 +8,8 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_TAG_LABELS +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_TAGS #include "mock_wire_proto.h" #include "tests_utils.h" @@ -23,6 +25,26 @@ static struct lgtd_router_device_list devices = static struct lgtd_router_device_list device_1_only = SLIST_HEAD_INITIALIZER(&device_1_only); +static int lifx_wire_encode_tag_labels_call_count = 0; + +void +lgtd_lifx_wire_encode_tag_labels(struct lgtd_lifx_packet_tag_labels *pkt) +{ + (void)pkt; + + lifx_wire_encode_tag_labels_call_count++; +} + +static int lifx_wire_encode_tags_call_count = 0; + +void +lgtd_lifx_wire_encode_tags(struct lgtd_lifx_packet_tags *pkt) +{ + (void)pkt; + + lifx_wire_encode_tags_call_count++; +} + static bool send_to_device_called = false; void @@ -55,11 +77,16 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, } const struct lgtd_lifx_packet_tags *pkt_tags = pkt; - uint64_t tags = le64toh(pkt_tags->tags); - if (tags != 0x1) { + if (lifx_wire_encode_tags_call_count != 1) { + errx( + 1, "lifx_wire_encode_tags_call_count = %d (expected 1)", + lifx_wire_encode_tags_call_count + ); + } + if (pkt_tags->tags != 0x1) { errx( 1, "invalid SET_TAGS payload=%#jx (expected %#x)", - (uintmax_t)tags, 0x1 + (uintmax_t)pkt_tags->tags, 0x1 ); } @@ -85,11 +112,18 @@ lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, } const struct lgtd_lifx_packet_tag_labels *pkt_tag_labels = pkt; - uint64_t tags = le64toh(pkt_tag_labels->tags); - if (tags != 0x1) { - errx(1, "got tags %#jx (expected %#x)", (uintmax_t)tags, 0x1); + if (lifx_wire_encode_tag_labels_call_count != 1) { + errx( + 1, "lifx_wire_encode_tag_labels_call_count = %d (expected 1)", + lifx_wire_encode_tag_labels_call_count + ); + } + if (pkt_tag_labels->tags != 0x1) { + errx( + 1, "got tags %#jx (expected %#x)", + (uintmax_t)pkt_tag_labels->tags, 0x1 + ); } - if (strcmp(pkt_tag_labels->label, "dub")) { errx(1, "got label %s (expected dub)", pkt_tag_labels->label); } diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c index 4de4ef9..c6f09aa 100644 --- a/tests/core/proto/test_proto_tag_update.c +++ b/tests/core/proto/test_proto_tag_update.c @@ -8,6 +8,8 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_TAG_LABELS +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_TAGS #include "mock_wire_proto.h" #include "tests_utils.h" @@ -21,6 +23,26 @@ static struct lgtd_router_device_list devices = SLIST_HEAD_INITIALIZER(&devices); +static int lifx_wire_encode_tag_labels_call_count = 0; + +void +lgtd_lifx_wire_encode_tag_labels(struct lgtd_lifx_packet_tag_labels *pkt) +{ + (void)pkt; + + lifx_wire_encode_tag_labels_call_count++; +} + +static int lifx_wire_encode_tags_call_count = 0; + +void +lgtd_lifx_wire_encode_tags(struct lgtd_lifx_packet_tags *pkt) +{ + (void)pkt; + + lifx_wire_encode_tags_call_count++; +} + static bool send_to_device_called = false; void @@ -57,12 +79,16 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, } const struct lgtd_lifx_packet_tags *pkt_tags = pkt; - uint64_t tags = le64toh(pkt_tags->tags); - - if (tags != 0x7) { + if (lifx_wire_encode_tags_call_count != 1) { + errx( + 1, "lifx_wire_encode_tags_call_count = %d (expected 1)", + lifx_wire_encode_tags_call_count + ); + } + if (pkt_tags->tags != 0x7) { errx( 1, "invalid SET_TAGS payload=%#jx (expected %#x)", - (uintmax_t)tags, 0x7 + (uintmax_t)pkt_tags->tags, 0x7 ); } @@ -89,23 +115,28 @@ lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, } const struct lgtd_lifx_packet_tag_labels *pkt_tag_labels = pkt; - uint64_t tags = le64toh(pkt_tag_labels->tags); if (strcmp(pkt_tag_labels->label, "dub")) { errx(1, "got label %s (expected dub)", pkt_tag_labels->label); } if (gw->site.as_integer == 42) { - if (tags != 0x1) { - errx(1, "got tags %#jx (expected %#x)", (uintmax_t)tags, 0x1); + if (pkt_tag_labels->tags != 0x1) { + errx( + 1, "got tags %#jx (expected %#x)", + (uintmax_t)pkt_tag_labels->tags, 0x1 + ); } if (gateway_send_to_site_called_for_gw_1) { errx(1, "LGTD_LIFX_SET_TAG_LABELS already called for gw 1"); } gateway_send_to_site_called_for_gw_1 = true; } else if (gw->site.as_integer == 44) { - if (tags != 0x4) { - errx(1, "got tags %#jx (expected %#x)", (uintmax_t)tags, 0x4); + if (pkt_tag_labels->tags != 0x4) { + errx( + 1, "got tags %#jx (expected %#x)", + (uintmax_t)pkt_tag_labels->tags, 0x4 + ); } if (gateway_send_to_site_called_for_gw_2) { errx(1, "LGTD_LIFX_SET_TAG_LABELS already called for gw 2"); @@ -115,6 +146,19 @@ lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, errx(1, "LGTD_LIFX_SET_TAG_LABELS received an invalid gateway"); } + int expected_tag_encode_tag_labels_call_count = ( + (int)(gateway_send_to_site_called_for_gw_1 == true) + + (int)(gateway_send_to_site_called_for_gw_2 == true) + ); + if (lifx_wire_encode_tag_labels_call_count + != expected_tag_encode_tag_labels_call_count) { + errx( + 1, "lifx_wire_encode_tag_labels_call_count = %d (expected %d)", + lifx_wire_encode_tag_labels_call_count, + expected_tag_encode_tag_labels_call_count + ); + } + return true; } diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c index 38e2df5..2077aaa 100644 --- a/tests/core/proto/test_proto_untag.c +++ b/tests/core/proto/test_proto_untag.c @@ -6,6 +6,7 @@ #include "mock_event2.h" #include "mock_log.h" #include "mock_timer.h" +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_TAGS #include "mock_wire_proto.h" #include "tests_utils.h" @@ -16,6 +17,16 @@ static bool device_list_free_called = false; +static int lifx_wire_encode_tags_call_count = 0; + +void +lgtd_lifx_wire_encode_tags(struct lgtd_lifx_packet_tags *pkt) +{ + (void)pkt; + + lifx_wire_encode_tags_call_count++; +} + void lgtd_router_device_list_free(struct lgtd_router_device_list *devices) { @@ -125,10 +136,16 @@ lgtd_router_send_to_device(struct lgtd_lifx_bulb *bulb, } struct lgtd_lifx_packet_tags *pkt_tags = pkt; - if (le64toh(pkt_tags->tags) != 0x2) { + if (lifx_wire_encode_tags_call_count != 1) { + errx( + 1, "lifx_wire_encode_tags_call_count = %d (expected 1)", + lifx_wire_encode_tags_call_count + ); + } + if (pkt_tags->tags != 0x2) { errx( 1, "invalid SET_TAGS payload=%#jx (expected %#x)", - (uintmax_t)le64toh(pkt_tags->tags), 0x2 + (uintmax_t)pkt_tags->tags, 0x2 ); } diff --git a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c index 3929a2a..ac0d38c 100644 --- a/tests/lifx/gateway/test_gateway_update_tag_refcounts.c +++ b/tests/lifx/gateway/test_gateway_update_tag_refcounts.c @@ -3,8 +3,19 @@ #include "test_gateway_utils.h" #include "mock_log.h" #include "mock_timer.h" +#define MOCKED_LGTD_LIFX_WIRE_ENCODE_TAG_LABELS #include "mock_wire_proto.h" +static int lifx_wire_encode_tag_labels_call_count = 0; + +void +lgtd_lifx_wire_encode_tag_labels(struct lgtd_lifx_packet_tag_labels *pkt) +{ + (void)pkt; + + lifx_wire_encode_tag_labels_call_count++; +} + int main(void) { @@ -73,11 +84,16 @@ main(void) struct lgtd_lifx_packet_tag_labels *pkt = (void *)&gw_write_buf[sizeof(struct lgtd_lifx_packet_header)]; - uint64_t tags = le64toh(pkt->tags); - if (tags != ~2ULL) { + if (lifx_wire_encode_tag_labels_call_count != 1) { + errx( + 1, "lifx_wire_encode_tag_labels_call_count == %d (expected 1)", + lifx_wire_encode_tag_labels_call_count + ); + } + if (pkt->tags != ~2ULL) { errx( 1, "tags on LGTD_LIFX_SET_TAG_LABELS was %#jx (expected %#jx)", - (uintmax_t)tags, (uintmax_t)~2ULL + (uintmax_t)pkt->tags, (uintmax_t)~2ULL ); } const char blank_label[LGTD_LIFX_LABEL_SIZE] = { 0 }; diff --git a/tests/lifx/mock_wire_proto.h b/tests/lifx/mock_wire_proto.h index 91213a1..a0da12b 100644 --- a/tests/lifx/mock_wire_proto.h +++ b/tests/lifx/mock_wire_proto.h @@ -353,11 +353,19 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, const uint8_t *site, enum lgtd_lifx_packet_type packet_type) { - (void)hdr; (void)target_type; (void)target; (void)site; - return lgtd_lifx_wire_get_packet_info(packet_type); + + const struct lgtd_lifx_packet_info *pkt_info = + lgtd_lifx_wire_get_packet_info(packet_type); + hdr->size = pkt_info->size + sizeof(*hdr); + hdr->packet_type = packet_type; + if (site) { + memcpy(hdr->site, site, sizeof(hdr->site)); + } + + return pkt_info; } #endif diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_light_color.c b/tests/lifx/wire_proto/test_wire_proto_encode_light_color.c new file mode 100644 index 0000000..4239e6d --- /dev/null +++ b/tests/lifx/wire_proto/test_wire_proto_encode_light_color.c @@ -0,0 +1,43 @@ +#include + +#include "wire_proto.c" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "mock_log.h" + +int +main(void) +{ + struct lgtd_lifx_packet_light_color pkt = { + .hue = 42, + .saturation = 10000, + .brightness = 20000, + .kelvin = 4500, + .transition = 150 + }; + + lgtd_lifx_wire_encode_light_color(&pkt); + + int hue = le16toh(pkt.hue); + int saturation = le16toh(pkt.saturation); + int brightness = le16toh(pkt.brightness); + int kelvin = le16toh(pkt.kelvin); + int transition = htole32(pkt.transition); + if (hue != 42) { + errx(1, "got hue = %d (expected 42)", hue); + } + if (saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", saturation); + } + if (brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", brightness); + } + if (kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", kelvin); + } + if (transition != 150) { + errx(1, "got transition = %d (expected 150)", transition); + } + + return 0; +} diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_tag_labels.c b/tests/lifx/wire_proto/test_wire_proto_encode_tag_labels.c new file mode 100644 index 0000000..a53451b --- /dev/null +++ b/tests/lifx/wire_proto/test_wire_proto_encode_tag_labels.c @@ -0,0 +1,29 @@ +#include + +#include "wire_proto.c" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "mock_log.h" + +int +main(void) +{ + struct lgtd_lifx_packet_tag_labels pkt = { .tags = 0x2a, .label = "42" }; + + lgtd_lifx_wire_encode_tag_labels(&pkt); + + if (pkt.tags != le64toh(0x2a)) { + errx( + 1, "got tags = %#jx (expected %#jx)", + (uintmax_t)pkt.tags, (uintmax_t)le64toh(0x2a) + ); + } + if (strcmp(pkt.label, "42")) { + errx( + 1, "got label = %.*s (expected 42)", + (int)sizeof(pkt.label), pkt.label + ); + } + + return 0; +} diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_tags.c b/tests/lifx/wire_proto/test_wire_proto_encode_tags.c new file mode 100644 index 0000000..6d979f5 --- /dev/null +++ b/tests/lifx/wire_proto/test_wire_proto_encode_tags.c @@ -0,0 +1,23 @@ +#include + +#include "wire_proto.c" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "mock_log.h" + +int +main(void) +{ + struct lgtd_lifx_packet_tags pkt = { .tags = 0x2a }; + + lgtd_lifx_wire_encode_tags(&pkt); + + if (pkt.tags != le64toh(0x2a)) { + errx( + 1, "got tags = %#jx (expected 0x2a)", + (uintmax_t)le64toh(pkt.tags) + ); + } + + return 0; +} diff --git a/tests/lifx/wire_proto/test_wire_proto_encode_waveform.c b/tests/lifx/wire_proto/test_wire_proto_encode_waveform.c new file mode 100644 index 0000000..495ac89 --- /dev/null +++ b/tests/lifx/wire_proto/test_wire_proto_encode_waveform.c @@ -0,0 +1,53 @@ +#include + +#include "wire_proto.c" +#include "mock_daemon.h" +#include "mock_gateway.h" +#include "mock_log.h" + +int +main(void) +{ + struct lgtd_lifx_packet_waveform pkt = { + .hue = 42, + .saturation = 10000, + .brightness = 20000, + .kelvin = 4500, + .period = 200, + .cycles = 10., + .skew_ratio = 5000 + }; + + lgtd_lifx_wire_encode_waveform(&pkt); + + int hue = le16toh(pkt.hue); + int saturation = le16toh(pkt.saturation); + int brightness = le16toh(pkt.brightness); + int kelvin = le16toh(pkt.kelvin); + int period = le32toh(pkt.period); + float cycles = lgtd_lifx_wire_lefloattoh(pkt.cycles); + int skew_ratio = le16toh(pkt.skew_ratio); + if (hue != 42) { + errx(1, "got hue = %d (expected 42)", hue); + } + if (saturation != 10000) { + errx(1, "got saturation = %d (expected 10000)", saturation); + } + if (brightness != 20000) { + errx(1, "got brightness = %d (expected 20000)", brightness); + } + if (kelvin != 4500) { + errx(1, "got kelvin = %d (expected 4500)", kelvin); + } + if (period != 200) { + errx(1, "got period = %d (expected 200)", period); + } + if (cycles != 10.) { + errx(1, "got cycles = %f (expected 10)", cycles); + } + if (skew_ratio != 5000) { + errx(1, "got skew_ratio = %d (expected 5000)", skew_ratio); + } + + return 0; +} From 76748dc6832bf088058d455c08da7ffe9c3d069a Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Tue, 17 Nov 2015 02:05:58 -0800 Subject: [PATCH 163/181] Added tag 1.1.1 for changeset c3284cd6d1af --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index dbd43f4..a3a401f 100644 --- a/.hgtags +++ b/.hgtags @@ -6,3 +6,4 @@ 31b8ef65d3d239d7e94417de50b4b7e1c6c0a624 1.0.0 1f7c28381493a9e0a24bf5b918d3772241d15587 1.0.1 39e9e2070a25b17fac56ebc61338024b20538cc4 1.1.0 +c3284cd6d1af24bff19327344e89032967ce4ad2 1.1.1 From dfc831a74a17bdd62ebdd1208030f5a6fa3f6133 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 30 Nov 2015 01:14:18 -0800 Subject: [PATCH 164/181] Bump the bulb timeout from 3s to 20s --- lifx/discovery.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lifx/discovery.h b/lifx/discovery.h index 43cac29..bd15eb9 100644 --- a/lifx/discovery.h +++ b/lifx/discovery.h @@ -19,7 +19,7 @@ enum lgtd_lifx_discovery_constants { LGTD_LIFX_DISCOVERY_WATCHDOG_INTERVAL_MSECS = 500, - LGTD_LIFX_DISCOVERY_DEVICE_TIMEOUT_MSECS = 3000, + LGTD_LIFX_DISCOVERY_DEVICE_TIMEOUT_MSECS = 20000, LGTD_LIFX_DISCOVERY_DEVICE_FORCE_REFRESH_MSECS = 2000, LGTD_LIFX_DISCOVERY_ACTIVE_DISCOVERY_INTERVAL_MSECS = 2000, LGTD_LIFX_DISCOVERY_PASSIVE_DISCOVERY_INTERVAL_MSECS = 10000, From c851d6a7a44ad6b008ba4fb26d34236fc64dd7ad Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 30 Nov 2015 01:14:18 -0800 Subject: [PATCH 165/181] Improve LIFX traffic logging --- lifx/wire_proto.c | 240 ++++++++++++++++++++++++++++++++++- lifx/wire_proto.h | 56 +++++++- tests/lifx/mock_wire_proto.h | 234 +++++++++++++++++++++++++++++++++- 3 files changed, 518 insertions(+), 12 deletions(-) diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index 40b2d86..a36d0e8 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -90,10 +90,12 @@ lgtd_lifx_wire_enosys_packet_handler(struct lgtd_lifx_gateway *gw, bool addressable = hdr->protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE; bool tagged = hdr->protocol & LGTD_LIFX_PROTOCOL_TAGGED; unsigned int protocol = hdr->protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; + char target[LGTD_LIFX_ADDR_STRLEN]; + LGTD_LIFX_WIRE_PRINT_TARGET(hdr, target); lgtd_info( "%s <-- %s - (Unimplemented, header info: " - "addressable=%d, tagged=%d, protocol=%d)", - pkt_info->name, gw->peeraddr, addressable, tagged, protocol + "addressable=%d, tagged=%d, protocol=%d, target=%s", + pkt_info->name, gw->peeraddr, addressable, tagged, protocol, target ); } @@ -354,6 +356,21 @@ lgtd_lifx_wire_load_packet_info_map(void) .name = "RESET_SWITCH_STATE", .type = LGTD_LIFX_RESET_SWITCH_STATE }, + { + UNIMPLEMENTED, + .name = "GET_DUMMY_PAYLOAD", + .type = LGTD_LIFX_GET_DUMMY_PAYLOAD + }, + { + UNIMPLEMENTED, + .name = "SET_DUMMY_PAYLOAD", + .type = LGTD_LIFX_SET_DUMMY_PAYLOAD + }, + { + UNIMPLEMENTED, + .name = "STATE_DUMMY_PAYLOAD", + .type = LGTD_LIFX_STATE_DUMMY_PAYLOAD + }, { UNIMPLEMENTED, .name = "GET_BULB_LABEL", @@ -384,11 +401,91 @@ lgtd_lifx_wire_load_packet_info_map(void) .name = "DISABLE_FACTORY_TEST_MODE", .type = LGTD_LIFX_DISABLE_FACTORY_TEST_MODE }, + { + UNIMPLEMENTED, + .name = "STATE_FACTORY_TEST_MODE", + .type = LGTD_LIFX_STATE_FACTORY_TEST_MODE + }, + { + UNIMPLEMENTED, + .name = "STATE_SITE", + .type = LGTD_LIFX_STATE_SITE + }, + { + UNIMPLEMENTED, + .name = "STATE_REBOOT", + .type = LGTD_LIFX_STATE_REBOOT + }, + { + UNIMPLEMENTED, + .name = "SET_PAN_GATEWAY", + .type = LGTD_LIFX_SET_PAN_GATEWAY + }, { UNIMPLEMENTED, .name = "ACK", .type = LGTD_LIFX_ACK }, + { + UNIMPLEMENTED, + .name = "SET_FACTORY_RESET", + .type = LGTD_LIFX_SET_FACTORY_RESET + }, + { + UNIMPLEMENTED, + .name = "STATE_FACTORY_RESET", + .type = LGTD_LIFX_STATE_FACTORY_RESET + }, + { + UNIMPLEMENTED, + .name = "GET_LOCATION", + .type = LGTD_LIFX_GET_LOCATION + }, + { + UNIMPLEMENTED, + .name = "SET_LOCATION", + .type = LGTD_LIFX_SET_LOCATION + }, + { + UNIMPLEMENTED, + .name = "STATE_LOCATION", + .type = LGTD_LIFX_STATE_LOCATION + }, + { + UNIMPLEMENTED, + .name = "GET_GROUP", + .type = LGTD_LIFX_GET_GROUP + }, + { + UNIMPLEMENTED, + .name = "SET_GROUP", + .type = LGTD_LIFX_SET_GROUP + }, + { + UNIMPLEMENTED, + .name = "STATE_GROUP", + .type = LGTD_LIFX_STATE_GROUP + }, + { + UNIMPLEMENTED, + .name = "GET_OWNER", + .type = LGTD_LIFX_GET_OWNER + }, + { + UNIMPLEMENTED, + .name = "SET_OWNER", + .type = LGTD_LIFX_SET_OWNER + }, + { + UNIMPLEMENTED, + .name = "STATE_OWNER", + .type = LGTD_LIFX_STATE_OWNER + }, + { + UNIMPLEMENTED, + .name = "GET_FACTORY_TEST_MODE", + .type = LGTD_LIFX_GET_FACTORY_TEST_MODE + }, { UNIMPLEMENTED, .name = "ECHO_REQUEST", @@ -409,6 +506,126 @@ lgtd_lifx_wire_load_packet_info_map(void) .name = "SET_DIM_RELATIVE", .type = LGTD_LIFX_SET_DIM_RELATIVE }, + { + UNIMPLEMENTED, + .name = "SET_RGBW", + .type = LGTD_LIFX_SET_RGBW + }, + { + UNIMPLEMENTED, + .name = "GET_RAIL_VOLTAGE", + .type = LGTD_LIFX_GET_RAIL_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "STATE_RAIL_VOLTAGE", + .type = LGTD_LIFX_STATE_RAIL_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "GET_TEMPERATURE", + .type = LGTD_LIFX_GET_TEMPERATURE + }, + { + UNIMPLEMENTED, + .name = "STATE_TEMPERATURE", + .type = LGTD_LIFX_STATE_TEMPERATURE + }, + { + UNIMPLEMENTED, + .name = "SET_CALIBRATION_COEFFICIENTS", + .type = LGTD_LIFX_SET_CALIBRATION_COEFFICIENTS + }, + { + UNIMPLEMENTED, + .name = "SET_SIMPLE_EVENT", + .type = LGTD_LIFX_SET_SIMPLE_EVENT + }, + { + UNIMPLEMENTED, + .name = "GET_SIMPLE_EVENT", + .type = LGTD_LIFX_GET_SIMPLE_EVENT + }, + { + UNIMPLEMENTED, + .name = "STATE_SIMPLE_EVENT", + .type = LGTD_LIFX_STATE_SIMPLE_EVENT + }, + { + UNIMPLEMENTED, + .name = "GET_POWER", + .type = LGTD_LIFX_GET_POWER + }, + { + UNIMPLEMENTED, + .name = "SET_POWER", + .type = LGTD_LIFX_SET_POWER + }, + { + UNIMPLEMENTED, + .name = "STATE_POWER", + .type = LGTD_LIFX_STATE_POWER + }, + { + UNIMPLEMENTED, + .name = "SET_WAVEFORM_OPTIONAL", + .type = LGTD_LIFX_SET_WAVEFORM_OPTIONAL + }, + { + UNIMPLEMENTED, + .name = "CONNECT_PLAIN", + .type = LGTD_LIFX_CONNECT_PLAIN + }, + { + UNIMPLEMENTED, + .name = "CONNECT_KEY", + .type = LGTD_LIFX_CONNECT_KEY + }, + { + UNIMPLEMENTED, + .name = "STATE_CONNECT", + .type = LGTD_LIFX_STATE_CONNECT + }, + { + UNIMPLEMENTED, + .name = "GET_AUTH_KEY", + .type = LGTD_LIFX_GET_AUTH_KEY + }, + { + UNIMPLEMENTED, + .name = "SET_AUTH_KEY", + .type = LGTD_LIFX_SET_AUTH_KEY + }, + { + UNIMPLEMENTED, + .name = "STATE_AUTH_KEY", + .type = LGTD_LIFX_STATE_AUTH_KEY + }, + { + UNIMPLEMENTED, + .name = "SET_KEEP_ALIVE", + .type = LGTD_LIFX_SET_KEEP_ALIVE + }, + { + UNIMPLEMENTED, + .name = "STATE_KEEP_ALIVE", + .type = LGTD_LIFX_STATE_KEEP_ALIVE + }, + { + UNIMPLEMENTED, + .name = "SET_HOST", + .type = LGTD_LIFX_SET_HOST + }, + { + UNIMPLEMENTED, + .name = "GET_HOST", + .type = LGTD_LIFX_GET_HOST + }, + { + UNIMPLEMENTED, + .name = "STATE_HOST", + .type = LGTD_LIFX_STATE_HOST + }, { UNIMPLEMENTED, .name = "GET_WIFI_STATE", @@ -436,8 +653,23 @@ lgtd_lifx_wire_load_packet_info_map(void) }, { UNIMPLEMENTED, - .name = "ACCESS_POINT", - .type = LGTD_LIFX_ACCESS_POINT + .name = "STATE_ACCESS_POINTS", + .type = LGTD_LIFX_STATE_ACCESS_POINTS + }, + { + UNIMPLEMENTED, + .name = "GET_ACCESS_POINT", + .type = LGTD_LIFX_GET_ACCESS_POINT + }, + { + UNIMPLEMENTED, + .name = "STATE_ACCESS_POINT", + .type = LGTD_LIFX_STATE_ACCESS_POINT + }, + { + UNIMPLEMENTED, + .name = "SET_ACCESS_POINT_BROADCAST", + .type = LGTD_LIFX_SET_ACCESS_POINT_BROADCAST }, { UNIMPLEMENTED, diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index cf87740..d1248cc 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -97,6 +97,14 @@ struct lgtd_lifx_packet_header { uint8_t reserved[2]; }; +#define LGTD_LIFX_WIRE_PRINT_TARGET(hdr, buf) do { \ + if ((hdr)->protocol & LGTD_LIFX_PROTOCOL_TAGGED) { \ + snprintf((buf), sizeof((buf)), "%#jx", (uintmax_t)(hdr)->target.tags); \ + } else { \ + LGTD_IEEE8023MACTOA((hdr)->target.device_addr, (buf)); \ + } \ +} while (0) + enum { LGTD_LIFX_PACKET_HEADER_SIZE = sizeof(struct lgtd_lifx_packet_header) }; enum lgtd_lifx_protocol { @@ -123,7 +131,7 @@ enum lgtd_lifx_flags { // headers: enum { LGTD_LIFX_MAX_PACKET_SIZE = 4096 }; -enum lgtd_lifx_packet_type { +enum lgtd_lifx_packet_type { // FIXME: normalize and prefix everything correctly // Device LGTD_LIFX_SET_SITE = 0x01, LGTD_LIFX_GET_PAN_GATEWAY = 0x02, @@ -133,6 +141,9 @@ enum lgtd_lifx_packet_type { LGTD_LIFX_TIME_STATE = 0x06, LGTD_LIFX_GET_RESET_SWITCH_STATE = 0x07, LGTD_LIFX_RESET_SWITCH_STATE = 0x08, + LGTD_LIFX_GET_DUMMY_PAYLOAD = 0x09, + LGTD_LIFX_SET_DUMMY_PAYLOAD = 0x0a, + LGTD_LIFX_STATE_DUMMY_PAYLOAD = 0x0b, LGTD_LIFX_GET_MESH_INFO = 0x0c, LGTD_LIFX_MESH_INFO = 0x0d, LGTD_LIFX_GET_MESH_FIRMWARE = 0x0e, @@ -162,11 +173,23 @@ enum lgtd_lifx_packet_type { LGTD_LIFX_REBOOT = 0x26, LGTD_LIFX_SET_FACTORY_TEST_MODE = 0x27, LGTD_LIFX_DISABLE_FACTORY_TEST_MODE = 0x28, + LGTD_LIFX_STATE_FACTORY_TEST_MODE = 0x29, + LGTD_LIFX_STATE_SITE = 0x2a, + LGTD_LIFX_STATE_REBOOT = 0x2b, + LGTD_LIFX_SET_PAN_GATEWAY = 0x2c, LGTD_LIFX_ACK = 0x2d, - LGTD_LIFX_GET_LOCATION = 0x30, // I wonder what 0x31 and 0x34 are... + LGTD_LIFX_SET_FACTORY_RESET = 0x2e, + LGTD_LIFX_STATE_FACTORY_RESET = 0x2f, + LGTD_LIFX_GET_LOCATION = 0x30, + LGTD_LIFX_SET_LOCATION = 0x31, LGTD_LIFX_STATE_LOCATION = 0x32, LGTD_LIFX_GET_GROUP = 0x33, // TODO: replace GET/SET_TAG_LABELS ? + LGTD_LIFX_SET_GROUP = 0x34, LGTD_LIFX_STATE_GROUP = 0x35, + LGTD_LIFX_GET_OWNER = 0x36, + LGTD_LIFX_SET_OWNER = 0x37, + LGTD_LIFX_STATE_OWNER = 0x38, + LGTD_LIFX_GET_FACTORY_TEST_MODE = 0x39, LGTD_LIFX_ECHO_REQUEST = 0x3a, LGTD_LIFX_ECHO_RESPONSE = 0x3b, // Light @@ -175,21 +198,42 @@ enum lgtd_lifx_packet_type { LGTD_LIFX_SET_WAVEFORM = 0x67, LGTD_LIFX_SET_DIM_ABSOLUTE = 0x68, LGTD_LIFX_SET_DIM_RELATIVE = 0x69, + LGTD_LIFX_SET_RGBW = 0x6a, LGTD_LIFX_LIGHT_STATUS = 0x6b, + LGTD_LIFX_GET_RAIL_VOLTAGE = 0x6c, + LGTD_LIFX_STATE_RAIL_VOLTAGE = 0x6d, + LGTD_LIFX_GET_TEMPERATURE = 0x6e, + LGTD_LIFX_STATE_TEMPERATURE = 0x6f, + LGTD_LIFX_SET_CALIBRATION_COEFFICIENTS = 0x70, + LGTD_LIFX_SET_SIMPLE_EVENT = 0x71, + LGTD_LIFX_GET_SIMPLE_EVENT = 0x72, + LGTD_LIFX_STATE_SIMPLE_EVENT = 0x73, + LGTD_LIFX_GET_POWER = 0x74, + LGTD_LIFX_SET_POWER = 0x75, + LGTD_LIFX_STATE_POWER = 0x76, + LGTD_LIFX_SET_WAVEFORM_OPTIONAL = 0x77, // Wan LGTD_LIFX_CONNECT_PLAIN = 0xc9, LGTD_LIFX_CONNECT_KEY = 0xca, LGTD_LIFX_STATE_CONNECT = 0xcb, - LGTD_LIFX_SUB = 0xcc, - LGTD_LIFX_UNSUB = 0xcd, - LGTD_LIFX_STATE_SUB = 0xcd, + LGTD_LIFX_GET_AUTH_KEY = 0xcc, + LGTD_LIFX_SET_AUTH_KEY = 0xcd, + LGTD_LIFX_STATE_AUTH_KEY = 0xce, + LGTD_LIFX_SET_KEEP_ALIVE = 0xcf, + LGTD_LIFX_STATE_KEEP_ALIVE = 0xd0, + LGTD_LIFX_SET_HOST = 0xd1, + LGTD_LIFX_GET_HOST = 0xd2, + LGTD_LIFX_STATE_HOST = 0xd3, // Wifi LGTD_LIFX_GET_WIFI_STATE = 0x12d, LGTD_LIFX_SET_WIFI_STATE = 0x12e, LGTD_LIFX_WIFI_STATE = 0x12f, LGTD_LIFX_GET_ACCESS_POINTS = 0x130, LGTD_LIFX_SET_ACCESS_POINTS = 0x131, - LGTD_LIFX_ACCESS_POINT = 0x132, + LGTD_LIFX_STATE_ACCESS_POINTS = 0x132, + LGTD_LIFX_GET_ACCESS_POINT = 0x133, + LGTD_LIFX_STATE_ACCESS_POINT = 0x134, + LGTD_LIFX_SET_ACCESS_POINT_BROADCAST = 0x135, // Sensor LGTD_LIFX_GET_AMBIENT_LIGHT = 0x191, LGTD_LIFX_STATE_AMBIENT_LIGHT = 0x192, diff --git a/tests/lifx/mock_wire_proto.h b/tests/lifx/mock_wire_proto.h index a0da12b..018e291 100644 --- a/tests/lifx/mock_wire_proto.h +++ b/tests/lifx/mock_wire_proto.h @@ -238,6 +238,21 @@ lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type packet_type) .name = "RESET_SWITCH_STATE", .type = LGTD_LIFX_RESET_SWITCH_STATE }, + { + UNIMPLEMENTED, + .name = "GET_DUMMY_PAYLOAD", + .type = LGTD_LIFX_GET_DUMMY_PAYLOAD + }, + { + UNIMPLEMENTED, + .name = "SET_DUMMY_PAYLOAD", + .type = LGTD_LIFX_SET_DUMMY_PAYLOAD + }, + { + UNIMPLEMENTED, + .name = "STATE_DUMMY_PAYLOAD", + .type = LGTD_LIFX_STATE_DUMMY_PAYLOAD + }, { UNIMPLEMENTED, .name = "GET_BULB_LABEL", @@ -268,11 +283,91 @@ lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type packet_type) .name = "DISABLE_FACTORY_TEST_MODE", .type = LGTD_LIFX_DISABLE_FACTORY_TEST_MODE }, + { + UNIMPLEMENTED, + .name = "STATE_FACTORY_TEST_MODE", + .type = LGTD_LIFX_STATE_FACTORY_TEST_MODE + }, + { + UNIMPLEMENTED, + .name = "STATE_SITE", + .type = LGTD_LIFX_STATE_SITE + }, + { + UNIMPLEMENTED, + .name = "STATE_REBOOT", + .type = LGTD_LIFX_STATE_REBOOT + }, + { + UNIMPLEMENTED, + .name = "SET_PAN_GATEWAY", + .type = LGTD_LIFX_SET_PAN_GATEWAY + }, { UNIMPLEMENTED, .name = "ACK", .type = LGTD_LIFX_ACK }, + { + UNIMPLEMENTED, + .name = "SET_FACTORY_RESET", + .type = LGTD_LIFX_SET_FACTORY_RESET + }, + { + UNIMPLEMENTED, + .name = "STATE_FACTORY_RESET", + .type = LGTD_LIFX_STATE_FACTORY_RESET + }, + { + UNIMPLEMENTED, + .name = "GET_LOCATION", + .type = LGTD_LIFX_GET_LOCATION + }, + { + UNIMPLEMENTED, + .name = "SET_LOCATION", + .type = LGTD_LIFX_SET_LOCATION + }, + { + UNIMPLEMENTED, + .name = "STATE_LOCATION", + .type = LGTD_LIFX_STATE_LOCATION + }, + { + UNIMPLEMENTED, + .name = "GET_GROUP", + .type = LGTD_LIFX_GET_GROUP + }, + { + UNIMPLEMENTED, + .name = "SET_GROUP", + .type = LGTD_LIFX_SET_GROUP + }, + { + UNIMPLEMENTED, + .name = "STATE_GROUP", + .type = LGTD_LIFX_STATE_GROUP + }, + { + UNIMPLEMENTED, + .name = "GET_OWNER", + .type = LGTD_LIFX_GET_OWNER + }, + { + UNIMPLEMENTED, + .name = "SET_OWNER", + .type = LGTD_LIFX_SET_OWNER + }, + { + UNIMPLEMENTED, + .name = "STATE_OWNER", + .type = LGTD_LIFX_STATE_OWNER + }, + { + UNIMPLEMENTED, + .name = "GET_FACTORY_TEST_MODE", + .type = LGTD_LIFX_GET_FACTORY_TEST_MODE + }, { UNIMPLEMENTED, .name = "ECHO_REQUEST", @@ -293,6 +388,126 @@ lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type packet_type) .name = "SET_DIM_RELATIVE", .type = LGTD_LIFX_SET_DIM_RELATIVE }, + { + UNIMPLEMENTED, + .name = "SET_RGBW", + .type = LGTD_LIFX_SET_RGBW + }, + { + UNIMPLEMENTED, + .name = "GET_RAIL_VOLTAGE", + .type = LGTD_LIFX_GET_RAIL_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "STATE_RAIL_VOLTAGE", + .type = LGTD_LIFX_STATE_RAIL_VOLTAGE + }, + { + UNIMPLEMENTED, + .name = "GET_TEMPERATURE", + .type = LGTD_LIFX_GET_TEMPERATURE + }, + { + UNIMPLEMENTED, + .name = "STATE_TEMPERATURE", + .type = LGTD_LIFX_STATE_TEMPERATURE + }, + { + UNIMPLEMENTED, + .name = "SET_CALIBRATION_COEFFICIENTS", + .type = LGTD_LIFX_SET_CALIBRATION_COEFFICIENTS + }, + { + UNIMPLEMENTED, + .name = "SET_SIMPLE_EVENT", + .type = LGTD_LIFX_SET_SIMPLE_EVENT + }, + { + UNIMPLEMENTED, + .name = "GET_SIMPLE_EVENT", + .type = LGTD_LIFX_GET_SIMPLE_EVENT + }, + { + UNIMPLEMENTED, + .name = "STATE_SIMPLE_EVENT", + .type = LGTD_LIFX_STATE_SIMPLE_EVENT + }, + { + UNIMPLEMENTED, + .name = "GET_POWER", + .type = LGTD_LIFX_GET_POWER + }, + { + UNIMPLEMENTED, + .name = "SET_POWER", + .type = LGTD_LIFX_SET_POWER + }, + { + UNIMPLEMENTED, + .name = "STATE_POWER", + .type = LGTD_LIFX_STATE_POWER + }, + { + UNIMPLEMENTED, + .name = "SET_WAVEFORM_OPTIONAL", + .type = LGTD_LIFX_SET_WAVEFORM_OPTIONAL + }, + { + UNIMPLEMENTED, + .name = "CONNECT_PLAIN", + .type = LGTD_LIFX_CONNECT_PLAIN + }, + { + UNIMPLEMENTED, + .name = "CONNECT_KEY", + .type = LGTD_LIFX_CONNECT_KEY + }, + { + UNIMPLEMENTED, + .name = "STATE_CONNECT", + .type = LGTD_LIFX_STATE_CONNECT + }, + { + UNIMPLEMENTED, + .name = "GET_AUTH_KEY", + .type = LGTD_LIFX_GET_AUTH_KEY + }, + { + UNIMPLEMENTED, + .name = "SET_AUTH_KEY", + .type = LGTD_LIFX_SET_AUTH_KEY + }, + { + UNIMPLEMENTED, + .name = "STATE_AUTH_KEY", + .type = LGTD_LIFX_STATE_AUTH_KEY + }, + { + UNIMPLEMENTED, + .name = "SET_KEEP_ALIVE", + .type = LGTD_LIFX_SET_KEEP_ALIVE + }, + { + UNIMPLEMENTED, + .name = "STATE_KEEP_ALIVE", + .type = LGTD_LIFX_STATE_KEEP_ALIVE + }, + { + UNIMPLEMENTED, + .name = "SET_HOST", + .type = LGTD_LIFX_SET_HOST + }, + { + UNIMPLEMENTED, + .name = "GET_HOST", + .type = LGTD_LIFX_GET_HOST + }, + { + UNIMPLEMENTED, + .name = "STATE_HOST", + .type = LGTD_LIFX_STATE_HOST + }, { UNIMPLEMENTED, .name = "GET_WIFI_STATE", @@ -320,8 +535,23 @@ lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type packet_type) }, { UNIMPLEMENTED, - .name = "ACCESS_POINT", - .type = LGTD_LIFX_ACCESS_POINT + .name = "STATE_ACCESS_POINTS", + .type = LGTD_LIFX_STATE_ACCESS_POINTS + }, + { + UNIMPLEMENTED, + .name = "GET_ACCESS_POINT", + .type = LGTD_LIFX_GET_ACCESS_POINT + }, + { + UNIMPLEMENTED, + .name = "STATE_ACCESS_POINT", + .type = LGTD_LIFX_STATE_ACCESS_POINT + }, + { + UNIMPLEMENTED, + .name = "SET_ACCESS_POINT_BROADCAST", + .type = LGTD_LIFX_SET_ACCESS_POINT_BROADCAST }, { UNIMPLEMENTED, From 978c99ceca911d87ea253fcd122e68c01581e10b Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 30 Nov 2015 01:14:25 -0800 Subject: [PATCH 166/181] Fix LIFX v2 protocol handling - properly set res required; - properly listen on each gateway's socket; - improved traffic logging. --- CMakeLists.txt | 2 +- core/log.c | 2 + lifx/broadcast.c | 110 +--------------- lifx/gateway.c | 119 ++++++++++++------ lifx/gateway.h | 10 +- lifx/wire_proto.c | 92 +++++++++++++- lifx/wire_proto.h | 24 +++- .../router/test_router_send_to_broadcast.c | 3 +- .../core/router/test_router_send_to_device.c | 3 +- tests/core/router/test_router_send_to_label.c | 4 +- tests/core/router/test_router_send_to_tag.c | 3 +- tests/core/tests_utils.h | 2 +- .../gateway/test_gateway_enqueue_packet.c | 6 +- .../test_gateway_enqueue_packet_ring_full.c | 6 +- ...t_gateway_enqueue_packet_ring_wraparound.c | 6 +- .../gateway/test_gateway_write_callback.c | 4 +- ...way_write_callback_clears_ring_full_flag.c | 4 +- ...teway_write_callback_last_packet_on_ring.c | 4 +- ...est_gateway_write_callback_partial_write.c | 4 +- ...t_gateway_write_callback_ring_wraparound.c | 4 +- tests/lifx/mock_gateway.h | 20 +++ tests/lifx/mock_wire_proto.h | 23 ++++ .../test_wire_proto_float_endian_conversion.c | 4 +- 23 files changed, 281 insertions(+), 178 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 048dd0e..6d5a176 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ PROJECT(LIGHTSD C) SET(CPACK_PACKAGE_VERSION_MAJOR "1") SET(CPACK_PACKAGE_VERSION_MINOR "1") -SET(CPACK_PACKAGE_VERSION_PATCH "1") +SET(CPACK_PACKAGE_VERSION_PATCH "2") SET(LIGHTSD_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") MESSAGE(STATUS "lightsd version: ${LIGHTSD_VERSION}") diff --git a/core/log.c b/core/log.c index a0010c6..1751808 100644 --- a/core/log.c +++ b/core/log.c @@ -23,6 +23,8 @@ #include #include +#include + #include "lifx/wire_proto.h" #include "stats.h" #include "console.h" diff --git a/lifx/broadcast.c b/lifx/broadcast.c index b1f30fa..cbbf030 100644 --- a/lifx/broadcast.c +++ b/lifx/broadcast.c @@ -50,104 +50,6 @@ static struct { .write_ev = NULL, }; -static bool -lgtd_lifx_broadcast_handle_read(void) -{ - assert(lgtd_lifx_broadcast_endpoint.socket != -1); - - while (true) { - struct sockaddr_storage peer; - // if we get back from recvfrom with a sockaddr_in the end of the struct - // will not be initialized and we will be comparing unintialized stuff - // in lgtd_lifx_gateway_get: - memset(&peer, 0, sizeof(peer)); - ev_socklen_t addrlen = sizeof(peer); - union { - char buf[LGTD_LIFX_MAX_PACKET_SIZE]; - struct lgtd_lifx_packet_header hdr; - } read; - int nbytes = recvfrom( - lgtd_lifx_broadcast_endpoint.socket, - read.buf, - sizeof(read.buf), - 0, - (struct sockaddr *)&peer, - &addrlen - ); - if (nbytes == -1) { - int error = EVUTIL_SOCKET_ERROR(); - if (error == EINTR) { - continue; - } - if (error == EAGAIN) { - return true; - } - lgtd_warn("can't receive broadcast packet"); - return false; - } - - lgtd_time_mono_t received_at = lgtd_time_monotonic_msecs(); - char peer_addr[INET6_ADDRSTRLEN]; - LGTD_SOCKADDRTOA((const struct sockaddr *)&peer, peer_addr); - - if (nbytes < LGTD_LIFX_PACKET_HEADER_SIZE) { - lgtd_warnx("broadcast packet too short from %s", peer_addr); - return false; - } - - lgtd_lifx_wire_decode_header(&read.hdr); - if (read.hdr.size != nbytes) { - lgtd_warnx("incomplete broadcast packet from %s", peer_addr); - return false; - } - int proto_version = read.hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; - if (proto_version != LGTD_LIFX_PROTOCOL_V1) { - lgtd_warnx( - "unsupported protocol %d from %s", - read.hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK, peer_addr - ); - } - if (read.hdr.packet_type == LGTD_LIFX_GET_PAN_GATEWAY) { - continue; - } - - const struct lgtd_lifx_packet_info *pkt_info = - lgtd_lifx_wire_get_packet_info(read.hdr.packet_type); - if (!pkt_info) { - lgtd_warnx( - "received unknown packet %#x from %s", - read.hdr.packet_type, peer_addr - ); - continue; - } - if (!(read.hdr.protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE)) { - lgtd_warnx( - "received non-addressable packet %s from %s", - pkt_info->name, peer_addr - ); - continue; - } - struct lgtd_lifx_gateway *gw; - gw = lgtd_lifx_gateway_get((struct sockaddr *)&peer, addrlen); - if (!gw && read.hdr.packet_type == LGTD_LIFX_PAN_GATEWAY) { - gw = lgtd_lifx_gateway_open( - (struct sockaddr *)&peer, addrlen, read.hdr.site, received_at - ); - if (!gw) { - lgtd_err(1, "can't allocate gateway"); - } - } - if (gw) { - void *pkt = &read.buf[LGTD_LIFX_PACKET_HEADER_SIZE]; - gw->last_pkt_at = received_at; - pkt_info->decode(pkt); - pkt_info->handle(gw, &read.hdr, pkt); - } else { - lgtd_warnx("got packet from unknown gateway %s", peer_addr); - } - } -} - static bool lgtd_lifx_broadcast_handle_write(void) { @@ -208,15 +110,11 @@ lgtd_lifx_broadcast_event_callback(evutil_socket_t socket, lgtd_warnx("timeout on the udp broadcast socket"); goto error_reset; } - if (events & EV_READ) { - if (!lgtd_lifx_broadcast_handle_read()) { - goto error_reset; - } + if ((events & EV_READ) && !lgtd_lifx_wire_handle_receive(socket, NULL)) { + goto error_reset; } - if (events & EV_WRITE) { - if (!lgtd_lifx_broadcast_handle_write()) { - goto error_reset; - } + if ((events & EV_WRITE) && !lgtd_lifx_broadcast_handle_write()) { + goto error_reset; } return; diff --git a/lifx/gateway.c b/lifx/gateway.c index f1a139b..809ea3f 100644 --- a/lifx/gateway.c +++ b/lifx/gateway.c @@ -59,12 +59,12 @@ lgtd_lifx_gateway_close(struct lgtd_lifx_gateway *gw) LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, -1); lgtd_timer_stop(gw->refresh_timer); - event_del(gw->write_ev); + event_del(gw->socket_ev); if (gw->socket != -1) { evutil_closesocket(gw->socket); LIST_REMOVE(gw, link); } - event_free(gw->write_ev); + event_free(gw->socket_ev); evbuffer_free(gw->write_buf); for (int i = 0; i != LGTD_LIFX_GATEWAY_MAX_TAGS; i++) { if (gw->tags[i]) { @@ -102,9 +102,62 @@ lgtd_lifx_gateway_remove_and_close_bulb(struct lgtd_lifx_gateway *gw, lgtd_lifx_bulb_close(bulb); } +void +lgtd_lifx_gateway_handle_packet(struct lgtd_lifx_gateway *gw, + const struct sockaddr *peer, + ev_socklen_t addrlen, + const struct lgtd_lifx_packet_info *pkt_info, + const struct lgtd_lifx_packet_header *hdr, + const void *pkt, + lgtd_time_mono_t received_at) +{ + assert(peer); + assert(addrlen); + assert(pkt_info); + assert(hdr); + assert(pkt); + assert(received_at); + + if (gw) { + assert(gw->peerlen == addrlen); + assert(!memcmp(peer, gw->peer, addrlen)); + } else { + gw = lgtd_lifx_gateway_get(peer, addrlen); + if (!gw && hdr->packet_type == LGTD_LIFX_PAN_GATEWAY) { + gw = lgtd_lifx_gateway_open(peer, addrlen, hdr->site, received_at); + if (!gw) { + lgtd_warn("can't allocate gateway"); + return; + } + } + } + + if (gw) { + // gw->last_pkt_at is used to compute timeouts based on known + // traffic only: + if (pkt_info->handle != lgtd_lifx_wire_enosys_packet_handler) { + gw->last_pkt_at = received_at; + } + pkt_info->handle(gw, hdr, pkt); + } else { + bool addressable = hdr->protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE; + bool tagged = hdr->protocol & LGTD_LIFX_PROTOCOL_TAGGED; + unsigned int protocol = hdr->protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; + char target[LGTD_LIFX_ADDR_STRLEN], peer_addr[INET6_ADDRSTRLEN]; + LGTD_LIFX_WIRE_PRINT_TARGET(hdr, target); + LGTD_SOCKADDRTOA(peer, peer_addr); + lgtd_info( + "%s <-- %s - (Packet from unknown client or gateway, header " + "info: addressable=%d, tagged=%d, protocol=%d, target=%s)", + pkt_info->name, peer_addr, addressable, tagged, protocol, target + ); + } +} + static void -lgtd_lifx_gateway_write_callback(evutil_socket_t socket, - short events, void *ctx) +lgtd_lifx_gateway_socket_event_callback(evutil_socket_t socket, + short events, + void *ctx) { (void)socket; @@ -114,11 +167,14 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, if (events & EV_TIMEOUT) { // Not sure how that could happen in UDP but eh. lgtd_warn("lost connection with gateway bulb %s", gw->peeraddr); - lgtd_lifx_gateway_close(gw); - if (!lgtd_lifx_broadcast_discovery()) { - lgtd_err(1, "can't start auto discovery"); + goto drop_gw_and_restart_discovery; + } + + if (events & EV_READ) { + bool ok = lgtd_lifx_wire_handle_receive(gw->socket, gw); + if (!ok) { + goto drop_gw_and_restart_discovery; } - return; } if (events & EV_WRITE) { @@ -129,11 +185,7 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, int nbytes = evbuffer_write_atmost(gw->write_buf, gw->socket, to_write); if (nbytes == -1 && errno != EAGAIN) { lgtd_warn("can't write to %s", gw->peeraddr); - lgtd_lifx_gateway_close(gw); - if (!lgtd_lifx_broadcast_discovery()) { - lgtd_err(1, "can't start auto discovery"); - } - return; + goto drop_gw_and_restart_discovery; } // Callbacks are called in any order, so we keep two timers to make @@ -156,9 +208,18 @@ lgtd_lifx_gateway_write_callback(evutil_socket_t socket, } if (!evbuffer_get_length(gw->write_buf)) { - event_del(gw->write_ev); + event_del(gw->socket_ev); } } + + return; + +drop_gw_and_restart_discovery: + lgtd_lifx_gateway_close(gw); + if (!lgtd_lifx_broadcast_discovery()) { + lgtd_err(1, "can't start auto discovery"); + } + return; } static bool @@ -311,15 +372,15 @@ lgtd_lifx_gateway_open(const struct sockaddr *peer, goto error_connect; } - gw->write_ev = event_new( + gw->socket_ev = event_new( lgtd_ev_base, gw->socket, - EV_WRITE|EV_PERSIST, - lgtd_lifx_gateway_write_callback, + EV_READ|EV_WRITE|EV_PERSIST, + lgtd_lifx_gateway_socket_event_callback, gw ); gw->write_buf = evbuffer_new(); - if (!gw->write_ev || !gw->write_buf) { + if (!gw->socket_ev || !gw->write_buf) { goto error_allocate; } gw->peer = malloc(addrlen); @@ -364,8 +425,8 @@ lgtd_lifx_gateway_open(const struct sockaddr *peer, error_allocate: lgtd_warn("can't allocate a new gateway bulb"); - if (gw->write_ev) { - event_free(gw->write_ev); + if (gw->socket_ev) { + event_free(gw->socket_ev); } if (gw->write_buf) { evbuffer_free(gw->write_buf); @@ -443,7 +504,7 @@ lgtd_lifx_gateway_enqueue_packet(struct lgtd_lifx_gateway *gw, if (gw->pkt_ring_head == gw->pkt_ring_tail) { gw->pkt_ring_full = true; } - event_add(gw->write_ev, NULL); + event_add(gw->socket_ev, NULL); } void @@ -689,8 +750,6 @@ lgtd_lifx_gateway_handle_tag_labels(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_tag_labels *pkt) { - assert(gw && hdr && pkt); - char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "SET_TAG_LABELS <-- %s - %s label=%.*s, tags=%jx", @@ -713,8 +772,6 @@ lgtd_lifx_gateway_handle_tags(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_tags *pkt) { - assert(gw && hdr && pkt); - char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "SET_TAGS <-- %s - %s tags=%#jx", @@ -748,8 +805,6 @@ lgtd_lifx_gateway_handle_ip_state(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_ip_state *pkt) { - assert(gw && hdr && pkt); - const char *type; enum lgtd_lifx_bulb_ips ip_id; switch (hdr->packet_type) { @@ -788,8 +843,6 @@ lgtd_lifx_gateway_handle_ip_firmware_info(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_ip_firmware_info *pkt) { - assert(gw && hdr && pkt); - const char *type; enum lgtd_lifx_bulb_ips ip_id; switch (hdr->packet_type) { @@ -830,8 +883,6 @@ lgtd_lifx_gateway_handle_product_info(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_product_info *pkt) { - assert(gw && hdr && pkt); - char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "PRODUCT_INFO <-- %s - %s " @@ -851,8 +902,6 @@ lgtd_lifx_gateway_handle_runtime_info(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_runtime_info *pkt) { - assert(gw && hdr && pkt); - char device_time[64], uptime[64], downtime[64], addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "PRODUCT_INFO <-- %s - %s time=%s, uptime=%s, downtime=%s", @@ -873,8 +922,6 @@ lgtd_lifx_gateway_handle_bulb_label(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_label *pkt) { - assert(gw && hdr && pkt); - char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "BULB_LABEL <-- %s - %s label=%.*s", @@ -892,8 +939,6 @@ lgtd_lifx_gateway_handle_ambient_light(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const struct lgtd_lifx_packet_ambient_light *pkt) { - assert(gw && hdr && pkt); - char addr[LGTD_LIFX_ADDR_STRLEN]; lgtd_debug( "AMBIENT_LIGHT <-- %s - %s ambient_light=%flx", diff --git a/lifx/gateway.h b/lifx/gateway.h index 5337c1c..391cb1d 100644 --- a/lifx/gateway.h +++ b/lifx/gateway.h @@ -75,7 +75,7 @@ struct lgtd_lifx_gateway { int pkt_ring_head; int pkt_ring_tail; bool pkt_ring_full; - struct event *write_ev; + struct event *socket_ev; struct evbuffer *write_buf; bool pending_refresh_req; struct lgtd_timer *refresh_timer; @@ -126,6 +126,14 @@ int lgtd_lifx_gateway_get_tag_id(const struct lgtd_lifx_gateway *, const struct int lgtd_lifx_gateway_allocate_tag_id(struct lgtd_lifx_gateway *, int, const char *); void lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *, int); +void lgtd_lifx_gateway_handle_packet(struct lgtd_lifx_gateway *, + const struct sockaddr *, + ev_socklen_t, + const struct lgtd_lifx_packet_info *, + const struct lgtd_lifx_packet_header *, + const void *, + lgtd_time_mono_t); + void lgtd_lifx_gateway_handle_pan_gateway(struct lgtd_lifx_gateway *, const struct lgtd_lifx_packet_header *, const struct lgtd_lifx_packet_pan_gateway *); diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c index a36d0e8..4794dda 100644 --- a/lifx/wire_proto.c +++ b/lifx/wire_proto.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -78,7 +79,7 @@ lgtd_lifx_wire_null_packet_handler(struct lgtd_lifx_gateway *gw, (void)pkt; } -static void +void lgtd_lifx_wire_enosys_packet_handler(struct lgtd_lifx_gateway *gw, const struct lgtd_lifx_packet_header *hdr, const void *pkt) @@ -736,6 +737,93 @@ lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) return LGTD_LIFX_WAVEFORM_INVALID; } +bool +lgtd_lifx_wire_handle_receive(evutil_socket_t socket, + struct lgtd_lifx_gateway *gw) +{ + assert(socket != -1); + + while (true) { + struct sockaddr_storage peer; + // if we get back from recvfrom with a sockaddr_in the end of the struct + // will not be initialized and we will be comparing unintialized stuff + // in lgtd_lifx_gateway_get: + memset(&peer, 0, sizeof(peer)); + ev_socklen_t addrlen = sizeof(peer); + union { + char buf[LGTD_LIFX_MAX_PACKET_SIZE]; + struct lgtd_lifx_packet_header hdr; + } read; + int nbytes = recvfrom( + socket, + read.buf, + sizeof(read.buf), + 0, + (struct sockaddr *)&peer, + &addrlen + ); + if (nbytes == -1) { + int error = EVUTIL_SOCKET_ERROR(); + if (error == EINTR) { + continue; + } + if (error == EAGAIN) { + return true; + } + lgtd_warn("can't receive LIFX packet"); + return false; + } + + lgtd_time_mono_t received_at = lgtd_time_monotonic_msecs(); + char peer_addr[INET6_ADDRSTRLEN]; + LGTD_SOCKADDRTOA((const struct sockaddr *)&peer, peer_addr); + + if (nbytes < LGTD_LIFX_PACKET_HEADER_SIZE) { + lgtd_warnx("broadcast packet too short from %s", peer_addr); + return false; + } + + lgtd_lifx_wire_decode_header(&read.hdr); + if (read.hdr.size != nbytes) { + lgtd_warnx("incomplete broadcast packet from %s", peer_addr); + return false; + } + int proto_version = read.hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK; + if (proto_version != LGTD_LIFX_PROTOCOL_V1) { + lgtd_warnx( + "unsupported protocol %d from %s", + read.hdr.protocol & LGTD_LIFX_PROTOCOL_VERSION_MASK, peer_addr + ); + } + if (read.hdr.packet_type == LGTD_LIFX_GET_PAN_GATEWAY) { + continue; + } + + const struct lgtd_lifx_packet_info *pkt_info = + lgtd_lifx_wire_get_packet_info(read.hdr.packet_type); + if (!pkt_info) { + lgtd_warnx( + "received unknown packet %#x from %s", + read.hdr.packet_type, peer_addr + ); + continue; + } + if (!(read.hdr.protocol & LGTD_LIFX_PROTOCOL_ADDRESSABLE)) { + lgtd_warnx( + "received non-addressable packet %s from %s", + pkt_info->name, peer_addr + ); + continue; + } + void *pkt = &read.buf[LGTD_LIFX_PACKET_HEADER_SIZE]; + pkt_info->decode(pkt); + struct sockaddr *addr = (struct sockaddr *)&peer; + lgtd_lifx_gateway_handle_packet( + gw, addr, addrlen, pkt_info, &read.hdr, pkt, received_at + ); + } +} + static void lgtd_lifx_wire_encode_header(struct lgtd_lifx_packet_header *hdr, int flags) { @@ -807,7 +895,7 @@ lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, assert(target_type == LGTD_LIFX_TARGET_ALL_DEVICES); } - int flags = LGTD_LIFX_ADDRESSABLE; + int flags = LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_RES_REQUIRED; switch (target_type) { case LGTD_LIFX_TARGET_SITE: case LGTD_LIFX_TARGET_ALL_DEVICES: diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h index d1248cc..6d55306 100644 --- a/lifx/wire_proto.h +++ b/lifx/wire_proto.h @@ -88,7 +88,12 @@ struct lgtd_lifx_packet_header { //! - res required: true when a response is required (the response type //! depends on the request type). uint8_t flags; - //! Wrap-around sequence number, LIFX internal use. + //! The sequence number allows the client to provide a unique value, which + //! will be included by the LIFX device in any message that is sent in + //! response to a message sent by the client. This allows the client to + //! distinguish between different messages sent with the same source + //! identifier in the Frame. See _ack_required_ and _res_required_ fields in + //! the Frame Address. uint8_t seqn; //! Apparently this is a unix epoch timestamp in milliseconds at which the //! payload should be run. @@ -123,8 +128,8 @@ enum lgtd_lifx_protocol { }; enum lgtd_lifx_flags { + LGTD_LIFX_FLAG_RES_REQUIRED = 1, LGTD_LIFX_FLAG_ACK_REQUIRED = 1 << 1, - LGTD_LIFX_FLAG_RES_REQUIRED = 1 }; // Let's define a maximum packet size just in case somebody sends us weird @@ -444,13 +449,20 @@ const struct lgtd_lifx_packet_info *lgtd_lifx_wire_get_packet_info(enum lgtd_lif void lgtd_lifx_wire_setup(void); +bool lgtd_lifx_wire_handle_receive(evutil_socket_t, struct lgtd_lifx_gateway *); + const struct lgtd_lifx_packet_info *lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *, - enum lgtd_lifx_target_type, - union lgtd_lifx_target, - const uint8_t *, - enum lgtd_lifx_packet_type); + enum lgtd_lifx_target_type, + union lgtd_lifx_target, + const uint8_t *, + enum lgtd_lifx_packet_type); void lgtd_lifx_wire_decode_header(struct lgtd_lifx_packet_header *); + +void lgtd_lifx_wire_enosys_packet_handler(struct lgtd_lifx_gateway *, + const struct lgtd_lifx_packet_header *, + const void *); + void lgtd_lifx_wire_decode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *); void lgtd_lifx_wire_encode_pan_gateway(struct lgtd_lifx_packet_pan_gateway *); void lgtd_lifx_wire_decode_light_status(struct lgtd_lifx_packet_light_status *); diff --git a/tests/core/router/test_router_send_to_broadcast.c b/tests/core/router/test_router_send_to_broadcast.c index 02b19d6..7591e72 100644 --- a/tests/core/router/test_router_send_to_broadcast.c +++ b/tests/core/router/test_router_send_to_broadcast.c @@ -36,7 +36,8 @@ main(void) } const struct lgtd_lifx_packet_header *hdr; hdr = lgtd_tests_gw_pkt_queue[i].hdr; - int expected_flags = LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_TAGGED; + int expected_flags = + LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_TAGGED|LGTD_LIFX_RES_REQUIRED; if (!lgtd_tests_lifx_header_has_flags(hdr, expected_flags)) { lgtd_errx(1, "packet header doesn't have the right bits set"); } diff --git a/tests/core/router/test_router_send_to_device.c b/tests/core/router/test_router_send_to_device.c index e14b6ee..ecdeb99 100644 --- a/tests/core/router/test_router_send_to_device.c +++ b/tests/core/router/test_router_send_to_device.c @@ -36,7 +36,8 @@ main(void) lgtd_errx(1, "the packet has been sent to the wrong gateway"); } - if (!lgtd_tests_lifx_header_has_flags(hdr_queued, LGTD_LIFX_ADDRESSABLE)) { + int expected_flags = LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_RES_REQUIRED; + if (!lgtd_tests_lifx_header_has_flags(hdr_queued, expected_flags)) { lgtd_errx(1, "the packet header doesn't have the right protocol flags"); } diff --git a/tests/core/router/test_router_send_to_label.c b/tests/core/router/test_router_send_to_label.c index afea40c..b77838e 100644 --- a/tests/core/router/test_router_send_to_label.c +++ b/tests/core/router/test_router_send_to_label.c @@ -39,7 +39,9 @@ main(void) const void *pkt_queued = lgtd_tests_gw_pkt_queue[0].pkt; int pkt_size = lgtd_tests_gw_pkt_queue[0].pkt_size; - if (!lgtd_tests_lifx_header_has_flags(hdr_queued, LGTD_LIFX_ADDRESSABLE)) { + + int expected_flags = LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_RES_REQUIRED; + if (!lgtd_tests_lifx_header_has_flags(hdr_queued, expected_flags)) { lgtd_errx(1, "the packet header doesn't have the right protocol flags"); } if (pkt_queued != &payload) { diff --git a/tests/core/router/test_router_send_to_tag.c b/tests/core/router/test_router_send_to_tag.c index 476b969..8121d10 100644 --- a/tests/core/router/test_router_send_to_tag.c +++ b/tests/core/router/test_router_send_to_tag.c @@ -39,7 +39,8 @@ main(void) lgtd_errx(1, "the packet has been sent to the wrong gateway"); } - int expected_flags = LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_TAGGED; + int expected_flags = + LGTD_LIFX_ADDRESSABLE|LGTD_LIFX_TAGGED|LGTD_LIFX_RES_REQUIRED; if (!lgtd_tests_lifx_header_has_flags(hdr_queued, expected_flags)) { lgtd_errx(1, "the packet header doesn't have the right protocol flags"); } diff --git a/tests/core/tests_utils.h b/tests/core/tests_utils.h index 983fc6a..4eaa316 100644 --- a/tests/core/tests_utils.h +++ b/tests/core/tests_utils.h @@ -23,7 +23,7 @@ lgtd_tests_lifx_header_has_flags(const struct lgtd_lifx_packet_header *hdr, expected_flags |= LGTD_LIFX_FLAG_ACK_REQUIRED; } if (flags & LGTD_LIFX_RES_REQUIRED) { - expected_flags |= LGTD_LIFX_FLAG_ACK_REQUIRED; + expected_flags |= LGTD_LIFX_FLAG_RES_REQUIRED; } if (hdr->flags != expected_flags) { return false; diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet.c b/tests/lifx/gateway/test_gateway_enqueue_packet.c index d4a0f2f..9fc356d 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet.c @@ -12,7 +12,7 @@ main(void) struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); - gw.write_ev = (void *)42; + gw.socket_ev = (void *)42; struct lgtd_lifx_packet_power_state pkt; pkt.power = LGTD_LIFX_POWER_ON; @@ -58,8 +58,8 @@ main(void) errx(1, "packet ring shouldn't be full"); } - if (last_event_passed_to_event_add != gw.write_ev) { - errx(1, "event_add should have been called with gw.write_ev"); + if (last_event_passed_to_event_add != gw.socket_ev) { + errx(1, "event_add should have been called with gw.socket_ev"); } return 0; diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c index 702b095..346d929 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_full.c @@ -12,7 +12,7 @@ main(void) struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); - gw.write_ev = (void *)42; + gw.socket_ev = (void *)42; struct lgtd_lifx_packet_power_state pkt; pkt.power = LGTD_LIFX_POWER_ON; @@ -62,8 +62,8 @@ main(void) errx(1, "packet ring should be full"); } - if (last_event_passed_to_event_add != gw.write_ev) { - errx(1, "event_add should have been called with gw.write_ev"); + if (last_event_passed_to_event_add != gw.socket_ev) { + errx(1, "event_add should have been called with gw.socket_ev"); } lgtd_lifx_gateway_enqueue_packet(&gw, &hdr, pkt_info, &pkt); diff --git a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c index 0a5f69a..7319dd1 100644 --- a/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_enqueue_packet_ring_wraparound.c @@ -12,7 +12,7 @@ main(void) struct lgtd_lifx_gateway gw; memset(&gw, 0, sizeof(gw)); - gw.write_ev = (void *)42; + gw.socket_ev = (void *)42; struct lgtd_lifx_packet_power_state pkt; pkt.power = LGTD_LIFX_POWER_ON; @@ -64,8 +64,8 @@ main(void) errx(1, "packet ring shouldn't be full"); } - if (last_event_passed_to_event_add != gw.write_ev) { - errx(1, "event_add should have been called with gw.write_ev"); + if (last_event_passed_to_event_add != gw.socket_ev) { + errx(1, "event_add should have been called with gw.socket_ev"); } return 0; diff --git a/tests/lifx/gateway/test_gateway_write_callback.c b/tests/lifx/gateway/test_gateway_write_callback.c index 95f2138..dff4b27 100644 --- a/tests/lifx/gateway/test_gateway_write_callback.c +++ b/tests/lifx/gateway/test_gateway_write_callback.c @@ -53,7 +53,7 @@ main(void) // fake some values: gw.socket = 25; - gw.write_ev = (void *)21; + gw.socket_ev = (void *)21; gw.write_buf = (void *)42; gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_header); @@ -62,7 +62,7 @@ main(void) gw.pkt_ring_head++; gw.pkt_ring_head++; - lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + lgtd_lifx_gateway_socket_event_callback(-1, EV_WRITE, &gw); if (gw.pkt_ring[0].size != 0 || gw.pkt_ring[0].type != 0) { errx(1, "the ring entry should have been reset"); diff --git a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c index d9af5f9..05c9a58 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c +++ b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c @@ -53,7 +53,7 @@ main(void) // fake some values: gw.socket = 25; - gw.write_ev = (void *)21; + gw.socket_ev = (void *)21; gw.write_buf = (void *)42; gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_header); @@ -61,7 +61,7 @@ main(void) gw.pkt_ring[0].type = LGTD_LIFX_SET_POWER_STATE; gw.pkt_ring_full = true; - lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + lgtd_lifx_gateway_socket_event_callback(-1, EV_WRITE, &gw); if (gw.pkt_ring[0].size != 0 || gw.pkt_ring[0].type != 0) { errx(1, "the ring entry should have been reset"); diff --git a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c index ee122a3..a5fd2a3 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c +++ b/tests/lifx/gateway/test_gateway_write_callback_last_packet_on_ring.c @@ -52,7 +52,7 @@ main(void) // fake some values: gw.socket = 25; - gw.write_ev = (void *)21; + gw.socket_ev = (void *)21; gw.write_buf = (void *)42; gw.pkt_ring[0].size += sizeof(struct lgtd_lifx_packet_header); @@ -61,7 +61,7 @@ main(void) gw.pkt_ring_head++; gw.pkt_ring_head++; - lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + lgtd_lifx_gateway_socket_event_callback(-1, EV_WRITE, &gw); if (gw.pkt_ring[0].size != 0 || gw.pkt_ring[0].type != 0) { errx(1, "the ring entry should have been reset"); diff --git a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c index 6ca6206..d3e79f6 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_partial_write.c +++ b/tests/lifx/gateway/test_gateway_write_callback_partial_write.c @@ -68,7 +68,7 @@ main(void) gw.pkt_ring_head++; gw.pkt_ring_head++; - lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + lgtd_lifx_gateway_socket_event_callback(-1, EV_WRITE, &gw); if (gw.pkt_ring[0].type != LGTD_LIFX_SET_POWER_STATE) { errx(1, "the ring entry doesn't have the right packet type"); @@ -86,7 +86,7 @@ main(void) errx(1, "event_del shouldn't have ben called"); } - lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + lgtd_lifx_gateway_socket_event_callback(-1, EV_WRITE, &gw); if (gw.pkt_ring[0].size != 0 || gw.pkt_ring[0].type != 0) { errx(1, "the ring entry should have been reset"); diff --git a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c index 8a8e9e2..a85ad45 100644 --- a/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c +++ b/tests/lifx/gateway/test_gateway_write_callback_ring_wraparound.c @@ -53,7 +53,7 @@ main(void) // fake some values: gw.socket = 25; - gw.write_ev = (void *)21; + gw.socket_ev = (void *)21; gw.write_buf = (void *)42; int pkt_ring_last_idx = LGTD_ARRAY_SIZE(gw.pkt_ring) - 1; @@ -63,7 +63,7 @@ main(void) gw.pkt_ring[pkt_ring_last_idx].size += sizeof(struct lgtd_lifx_packet_power_state); gw.pkt_ring[pkt_ring_last_idx].type = LGTD_LIFX_SET_POWER_STATE; - lgtd_lifx_gateway_write_callback(-1, EV_WRITE, &gw); + lgtd_lifx_gateway_socket_event_callback(-1, EV_WRITE, &gw); if (gw.pkt_ring[pkt_ring_last_idx].size != 0 || gw.pkt_ring[pkt_ring_last_idx].type != 0) { diff --git a/tests/lifx/mock_gateway.h b/tests/lifx/mock_gateway.h index 1103085..0d10499 100644 --- a/tests/lifx/mock_gateway.h +++ b/tests/lifx/mock_gateway.h @@ -18,6 +18,26 @@ lgtd_lifx_gateway_latency(const struct lgtd_lifx_gateway *gw) } #endif +#ifndef MOCKED_LGTD_LIFX_GATEWAY_HANDLE_PACKET +void +lgtd_lifx_gateway_handle_packet(struct lgtd_lifx_gateway *gw, + const struct sockaddr *peer, + ev_socklen_t addrlen, + const struct lgtd_lifx_packet_info *pkt_info, + const struct lgtd_lifx_packet_header *hdr, + const void *pkt, + lgtd_time_mono_t received_at) +{ + (void)gw; + (void)peer; + (void)addrlen; + (void)pkt_info; + (void)hdr; + (void)pkt; + (void)received_at; +} +#endif + #ifndef MOCKED_LIFX_GATEWAY_SEND_TO_SITE bool lgtd_lifx_gateway_send_to_site(struct lgtd_lifx_gateway *gw, diff --git a/tests/lifx/mock_wire_proto.h b/tests/lifx/mock_wire_proto.h index 018e291..6fff15f 100644 --- a/tests/lifx/mock_wire_proto.h +++ b/tests/lifx/mock_wire_proto.h @@ -19,6 +19,18 @@ lgtd_lifx_wire_waveform_string_id_to_type(const char *s, int len) } #endif +#ifndef MOCKED_LGTD_LIFX_WIRE_ENOSYS_PACKET_HANDLER +void +lgtd_lifx_wire_enosys_packet_handler(struct lgtd_lifx_gateway *gw, + const struct lgtd_lifx_packet_header *hdr, + const void *pkt) +{ + (void)gw; + (void)hdr; + (void)pkt; +} +#endif + #ifndef MOCKED_LGTD_LIFX_WIRE_NULL_PACKET_ENCODER_DECODER static void lgtd_lifx_wire_null_packet_encoder_decoder(void *pkt) @@ -575,6 +587,17 @@ lgtd_lifx_wire_get_packet_info(enum lgtd_lifx_packet_type packet_type) } #endif +#ifndef MOCKED_LGTD_LIFX_WIRE_HANDLE_RECEIVE +bool +lgtd_lifx_wire_handle_receive(evutil_socket_t socket, + struct lgtd_lifx_gateway *gw) +{ + (void)socket; + (void)gw; + return false; +} +#endif + #ifndef MOCKED_LGTD_LIFX_WIRE_SETUP_HEADER const struct lgtd_lifx_packet_info * lgtd_lifx_wire_setup_header(struct lgtd_lifx_packet_header *hdr, diff --git a/tests/lifx/wire_proto/test_wire_proto_float_endian_conversion.c b/tests/lifx/wire_proto/test_wire_proto_float_endian_conversion.c index 26e6097..d0c01ef 100644 --- a/tests/lifx/wire_proto/test_wire_proto_float_endian_conversion.c +++ b/tests/lifx/wire_proto/test_wire_proto_float_endian_conversion.c @@ -1,8 +1,10 @@ #include - #include +#include #include +#include + #include "lifx/wire_proto.h" int From 829292bbb40462985859df6349695412001897ba Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 30 Nov 2015 02:23:15 -0800 Subject: [PATCH 167/181] Update docs for 1.1.2 Thanks a lot to /u/nullcompany for his feedback about the protocol documentation: https://www.reddit.com/r/lifx/comments/3s82yy/restrict_from_the_cloud/cxfnxlf --- docs/changelog.rst | 13 +++++++++++++ docs/known-issues.rst | 11 ++++++----- docs/protocol.rst | 35 ++++++++++++++++++++++++----------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 424b40e..4559a17 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,9 +1,22 @@ Changelog ========= +1.1.2 (2015-11-30) +------------------ + +- Fix LIFX LAN protocol V2 handling (properly set RES_REQUIRED and properly + listen on each gateway's socket); +- The bulb timeout has been increased from 3 to 20s; +- Improved LIFX traffic logging. + 1.1.1 (2015-11-17) ------------------ +.. warning:: + + This release broke the compatibility with the LIFX LAN protocol "v2", please + upgrade to 1.1.2. + - Greatly improve responsiveness by setting the LIFX source identifier [#]_. - Fix parallel builds in the Debian package & fix the homebrew formulae for OS X 10.11 (El Capitan). diff --git a/docs/known-issues.rst b/docs/known-issues.rst index e5e046a..debdd01 100644 --- a/docs/known-issues.rst +++ b/docs/known-issues.rst @@ -1,12 +1,13 @@ Known issues ============ -The White 800 appears to be less reliable than the LIFX Original or Color 650. -The grouping (tagging) code of the LIFX White 800 in particular appears to be -bugged: after a tagging operation the LIFX White 800 keep saying it has no tags. +Severe issues have been seen with firmwares released with new bulbs models +during 2015. lightsd appears to make those bulbs crash [#]_ after some period of +time; original bulbs with older firmware will not have those issues. It's +interesting to note that both original and newer bulbs are served the same +traffic by lightsd which might point out regressions in LIFX's firmwares. -The Color 650 with the firmware 2.1 appears to completely hang sometimes and -you have to restart it, the official LIFX app appears to be affected too. +.. [#] Forcing you to turn them off and back on using a regular light switch. Power ON/OFF are the only commands with auto-retry, i.e: lightsd will keep sending the command to the bulb until its state changes. This is not implemented diff --git a/docs/protocol.rst b/docs/protocol.rst index 34e136f..4851c05 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -13,17 +13,30 @@ bulb(s) the operation should apply. The target argument is either a string (identifying a target as explained in the following table), or an array of strings (targets). -+-----------------------------+-----------------------------------------------+ -| ``*`` | targets all bulbs | -+-----------------------------+-----------------------------------------------+ -| ``#TagName`` | targets bulbs tagged with *TagName* | -+-----------------------------+-----------------------------------------------+ -| ``124f31a5`` | directly target the bulb with the given id | -+-----------------------------+-----------------------------------------------+ -| ``label`` | directly target the bulb with the given label | -+-----------------------------+-----------------------------------------------+ -| ``[#TagName, 123f31a5]`` | composite target (JSON array) | -+-----------------------------+-----------------------------------------------+ ++-----------------------------+------------------------------------------------+ +| ``*`` | targets all bulbs | ++-----------------------------+------------------------------------------------+ +| ``#TagName`` | targets bulbs tagged with *TagName* | ++-----------------------------+------------------------------------------------+ +| ``124f31a5`` | directly target the bulb with the given id | +| | (mac addr, see below) | ++-----------------------------+------------------------------------------------+ +| ``label`` | directly target the bulb with the given Label | ++-----------------------------+------------------------------------------------+ +| ``[#TagName, 123f31a5]`` | composite target (JSON array) | ++-----------------------------+------------------------------------------------+ + +The mac address (id) of each bulb can be found with get_light_state_ under the +``_lifx`` map, e.g: + +:: + + "_lifx": { + "addr": "d0:73:d5:02:e5:30", + "gateway": { + […] + +This bulb has id ``d073d502e530``. .. note:: From 4b30e953b026d0e5e394cb23d437c02456625efa Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 30 Nov 2015 02:24:07 -0800 Subject: [PATCH 168/181] Added tag 1.1.2 for changeset a4a1ac21d226 --- .hgtags | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgtags b/.hgtags index a3a401f..c04cd3c 100644 --- a/.hgtags +++ b/.hgtags @@ -7,3 +7,4 @@ 1f7c28381493a9e0a24bf5b918d3772241d15587 1.0.1 39e9e2070a25b17fac56ebc61338024b20538cc4 1.1.0 c3284cd6d1af24bff19327344e89032967ce4ad2 1.1.1 +a4a1ac21d226ceb82e1a337123e1d5939ed116f0 1.1.2 From 065a81ed4512d34e5a3286fc9e3451de18c6ded5 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 2 Dec 2015 22:45:30 -0800 Subject: [PATCH 169/181] Remove a note that only applied to fw 1.1 of the Original from the docs --- docs/protocol.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/protocol.rst b/docs/protocol.rst index 4851c05..ef6c207 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -147,7 +147,6 @@ Available methods Notes ----- -- You cannot the set the color of the bulb while it's turned off; -- lightsd supports batch JSON-RPC requests, use them! +lightsd supports batch JSON-RPC requests, use them! .. vim: set tw=80 spelllang=en spell: From d138592950eda02b7f595ee6650309553c465e5e Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Fri, 4 Dec 2015 23:52:27 -0800 Subject: [PATCH 170/181] Move call to action at the end of the first step guide --- docs/first-steps.rst | 4 ++-- docs/protocol.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/first-steps.rst b/docs/first-steps.rst index f69b1ae..661bfcf 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -323,8 +323,6 @@ Fetch the state of all your bulbs: bulbs = {b["label"]: b for b in c.get_light_state("*")["result"]} -Check out :doc:`lightsd's protocol ` to see everything you can do. - lightsc.py also accepts an url which lets you connect to anything running lightsd, e.g: @@ -338,6 +336,8 @@ Or, for an Unix socket: lightsc.py -u unix:///path/to/lightsd/socket +Check out :doc:`lightsd's API ` to see everything you can do! + .. _lightsc.py: https://github.com/lopter/lightsd/blob/master/examples/lightsc.py .. vim: set tw=80 spelllang=en spell: diff --git a/docs/protocol.rst b/docs/protocol.rst index ef6c207..867d93e 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -147,6 +147,6 @@ Available methods Notes ----- -lightsd supports batch JSON-RPC requests, use them! +- lightsd supports batch JSON-RPC requests, use them! .. vim: set tw=80 spelllang=en spell: From 54c3ea6fee38640a5f9c1ff1fb954fb8e9515b93 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 27 Dec 2015 14:35:48 +0100 Subject: [PATCH 171/181] Docs improvements, add a contribution guide kinda doubles as a manifesto --- CONTRIBUTING.rst | 192 ++++++++++++++++++++++++++++++++++++++++++ README.rst | 5 ++ docs/first-steps.rst | 8 ++ docs/installation.rst | 8 +- docs/known-issues.rst | 29 +++++-- 5 files changed, 230 insertions(+), 12 deletions(-) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..163ca8e --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,192 @@ +Contributing to lightsd +======================= + +lightsd is open-source_ software licensed under the GPLv3_ (see `Rationale for +the GPLv3`_). This basically means that you can use lightsd free of charge, as +you see fit, for an unlimited amount of time, no ads, no evaluation period, no +activation key. + +Likewise, you can also download and consult the entire source code of lightsd. +Finally, you are welcome to modify lightsd but the GPLv3 forces you to make +those changes public if you decide to share your modified version of lightsd +with other people or companies. + +This document describes how to share your modifications directly with lightsd's +developers so that they can be incorporated, distributed and maintained with the +rest of the project. + +.. _open-source: http://opensource.org/faq#osd +.. _GPLv3: https://www.gnu.org/licenses/quick-guide-gplv3.html + +Project governance +------------------ + +lightsd is a personal project. Its primary goal is for its author_ to learn and +have fun. At the end of the day, no-one tells me what I should do and how I +should do it. I get to say the last word [#]_. + +I don't have any timeline nor any pressure and I will have no problem saying no +to any feature or contribution that doesn't fit my vision of the project or +doesn't reach the quality bar I set for myself and the project. + +Do not take a "no" personally and hold me accountable for constructive feedback. + +.. _author: mailto:Louis Opter + +.. [#] Ultimately, this is not true: the GPL license has the last word. + +Project goals +------------- + +The current goal is to be the best and most correct implementation of the `LIFX +LAN protocol`_. + +Unlike other projects around LIFX bulbs, lightsd is a background service (daemon +in the Unix terminology). It allows lightsd to act as a proxy for the bulbs and +to report or change the status of the bulbs in near real time. Those two +properties are fundamental to the project: + +- being able to work as a proxy makes lightsd easy to extend to other IoT + protocols. It also allows network isolation; +- being as real time as possible will –I hope– pave the way for new kind of + usages. For example lightsd is currently is used to pair LIFX bulbs with + motion sensors, such system requires low latency [#]_. Integrations with + video games also come to mind and may require low latency [#]_. + +lightsd took the bold choice of being implemented in pure C. C simply is the +most portable language both from a tool-chain perspective but also from a +hardware perspective: anything that can run a very stripped down version of +Linux should be able to run lightsd successfully. Portability is a goal and I +hope that it will also unlock new kind of usage & interactions. A corollary to +portability is that lightsd should have the shortest startup and discovery time +possible: lightsd might not always run as a background service. + +lightsd must work out of the box and be installable by a high school student who +discovered command line interfaces last week. When documenting something assume +readers kinda know how to copy paste commands in a terminal emulator and install +packages on their system but nothing more. Do not assume they know how to +properly administrate their system, lightsd should be an opportunity to learn +that and inspire the user to learn more. Using lightsd has to be a rewarding +experience. + +.. [#] When motion is detected you want to turn the lights immediatly not in 100 + or more milliseconds. +.. [#] This is actually `already happening`_. + +.. _already happening: https://community.lifx.com/t/lightsd-a-daemon-with-a-json-rpc-api-to-control-your-bulbs/446/41 +.. _LIFX LAN protocol: http://lan.developer.lifx.com/ + +Non-goals +--------- + +HTTP is overused, misused and is a non-goal, sorry. + +However, I'm very willing to have a lightsd client that can run inside web +browsers. I'm willing to embed a dead simple HTTP server within lightsd to serve +that web application. Communication between the web application and lightsd +cannot use HTTP, I believe this is possible using some newer Javascript +technologies; worst-case websockets may be an acceptable compromise. + +Reporting bugs +-------------- + +I can be joined via email, via IRC in `#lightsd`_ on Freenode, you can also post +in this topic_ and if you feel comfortable writing bug reports you can directly +open an issue on Github_. + +.. _#lightsd: irc://chat.freenode.net/#lightsd +.. _topic: https://community.lifx.com/t/lightsd-a-daemon-with-a-json-rpc-api-to-control-your-bulbs/446/ +.. _Github: https://github.com/lopter/lightsd/issues/new + +Submitting contributions +------------------------ + +Submitting a contribution is pretty much like reporting a bug. At this point, +I'll deal with pretty much any non f'ed-up format. What really matters to me is +the content: make sure your understand how the project is governed and what the +goals are. + +It's probably better to start a discussion with me before implementing +something. Feel free to pick an open bug and work on it, if you're not +comfortable in C I'll guide you through the whole thing. I'm ready to work with +motivated beginners, and whatever the outcome is (i.e: your contribution being +merged or not), I'll make sure it's constructive and that you learn something +useful. + +I'm trying to make this a cool project to learn programming: in the middle you +have lightsd: a traditional UNIX daemon written in C that doesn't compromise on +modernity (modular approach, evented I/O, unit-tested, fast and lightweight, +highly portable, well integrated in modern init systems). On the left you have +those bulbs that I'm sure could run cool home-brewed firmware and use some +reverse-engineering. And on the right you have this JSON-RPC API that can be +used to implement any kind of cool client. + +In other words, by simply using lightsd you are already contributing. For +example a better LIFX firmware would be an awesome contribution. A cool mobile +application would be even better. + +Coding style +------------ + +lightsd is written in C99, C11 doesn't bring much and will make us incompatible +with some platforms (e.g: gcc 4.2 and Microsoft is known to really lag behind on +C standards support). + +lightsd must work on 32/64 bits and little/big endian architectures. + +lightsd is designed to work on machines without an FPU_, do not use floats +unless there is no other choice. + +lightsd is portable and uses CMake_ to introspect the system it's being built +on. Currently supported systems are: Linux, BSD and Darwin (Mac OS X), new +features must work on all three of them. + +New code must be unit-tested, CMake is also used as a test runner. + +lightsd coding style is: + +- overall mostly `K&R`_/1TBS_; +- tabs are 4 spaces; +- C++ style comments; +- 80 columns max (kinda flexible in the headers); +- don't use typedef (rationale: reduces readability, typedefs in C are really + only useful on integers for things like ``pid_t`` where an int isn't the right + semantic or for fixed-width integers); +- no includes in the headers (rationale: avoid fucked-up circular dependencies + scenarios); +- includes order: alphabetically sorted system includes (with ``sys/`` includes + first however), then libraries includes, then local includes; +- when defining a function the return type and the function name must be on two + different lines (rationale: make searching a function definition really easy + with the ``^`` regular expression anchor). + +Overall, just be consistent with the existing coding-style, I'll setup `clang +format`_ or astyle_ when I get a chance, it should make the style a non-issue. + +.. _FPU: https://en.wikipedia.org/wiki/Floating-point_unit +.. _CMake: https://cmake.org/overview/ +.. _K&R: https://en.wikipedia.org/wiki/Indent_style#K.26R_style +.. _1TBS: https://en.wikipedia.org/wiki/Indent_style#Variant:_1TBS +.. _clang format: http://clang.llvm.org/docs/ClangFormat.html +.. _astyle: http://astyle.sourceforge.net/astyle.html + +Rationale for the GPLv3 +----------------------- + +I chose the GPLv3 license for lightsd because it's a personal project and I +don't want lightsd to be used then modified to make a new (or improve an +existing) product behind closed doors. Moreover, I work on lightsd on my free +time and I don't want my time to be used by a company for free. + +That said I'd like to make lightsd easy to integrate (i.e: without any +modification) in a closed source context. For example I'm perfectly fine if +lightsd is bundled with a mobile application as long as it's not modified and +that application stays transparent on its use of lightsd and links to lightsd's +homepage. + +In the unlikely event that lightsd gains significant adoption I want it to be +the reference and unique implementation of its own protocol but also a reference +implementation for the other protocols it implements. I hope that the GPL will +be a good incentive to achieve that goal. + +.. vim: set tw=80 spelllang=en spell: diff --git a/README.rst b/README.rst index 69a257d..0390c35 100644 --- a/README.rst +++ b/README.rst @@ -77,4 +77,9 @@ Developers Feel free to reach out via email or irc (kalessin on freenode, insist if I don't reply). As the project name implies, I'm fairly interested in other smart bulbs. +Check out the `contribution guide`_ for the vision behind the project and how to +contribute. + +.. _contribution guide: https://github.com/lopter/lightsd/blob/master/CONTRIBUTING.rst + .. vim: set tw=80 spelllang=en spell: diff --git a/docs/first-steps.rst b/docs/first-steps.rst index 661bfcf..d933716 100644 --- a/docs/first-steps.rst +++ b/docs/first-steps.rst @@ -228,6 +228,12 @@ Or, from the root of the repository: examples/toggle +Here is the source code of this example, it uses a small client —lightsc.sh— the +next section covers it: + +.. literalinclude:: ../examples/toggle + :language: sh + .. _examples: Using lightsc.sh @@ -279,6 +285,8 @@ Examples: Build a batch request manually: +.. pygmentize sucks on heredocs: + :: tee `lightsc_get_pipe` < Date: Sun, 27 Dec 2015 16:18:19 +0100 Subject: [PATCH 172/181] Keep working on the documentation --- CONTRIBUTING.rst | 2 +- README.rst | 37 ++++++++++++++++++++++++++----------- docs/changelog.rst | 2 ++ docs/developers.rst | 9 +++++---- docs/index.rst | 1 + docs/packaging.rst | 39 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 docs/packaging.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 163ca8e..80560ae 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -92,7 +92,7 @@ Reporting bugs I can be joined via email, via IRC in `#lightsd`_ on Freenode, you can also post in this topic_ and if you feel comfortable writing bug reports you can directly -open an issue on Github_. +open an issue on GitHub_. .. _#lightsd: irc://chat.freenode.net/#lightsd .. _topic: https://community.lifx.com/t/lightsd-a-daemon-with-a-json-rpc-api-to-control-your-bulbs/446/ diff --git a/README.rst b/README.rst index 0390c35..2469b22 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -lightsd, a LIFX broker -====================== +lightsd, a daemon with a JSON-RPC API to control your light bulbs +================================================================= lightsd acts a central point of control for your LIFX_ WiFi bulbs. lightsd should be a small, simple and fast daemon exposing an easy to use protocol @@ -44,7 +44,8 @@ lightsd can target single or multiple bulbs at once: - broadcast; - composite (list of targets); -lightsd works and is developed against LIFX firmwares 1.1, 1.5, 2.0 and 2.1. +lightsd works and is developed against a variety of LIFX firmwares from the +oldest ones to the newest ones. .. _JSON-RPC: http://www.jsonrpc.org/specification .. _mkfifo(1): http://www.openbsd.org/cgi-bin/man.cgi?query=mkfifo @@ -59,10 +60,9 @@ and a walk-through some interactive examples. Requirements ------------ -lightsd aims to be highly portable on any slightly POSIX system (native Windows -support has been kept in mind should be quite easy, but isn't really the focus) -and on any kind of hardware including embedded devices. Hence why lightsd is -written in C with reasonable dependencies: +lightsd aims to be highly portable on any slightly POSIX system and on any kind +of hardware including embedded devices. Hence why lightsd is written in C with +reasonable dependencies: - libevent ≥ 2.0.19 (released May 2012); - CMake ≥ 2.8.9 (released August 2012): only if you want to build lightsd from @@ -71,15 +71,30 @@ written in C with reasonable dependencies: lightsd is actively developed and tested from Arch Linux, Debian, Mac OS X, OpenWRT and OpenBSD; both for 32/64 bits and little/big endian architectures. -Developers ----------- +Native Windows support has been kept in mind and will be addressed, but isn't +really the focus. Windows build will be targeted at `Windows 10 IoT Core`_ which +is the only one that you don't have to pay for [#]_. -Feel free to reach out via email or irc (kalessin on freenode, insist if I don't -reply). As the project name implies, I'm fairly interested in other smart bulbs. +.. [#] I do need a version of ffu2img_ that actually works, because otherwise you + need Windows to install Windows 10 IoT Core; be my guest. + +.. _Windows 10 IoT Core: https://dev.windows.com/en-us/iot +.. _ffu2img: https://msdn.microsoft.com/en-us/library/windows/hardware/dn757539.aspx + +Contact +------- + +Feel free to reach out via email or irc (`#lightsd`_ on Freenode, insist if I +don't reply). As the project name implies, I'm fairly interested in other smart +bulbs. Check out the `contribution guide`_ for the vision behind the project and how to contribute. +Join the conversation on the `LIFX forum`_. + +.. _#lightsd: irc://chat.freenode.net/#lightsd .. _contribution guide: https://github.com/lopter/lightsd/blob/master/CONTRIBUTING.rst +.. _LIFX forum: https://community.lifx.com/t/lightsd-a-daemon-with-a-json-rpc-api-to-control-your-bulbs/446 .. vim: set tw=80 spelllang=en spell: diff --git a/docs/changelog.rst b/docs/changelog.rst index 4559a17..55fcabc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,8 @@ Changelog ========= +lightsd uses `semantic versionning `_. + 1.1.2 (2015-11-30) ------------------ diff --git a/docs/developers.rst b/docs/developers.rst index ea9cf92..9bffd74 100644 --- a/docs/developers.rst +++ b/docs/developers.rst @@ -3,7 +3,7 @@ Developers & companies lightsd's development takes place on github: https://github.com/lopter/lightsd. -lightsd follows semver_. +Check-out the `contribution guide`_! If you wanna get a feel of what I'm working on you can watch my patch queue: https://github.com/lopter/lightsd-mq. You will need to install Mercurial_ and @@ -14,10 +14,11 @@ Feel free to reach out via email or irc (kalessin on freenode, insist if I don't reply). As the project name implies, I'm fairly interested in other smart bulbs. If you are a company trying to use lightsd feel free to reach me out as well. lightsd is free software under the GPLv3_ but has been designed in a way -that make it usable in closed source environments. I'm not looking for a new -job, thanks. +that make it usable in closed source environments. -.. _semver: http://semver.org/ +I'm not looking for a new job, thanks. + +.. _contribution guide: https://github.com/lopter/lightsd/blob/master/CONTRIBUTING.rst .. _Mercurial: https://mercurial.selenic.com/ .. _hg-git: http://hg-git.github.io/ .. _series: https://github.com/lopter/lightsd-mq diff --git a/docs/index.rst b/docs/index.rst index f73a701..f713901 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,6 +22,7 @@ instructions: protocol known-issues developers + packaging changelog .. [#bulbs] Currently only LIFX_ WiFi smart bulbs are supported. diff --git a/docs/packaging.rst b/docs/packaging.rst new file mode 100644 index 0000000..c39f04f --- /dev/null +++ b/docs/packaging.rst @@ -0,0 +1,39 @@ +Packaging lightsd +================= + +lightsd has already been packaged for: + +- Mac OS X's Homebrew: https://github.com/lopter/homebrew-lightsd; +- Arch Linux (AUR): https://aur.archlinux.org/packages/lightsd/; +- Debian/Ubuntu: https://github.com/lopter/dpkg-lightsd; +- OpenWRT: https://github.com/lopter/openwrt-lightsd. + +Here is what you need to know to build lightsd in order to be distributed: + +- make sure CMake's build type is set to ``RELEASE``; +- lightsd needs a runtime directory, this must be configured via CMake using + ``LGTD_RUNTIME_DIRECTORY`` (e.g: ``/run/lightsd``); +- lightsd needs a lightsd user, make sure it is created when the package gets + installed and make sure the user is informed, e.g:: + + lightsd runs under the `lightsd' user and group by default; add yourself + to this group to be able to open lightsd's socket and pipe under + /run/lightsd: + + gpasswd -a $USER lightsd + + Re-open your current desktop or ssh session for the change to take effect. + Then use systemctl to start lightsd; you can start playing with lightsd + with: + + `lightsd --prefix`/share/doc/lightsd/examples/lightsc.py +- make sure lightsd is built with hardening flags; +- make sure the protocol documentation and the examples are properly shipped. + +Overall, I'm all in favor of a tight collaboration between up and downstream and +have enough experience to package lightsd myself on pretty much any operating +system; most of it will be automated with the release process down the road. +What I really need is help to get lightsd in official distribution channels and +repositories. + +.. vim: set tw=80 spelllang=en spell: From b36893e3bb0755cf832a926a07aecba0b909fa94 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 27 Dec 2015 16:20:39 +0100 Subject: [PATCH 173/181] Remove the title from the README --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index 2469b22..eff9f89 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,3 @@ -lightsd, a daemon with a JSON-RPC API to control your light bulbs -================================================================= - lightsd acts a central point of control for your LIFX_ WiFi bulbs. lightsd should be a small, simple and fast daemon exposing an easy to use protocol inspired by how musicpd_ works. From 29ef3c8af7a588c5800d0642f9f4b65b4e9d275e Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Sun, 27 Dec 2015 16:23:46 +0100 Subject: [PATCH 174/181] Put a title back in the README, minor adjustments --- README.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index eff9f89..2ffe040 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,6 @@ +lightsd, a daemon to control smart bulbs +======================================== + lightsd acts a central point of control for your LIFX_ WiFi bulbs. lightsd should be a small, simple and fast daemon exposing an easy to use protocol inspired by how musicpd_ works. @@ -51,8 +54,8 @@ Documentation ------------- lightsd is packaged for Mac OS X, Arch Linux, Debian based systems and OpenWRT. -Checkout http://lightsd.readthedocs.org/en/latest/ for installation instructions -and a walk-through some interactive examples. +Check out http://lightsd.readthedocs.org/en/latest/ for installation +instructions and a walk-through some interactive examples. Requirements ------------ @@ -61,9 +64,9 @@ lightsd aims to be highly portable on any slightly POSIX system and on any kind of hardware including embedded devices. Hence why lightsd is written in C with reasonable dependencies: -- libevent ≥ 2.0.19 (released May 2012); -- CMake ≥ 2.8.9 (released August 2012): only if you want to build lightsd from - its sources. +- libevent ≥ 2.0.19 (released in May 2012); +- CMake ≥ 2.8.9 (released in August 2012): only if you want to build lightsd + from its sources. lightsd is actively developed and tested from Arch Linux, Debian, Mac OS X, OpenWRT and OpenBSD; both for 32/64 bits and little/big endian architectures. From f029a4bace0bfe62681232ad8df667a63cfd3dd5 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 4 Jan 2016 16:01:05 +0100 Subject: [PATCH 175/181] Document semver a little bit better --- docs/changelog.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 55fcabc..92170ce 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,15 @@ Changelog ========= -lightsd uses `semantic versionning `_. +lightsd uses `semantic versionning `_, here is the summary: + +Given a version number MAJOR.MINOR.PATCH: + +- MAJOR version gets incremented when you may need to modify your lightsd + configuration to keep your setup running; +- MINOR version gets incremented when functionality or substantial improvements + are added in a backwards-compatible manner; +- PATCH version gets incremented for backwards-compatible bug fixes. 1.1.2 (2015-11-30) ------------------ From 8ffd7806138f893eb6d01ffaa6ac6314abec497f Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 4 Jan 2016 16:01:05 +0100 Subject: [PATCH 176/181] Fix lightsc.py's read loop and document it It now handles arbitrarily large and partial responses properly. --- docs/protocol.rst | 107 ++++++++++++++++++++++++++++++++++++++++++-- examples/lightsc.py | 58 +++++++++++++++++------- 2 files changed, 146 insertions(+), 19 deletions(-) diff --git a/docs/protocol.rst b/docs/protocol.rst index 867d93e..58a9698 100644 --- a/docs/protocol.rst +++ b/docs/protocol.rst @@ -1,7 +1,12 @@ The lights daemon protocol ========================== -The lightsd protocol is implemented on top of `JSON-RPC 2.0`_. +The lightsd protocol is implemented on top of `JSON-RPC 2.0`_. This section +covers the available methods and how to target bulbs. + +Since lightsd implements JSON-RPC without any kind of framing like it usually is +the case (using HTTP), this section also explains how to implement your own +lightsd client in `Writing a client for lightsd`_. .. _JSON-RPC 2.0: http://www.jsonrpc.org/specification @@ -19,7 +24,7 @@ strings (targets). | ``#TagName`` | targets bulbs tagged with *TagName* | +-----------------------------+------------------------------------------------+ | ``124f31a5`` | directly target the bulb with the given id | -| | (mac addr, see below) | +| | (that's the bulb mac address, see below) | +-----------------------------+------------------------------------------------+ | ``label`` | directly target the bulb with the given Label | +-----------------------------+------------------------------------------------+ @@ -144,9 +149,105 @@ Available methods untag("#myexistingtag", "myexistingtag") +Writing a client for lightsd +---------------------------- + +lightsd does JSON-RPC directly over TCP, requests and responses aren't framed in +any way like it is usually done by using HTTP. + +This means that you will very likely need to write a JSON-RPC client +specifically for lightsd. You're actually encouraged to do that as lightsd will +probably augment JSON-RPC via lightsd specific `JSON-RPC extensions`_ in the +future. + +.. _JSON-RPC extensions: http://www.jsonrpc.org/specification#extensions + +JSON-RPC over TCP +~~~~~~~~~~~~~~~~~ + +JSON-RPC works in a request/response fashion: the socket (network connection) is +never used in a full-duplex fashion (data never flows in both direction at the +same time): + +#. Write (send) a request on the socket; +#. Read (receive) the response on the socket; +#. Repeat. + +Writing the request is easy: do successive write (send) calls until you have +successfully sent the whole request. The next step (reading/receiving) is a bit +more complex. And that said, if the response isn't useful to you, you can ask +lightsd to omit it by turning your request into a `notification`_: if you remove +the JSON-RPC id, then you can just send your requests (now notifications) on the +socket in a fire and forget fashion. + +.. _notification: http://www.jsonrpc.org/specification#notification + +Otherwise to successfully read and decode JSON-RPC over TCP you will need to +implement your own read loop, the algorithm follows. It focuses on the low-level +details, adapt it for the language and platform you are using: + +#. Prepare an empty buffer that you can grow, we will accumulate received data + in it; +#. Start an infinite loop and start a read (receive) for a chunk of data (e.g: + 4KiB), accumulate the received data in the previous buffer, then try to + interpret the data as JSON: + + - if valid JSON can be decoded then break out of the loop; + - else data is missing and continue the loop; +#. Decode the JSON data. + +Here is a complete Python 3 request/response example: + +.. code-block:: python + :linenos: + + import json + import socket + import uuid + + READ_SIZE = 4096 + ENCODING = "utf-8" + + # Connect to lightsd, here using an Unix socket. The rest of the example is + # valid for TCP sockets too. Replace /run/lightsd/socket by the output of: + # echo $(lightsd --rundir)/socket + lightsd_socket = socket.socket(socket.AF_UNIX) + lightsd_socket.connect("/run/lightsd/socket") + lightsd_socket.settimeout(2) # seconds + + # Prepare the request: + request = json.dumps({ + "method": "get_light_state", + "params": ["*"], + "jsonrpc": "2.0", + "id": str(uuid.uuid4()), + }).encode(ENCODING, "surrogateescape") + + # Send it: + lightsd_socket.sendall(request) + + # Prepare an empty buffer to accumulate the received data: + response = bytearray() + while True: + # Read a chunk of data, and accumulate it in the response buffer: + response += lightsd_socket.recv(READ_SIZE) + try: + # Try to load the received the data, we ignore encoding errors + # since we only wanna know if the received data is complete. + json.loads(response.decode(ENCODING, "ignore")) + break # Decoding was successful, we have received everything. + except Exception: + continue # Decoding failed, data must be missing. + + response = response.decode(ENCODING, "surrogateescape") + print(json.loads(response)) + Notes ------ +~~~~~ +- Use an incremental JSON parser if you have one handy: for responses multiple + times the size of your receive window it will let you avoid decoding the whole + response at each iteration of the read loop; - lightsd supports batch JSON-RPC requests, use them! .. vim: set tw=80 spelllang=en spell: diff --git a/examples/lightsc.py b/examples/lightsc.py index dcd83e8..93b96a4 100755 --- a/examples/lightsc.py +++ b/examples/lightsc.py @@ -42,8 +42,28 @@ class LightsClient: - def __init__(self, url): + READ_SIZE = 4096 + TIMEOUT = 2 # seconds + ENCODING = "utf-8" + + class Error(Exception): + pass + + class TimeoutError(Error): + pass + + class JSONError(Error): + + def __init__(self, response): + self._response = response + + def __str__(self): + return "received invalid JSON: {}".format(self._response) + + def __init__(self, url, encoding=ENCODING, timeout=TIMEOUT, + read_size=READ_SIZE): self.url = url + self.encoding = encoding parts = urllib.parse.urlparse(args.url) @@ -55,7 +75,9 @@ def __init__(self, url): self._socket = socket.create_connection((parts.hostname, parts.port)) else: raise ValueError("Unsupported url {}".format(url)) + self._socket.settimeout(timeout) + self._read_size = read_size self._pipeline = [] self._batch = False @@ -75,15 +97,24 @@ def _make_payload(cls, method, params): } def _execute_payload(self, payload): - self._socket.send(json.dumps(payload).encode("utf-8")) - # FIXME: proper read loop - response = self._socket.recv(64 * 1024).decode("utf-8") + response = bytearray() try: - response = json.loads(response) - except Exception: - print("received invalid json: {}".format(response)) - - return response + payload = json.dumps(payload) + payload = payload.encode(self.encoding, "surrogateescape") + self._socket.sendall(payload) + + while True: + response += self._socket.recv(self._read_size) + try: + return json.loads(response.decode( + self.encoding, "surrogateescape" + )) + except Exception: + continue + except socket.timeout: + if not response: + raise self.TimeoutError + raise self.JSONError(response) def _jsonrpc_call(self, method, params): payload = self._make_payload(method, params) @@ -199,11 +230,6 @@ def adjust_brightness(self, target, adjustment): def _drop_to_shell(lightsc): c = lightsc # noqa - nb = "d073d501a0d5" # noqa - fugu = "d073d500603b" # noqa - neko = "d073d5018fb6" # noqa - middle = "d073d502e530" # noqa - banner = ( "Connected to {}, use the variable c to interact with your " "bulbs:\n\n>>> r = c.get_light_state(\"*\")".format(c.url) @@ -227,7 +253,7 @@ def _drop_to_shell(lightsc): lightsdrundir = subprocess.check_output(["lightsd", "--rundir"]) except Exception as ex: print( - "Couldn't infer lightsd's runtime directory is lightsd installed? " + "Couldn't infer lightsd's runtime directory, is lightsd installed? " "({})\nTrying build/socket...".format(ex), file=sys.stderr ) @@ -238,7 +264,7 @@ def _drop_to_shell(lightsc): lightsdrundir = lightsdrundir.decode(encoding).strip() parser = argparse.ArgumentParser( - description="lightsc.py is an interactive lightsd Python client" + description="Interactive lightsd Python client" ) parser.add_argument( "-u", "--url", type=str, From 2ffaf3a11964466b08c56b550c395c71a74af5e1 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 4 Jan 2016 22:13:18 +0100 Subject: [PATCH 177/181] Add missing call to action in the dpkg build instructions --- docs/installation.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/installation.rst b/docs/installation.rst index b74a048..d114ef1 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -139,6 +139,8 @@ Still as root, run the command the package asks you to run: Log out and back in as ``$USER`` for the change to take effect. +Read on :doc:`/first-steps` to see how to use lightsd. + .. _sudo(8): http://manpages.debian.org/cgi-bin/man.cgi?query=sudo&sektion=8 Build instructions (for other systems) From 78783c545cab280769f31e8918d9aee25e3cbebf Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Mon, 4 Jan 2016 22:41:31 +0100 Subject: [PATCH 178/181] Use apt-get to install packages on Debian based systems After all people who don't use apt-get will figure it out... --- docs/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.rst b/docs/installation.rst index d114ef1..0cfc58b 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -98,7 +98,7 @@ Install the following packages: :: - build-essential cmake libevent-dev git ca-certificates ipython3 fakeroot wget devscripts debhelper + apt-get install build-essential cmake libevent-dev git ca-certificates ipython3 fakeroot wget devscripts debhelper Download and extract lightsd: From 2f7018d9f77c35d3167edc8d48b1dbaf57ff3144 Mon Sep 17 00:00:00 2001 From: Charlie Root Date: Mon, 9 May 2016 11:31:39 +0100 Subject: [PATCH 179/181] Add freebsd rc script --- dist/lightsd-freebsd-rc.sh | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100755 dist/lightsd-freebsd-rc.sh diff --git a/dist/lightsd-freebsd-rc.sh b/dist/lightsd-freebsd-rc.sh new file mode 100755 index 0000000..0a86dfd --- /dev/null +++ b/dist/lightsd-freebsd-rc.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# $FreeBSD: head/net/lightsg/files/lightsd.in 367807 2014-09-10 09:36:24Z ehaupt $ +# + +# PROVIDE: lightsd +# REQUIRE: LOGIN +# BEFORE: securelevel +# KEYWORD: shutdown + +# Add the following lines to /etc/rc.conf to enable `lightsd': +# +# lightsd_enable="YES" +# lightsd_flags="" +# +# See lightsg(1) for lightsd_flags +# + +. /etc/rc.subr + +name="lightsd" +rcvar=lightsd_enable + +command="/usr/sbin/daemon" +start_precmd="lightsd_precmd" +pidfile="/var/run/lightsd/$name.pid" + +# read configuration and set defaults +load_rc_config "$name" +: ${lightsd_enable="NO"} +: ${lightsd_user:="lightsd"} +: ${lightsd_group:="lightsd"} +: ${lightsd_listen:="localhost:23456"} +: ${lightsd_log:="info"} + + +command_args="-P $pidfile -p $pidfile.cl -r /usr/local/bin/$name -l ${lightsd_listen} -v $lightsd_log -S 2>&1 " +#command_args="-l ${lightsd_listen} -v debug " + +lightsd_precmd() +{ + # not a lot +} + +run_rc_command "$1" From 805f7f46050068ef28a6f274fe89264cd15b3a59 Mon Sep 17 00:00:00 2001 From: Charlie Root Date: Mon, 9 May 2016 12:29:13 +0100 Subject: [PATCH 180/181] add generic flags section --- dist/lightsd-freebsd-rc.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dist/lightsd-freebsd-rc.sh b/dist/lightsd-freebsd-rc.sh index 0a86dfd..de9a002 100755 --- a/dist/lightsd-freebsd-rc.sh +++ b/dist/lightsd-freebsd-rc.sh @@ -32,9 +32,10 @@ load_rc_config "$name" : ${lightsd_group:="lightsd"} : ${lightsd_listen:="localhost:23456"} : ${lightsd_log:="info"} +: ${lightsd_flags:=""} -command_args="-P $pidfile -p $pidfile.cl -r /usr/local/bin/$name -l ${lightsd_listen} -v $lightsd_log -S 2>&1 " +command_args="-P $pidfile -p $pidfile.cl -r /usr/local/bin/$name -l ${lightsd_listen} -v $lightsd_log -S $lightsd_flags 2>&1 " #command_args="-l ${lightsd_listen} -v debug " lightsd_precmd() From bca656cf5dccfc64ab7eae8bca865f84d3d30973 Mon Sep 17 00:00:00 2001 From: Charlie Root Date: Tue, 10 May 2016 08:47:18 +0100 Subject: [PATCH 181/181] put in pid dir creation --- dist/lightsd-freebsd-rc.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dist/lightsd-freebsd-rc.sh b/dist/lightsd-freebsd-rc.sh index de9a002..dfb503d 100755 --- a/dist/lightsd-freebsd-rc.sh +++ b/dist/lightsd-freebsd-rc.sh @@ -35,12 +35,15 @@ load_rc_config "$name" : ${lightsd_flags:=""} -command_args="-P $pidfile -p $pidfile.cl -r /usr/local/bin/$name -l ${lightsd_listen} -v $lightsd_log -S $lightsd_flags 2>&1 " +command_args="-P $pidfile -p ${pidfile}_child -r /usr/local/bin/$name -l ${lightsd_listen} -v $lightsd_log -S $lightsd_flags 2>&1 " #command_args="-l ${lightsd_listen} -v debug " lightsd_precmd() { - # not a lot + if [ ! -d "/var/run/lightsd/" ] ; then + mkdir -pv /var/run/lightsd/ + chown ${lightsd_user}:${lightsd_group} /var/run/lightsd/ + fi } run_rc_command "$1"