changeset 206:18000309f5f7

Start the daemon module
author Louis Opter <kalessin@kalessin.fr>
date Tue, 28 Jul 2015 01:33:49 -0700
parents 84de8ebabd62
children de8eb0d227da
files daemon_module.patch series tag_untag.patch tag_untag_testing.patch
diffstat 4 files changed, 1403 insertions(+), 1459 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/daemon_module.patch	Tue Jul 28 01:33:49 2015 -0700
@@ -0,0 +1,369 @@
+# HG changeset patch
+# Parent  8a07956fa7acc29f77e279992ffc31813172ece4
+
+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
+diff --git a/core/daemon.c b/core/daemon.c
+new file mode 100644
+--- /dev/null
++++ b/core/daemon.c
+@@ -0,0 +1,107 @@
++// 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/types.h>
++#include <fcntl.h>
++#include <stdbool.h>
++#include <stdint.h>
++#include <stdio.h>
++#include <unistd.h>
++
++#if LGTD_HAVE_LIBBSD
++#include <bsd/bsd.h>
++#endif
++
++#include "listen.h"
++#include "daemon.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);
++#else
++    (void)argc;
++    (void)argv;
++    (void)envp;
++#endif
++}
++
++void
++lgtd_daemon_update_proctitle(void)
++{
++    char title[LGTD_DAEMON_TITLE_SIZE] = { 0 };
++    int i = snprintf(title, sizeof(title), "lightsd: ");
++
++#define INC(idx) LGTD_MIN((idx), (int)sizeof(title))
++#define PREFIX() (title[i - 1] == ')' ? "; " : "")
++#define APPEND() (title[i - 1] != '(' ? ", " : "")
++
++    if (!SLIST_EMPTY(&lgtd_listeners)) {
++        i = INC(snprintf(
++            title, sizeof(title) - i, "%slistening_on(", PREFIX()
++        ));
++        struct lgtd_listen *listener;
++        SLIST_FOREACH(listener, &lgtd_listeners, link) {
++            i = INC(snprintf(
++                title, sizeof(title) - i, "%s%s[:%s]",
++                APPEND(), listener->addr, listener->port
++            ));
++        }
++    }
++}
+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
+@@ -17,13 +17,11 @@
+ 
+ #include <sys/queue.h>
+ #include <sys/tree.h>
+-#include <sys/types.h>
+ #include <arpa/inet.h>
+ #include <assert.h>
+ #include <endian.h>
+ #include <err.h>
+ #include <errno.h>
+-#include <fcntl.h>
+ #include <getopt.h>
+ #include <signal.h>
+ #include <stdarg.h>
+@@ -33,11 +31,6 @@
+ #include <stdlib.h>
+ #include <string.h>
+ #include <strings.h>
+-#include <unistd.h>
+-
+-#if LGTD_HAVE_LIBBSD
+-#include <bsd/bsd.h>
+-#endif
+ 
+ #include <event2/event.h>
+ #include <event2/event_struct.h>
+@@ -54,6 +47,7 @@
+ #include "client.h"
+ #include "pipe.h"
+ #include "listen.h"
++#include "daemon.h"
+ #include "lightsd.h"
+ 
+ struct lgtd_opts lgtd_opts = {
+@@ -64,8 +58,6 @@
+ 
+ struct event_base *lgtd_ev_base = NULL;
+ 
+-const char *lgtd_binds;
+-
+ void
+ lgtd_cleanup(void)
+ {
+@@ -151,57 +143,11 @@
+     exit(0);
+ }
+ 
+-static bool
+-lgtd_daemonize(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 FORK()  do {        \
+-    switch (fork()) {       \
+-        case 0:             \
+-            break;          \
+-        case -1:            \
+-            return false;   \
+-        default:            \
+-            exit(0);        \
+-    }                       \
+-} while (0)
+-
+-    FORK();
+-    setsid();
+-
+-    FORK();
+-
+-    return true;
+-}
+-
+ int
+ main(int argc, char *argv[], char *envp[])
+ {
+-    char binds[512] = { 0 };
+-    lgtd_binds = binds;
++    lgtd_daemon_setup_proctitle(argc, argv, envp);
+ 
+-#if LGTD_HAVE_LIBBSD
+-    setproctitle_init(argc, argv, envp);
+-#else
+-    (void)envp;
+-#endif
+     lgtd_configure_libevent();
+     lgtd_configure_signal_handling();
+ 
+@@ -231,12 +177,6 @@
+             if (!sep || !sep[1]) {
+                 lgtd_usage(argv[0]);
+             }
+-            strncat(binds, optarg, LGTD_MIN(
+-                sizeof(binds) - strlen(binds) - 1, strlen(optarg)
+-            ));
+-            strncat(binds, ", ", LGTD_MIN(
+-                sizeof(binds) - strlen(binds) - 1, 2
+-            ));
+             *sep = '\0';
+             if (!lgtd_listen_open(optarg, sep + 1)) {
+                 exit(1);
+@@ -278,12 +218,6 @@
+         }
+     }
+ 
+-    binds[LGTD_MAX(strlen(binds) - 1, 0)] = '\0';
+-    binds[LGTD_MAX(strlen(binds) - 2, 0)] = '\0';
+-    if (binds[sizeof(binds) - 2]) {
+-        memset(binds + sizeof(binds) - 4, '.', 3);
+-    }
+-
+     argc -= optind;
+     argv += optind;
+ 
+@@ -292,7 +226,7 @@
+         lgtd_err(1, "can't setup lightsd");
+     }
+ 
+-    if (!lgtd_opts.foreground && !lgtd_daemonize()) {
++    if (!lgtd_opts.foreground && !lgtd_daemon_unleash()) {
+         lgtd_err(1, "can't fork to the background");
+     }
+ 
+diff --git a/core/lightsd.h b/core/lightsd.h
+--- a/core/lightsd.h
++++ b/core/lightsd.h
+@@ -47,7 +47,6 @@
+     enum lgtd_verbosity verbosity;
+ };
+ 
+-extern const char *lgtd_binds;
+ extern struct lgtd_opts lgtd_opts;
+ extern struct event_base *lgtd_ev_base;
+ 
+@@ -66,6 +65,5 @@
+ void lgtd_info(const char *, ...) __attribute__((format(printf, 1, 2)));
+ void lgtd_debug(const char *, ...) __attribute__((format(printf, 1, 2)));
+ void lgtd_libevent_log(int, const char *);
+-void lgtd_update_proctitle(void);
+ 
+ void lgtd_cleanup(void);
+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
+@@ -180,17 +180,3 @@
+     default:                                       break;
+     }
+ }
+-
+-void
+-lgtd_update_proctitle(void)
+-{
+-#if LGTD_HAVE_PROCTITLE
+-    setproctitle(
+-        "listening_on(%s); lifx_gateways(found=%d); bulbs(found=%d, on=%d)",
+-        lgtd_binds,
+-        LGTD_STATS_GET(gateways),
+-        LGTD_STATS_GET(bulbs),
+-        LGTD_STATS_GET(bulbs_powered_on)
+-    );
+-#endif
+-}
+diff --git a/core/stats.h b/core/stats.h
+--- a/core/stats.h
++++ b/core/stats.h
+@@ -27,7 +27,8 @@
+ 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_update_proctitle();                                    \
++
++#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)
--- a/series	Sat Jul 25 21:14:49 2015 -0700
+++ b/series	Tue Jul 28 01:33:49 2015 -0700
@@ -3,4 +3,4 @@
 ignore_duplicated_listening_addresses.patch
 add_command_pipe.patch
 fix_usage_and_version.patch
-tag_untag_testing.patch
+daemon_module.patch
--- a/tag_untag.patch	Sat Jul 25 21:14:49 2015 -0700
+++ b/tag_untag.patch	Tue Jul 28 01:33:49 2015 -0700
@@ -1,10 +1,16 @@
 # HG changeset patch
-# Parent  40af4d3ae6619e5faeef92beecc2e0d876eaf91b
+# Parent  c61333b9502c69273d1ea2eb5aee2ffed2cdc9c7
 Add the ability to tag (group) or untag (ungroup) bulbs
 
 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)
+ 
 @@ -35,7 +35,7 @@
  INCLUDE(AddAllSubdirectories)
  INCLUDE(AddTestFromSources)
@@ -14,6 +20,50 @@
  
  ADD_DEFINITIONS(
      # Only relevant for the GNU libc:
+@@ -58,10 +58,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/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/jsonrpc.c b/core/jsonrpc.c
 --- a/core/jsonrpc.c
 +++ b/core/jsonrpc.c
@@ -334,7 +384,29 @@
 diff --git a/lifx/bulb.c b/lifx/bulb.c
 --- a/lifx/bulb.c
 +++ b/lifx/bulb.c
-@@ -81,7 +81,6 @@
+@@ -76,12 +76,28 @@
+     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) {
++            if (LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id) & gw_bulb->state.tags) {
++                n++;
++            }
++        }
++        assert(bulb->gw->tag_refcounts[tag_id] == n - 1);
++    }
++#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);
@@ -342,7 +414,7 @@
      lgtd_info(
          "closed bulb \"%.*s\" (%s) on [%s]:%hu",
          LGTD_LIFX_LABEL_SIZE,
-@@ -107,6 +106,8 @@
+@@ -107,6 +123,8 @@
          );
      }
  
@@ -351,7 +423,7 @@
      bulb->last_light_state_at = received_at;
      memcpy(&bulb->state, state, sizeof(bulb->state));
  }
-@@ -124,3 +125,13 @@
+@@ -124,3 +142,13 @@
  
      bulb->state.power = power;
  }
@@ -1046,6 +1118,128 @@
 +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/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/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/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt
+--- a/tests/core/jsonrpc/CMakeLists.txt
++++ b/tests/core/jsonrpc/CMakeLists.txt
+@@ -3,7 +3,7 @@
+     ${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
+@@ -13,10 +13,6 @@
+     ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c
+     ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c
+ )
+-TARGET_LINK_LIBRARIES(test_core_jsonrpc ${TIME_MONOTONIC_LIBRARY})
+-IF (HAVE_LIBBSD)
+-    TARGET_LINK_LIBRARIES(test_core_jsonrpc ${LIBBSD_LIBRARY})
+-ENDIF (HAVE_LIBBSD)
+ 
+ FUNCTION(ADD_JSONRPC_TEST TEST_SOURCE)
+     ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_jsonrpc)
+diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c
+--- a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c
++++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ static void
+diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c
+--- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c
++++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ 
+ #define LGTD_TESTING_POWER_OFF
+ #include "test_jsonrpc_utils.h"
+diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c
+--- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c
++++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ 
+ #define LGTD_TESTING_POWER_OFF
+ #include "test_jsonrpc_utils.h"
+diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c
+--- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c
++++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ 
+ #define LGTD_TESTING_POWER_ON
+ #include "test_jsonrpc_utils.h"
+diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c
+--- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c
++++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ 
+ #define LGTD_TESTING_POWER_ON
+ #include "test_jsonrpc_utils.h"
+diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c
+--- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c
++++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ 
+ #define LGTD_TESTING_SET_LIGHT_FROM_HSBK
+ #include "test_jsonrpc_utils.h"
 diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c
 --- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c
 +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c
@@ -1053,7 +1247,7 @@
  #include "jsonrpc.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  
  #define LGTD_TESTING_SET_LIGHT_FROM_HSBK
  #include "test_jsonrpc_utils.h"
@@ -1064,18 +1258,41 @@
  #include "jsonrpc.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  
  #define LGTD_TESTING_SET_LIGHT_FROM_HSBK
  #include "test_jsonrpc_utils.h"
+diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c
+--- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c
++++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ 
+ #define LGTD_TESTING_SET_WAVEFORM
+ #include "test_jsonrpc_utils.h"
+diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c
+--- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c
++++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ 
+ #define LGTD_TESTING_SET_WAVEFORM
+ #include "test_jsonrpc_utils.h"
 diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c
 new file mode 100644
 --- /dev/null
 +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c
-@@ -0,0 +1,64 @@
+@@ -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"
@@ -1141,10 +1358,11 @@
 new file mode 100644
 --- /dev/null
 +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c
-@@ -0,0 +1,52 @@
+@@ -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"
@@ -1198,10 +1416,11 @@
 new file mode 100644
 --- /dev/null
 +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c
-@@ -0,0 +1,64 @@
+@@ -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"
@@ -1267,10 +1486,11 @@
 new file mode 100644
 --- /dev/null
 +++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c
-@@ -0,0 +1,52 @@
+@@ -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"
@@ -1320,6 +1540,182 @@
 +
 +    return 0;
 +}
+diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c
+--- a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c
++++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c
+--- a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c
++++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c
+--- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c
++++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c
+--- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c
++++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c
+--- a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c
++++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c
+--- a/tests/core/jsonrpc/test_jsonrpc_send_error.c
++++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_send_response.c b/tests/core/jsonrpc/test_jsonrpc_send_response.c
+--- a/tests/core/jsonrpc/test_jsonrpc_send_response.c
++++ b/tests/core/jsonrpc/test_jsonrpc_send_response.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c
+--- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c
++++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ static void
+diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c
+--- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c
++++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ static void
+diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c
+--- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c
++++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ static void
+diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c
+--- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c
++++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ static void
+diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer.c b/tests/core/jsonrpc/test_jsonrpc_type_integer.c
+--- a/tests/core/jsonrpc/test_jsonrpc_type_integer.c
++++ b/tests/core/jsonrpc/test_jsonrpc_type_integer.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c
+--- a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c
++++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c
+--- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c
++++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c
+--- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c
++++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c
+@@ -1,6 +1,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
+diff --git a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c
+--- a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c
++++ b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c
+@@ -3,6 +3,7 @@
+ #include "jsonrpc.c"
+ 
+ #include "mock_client_buf.h"
++#include "mock_gateway.h"
+ #include "test_jsonrpc_utils.h"
+ 
+ int
 diff --git a/tests/core/jsonrpc/test_jsonrpc_utils.h b/tests/core/jsonrpc/test_jsonrpc_utils.h
 --- a/tests/core/jsonrpc/test_jsonrpc_utils.h
 +++ b/tests/core/jsonrpc/test_jsonrpc_utils.h
@@ -1354,7 +1750,14 @@
 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
-@@ -9,6 +9,7 @@
+@@ -3,21 +3,18 @@
+     ${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
@@ -1362,6 +1765,15 @@
      ${LIGHTSD_SOURCE_DIR}/lifx/timer.c
      ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c
      ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c
+     ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c
+ )
+-TARGET_LINK_LIBRARIES(test_core_proto ${TIME_MONOTONIC_LIBRARY})
+-IF (HAVE_LIBBSD)
+-    TARGET_LINK_LIBRARIES(test_core_proto ${LIBBSD_LIBRARY})
+-ENDIF (HAVE_LIBBSD)
+ 
+ FUNCTION(ADD_ROUTER_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
@@ -1369,7 +1781,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_ROUTER_TARGETS_TO_DEVICES
@@ -1413,7 +1825,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_ROUTER_TARGETS_TO_DEVICES
@@ -1433,7 +1845,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_ROUTER_TARGETS_TO_DEVICES
@@ -1453,7 +1865,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_CLIENT_SEND_RESPONSE
@@ -1482,7 +1894,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_CLIENT_SEND_RESPONSE
@@ -1502,7 +1914,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_CLIENT_SEND_RESPONSE
@@ -1522,7 +1934,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_CLIENT_SEND_RESPONSE
@@ -1542,7 +1954,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_CLIENT_SEND_RESPONSE
@@ -1553,7 +1965,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_CLIENT_SEND_RESPONSE
@@ -1564,7 +1976,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_CLIENT_SEND_RESPONSE
@@ -1584,7 +1996,7 @@
  #include "proto.c"
  
  #include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
  #include "tests_utils.h"
  
  #define MOCKED_CLIENT_SEND_RESPONSE
@@ -1607,7 +2019,7 @@
 +#include "mock_client_buf.h"
 +#define MOCKED_LIFX_GATEWAY_SEND_TO_SITE
 +#define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID
-+#include "tests_shims.h"
++#include "mock_gateway.h"
 +#include "tests_utils.h"
 +
 +#define MOCKED_ROUTER_TARGETS_TO_DEVICES
@@ -1864,7 +2276,7 @@
 +#include "mock_client_buf.h"
 +#define MOCKED_LIFX_GATEWAY_SEND_TO_SITE
 +#define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID
-+#include "tests_shims.h"
++#include "mock_gateway.h"
 +#include "tests_utils.h"
 +
 +#define MOCKED_CLIENT_SEND_ERROR
@@ -2077,7 +2489,7 @@
 +#include "mock_client_buf.h"
 +#define MOCKED_LIFX_GATEWAY_SEND_TO_SITE
 +#define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID
-+#include "tests_shims.h"
++#include "mock_gateway.h"
 +#include "tests_utils.h"
 +
 +#define MOCKED_ROUTER_TARGETS_TO_DEVICES
@@ -2362,7 +2774,7 @@
 +#include "proto.c"
 +
 +#include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
 +#include "tests_utils.h"
 +
 +#define MOCKED_ROUTER_TARGETS_TO_DEVICES
@@ -2536,7 +2948,7 @@
 +#include "proto.c"
 +
 +#include "mock_client_buf.h"
-+#include "tests_shims.h"
++#include "mock_gateway.h"
 +#include "tests_utils.h"
 +
 +#define MOCKED_ROUTER_TARGETS_TO_DEVICES
@@ -2655,7 +3067,14 @@
 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
-@@ -9,6 +9,7 @@
+@@ -3,24 +3,20 @@
+     ${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
@@ -2663,6 +3082,19 @@
      ${LIGHTSD_SOURCE_DIR}/lifx/timer.c
      ${LIGHTSD_SOURCE_DIR}/lifx/wire_proto.c
      ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c
+     ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c
+ )
+ 
+-TARGET_LINK_LIBRARIES(
+-    test_core_router ${EVENT2_CORE_LIBRARY} ${TIME_MONOTONIC_LIBRARY}
+-)
+-IF (HAVE_LIBBSD)
+-    TARGET_LINK_LIBRARIES(test_core_router ${LIBBSD_LIBRARY})
+-ENDIF (HAVE_LIBBSD)
++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
@@ -2674,91 +3106,73 @@
  #include "tests_router_utils.h"
  
  int
+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
-@@ -70,14 +70,42 @@
-     (void)pkt;
+@@ -32,52 +32,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)
-+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)
- {
+-{
 -    struct lgtd_lifx_tag *tag = NULL;
 -    LIST_FOREACH(tag, &lgtd_lifx_tags, link) {
 -        if (!strcmp(tag->label, tag_label)) {
 -            break;
-+    (void)gw;
-+    (void)hdr;
-+    (void)pkt;
-+}
-+
-+void
-+lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id)
-+{
-+    (void)gw;
-+    (void)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 tag;
-+
-+    return -1;
- }
-+
-+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;
-+}
-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,27 @@
-+#pragma once
-+
-+#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
+-}
 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
@@ -2784,6 +3198,362 @@
 +
      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)",
++            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/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt
+--- a/tests/lifx/gateway/CMakeLists.txt
++++ b/tests/lifx/gateway/CMakeLists.txt
+@@ -3,25 +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})
+-IF (HAVE_LIBBSD)
+-    TARGET_LINK_LIBRARIES(test_lifx_gateway ${LIBBSD_LIBRARY})
+-ENDIF (HAVE_LIBBSD)
+ 
+ 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/gateway/test_gateway_allocate_tag_id.c b/tests/lifx/gateway/test_gateway_allocate_tag_id.c
 new file mode 100644
 --- /dev/null
@@ -3096,6 +3866,167 @@
 +
 +    return 0;
 +}
+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,131 @@
++#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_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)
++{
++    (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
+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,18 +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
+ )
+-IF (HAVE_LIBBSD)
+-    TARGET_LINK_LIBRARIES(test_lifx_wire_proto ${LIBBSD_LIBRARY})
+-ENDIF (HAVE_LIBBSD)
+ 
+ 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")
 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
--- a/tag_untag_testing.patch	Sat Jul 25 21:14:49 2015 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1356 +0,0 @@
-# HG changeset patch
-# Parent  b82dfd3266817224388aad97e687b852b4ae658f
-
-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)
- 
-@@ -58,10 +58,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/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/lifx/bulb.c b/lifx/bulb.c
---- a/lifx/bulb.c
-+++ b/lifx/bulb.c
-@@ -76,6 +76,23 @@
-     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) {
-+            if (LGTD_LIFX_WIRE_TAG_ID_TO_VALUE(tag_id) & gw_bulb->state.tags) {
-+                n++;
-+            }
-+        }
-+        assert(bulb->gw->tag_refcounts[tag_id] == n - 1);
-+    }
-+#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);
-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/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/CMakeLists.txt b/tests/core/jsonrpc/CMakeLists.txt
---- a/tests/core/jsonrpc/CMakeLists.txt
-+++ b/tests/core/jsonrpc/CMakeLists.txt
-@@ -3,7 +3,7 @@
-     ${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
-@@ -13,10 +13,6 @@
-     ${CMAKE_CURRENT_SOURCE_DIR}/../tests_shims.c
-     ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c
- )
--TARGET_LINK_LIBRARIES(test_core_jsonrpc ${TIME_MONOTONIC_LIBRARY})
--IF (HAVE_LIBBSD)
--    TARGET_LINK_LIBRARIES(test_core_jsonrpc ${LIBBSD_LIBRARY})
--ENDIF (HAVE_LIBBSD)
- 
- FUNCTION(ADD_JSONRPC_TEST TEST_SOURCE)
-     ADD_TEST_FROM_C_SOURCES(${TEST_SOURCE} test_core_jsonrpc)
-diff --git a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c
---- a/tests/core/jsonrpc/test_jsonrpc_build_target_list.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_build_target_list.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- static void
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_POWER_OFF
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_off_missing_target.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_POWER_OFF
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_POWER_ON
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_power_on_missing_target.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_POWER_ON
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_SET_LIGHT_FROM_HSBK
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_from_array.c
-@@ -1,7 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_SET_LIGHT_FROM_HSBK
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_light_from_hsbk_invalid_params.c
-@@ -1,7 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_SET_LIGHT_FROM_HSBK
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_SET_WAVEFORM
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_set_waveform_invalid_params.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define LGTD_TESTING_SET_WAVEFORM
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define MOCKED_LGTD_TAG
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_tag_missing_params.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define MOCKED_LGTD_TAG
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define MOCKED_LGTD_UNTAG
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c
---- a/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_check_and_call_untag_invalid_params.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- 
- #define MOCKED_LGTD_UNTAG
- #include "test_jsonrpc_utils.h"
-diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c
---- a/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_no_params.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c
---- a/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_notification_no_params.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c
---- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_array.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c
---- a/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_params_obj.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c
---- a/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_extract_request_valid_notification.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_send_error.c b/tests/core/jsonrpc/test_jsonrpc_send_error.c
---- a/tests/core/jsonrpc/test_jsonrpc_send_error.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_send_error.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_send_response.c b/tests/core/jsonrpc/test_jsonrpc_send_response.c
---- a/tests/core/jsonrpc/test_jsonrpc_send_response.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_send_response.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c
---- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_invalid.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- static void
-diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c
---- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_1_valid.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- static void
-diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c
---- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_invalid.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- static void
-diff --git a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c
---- a/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_type_float_between_0_and_360_valid.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- static void
-diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer.c b/tests/core/jsonrpc/test_jsonrpc_type_integer.c
---- a/tests/core/jsonrpc/test_jsonrpc_type_integer.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_type_integer.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c
---- a/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_invalid_characters.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c
---- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_big.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c
---- a/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_type_integer_too_small.c
-@@ -1,6 +1,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-diff --git a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c
---- a/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c
-+++ b/tests/core/jsonrpc/test_jsonrpc_uint16_range_to_float_string.c
-@@ -3,6 +3,7 @@
- #include "jsonrpc.c"
- 
- #include "mock_client_buf.h"
-+#include "mock_gateway.h"
- #include "test_jsonrpc_utils.h"
- 
- int
-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,7 +3,7 @@
-     ${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
-@@ -15,10 +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})
--IF (HAVE_LIBBSD)
--    TARGET_LINK_LIBRARIES(test_core_proto ${LIBBSD_LIBRARY})
--ENDIF (HAVE_LIBBSD)
- 
- FUNCTION(ADD_ROUTER_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,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.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,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.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_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,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.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,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- #include "tests_utils.h"
- 
- #define MOCKED_CLIENT_SEND_RESPONSE
-diff --git a/tests/core/proto/test_proto_power_off_routing_error.c b/tests/core/proto/test_proto_power_off_routing_error.c
---- a/tests/core/proto/test_proto_power_off_routing_error.c
-+++ b/tests/core/proto/test_proto_power_off_routing_error.c
-@@ -1,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.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,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.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,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.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,7 +3,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.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,7 +3,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.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,7 +3,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.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,7 +3,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- #include "tests_utils.h"
- 
- #define MOCKED_CLIENT_SEND_RESPONSE
-diff --git a/tests/core/proto/test_proto_tag_create.c b/tests/core/proto/test_proto_tag_create.c
---- a/tests/core/proto/test_proto_tag_create.c
-+++ b/tests/core/proto/test_proto_tag_create.c
-@@ -3,7 +3,7 @@
- #include "mock_client_buf.h"
- #define MOCKED_LIFX_GATEWAY_SEND_TO_SITE
- #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- #include "tests_utils.h"
- 
- #define MOCKED_ROUTER_TARGETS_TO_DEVICES
-diff --git a/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c b/tests/core/proto/test_proto_tag_create_lifx_gw_tag_ids_full.c
---- 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
-@@ -3,7 +3,7 @@
- #include "mock_client_buf.h"
- #define MOCKED_LIFX_GATEWAY_SEND_TO_SITE
- #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- #include "tests_utils.h"
- 
- #define MOCKED_CLIENT_SEND_ERROR
-diff --git a/tests/core/proto/test_proto_tag_update.c b/tests/core/proto/test_proto_tag_update.c
---- a/tests/core/proto/test_proto_tag_update.c
-+++ b/tests/core/proto/test_proto_tag_update.c
-@@ -3,7 +3,7 @@
- #include "mock_client_buf.h"
- #define MOCKED_LIFX_GATEWAY_SEND_TO_SITE
- #define MOCKED_LIFX_GATEWAY_ALLOCATE_TAG_ID
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- #include "tests_utils.h"
- 
- #define MOCKED_ROUTER_TARGETS_TO_DEVICES
-diff --git a/tests/core/proto/test_proto_untag.c b/tests/core/proto/test_proto_untag.c
---- a/tests/core/proto/test_proto_untag.c
-+++ b/tests/core/proto/test_proto_untag.c
-@@ -1,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- #include "tests_utils.h"
- 
- #define MOCKED_ROUTER_TARGETS_TO_DEVICES
-diff --git a/tests/core/proto/test_proto_untag_tag_does_not_exist.c b/tests/core/proto/test_proto_untag_tag_does_not_exist.c
---- a/tests/core/proto/test_proto_untag_tag_does_not_exist.c
-+++ b/tests/core/proto/test_proto_untag_tag_does_not_exist.c
-@@ -1,7 +1,7 @@
- #include "proto.c"
- 
- #include "mock_client_buf.h"
--#include "tests_shims.h"
-+#include "mock_gateway.h"
- #include "tests_utils.h"
- 
- #define MOCKED_ROUTER_TARGETS_TO_DEVICES
-diff --git a/tests/core/router/CMakeLists.txt b/tests/core/router/CMakeLists.txt
---- a/tests/core/router/CMakeLists.txt
-+++ b/tests/core/router/CMakeLists.txt
-@@ -3,7 +3,7 @@
-     ${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
-@@ -16,12 +16,7 @@
-     ${CMAKE_CURRENT_SOURCE_DIR}/../tests_utils.c
- )
- 
--TARGET_LINK_LIBRARIES(
--    test_core_router ${EVENT2_CORE_LIBRARY} ${TIME_MONOTONIC_LIBRARY}
--)
--IF (HAVE_LIBBSD)
--    TARGET_LINK_LIBRARIES(test_core_router ${LIBBSD_LIBRARY})
--ENDIF (HAVE_LIBBSD)
-+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/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
-@@ -32,80 +32,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;
--}
--
--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;
--}
--
--void
--lgtd_lifx_gateway_deallocate_tag_id(struct lgtd_lifx_gateway *gw, int tag_id)
--{
--    (void)gw;
--    (void)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;
--}
--
--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;
--}
-diff --git a/tests/core/tests_shims.h b/tests/core/tests_shims.h
-deleted file mode 100644
---- a/tests/core/tests_shims.h
-+++ /dev/null
-@@ -1,27 +0,0 @@
--#pragma once
--
--#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
-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)",
-+            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/CMakeLists.txt b/tests/lifx/gateway/CMakeLists.txt
---- a/tests/lifx/gateway/CMakeLists.txt
-+++ b/tests/lifx/gateway/CMakeLists.txt
-@@ -3,25 +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})
--IF (HAVE_LIBBSD)
--    TARGET_LINK_LIBRARIES(test_lifx_gateway ${LIBBSD_LIBRARY})
--ENDIF (HAVE_LIBBSD)
- 
- 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/mock_gateway.h b/tests/lifx/mock_gateway.h
-new file mode 100644
---- /dev/null
-+++ b/tests/lifx/mock_gateway.h
-@@ -0,0 +1,131 @@
-+#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_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)
-+{
-+    (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
-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,18 +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
- )
--IF (HAVE_LIBBSD)
--    TARGET_LINK_LIBRARIES(test_lifx_wire_proto ${LIBBSD_LIBRARY})
--ENDIF (HAVE_LIBBSD)
- 
- 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")