Mercurial > louis > mq > lightsd
changeset 228:a655a3b88d62
finish the mq
author | Louis Opter <kalessin@kalessin.fr> |
---|---|
date | Sat, 08 Aug 2015 02:36:59 -0700 |
parents | 0f355a601f13 |
children | 1a2bd9fc41b1 |
files | add_command_pipe.patch add_daemon_module.patch add_tag_and_untag.patch fix_crash_in_get_light_state_when_tag_does_not_exist.patch fix_lifx_wire_float_endian_functions_naming.patch fix_set_power_state.patch fix_targeting_on_big_endian_architectures.patch fix_unused_unused_attribute.patch fix_usage_and_version.patch ignore_duplicate_listening_address.patch ignore_pcaps.patch pass_pkt_info_around.patch relax_timings.patch remove_assert_on_request_id.patch series update_readme.patch |
diffstat | 16 files changed, 0 insertions(+), 8339 deletions(-) [+] |
line wrap: on
line diff
--- a/add_command_pipe.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2506 +0,0 @@ -# HG changeset patch -# Parent 51336c56c6a8d78e341c3aef261c9014d41823eb -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. - -diff --git a/CMakeLists.txt b/CMakeLists.txt ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -42,6 +42,8 @@ - "-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 ---- a/README.rst -+++ b/README.rst -@@ -33,8 +33,8 @@ - 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 ---- a/core/CMakeLists.txt -+++ b/core/CMakeLists.txt -@@ -20,6 +20,7 @@ - listen.c - lightsd.c - log.c -+ pipe.c - proto.c - router.c - ) -diff --git a/core/client.c b/core/client.c ---- a/core/client.c -+++ b/core/client.c -@@ -23,6 +23,7 @@ - #include <stdbool.h> - #include <stdlib.h> - #include <stdint.h> -+#include <string.h> - - #include <event2/buffer.h> - #include <event2/bufferevent.h> -@@ -138,6 +139,29 @@ - } - - 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) - { - lgtd_jsonrpc_send_response(client, msg); -@@ -195,3 +219,13 @@ - - 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 ---- a/core/client.h -+++ b/core/client.h -@@ -42,13 +42,12 @@ - }; - 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 ---- a/core/jsonrpc.c -+++ b/core/jsonrpc.c -@@ -480,7 +480,7 @@ - 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 @@ - 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 @@ - 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 @@ - 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 @@ - { - 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 ---- 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 @@ - 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 @@ - 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 @@ - - 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 @@ - {"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 @@ - 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 ---- /dev/null -+++ b/core/pipe.c -@@ -0,0 +1,248 @@ -+// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> -+// -+// 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 <http://www.gnu.org/licenses/>. -+ -+#include <sys/queue.h> -+#include <sys/types.h> -+#include <sys/stat.h> -+#include <assert.h> -+#include <err.h> -+#include <errno.h> -+#include <fcntl.h> -+#include <stdarg.h> -+#include <stdbool.h> -+#include <stdlib.h> -+#include <string.h> -+#include <unistd.h> -+ -+#include <event2/buffer.h> -+#include <event2/event.h> -+ -+#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 ---- /dev/null -+++ b/core/pipe.h -@@ -0,0 +1,33 @@ -+// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> -+// -+// 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 <http://www.gnu.org/licenses/>. -+ -+#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 ---- a/core/proto.c -+++ b/core/proto.c -@@ -178,7 +178,7 @@ - ) - - 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 @@ - ); - 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 -rename from tests/lightsc -rename to examples/lightsc.py -diff --git a/examples/lightsc.sh b/examples/lightsc.sh -new file mode 100644 ---- /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 <<EOF -+{ -+ "jsonrpc": "2.0", -+ "method": "$method", -+ "params": [$params], -+ "id": `_gen_request_id` -+} -+EOF -+} -diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt ---- a/tests/core/CMakeLists.txt -+++ b/tests/core/CMakeLists.txt -@@ -2,9 +2,11 @@ - ${LIGHTSD_SOURCE_DIR} - ${LIGHTSD_SOURCE_DIR}/core/ - ${CMAKE_CURRENT_SOURCE_DIR} -+ ${CMAKE_CURRENT_SOURCE_DIR}/../lifx - ${LIGHTSD_BINARY_DIR} - ${LIGHTSD_BINARY_DIR}/core/ - ${CMAKE_CURRENT_BINARY_DIR} -+ ${CMAKE_CURRENT_BINARY_DIR}/../lifx - ) - - ADD_ALL_SUBDIRECTORIES() -diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h ---- a/tests/core/jsonrpc/test_jsonrpc_utils.h -+++ b/tests/core/jsonrpc/test_jsonrpc_utils.h -@@ -1,5 +1,7 @@ - #pragma once - -+#include "mock_gateway.h" -+ - #define TEST_REQUEST_INITIALIZER { NULL, NULL, 0, NULL } - - static inline int -diff --git a/tests/core/mock_client_buf.h b/tests/core/mock_client_buf.h ---- a/tests/core/mock_client_buf.h -+++ b/tests/core/mock_client_buf.h -@@ -21,3 +21,15 @@ - client_write_buf_idx += to_write; - return 0; - } -+ -+void -+lgtd_client_write_string(struct lgtd_client *client, const char *msg) -+{ -+ bufferevent_write(client->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 ---- /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 ---- /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 ---- /dev/null -+++ b/tests/core/pipe/test_pipe_close.c -@@ -0,0 +1,116 @@ -+#include "pipe.c" -+ -+#include <sys/tree.h> -+#include <endian.h> -+#include <limits.h> -+ -+#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 ---- /dev/null -+++ b/tests/core/pipe/test_pipe_open.c -@@ -0,0 +1,170 @@ -+#include "pipe.c" -+ -+#include <sys/tree.h> -+#include <endian.h> -+#include <limits.h> -+ -+#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 ---- /dev/null -+++ b/tests/core/pipe/test_pipe_open_fifo_already_exists.c -@@ -0,0 +1,175 @@ -+#include "pipe.c" -+ -+#include <sys/tree.h> -+#include <endian.h> -+#include <limits.h> -+ -+#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 ---- /dev/null -+++ b/tests/core/pipe/test_pipe_read_callback.c -@@ -0,0 +1,191 @@ -+#include "pipe.c" -+ -+#include <sys/tree.h> -+#include <endian.h> -+#include <limits.h> -+ -+#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 ---- /dev/null -+++ b/tests/core/pipe/test_pipe_read_callback_extra_data.c -@@ -0,0 +1,218 @@ -+#include "pipe.c" -+ -+#include <sys/tree.h> -+#include <endian.h> -+#include <limits.h> -+ -+#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 ---- /dev/null -+++ b/tests/core/pipe/test_pipe_read_callback_multiple_requests.c -@@ -0,0 +1,258 @@ -+#include "pipe.c" -+ -+#include <sys/tree.h> -+#include <endian.h> -+#include <limits.h> -+ -+#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 ---- /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 ---- a/tests/core/proto/CMakeLists.txt -+++ b/tests/core/proto/CMakeLists.txt -@@ -8,6 +8,7 @@ - ${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 @@ - ) - 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 ---- a/tests/core/proto/test_proto_get_light_state.c -+++ b/tests/core/proto/test_proto_get_light_state.c -@@ -29,6 +29,9 @@ - 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 @@ - .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 @@ - 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 @@ - 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 ---- 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 @@ - 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 @@ - 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 ---- 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 @@ - 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 ---- a/tests/core/proto/test_proto_power_off.c -+++ b/tests/core/proto/test_proto_power_off.c -@@ -52,7 +52,7 @@ - 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 ---- 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 @@ - 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 ---- a/tests/core/proto/test_proto_power_on.c -+++ b/tests/core/proto/test_proto_power_on.c -@@ -52,7 +52,7 @@ - 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 ---- 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 @@ - 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 ---- 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 @@ - 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 ---- a/tests/core/router/CMakeLists.txt -+++ b/tests/core/router/CMakeLists.txt -@@ -8,6 +8,7 @@ - ${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 ---- 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 ---- a/tests/core/tests_shims.c -+++ b/tests/core/tests_shims.c -@@ -30,52 +30,3 @@ - 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 ---- a/tests/core/tests_utils.c -+++ b/tests/core/tests_utils.c -@@ -2,13 +2,18 @@ - #include <sys/tree.h> - #include <sys/socket.h> - #include <assert.h> -+#include <dirent.h> - #include <endian.h> -+#include <err.h> -+#include <limits.h> - #include <netinet/in.h> - #include <stdarg.h> - #include <stdbool.h> - #include <stdlib.h> - #include <stdint.h> -+#include <stdio.h> - #include <string.h> -+#include <unistd.h> - - #include <event2/util.h> - -@@ -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 @@ - 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 ---- a/tests/core/tests_utils.h -+++ b/tests/core/tests_utils.h -@@ -30,6 +30,9 @@ - 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 ---- /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
--- a/add_daemon_module.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1323 +0,0 @@ -# HG changeset patch -# Parent fa84fd51301cf3bf3786f7e3c03031488f31d1ef -Actual support for daemonization with a nice process name - -diff --git a/CMakeLists.txt b/CMakeLists.txt ---- 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 @@ - # 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") -+SET(CMAKE_C_FLAGS "${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( -+ # 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 @@ - 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 ---- 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 ---- /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 ---- 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 ---- /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 ---- a/README.rst -+++ b/README.rst -@@ -65,6 +65,9 @@ - - 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 @@ - - .../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 ---- /dev/null -+++ b/compat/CMakeLists.txt -@@ -0,0 +1,1 @@ -+ADD_SUBDIRECTORY(generic) -diff --git a/compat/generic/CMakeLists.txt b/compat/generic/CMakeLists.txt -new file mode 100644 ---- /dev/null -+++ b/compat/generic/CMakeLists.txt -@@ -0,0 +1,1 @@ -+FILE(COPY sys DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) -diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt ---- a/core/CMakeLists.txt -+++ b/core/CMakeLists.txt -@@ -15,6 +15,7 @@ - ADD_EXECUTABLE( - lightsd - client.c -+ daemon.c - jsmn.c - jsonrpc.c - listen.c -@@ -23,6 +24,7 @@ - pipe.c - proto.c - router.c -+ stats.c - ) - - TARGET_LINK_LIBRARIES( -@@ -31,3 +33,7 @@ - ${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 ---- 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 @@ - 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 @@ - - 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 ---- /dev/null -+++ b/core/daemon.c -@@ -0,0 +1,155 @@ -+// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> -+// -+// 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 <http://www.gnu.org/licenses/>. -+ -+#include <sys/queue.h> -+#include <sys/tree.h> -+#include <sys/types.h> -+#include <endian.h> -+#include <fcntl.h> -+#include <stdbool.h> -+#include <stdint.h> -+#include <stdio.h> -+#include <stdlib.h> -+#include <string.h> -+#include <unistd.h> -+ -+#if LGTD_HAVE_LIBBSD -+#include <bsd/bsd.h> -+#endif -+ -+#include <event2/util.h> -+ -+#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 ---- /dev/null -+++ b/core/daemon.h -@@ -0,0 +1,24 @@ -+// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> -+// -+// 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 <http://www.gnu.org/licenses/>. -+ -+#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 ---- 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 @@ - } - - 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 @@ - 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 ---- 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 @@ - evconnlistener_free(listener->evlistener); - free(listener); - } -+ -+ lgtd_daemon_update_proctitle(); - } - - bool -@@ -130,6 +133,8 @@ - - evutil_freeaddrinfo(res); - -+ lgtd_daemon_update_proctitle(); -+ - return true; - - error: -diff --git a/core/listen.h b/core/listen.h ---- 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 @@ - }; - 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 ---- a/core/log.c -+++ b/core/log.c -@@ -28,9 +28,14 @@ - #include <stdio.h> - #include <time.h> - -+#if LGTD_HAVE_LIBBSD -+#include <bsd/unistd.h> -+#endif -+ - #include <event2/event.h> - - #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 ---- /dev/null -+++ b/core/stats.c -@@ -0,0 +1,47 @@ -+// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> -+// -+// 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 <http://www.gnu.org/licenses/>. -+ -+#include <assert.h> -+#include <stdint.h> -+ -+#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 ---- /dev/null -+++ b/core/stats.h -@@ -0,0 +1,35 @@ -+// Copyright (c) 2015, Louis Opter <kalessin@kalessin.fr> -+// -+// 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 <http://www.gnu.org/licenses/>. -+ -+#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 ---- 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 @@ - 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 @@ - 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 @@ - { - 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 @@ - 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 ---- 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 @@ - { - 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 @@ - // 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 ---- a/tests/CMakeLists.txt -+++ b/tests/CMakeLists.txt -@@ -1,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 ---- /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 ---- /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 ---- /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 <err.h> -+ -+#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 ---- a/tests/core/jsonrpc/CMakeLists.txt -+++ b/tests/core/jsonrpc/CMakeLists.txt -@@ -3,16 +3,16 @@ - ${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 ---- /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 ---- a/tests/core/pipe/CMakeLists.txt -+++ b/tests/core/pipe/CMakeLists.txt -@@ -7,6 +7,7 @@ - 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 ---- 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 ---- a/tests/core/proto/CMakeLists.txt -+++ b/tests/core/proto/CMakeLists.txt -@@ -3,10 +3,11 @@ - ${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 @@ - ${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 ---- 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 ---- 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 ---- 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 ---- 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 @@ - 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 ---- 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 ---- 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 ---- 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 ---- 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 ---- 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 ---- 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 ---- 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 ---- a/tests/core/router/CMakeLists.txt -+++ b/tests/core/router/CMakeLists.txt -@@ -3,10 +3,11 @@ - ${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 @@ - ${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 ---- 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 ---- 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 ---- 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 ---- 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 ---- 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 ---- 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 ---- 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 @@ - - LIST_INSERT_HEAD(&lgtd_lifx_gateways, gw, link); - -+ LGTD_STATS_ADD_AND_UPDATE_PROCTITLE(gateways, 1); -+ - return gw; - } - -@@ -115,6 +123,17 @@ - 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 ---- a/tests/core/tests_utils.h -+++ b/tests/core/tests_utils.h -@@ -40,3 +40,4 @@ - 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 ---- a/tests/lifx/gateway/CMakeLists.txt -+++ b/tests/lifx/gateway/CMakeLists.txt -@@ -3,21 +3,27 @@ - ${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 ---- a/tests/lifx/tagging/CMakeLists.txt -+++ b/tests/lifx/tagging/CMakeLists.txt -@@ -6,8 +6,12 @@ - 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 ---- a/tests/lifx/tests_shims.c -+++ b/tests/lifx/tests_shims.c -@@ -35,3 +35,8 @@ - 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 ---- a/tests/lifx/wire_proto/CMakeLists.txt -+++ b/tests/lifx/wire_proto/CMakeLists.txt -@@ -3,14 +3,15 @@ - ${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")
--- a/add_tag_and_untag.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3059 +0,0 @@ -# HG changeset patch -# Parent 803d8ed2122a3e2d6c5f0c6c0bfbbd6b3c5cec1c -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. - -diff --git a/README.rst b/README.rst ---- a/README.rst -+++ b/README.rst -@@ -29,8 +29,7 @@ - - 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 ---- a/core/jsonrpc.c -+++ b/core/jsonrpc.c -@@ -977,6 +977,90 @@ - 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_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 ---- a/core/proto.c -+++ b/core/proto.c -@@ -224,3 +224,145 @@ - - 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 ---- a/core/proto.h -+++ b/core/proto.h -@@ -39,3 +39,5 @@ - 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 ---- a/core/router.c -+++ b/core/router.c -@@ -372,8 +372,8 @@ - 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 ---- a/examples/lightsc.py -+++ b/examples/lightsc.py -@@ -72,6 +72,14 @@ - 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 ---- a/lifx/bulb.c -+++ b/lifx/bulb.c -@@ -77,12 +77,29 @@ - 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_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 @@ - - 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 ---- a/lifx/bulb.h -+++ b/lifx/bulb.h -@@ -68,3 +68,4 @@ - 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 ---- a/lifx/gateway.c -+++ b/lifx/gateway.c -@@ -71,9 +71,9 @@ - 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 @@ - 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 @@ - 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,36 +161,77 @@ - } - } - -+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 }; -+ *pkt_infos = lgtd_lifx_wire_setup_header( -+ &hdr, -+ LGTD_LIFX_TARGET_SITE, -+ target, -+ gw->site.as_array, -+ pkt_type -+ ); -+ 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 -+ ); -+ -+ lgtd_debug( -+ "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... -+} -+ -+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); - -- struct lgtd_lifx_packet_header hdr; -- union lgtd_lifx_target target = { .addr = gw->site.as_array }; -+ lgtd_lifx_gateway_send_to_site_quiet(gw, LGTD_LIFX_GET_LIGHT_STATE, NULL); - -- lgtd_lifx_wire_setup_header( -- &hdr, -- LGTD_LIFX_TARGET_SITE, -- target, -- gw->site.as_array, -- LGTD_LIFX_GET_LIGHT_STATE -- ); -- lgtd_lifx_gateway_enqueue_packet( -- 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) -- ); -+ 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; - } -@@ -371,19 +422,55 @@ - } - - 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 - ); - } - -@@ -485,16 +572,44 @@ - } - - 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, - 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 >= -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 @@ - 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 @@ - } - } - } -+ -+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 ---- a/lifx/gateway.h -+++ b/lifx/gateway.h -@@ -51,6 +51,7 @@ - } 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 @@ - - 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 @@ - 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_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 ---- a/lifx/tagging.c -+++ b/lifx/tagging.c -@@ -66,6 +66,32 @@ - } - - 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, - int tag_id) -@@ -77,12 +103,10 @@ - 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 @@ - 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 @@ - 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 @@ - 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 ---- a/lifx/tagging.h -+++ b/lifx/tagging.h -@@ -39,3 +39,6 @@ - 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 ---- a/lifx/timer.c -+++ b/lifx/timer.c -@@ -95,7 +95,7 @@ - "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 ---- a/lifx/wire_proto.c -+++ b/lifx/wire_proto.c -@@ -92,6 +92,7 @@ - .handle = lgtd_lifx_wire_null_packet_handler - - static struct lgtd_lifx_packet_infos packet_table[] = { -+ // Gateway packets: - { - REQUEST_ONLY, - NO_PAYLOAD, -@@ -108,6 +109,43 @@ - }, - { - 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, - .name = "GET_LIGHT_STATUS", - .type = LGTD_LIFX_GET_LIGHT_STATE -@@ -128,6 +166,7 @@ - .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 @@ - }, - { - 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) - } - }; - -@@ -356,6 +381,14 @@ - } - - 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) - { - assert(pkt); -@@ -363,3 +396,19 @@ - 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 ---- a/lifx/wire_proto.h -+++ b/lifx/wire_proto.h -@@ -238,7 +238,7 @@ - }; - - 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_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 ---- /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 ---- /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 ---- /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 ---- /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 ---- a/tests/core/jsonrpc/test_jsonrpc_utils.h -+++ b/tests/core/jsonrpc/test_jsonrpc_utils.h -@@ -97,3 +97,27 @@ - (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 ---- /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 ---- /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 ---- /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 ---- /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 ---- /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 ---- a/tests/core/proto/tests_proto_utils.h -+++ b/tests/core/proto/tests_proto_utils.h -@@ -36,6 +36,18 @@ - } - #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 ---- /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 ---- a/tests/core/tests_utils.c -+++ b/tests/core/tests_utils.c -@@ -118,8 +118,11 @@ - struct lgtd_lifx_site *site = calloc(1, sizeof(*site)); - site->gw = gw; - site->tag_id = tag_id; -+ LIST_INSERT_HEAD(&tag->sites, site, link); -+ - gw->tags[tag_id] = tag; -- LIST_INSERT_HEAD(&tag->sites, site, link); -+ 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 ---- /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 ---- /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 ---- /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 ---- /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 ---- /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 ---- /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 ---- /dev/null -+++ b/tests/lifx/gateway/test_gateway_allocate_tag_id.c -@@ -0,0 +1,102 @@ -+#include "gateway.c" -+ -+#include <string.h> -+ -+#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 ---- /dev/null -+++ b/tests/lifx/gateway/test_gateway_allocate_tag_id_no_tag_id_left.c -@@ -0,0 +1,89 @@ -+ -+#include <string.h> -+ -+#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 ---- /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 ---- a/tests/lifx/mock_gateway.h -+++ b/tests/lifx/mock_gateway.h -@@ -81,6 +81,18 @@ - } - #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 ---- 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)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; -+}
--- a/fix_crash_in_get_light_state_when_tag_does_not_exist.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -# HG changeset patch -# Parent 97f7fd10087f08547f48c20644461f2356e7399b -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. - -diff --git a/core/proto.c b/core/proto.c ---- a/core/proto.c -+++ b/core/proto.c -@@ -209,10 +209,20 @@ - 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 ---- /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; -+}
--- a/fix_lifx_wire_float_endian_functions_naming.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -# HG changeset patch -# Parent 493a81b724f08c6d746c3879bafd86e7f05342fa -Fix a couple function names - -diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c ---- a/lifx/wire_proto.c -+++ b/lifx/wire_proto.c -@@ -382,7 +382,7 @@ - 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 ---- a/lifx/wire_proto.h -+++ b/lifx/wire_proto.h -@@ -26,14 +26,14 @@ - 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 ---- a/tests/core/proto/test_proto_set_waveform.c -+++ b/tests/core/proto/test_proto_set_waveform.c -@@ -36,7 +36,7 @@ - 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 ---- 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 @@ - 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) {
--- a/fix_set_power_state.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,35 +0,0 @@ -# HG changeset patch -# Parent b5f496df71441bfe4bb153a027acf832255ea81a -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. - -diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c ---- a/lifx/wire_proto.c -+++ b/lifx/wire_proto.c -@@ -342,16 +342,22 @@ - 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); - -- assert(pkt); -+ if (pkt->power != LGTD_LIFX_POWER_ON) { -+ pkt->power = LGTD_LIFX_POWER_OFF; -+ } - } - - void
--- a/fix_targeting_on_big_endian_architectures.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -# HG changeset patch -# Parent 75b032c23f91d4ddf3b4842bb0f1b7839fdf7ecf -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. - -diff --git a/lifx/wire_proto.c b/lifx/wire_proto.c ---- a/lifx/wire_proto.c -+++ b/lifx/wire_proto.c -@@ -246,7 +246,7 @@ - } - 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 @@ - 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 ---- 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 @@ - 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) {
--- a/fix_unused_unused_attribute.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -# HG changeset patch -# Parent 77e68f94c2d022c1ca8a62bc3e6c5305867b2709 -Remove unused __attribute__((unused)) - -diff --git a/core/jsonrpc.c b/core/jsonrpc.c ---- a/core/jsonrpc.c -+++ b/core/jsonrpc.c -@@ -129,7 +129,7 @@ - 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) {
--- a/fix_usage_and_version.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -# HG changeset patch -# Parent d0442b5bc172c3841cc5a2b577d4ffdf311c9116 -UI/Cosmetic fixes - -diff --git a/CMakeLists.txt b/CMakeLists.txt ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -35,7 +35,9 @@ - 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 @@ - 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 ---- a/core/lightsd.c -+++ b/core/lightsd.c -@@ -139,6 +139,7 @@ - " [-v,--verbosity debug|info|warning|error]\n", - progname - ); -+ lgtd_cleanup(); - exit(0); - } - -@@ -162,6 +163,10 @@ - }; - 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 @@ - } - 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 ---- 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 <kalessin@kalessin.fr>" -+);
--- a/ignore_duplicate_listening_address.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -# HG changeset patch -# Parent d8e34f128c677442c5c047793207dbc1664b6c2e -Allow duplicate listening address in the command line - -diff --git a/core/listen.c b/core/listen.c ---- a/core/listen.c -+++ b/core/listen.c -@@ -20,6 +20,7 @@ - #include <err.h> - #include <stdbool.h> - #include <stdlib.h> -+#include <string.h> - #include <unistd.h> - - #include <event2/listener.h> -@@ -76,6 +77,13 @@ - 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 @@ - return false; - } - -- struct lgtd_listen *listener; - struct evconnlistener *evlistener; - for (struct evutil_addrinfo *it = res; it; it = it->ai_next) { - evlistener = NULL;
--- a/ignore_pcaps.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -# HG changeset patch -# Parent 3c1187a6876d8ff634acc7f75b5d8ae23feda534 -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. - -diff --git a/.hgignore b/.hgignore ---- a/.hgignore -+++ b/.hgignore -@@ -1,3 +1,4 @@ - .*\.sw[a-z]$ - .*\.py[co]$ - ^build -+^pcaps
--- a/pass_pkt_info_around.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,878 +0,0 @@ -# HG changeset patch -# Parent 9f4ce61eb90c9c37e29271f680060561a36b6f44 -Pass pkt_info around instead of looking it up twice - -And do that spelling fix too. - -diff --git a/core/lightsd.c b/core/lightsd.c ---- a/core/lightsd.c -+++ b/core/lightsd.c -@@ -220,7 +220,7 @@ - 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 ---- a/core/router.c -+++ b/core/router.c -@@ -49,21 +49,19 @@ - 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 @@ - } - } - -- if (pkt_infos) { -- lgtd_info("broadcasting %s", pkt_infos->name); -+ if (pkt_info) { -+ lgtd_info("broadcasting %s", pkt_info->name); - } - } - -@@ -91,19 +89,16 @@ - 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 @@ - 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 @@ - 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 @@ - 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 @@ - } - } - -- 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 @@ - 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 @@ - } - } - -- 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 ---- a/lifx/broadcast.c -+++ b/lifx/broadcast.c -@@ -117,9 +117,9 @@ - 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 @@ - 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 @@ - 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 ---- a/lifx/gateway.c -+++ b/lifx/gateway.c -@@ -164,24 +164,24 @@ - 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 @@ - 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 @@ - 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 @@ - 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 @@ - 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 ---- a/lifx/gateway.h -+++ b/lifx/gateway.h -@@ -91,13 +91,12 @@ - - 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 ---- a/lifx/wire_proto.c -+++ b/lifx/wire_proto.c -@@ -48,14 +48,14 @@ - 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 @@ - } - - 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 @@ - .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 @@ - - 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 @@ - 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 @@ - { - 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_encode_header(hdr, flags); - -- return pkt_infos; -+ return pkt_info; - } - - void -diff --git a/lifx/wire_proto.h b/lifx/wire_proto.h ---- a/lifx/wire_proto.h -+++ b/lifx/wire_proto.h -@@ -265,8 +265,8 @@ - - 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 @@ - 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 @@ - - 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 ---- a/tests/core/proto/test_proto_tag_create.c -+++ b/tests/core/proto/test_proto_tag_create.c -@@ -65,7 +65,7 @@ - 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 ---- 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 @@ - 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 ---- a/tests/core/proto/test_proto_tag_update.c -+++ b/tests/core/proto/test_proto_tag_update.c -@@ -69,7 +69,7 @@ - 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 ---- 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 ---- 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 ---- 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 @@ - 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 ---- 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 ---- 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 ---- a/tests/core/router/test_router_targets_to_devices.c -+++ b/tests/core/router/test_router_targets_to_devices.c -@@ -41,7 +41,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/tests_router_utils.h b/tests/core/router/tests_router_utils.h ---- a/tests/core/router/tests_router_utils.h -+++ b/tests/core/router/tests_router_utils.h -@@ -13,12 +13,9 @@ - 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 @@ - 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 ---- a/tests/lifx/gateway/test_gateway_allocate_tag_id.c -+++ b/tests/lifx/gateway/test_gateway_allocate_tag_id.c -@@ -40,7 +40,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_allocate_tag_id_from_lifx_network.c b/tests/lifx/gateway/test_gateway_allocate_tag_id_from_lifx_network.c ---- 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 @@ - 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 ---- 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 @@ - 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 ---- 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 @@ - 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 ---- 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 @@ - 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 @@ - 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 ---- 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 @@ - 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 @@ - 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 @@ - 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 ---- 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 @@ - 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 @@ - 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 ---- 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 ---- 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 ---- a/tests/lifx/gateway/test_gateway_write_callback.c -+++ b/tests/lifx/gateway/test_gateway_write_callback.c -@@ -43,7 +43,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_clears_ring_full_flag.c b/tests/lifx/gateway/test_gateway_write_callback_clears_ring_full_flag.c ---- 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 @@ - 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 ---- 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 @@ - 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 ---- 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 @@ - 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 ---- 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 @@ - 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 ---- a/tests/lifx/mock_gateway.h -+++ b/tests/lifx/mock_gateway.h -@@ -11,7 +11,7 @@ - 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;
--- a/relax_timings.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,34 +0,0 @@ -# HG changeset patch -# Parent fbfdbecbaa8d13e475e4851ccda9065c6369c437 -Relax different timings a bit (until we get auto-retry on everything) - -diff --git a/lifx/gateway.h b/lifx/gateway.h ---- 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 ---- 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);
--- a/remove_assert_on_request_id.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -# HG changeset patch -# Parent 5b607d851a9c8d71384dd29984f629f18f3366ca -Don't abort on request without an id in debug builds - -We made it optional. - -diff --git a/core/jsonrpc.c b/core/jsonrpc.c ---- a/core/jsonrpc.c -+++ b/core/jsonrpc.c -@@ -1118,7 +1118,6 @@ - } - - 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);
--- a/series Sat Aug 08 02:35:14 2015 -0700 +++ b/series Sat Aug 08 02:36:59 2015 -0700 @@ -1,15 +0,0 @@ -ignore_duplicate_listening_address.patch -add_command_pipe.patch -add_daemon_module.patch -fix_usage_and_version.patch -add_tag_and_untag.patch -update_readme.patch -fix_set_power_state.patch -fix_crash_in_get_light_state_when_tag_does_not_exist.patch -fix_lifx_wire_float_endian_functions_naming.patch -ignore_pcaps.patch -fix_unused_unused_attribute.patch -pass_pkt_info_around.patch #-skip -relax_timings.patch -remove_assert_on_request_id.patch -fix_targeting_on_big_endian_architectures.patch
--- a/update_readme.patch Sat Aug 08 02:35:14 2015 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -# HG changeset patch -# Parent f4870e0a3ea73f505d404cb84b496614a3db2af2 -Reword the README a bit - -diff --git a/README.rst b/README.rst ---- a/README.rst -+++ b/README.rst -@@ -10,8 +10,8 @@ - - - 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 @@ - 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 @@ - - 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: