From e47e90488ae6ebc730b0722a3f2bcd0a359247a3 Mon Sep 17 00:00:00 2001
From: Daniel Salzman <daniel.salzman@nic.cz>
Date: Fri, 18 Mar 2016 08:54:13 +0100
Subject: [PATCH] libknot: add server control interface

---
 Knot.files                    |   3 +
 src/Makefile.am               |   2 +
 src/libknot/control/control.c | 565 ++++++++++++++++++++++++++++++++++
 src/libknot/control/control.h | 157 ++++++++++
 src/libknot/libknot.h         |   3 +-
 tests/.gitignore              |   1 +
 tests/Makefile.am             |   1 +
 tests/control.c               | 221 +++++++++++++
 8 files changed, 952 insertions(+), 1 deletion(-)
 create mode 100644 src/libknot/control/control.c
 create mode 100644 src/libknot/control/control.h
 create mode 100644 tests/control.c

diff --git a/Knot.files b/Knot.files
index dd77c651d..fe098c00f 100644
--- a/Knot.files
+++ b/Knot.files
@@ -347,6 +347,8 @@ src/libknot/binary.h
 src/libknot/codes.c
 src/libknot/codes.h
 src/libknot/consts.h
+src/libknot/control/control.c
+src/libknot/control/control.h
 src/libknot/db/db.h
 src/libknot/db/db_lmdb.c
 src/libknot/db/db_lmdb.h
@@ -497,6 +499,7 @@ tests/conf.c
 tests/conf_tools.c
 tests/confdb.c
 tests/confio.c
+tests/control.c
 tests/db.c
 tests/descriptor.c
 tests/dname.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 2f1ba223a..cee2e0eb5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -98,6 +98,7 @@ nobase_libknot_la_HEADERS =			\
 	libknot/binary.h			\
 	libknot/codes.h				\
 	libknot/consts.h			\
+	libknot/control/control.h		\
 	libknot/descriptor.h			\
 	libknot/dname.h				\
 	libknot/dnssec/rrset-sign.h		\
@@ -141,6 +142,7 @@ nobase_libknot_la_HEADERS =			\
 libknot_la_SOURCES =				\
 	libknot/binary.c			\
 	libknot/codes.c				\
+	libknot/control/control.c		\
 	libknot/descriptor.c			\
 	libknot/dname.c				\
 	libknot/dnssec/rrset-sign.c		\
diff --git a/src/libknot/control/control.c b/src/libknot/control/control.c
new file mode 100644
index 000000000..677c1e1ae
--- /dev/null
+++ b/src/libknot/control/control.c
@@ -0,0 +1,565 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program 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.
+
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libknot/control/control.h"
+#include "libknot/attribute.h"
+#include "libknot/error.h"
+#include "contrib/mempattern.h"
+#include "contrib/net.h"
+#include "contrib/sockaddr.h"
+#include "contrib/ucw/mempool.h"
+#include "contrib/wire_ctx.h"
+
+/*! Size of the input and output buffers. */
+#ifndef CTL_BUFF_SIZE
+#define CTL_BUFF_SIZE		(256 * 1024)
+#endif
+
+/*! Default socket operations timeout in milliseconds. */
+#define DEFAULT_TIMEOUT		(5 * 1000)
+
+/*! The first data item code. */
+#define DATA_CODE_OFFSET	16
+
+/*! Control context structure. */
+struct knot_ctl {
+	/*! Memory pool context. */
+	knot_mm_t mm;
+	/*! Network operations timeout. */
+	int timeout;
+	/*! Server listening socket. */
+	int listen_sock;
+	/*! Remote server/client socket. */
+	int sock;
+
+	/*! The latter read data. */
+	knot_ctl_data_t data;
+
+	/*! Write wire context. */
+	wire_ctx_t wire_out;
+	/*! Read wire context. */
+	wire_ctx_t wire_in;
+
+	/*! Write buffer. */
+	uint8_t buff_out[CTL_BUFF_SIZE];
+	/*! Read buffer. */
+	uint8_t buff_in[CTL_BUFF_SIZE];
+};
+
+static int type_to_code(knot_ctl_type_t type)
+{
+	switch (type) {
+	case KNOT_CTL_TYPE_END:   return  0;
+	case KNOT_CTL_TYPE_DATA:  return  1;
+	case KNOT_CTL_TYPE_EXTRA: return  2;
+	case KNOT_CTL_TYPE_BLOCK: return  3;
+	default:                  return -1;
+	}
+}
+
+static int code_to_type(uint8_t code)
+{
+	switch (code) {
+	case 0:  return KNOT_CTL_TYPE_END;
+	case 1:  return KNOT_CTL_TYPE_DATA;
+	case 2:  return KNOT_CTL_TYPE_EXTRA;
+	case 3:  return KNOT_CTL_TYPE_BLOCK;
+	default: return -1;
+	}
+}
+
+static bool is_data_type(knot_ctl_type_t type)
+{
+	switch (type) {
+	case KNOT_CTL_TYPE_DATA:
+	case KNOT_CTL_TYPE_EXTRA:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static int idx_to_code(knot_ctl_idx_t idx)
+{
+	if (idx >= KNOT_CTL_IDX__COUNT) {
+		return -1;
+	}
+
+	return DATA_CODE_OFFSET + idx;
+}
+
+static int code_to_idx(uint8_t code)
+{
+	if (code <  DATA_CODE_OFFSET ||
+	    code >= DATA_CODE_OFFSET + KNOT_CTL_IDX__COUNT) {
+		return -1;
+	}
+
+	return code - DATA_CODE_OFFSET;
+}
+
+static void reset_buffers(knot_ctl_t *ctx)
+{
+	ctx->wire_out = wire_ctx_init(ctx->buff_out, CTL_BUFF_SIZE);
+	ctx->wire_in = wire_ctx_init(ctx->buff_in, 0);
+}
+
+static void clean_data(knot_ctl_t *ctx)
+{
+	for (knot_ctl_idx_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) {
+		if (ctx->data[i] != NULL) {
+			mm_free(&ctx->mm, (void *)ctx->data[i]);
+			ctx->data[i] = NULL;
+		}
+	}
+}
+
+static void close_sock(int *sock)
+{
+	if (*sock < 0) {
+		return;
+	}
+
+	close(*sock);
+	*sock = -1;
+}
+
+_public_
+knot_ctl_t* knot_ctl_alloc(void)
+{
+	knot_ctl_t *ctx = malloc(sizeof(*ctx));
+	if (ctx == NULL) {
+		return NULL;
+	}
+	memset(ctx, 0, sizeof(*ctx));
+
+	mm_ctx_mempool(&ctx->mm, MM_DEFAULT_BLKSIZE);
+	ctx->timeout = DEFAULT_TIMEOUT;
+	ctx->listen_sock = -1;
+	ctx->sock = -1;
+
+	reset_buffers(ctx);
+
+	return ctx;
+}
+
+_public_
+void knot_ctl_free(knot_ctl_t *ctx)
+{
+	if (ctx == NULL) {
+		return;
+	}
+
+	close_sock(&ctx->listen_sock);
+	close_sock(&ctx->sock);
+
+	clean_data(ctx);
+
+	mp_delete(ctx->mm.ctx);
+
+	memset(ctx, 0, sizeof(*ctx));
+	free(ctx);
+}
+
+_public_
+void knot_ctl_set_timeout(knot_ctl_t *ctx, int timeout_ms)
+{
+	if (ctx == NULL) {
+		return;
+	}
+
+	ctx->timeout = (timeout_ms > 0) ? timeout_ms : -1;
+}
+
+_public_
+int knot_ctl_bind(knot_ctl_t *ctx, const char *path)
+{
+	if (ctx == NULL || path == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	// Prepare socket address.
+	struct sockaddr_storage addr;
+	int ret = sockaddr_set(&addr, AF_UNIX, path, 0);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Bind the socket.
+	ctx->listen_sock = net_bound_socket(SOCK_STREAM, &addr, 0);
+	if (ctx->listen_sock < 0) {
+		return ctx->listen_sock;
+	}
+
+	// Start listening.
+	if (listen(ctx->listen_sock, 1) != 0) {
+		close_sock(&ctx->listen_sock);
+		return knot_map_errno();
+	}
+
+	return KNOT_EOK;
+}
+
+_public_
+void knot_ctl_unbind(knot_ctl_t *ctx)
+{
+	if (ctx == NULL || ctx->listen_sock < 0) {
+		return;
+	}
+
+	// Remove the control socket file.
+	struct sockaddr_storage addr;
+	socklen_t addr_len = sizeof(addr);
+	if (getsockname(ctx->listen_sock, (struct sockaddr *)&addr, &addr_len) == 0) {
+		char addr_str[SOCKADDR_STRLEN] = { 0 };
+		if (sockaddr_tostr(addr_str, sizeof(addr_str), &addr) > 0) {
+			(void)unlink(addr_str);
+		}
+	}
+
+	// Close the listening socket.
+	close_sock(&ctx->listen_sock);
+}
+
+_public_
+int knot_ctl_accept(knot_ctl_t *ctx)
+{
+	if (ctx == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	knot_ctl_close(ctx);
+
+	// Control interface.
+	struct pollfd pfd = { .fd = ctx->listen_sock, .events = POLLIN };
+	int ret = poll(&pfd, 1, -1);
+	if (ret <= 0) {
+		return knot_map_errno();
+	}
+
+	int client = net_accept(ctx->listen_sock, NULL);
+	if (client < 0) {
+		return client;
+	}
+
+	ctx->sock = client;
+
+	reset_buffers(ctx);
+
+	return KNOT_EOK;
+}
+
+_public_
+int knot_ctl_connect(knot_ctl_t *ctx, const char *path)
+{
+	if (ctx == NULL || path == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	// Prepare socket address.
+	struct sockaddr_storage addr;
+	int ret = sockaddr_set(&addr, AF_UNIX, path, 0);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Connect to socket.
+	ctx->sock = net_connected_socket(SOCK_STREAM, &addr, NULL);
+	if (ctx->sock < 0) {
+		return ctx->sock;
+	}
+
+	reset_buffers(ctx);
+
+	return KNOT_EOK;
+}
+
+_public_
+void knot_ctl_close(knot_ctl_t *ctx)
+{
+	if (ctx == NULL) {
+		return;
+	}
+
+	close_sock(&ctx->sock);
+}
+
+static int ensure_output(knot_ctl_t *ctx, uint16_t len)
+{
+	wire_ctx_t *w = &ctx->wire_out;
+
+	// Check for enough available room in the output buffer.
+	size_t available = wire_ctx_available(w);
+	if (available >= len) {
+		return KNOT_EOK;
+	}
+
+	// Flush the buffer.
+	int ret = net_stream_send(ctx->sock, w->wire, wire_ctx_offset(w),
+	                          ctx->timeout);
+	if (ret < 0) {
+		return ret;
+	}
+
+	*w = wire_ctx_init(w->wire, CTL_BUFF_SIZE);
+
+	return KNOT_EOK;
+}
+
+static int send_item(knot_ctl_t *ctx, uint8_t code, const char *data, bool flush)
+{
+	wire_ctx_t *w = &ctx->wire_out;
+
+	// Write the control block code.
+	int ret = ensure_output(ctx, sizeof(uint8_t));
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	wire_ctx_write_u8(w, code);
+	if (w->error != KNOT_EOK) {
+		return w->error;
+	}
+
+	// Control block data is optional.
+	if (data != NULL) {
+		// Get the data length.
+		size_t data_len = strlen(data);
+		if (data_len > UINT16_MAX) {
+			return KNOT_ERANGE;
+		}
+
+		// Write the data length.
+		ret = ensure_output(ctx, sizeof(uint16_t));
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+		wire_ctx_write_u16(w, data_len);
+		if (w->error != KNOT_EOK) {
+			return w->error;
+		}
+
+		// Write the data.
+		ret = ensure_output(ctx, data_len);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+		wire_ctx_write(w, (uint8_t *)data, data_len);
+		if (w->error != KNOT_EOK) {
+			return w->error;
+		}
+	}
+
+	// Send finalized buffer.
+	if (flush && wire_ctx_offset(w) > 0) {
+		ret = net_stream_send(ctx->sock, w->wire, wire_ctx_offset(w),
+		                      ctx->timeout);
+		if (ret < 0) {
+			return ret;
+		}
+
+		*w = wire_ctx_init(w->wire, CTL_BUFF_SIZE);
+	}
+
+	return KNOT_EOK;
+}
+
+_public_
+int knot_ctl_send(knot_ctl_t *ctx, knot_ctl_type_t type, knot_ctl_data_t *data)
+{
+	if (ctx == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	// Get the type code.
+	int code = type_to_code(type);
+	if (code == -1) {
+		return KNOT_EINVAL;
+	}
+
+	// Send unit type.
+	int ret = send_item(ctx, code, NULL, !is_data_type(type));
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Send unit data.
+	if (is_data_type(type) && data != NULL) {
+		// Send all non-empty data items.
+		for (knot_ctl_idx_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) {
+			const char *value = (*data)[i];
+			if (value == NULL) {
+				continue;
+			}
+
+			ret = send_item(ctx, idx_to_code(i), value, false);
+			if (ret != KNOT_EOK) {
+				return ret;
+			}
+		}
+	}
+
+	return KNOT_EOK;
+}
+
+static int ensure_input(knot_ctl_t *ctx, uint16_t len)
+{
+	wire_ctx_t *w = &ctx->wire_in;
+
+	// Check for enough available room in the input buffer.
+	size_t available = wire_ctx_available(w);
+	if (available >= len) {
+		return KNOT_EOK;
+	}
+
+	// Move unprocessed data to the beginning of the buffer.
+	memmove(w->wire, w->wire + wire_ctx_offset(w), available);
+
+	// Receive enough data.
+	while (available < len) {
+		int ret = net_stream_recv(ctx->sock, w->wire + available,
+		                          CTL_BUFF_SIZE - available,
+		                          ctx->timeout);
+		if (ret < 0) {
+			return ret;
+		}
+		assert(ret > 0);
+		available += ret;
+	}
+
+	ctx->wire_in = wire_ctx_init(w->wire, available);
+
+	return KNOT_EOK;
+}
+
+static int receive_item_code(knot_ctl_t *ctx, uint8_t *code)
+{
+	wire_ctx_t *w = &ctx->wire_in;
+
+	// Read the type.
+	int ret = ensure_input(ctx, sizeof(uint8_t));
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	*code = wire_ctx_read_u8(w);
+	if (w->error != KNOT_EOK) {
+		return w->error;
+	}
+
+	return KNOT_EOK;
+}
+
+static int receive_item_value(knot_ctl_t *ctx, char **value)
+{
+	wire_ctx_t *w = &ctx->wire_in;
+
+	// Read value length.
+	int ret = ensure_input(ctx, sizeof(uint16_t));
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	uint16_t data_len = wire_ctx_read_u16(w);
+	if (w->error != KNOT_EOK) {
+		return w->error;
+	}
+
+	// Read the value.
+	ret = ensure_input(ctx, data_len);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	*value = mm_alloc(&ctx->mm, data_len + 1);
+	if (*value == NULL) {
+		return KNOT_ENOMEM;
+	}
+	wire_ctx_read(w, (uint8_t *)*value, data_len);
+	if (w->error != KNOT_EOK) {
+		return w->error;
+	}
+	(*value)[data_len] = '\0';
+
+	return KNOT_EOK;
+}
+
+_public_
+int knot_ctl_receive(knot_ctl_t *ctx, knot_ctl_type_t *type, knot_ctl_data_t *data)
+{
+	if (ctx == NULL || type == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	wire_ctx_t *w = &ctx->wire_in;
+
+	// Reset output variables.
+	*type = KNOT_CTL_TYPE_END;
+	clean_data(ctx);
+
+	// Read data units until end of message.
+	bool have_type = false;
+	while (true) {
+		uint8_t code;
+		int ret = receive_item_code(ctx, &code);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+
+		// Process unit type.
+		int current_type = code_to_type(code);
+		if (current_type != -1) {
+			if (have_type) {
+				// Revert parsed type.
+				wire_ctx_skip(w, -sizeof(uint8_t));
+				assert(w->error == KNOT_EOK);
+				break;
+			}
+
+			// Set the unit type.
+			*type = current_type;
+
+			if (is_data_type(current_type)) {
+				have_type = true;
+				continue;
+			} else {
+				break;
+			}
+		}
+
+		// Check for data item code.
+		int idx = code_to_idx(code);
+		if (idx == -1) {
+			return KNOT_EINVAL;
+		}
+
+		// Store the item data value.
+		ret = receive_item_value(ctx, (char **)&ctx->data[idx]);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+	}
+
+	// Set the output data.
+	if (data != NULL) {
+		memcpy(*data, ctx->data, sizeof(*data));
+	}
+
+	return KNOT_EOK;
+}
diff --git a/src/libknot/control/control.h b/src/libknot/control/control.h
new file mode 100644
index 000000000..ad91cc05d
--- /dev/null
+++ b/src/libknot/control/control.h
@@ -0,0 +1,157 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program 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.
+
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+/*!
+ * \file
+ *
+ * \brief A server control interface.
+ *
+ * \addtogroup libknot
+ * @{
+ */
+
+#pragma once
+
+/*! Control data item indexes. */
+typedef enum {
+	KNOT_CTL_IDX_CMD = 0, /*!< Control command name. */
+	KNOT_CTL_IDX_FLAGS,   /*!< Control command flags. */
+	KNOT_CTL_IDX_ERROR,   /*!< Error message. */
+	KNOT_CTL_IDX_SECTION, /*!< Configuration section name. */
+	KNOT_CTL_IDX_ITEM,    /*!< Configuration item name. */
+	KNOT_CTL_IDX_ID,      /*!< Congiguration item identifier. */
+	KNOT_CTL_IDX_ZONE,    /*!< Zone name. */
+	KNOT_CTL_IDX_OWNER,   /*!< Zone record owner */
+	KNOT_CTL_IDX_TTL,     /*!< Zone record TTL. */
+	KNOT_CTL_IDX_TYPE,    /*!< Zone record type name. */
+	KNOT_CTL_IDX_DATA,    /*!< Configuration item/zone record data. */
+	KNOT_CTL_IDX__COUNT,  /*!< The number of data items. */
+} knot_ctl_idx_t;
+
+/*! Control unit types. */
+typedef enum {
+	KNOT_CTL_TYPE_END,   /*!< End of message, cache flushed. */
+	KNOT_CTL_TYPE_DATA,  /*!< Data unit, cached. */
+	KNOT_CTL_TYPE_EXTRA, /*!< Extra value data unit, cached. */
+	KNOT_CTL_TYPE_BLOCK, /*!< End of data block, cache flushed. */
+} knot_ctl_type_t;
+
+/*! Control input/output string data. */
+typedef const char* knot_ctl_data_t[KNOT_CTL_IDX__COUNT];
+
+/*! A control context. */
+struct knot_ctl;
+typedef struct knot_ctl knot_ctl_t;
+
+/*!
+ * Allocates a control context.
+ *
+ * \return Control context.
+ */
+knot_ctl_t* knot_ctl_alloc(void);
+
+/*!
+ * Deallocates a control context.
+ *
+ * \param[in] ctx  Control context.
+ */
+void knot_ctl_free(knot_ctl_t *ctx);
+
+/*!
+ * Sets the timeout for socket operations.
+ *
+ * Default value is 5 seconds.
+ *
+ * \param[in] ctx      Control context.
+ * \param[in] timeout  Timeout in milliseconds (0 for infinity).
+ */
+void knot_ctl_set_timeout(knot_ctl_t *ctx, int timeout_ms);
+
+/*!
+ * Binds a specified UNIX socket path.
+ *
+ * \note Server operation.
+ *
+ * \param[in] ctx   Control context.
+ * \param[in] path  Control UNIX socket path.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int knot_ctl_bind(knot_ctl_t *ctx, const char *path);
+
+/*!
+ * Unbinds a control socket.
+ *
+ * \note Server operation.
+ *
+ * \param[in] ctx  Control context.
+ */
+void knot_ctl_unbind(knot_ctl_t *ctx);
+
+/*!
+ * Connects to a specified UNIX socket path.
+ *
+ * \note Client operation.
+ *
+ * \param[in] ctx   Control context.
+ * \param[in] path  Control UNIX socket path.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int knot_ctl_connect(knot_ctl_t *ctx, const char *path);
+
+/*!
+ * Waits for an incomming connection.
+ *
+ * \note Server operation.
+ *
+ * \param[in] ctx  Control context.
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int knot_ctl_accept(knot_ctl_t *ctx);
+
+/*!
+ * Closes the remote connections.
+ *
+ * \note Applies to both server and client.
+ *
+ * \param[in] ctx  Control context.
+ */
+void knot_ctl_close(knot_ctl_t *ctx);
+
+/*!
+ * Sends one control unit.
+ *
+ * \param[in] ctx   Control context.
+ * \param[in] type  Unit type to send.
+ * \param[in] data  Data unit to send (optional, ignored if non-data type).
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int knot_ctl_send(knot_ctl_t *ctx, knot_ctl_type_t type, knot_ctl_data_t *data);
+
+/*!
+ * Receives one control unit.
+ *
+ * \param[in] ctx    Control context.
+ * \param[out] type  Received unit type.
+ * \param[out] data  Received data unit (optional).
+ *
+ * \return Error code, KNOT_EOK if successful.
+ */
+int knot_ctl_receive(knot_ctl_t *ctx, knot_ctl_type_t *type, knot_ctl_data_t *data);
+
+/*! @} */
diff --git a/src/libknot/libknot.h b/src/libknot/libknot.h
index b925b7764..712109879 100644
--- a/src/libknot/libknot.h
+++ b/src/libknot/libknot.h
@@ -1,4 +1,4 @@
-/*  Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
 
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@
 #include "libknot/rrset.h"
 #include "libknot/tsig-op.h"
 #include "libknot/tsig.h"
+#include "libknot/control/control.h"
 #include "libknot/db/db.h"
 #include "libknot/db/db_lmdb.h"
 #include "libknot/db/db_trie.h"
diff --git a/tests/.gitignore b/tests/.gitignore
index a45978541..986a1266d 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -11,6 +11,7 @@ conf
 conf_tools
 confdb
 confio
+control
 db
 descriptor
 dname
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 69be33925..86db0ff2c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -19,6 +19,7 @@ check_PROGRAMS = \
 	conf_tools			\
 	confdb				\
 	confio				\
+	control				\
 	db				\
 	descriptor			\
 	dname				\
diff --git a/tests/control.c b/tests/control.c
new file mode 100644
index 000000000..e4052f954
--- /dev/null
+++ b/tests/control.c
@@ -0,0 +1,221 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program 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.
+
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <tap/basic.h>
+#include <tap/files.h>
+
+#define CTL_BUFF_SIZE	18
+#include "libknot/control/control.c"
+
+#define fake_ok(condition, msg, ...) \
+	if (!(condition)) { \
+		if (msg != NULL) { \
+			printf("error: " msg "\n", ##__VA_ARGS__); \
+		} \
+		exit(-1); \
+	}
+
+static void ctl_client(const char *socket, size_t argc, knot_ctl_data_t *argv)
+{
+	knot_ctl_t *ctl = knot_ctl_alloc();
+	fake_ok(ctl != NULL, "Allocate control");
+
+	int ret;
+	for (int i = 0; i < 20; i++) {
+		ret = knot_ctl_connect(ctl, socket);
+		if (ret == KNOT_EOK) {
+			break;
+		}
+		usleep(100000);
+	}
+	fake_ok(ret == KNOT_EOK, "Connect to socket");
+
+	diag("BEGIN: Client -> Server");
+
+	if (argc > 0) {
+		for (size_t i = 0; i < argc; i++) {
+			if (argv[i][KNOT_CTL_IDX_CMD] != NULL &&
+			    argv[i][KNOT_CTL_IDX_CMD][0] == '\0') {
+				ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL);
+				fake_ok(ret == KNOT_EOK, "Client send data block end type");
+			} else {
+				ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &argv[i]);
+				fake_ok(ret == KNOT_EOK, "Client send data %zu", i);
+			}
+		}
+	}
+
+	ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL);
+	fake_ok(ret == KNOT_EOK, "Client send final data");
+
+	diag("END: Client -> Server");
+	diag("BEGIN: Client <- Server");
+
+	size_t count = 0;
+	knot_ctl_data_t data;
+	knot_ctl_type_t type;
+	while ((ret = knot_ctl_receive(ctl, &type, &data)) == KNOT_EOK) {
+		if (type == KNOT_CTL_TYPE_END) {
+			break;
+		}
+		if (argv[count][KNOT_CTL_IDX_CMD] != NULL &&
+		    argv[count][KNOT_CTL_IDX_CMD][0] == '\0') {
+			fake_ok(type == KNOT_CTL_TYPE_BLOCK, "Receive block end type");
+		} else {
+			fake_ok(type == KNOT_CTL_TYPE_DATA, "Check data type");
+			for (size_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) {
+				fake_ok((data[i] == NULL && argv[count][i] == NULL) ||
+					(data[i] != NULL && argv[count][i] != NULL),
+					"Client compare input item occupation %zu", i);
+				if (data[i] == NULL) {
+					continue;
+				}
+
+				fake_ok(strcmp(data[i], argv[count][i]) == 0,
+					"Client compare input item '%s", argv[count][i]);
+			}
+		}
+		count++;
+	}
+	fake_ok(ret == KNOT_EOK, "Receive OK check");
+	fake_ok(type == KNOT_CTL_TYPE_END, "Receive EOF type");
+	fake_ok(count == argc, "Client compare input count '%zu'", argc);
+
+	diag("END: Client <- Server");
+
+	knot_ctl_close(ctl);
+	knot_ctl_free(ctl);
+}
+
+static void ctl_server(const char *socket, size_t argc, knot_ctl_data_t *argv)
+{
+	knot_ctl_t *ctl = knot_ctl_alloc();
+	ok(ctl != NULL, "Allocate control");
+
+	int ret = knot_ctl_bind(ctl, socket);
+	ok(ret == KNOT_EOK, "Bind control socket");
+
+	ret = knot_ctl_accept(ctl);
+	ok(ret == KNOT_EOK, "Accept a connection");
+
+	diag("BEGIN: Server <- Client");
+
+	size_t count = 0;
+	knot_ctl_data_t data;
+	knot_ctl_type_t type;
+	while ((ret = knot_ctl_receive(ctl, &type, &data)) == KNOT_EOK) {
+		if (type == KNOT_CTL_TYPE_END) {
+			break;
+		}
+		if (argv[count][KNOT_CTL_IDX_CMD] != NULL &&
+		    argv[count][KNOT_CTL_IDX_CMD][0] == '\0') {
+			ok(type == KNOT_CTL_TYPE_BLOCK, "Receive block end type");
+		} else {
+			ok(type == KNOT_CTL_TYPE_DATA, "Check data type");
+			for (size_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) {
+				ok((data[i] == NULL && argv[count][i] == NULL) ||
+				   (data[i] != NULL && argv[count][i] != NULL),
+				   "Server compare input item occupation %zu", i);
+				if (data[i] == NULL) {
+					continue;
+				}
+
+				ok(strcmp(data[i], argv[count][i]) == 0,
+				   "Server compare input item '%s", argv[count][i]);
+			}
+		}
+		count++;
+	}
+	ok(ret == KNOT_EOK, "Receive OK check");
+	ok(type == KNOT_CTL_TYPE_END, "Receive EOF type");
+	ok(count == argc, "Server compare input count '%zu'", argc);
+
+	diag("END: Server <- Client");
+	diag("BEGIN: Server -> Client");
+
+	if (argc > 0) {
+		for (size_t i = 0; i < argc; i++) {
+			if (argv[i][KNOT_CTL_IDX_CMD] != NULL &&
+			    argv[i][KNOT_CTL_IDX_CMD][0] == '\0') {
+				ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL);
+				ok(ret == KNOT_EOK, "Client send data block end type");
+			} else {
+				ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &argv[i]);
+				ok(ret == KNOT_EOK, "Server send data %zu", i);
+			}
+		}
+	}
+
+	ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL);
+	ok(ret == KNOT_EOK, "Server send final data");
+
+	diag("END: Server -> Client");
+
+	knot_ctl_close(ctl);
+	knot_ctl_unbind(ctl);
+	knot_ctl_free(ctl);
+}
+
+static void test_client_server_client(void)
+{
+	char *socket = test_mktemp();
+	ok(socket != NULL, "Make a temporary socket file '%s'", socket);
+
+	size_t data_len = 5;
+	knot_ctl_data_t data[] = {
+		{ "command", "error", "section", "item", "identifier",
+		  "zone", "owner", "ttl", "type", "data" },
+		{ [KNOT_CTL_IDX_DATA] = "\x01\x02" },
+		{ [KNOT_CTL_IDX_CMD] = "\0" }, // This means block end in this test!
+		{ NULL },
+		{ [KNOT_CTL_IDX_ERROR] = "Ultra long message" }
+	};
+
+	// Fork a client process.
+	pid_t child_pid = fork();
+	if (child_pid == -1) {
+		ok(child_pid >= 0, "Process fork");
+		return;
+	}
+	if (child_pid == 0) {
+		ctl_client(socket, data_len, data);
+		free(socket);
+		return;
+	} else {
+		ctl_server(socket, data_len, data);
+	}
+
+	int status;
+	wait(&status);
+	ok(status >= 0, "Wait for client");
+
+	test_rm_rf(socket);
+	free(socket);
+}
+
+int main(int argc, char *argv[])
+{
+	plan_lazy();
+
+	diag("Client -> Server -> Client");
+	test_client_server_client();
+
+	return 0;
+}
-- 
GitLab