aboutsummaryrefslogtreecommitdiffstats
path: root/src/mds-server/receiving.c
blob: 09684aefc6528169a9f9700124da524282fe8ba6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/**
 * mds — A micro-display server
 * Copyright © 2014, 2015, 2016  Mattias Andrée (maandree@member.fsf.org)
 * 
 * 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 "receiving.h"

#include "globals.h"
#include "client.h"
#include "interceptors.h"

#include <libmdsserver/hash-table.h>
#include <libmdsserver/mds-message.h>
#include <libmdsserver/macros.h>

#include <stddef.h>
#include <inttypes.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>


/**
 * Queue a message for multicasting
 * 
 * @param  message  The message
 * @param  length   The length of the message
 * @param  sender   The original sender of the message
 */
__attribute__((nonnull))
void queue_message_multicast(char *message, size_t length, client_t *sender);


/**
 * Notify waiting client about a received message modification
 * 
 * @param   client     The client whom sent the message
 * @param   message    The message
 * @param   modify_id  The modify ID of the message
 * @return             Normally zero, but 1 if exited because of re-exec or termination
 */
static int __attribute__((nonnull))
modifying_notify(client_t *client, mds_message_t message, uint64_t modify_id)
{
	/* pthread_cond_timedwait is required to handle re-exec and termination because
	   pthread_cond_timedwait and pthread_cond_wait ignore interruptions via signals. */
	struct timespec timeout = {
		.tv_sec = 1,
		.tv_nsec = 0
	};
	size_t address;
	client_t *recipient;
	mds_message_t *multicast;
	size_t i;

	pthread_mutex_lock(&(modify_mutex));
	while (!hash_table_contains_key(&modify_map, (size_t)modify_id)) {
		if (terminating) {
			pthread_mutex_unlock(&(modify_mutex));
			return 1;
		}
		pthread_cond_timedwait(&slave_cond, &slave_mutex, &timeout);
	}
	address = hash_table_get(&modify_map, (size_t)modify_id);
	recipient = (void *)address;
	fail_if (xmalloc(multicast = recipient->modify_message, 1, mds_message_t));
	mds_message_zero_initialise(multicast);
	fail_if (xmemdup(multicast->payload, message.payload, message.payload_size, char));
	fail_if (xmalloc(multicast->headers, message.header_count, char*));
	for (i = 0; i < message.header_count; i++, multicast->header_count++)
		fail_if (xstrdup(multicast->headers[i], message.headers[i]));
done:
	pthread_mutex_unlock(&(modify_mutex));
	with_mutex (client->modify_mutex, pthread_cond_signal(&(client->modify_cond)););

	return 0;

fail:
	xperror(*argv);
	if (multicast) {
		mds_message_destroy(multicast);
		free(multicast);
		recipient->modify_message = NULL;
	}
	goto done;
}


/**
 * Add intercept conditions listed in the payload of a message
 * 
 * @param   client     The intercepting client
 * @param   modifying  Whether then client may modify the messages
 * @param   priority   The client's interception priority
 * @param   stop       Whether to stop listening rather than start or reconfigure
 * @return             Zero on success, -1 on error
 */
static int __attribute__((nonnull))
add_intercept_conditions_from_message(client_t *client, int modifying, int64_t priority, int stop)
{
	int saved_errno;
	char *payload = client->message.payload;
	size_t payload_size = client->message.payload_size;
	size_t size = 64, len;
	char *buf, *end, *old_buf;

	fail_if (xmalloc(buf, size + 1, char));

	/* All messages. */
	if (!client->message.payload_size) {
		*buf = '\0';
		add_intercept_condition(client, buf, priority, modifying, stop);
		goto done;
	}

	/* Filtered messages. */
	for (;;) {
		end = memchr(payload, '\n', payload_size);
		len = !end ? payload_size : (size_t)(end - payload);
		if (len == 0) {
			payload++;
			payload_size--;
			break;
		}
		if (len > size) {
			old_buf = buf;
			if (xrealloc(buf, (size <<= 1) + 1, char)) {
				saved_errno = errno;
				free(old_buf);
				pthread_mutex_unlock(&(client->mutex));
				fail_if (errno = saved_errno, 1);
			}
		}
		memcpy(buf, payload, len);
		buf[len] = '\0';
		add_intercept_condition(client, buf, priority, modifying, stop);
		if (!end)
			break;
		payload = end + 1;
		payload_size -= len + 1;
	}

done:
	free(buf);
	return 0;
fail:
	return -1;
}


/**
 * Assign and ID to a client, if not already assigned, and send it to that client
 * 
 * @param   client      The client to who an ID should be assigned
 * @param   message_id  The message ID of the ID request
 * @return              Zero on success, -1 on error
 */
static int __attribute__((nonnull(1)))
assign_and_send_id(client_t *client, const char *message_id)
{
	char *msgbuf = NULL;
	char *msgbuf_;
	char *msg_new;
	size_t n, new_len;
	int rc = -1;

	/* Construct response. */
	n = 2 * 10 + strlen(message_id);
	n += sizeof("ID assignment: :\nIn response to: \n\n") / sizeof(char);
	fail_if (xmalloc(msgbuf, n, char));
	snprintf(msgbuf, n,
	         "ID assignment: %" PRIu32 ":%" PRIu32 "\n"
	         "In response to: %s\n"
	         "\n",
	         (uint32_t)(client->id >> 32),
	         (uint32_t)(client->id >>  0),
	         !message_id ? "" : message_id);
	n = strlen(msgbuf);

	/* Multicast the reply. */
	fail_if (xstrdup(msgbuf_, msgbuf));
	queue_message_multicast(msgbuf_, n, client);

	/* Queue message to be sent when this function returns.
	   This done to simplify `multicast_message` for re-exec and termination. */
#define fail fail_in_mutex
	with_mutex (client->mutex,
	            if (!client->send_pending_size) {
	                    /* Set the pending message. */
	                    client->send_pending = msgbuf;
	                    client->send_pending_size = n;
	            } else {
	                    /* Concatenate message to already pending messages. */
	                    new_len = client->send_pending_size + n;
	                    msg_new = client->send_pending;
	                    fail_if (xrealloc(msg_new, new_len, char));
	                    memcpy(msg_new + client->send_pending_size, msgbuf, n * sizeof(char));
	                    client->send_pending = msg_new;
	                    client->send_pending_size = new_len;
	            }
	            (msgbuf = NULL, rc = 0, errno = 0);
	fail_in_mutex:
	           );
#undef fail
  
fail: /* Also success. */
	xperror(*argv);
	free(msgbuf);
	return rc;
}


/**
 * Perform actions that should be taken when
 * a message has been received from a client
 * 
 * @param   client  The client whom sent the message
 * @return          Normally zero, but 1 if exited because of re-exec or termination
 */
int
message_received(client_t *client)
{
	mds_message_t message = client->message;
	int assign_id = 0;
	int modifying = 0;
	int intercept = 0;
	int64_t priority = 0;
	int stop = 0;
	const char *message_id = NULL;
	uint64_t modify_id = 0;
	char *msgbuf = NULL;
	size_t i, n;
	const char *h;
	char buf[26];


	/* Parser headers. */
	for (i = 0; i < message.header_count; i++) {
		h = message.headers[i];
		if      (strequals(h,  "Command: assign-id")) assign_id  = 1;
		else if (strequals(h,  "Command: intercept")) intercept  = 1;
		else if (strequals(h,  "Modifying: yes"))     modifying  = 1;
		else if (strequals(h,  "Stop: yes"))          stop       = 1;
		else if (startswith(h, "Message ID: "))       message_id = strstr(h, ": ") + 2;
		else if (startswith(h, "Priority: "))         priority   = ato64(strstr(h, ": ") + 2);
		else if (startswith(h, "Modify ID: "))        modify_id  = atou64(strstr(h, ": ") + 2);
	}


	/* Notify waiting client about a received message modification. */
	if (modifying)
		return modifying_notify(client, message, modify_id);
	/* Do nothing more, not not even multicast this message. */


	if (!message_id) {
		eprint("received message without a message ID, ignoring.");
		return 0;
	}

	/* Assign ID if not already assigned. */
	if (assign_id && !client->id) {
		intercept |= 2;
		with_mutex_if (slave_mutex, !(client->id = next_client_id++),
		               eprint("this is impossible, ID counter has overflowed.");
		               /* If the program ran for a millennium it would
		                  take c:a 585 assignments per nanosecond. This
		                  cannot possibly happen. (It would require serious
		                  dedication by generations of ponies (or just an alicorn)
		                  to maintain the process and transfer it new hardware.) */
		               abort();
		              );
	}

	/* Make the client listen for messages addressed to it. */
	if (intercept) {
		pthread_mutex_lock(&(client->mutex));
		if ((intercept & 1)) /* from payload */
			fail_if (add_intercept_conditions_from_message(client, modifying, priority, stop) < 0);
		if ((intercept & 2)) { /* "To: $(client->id)" */
			xsnprintf(buf, "To: %" PRIu32 ":%" PRIu32,
			          (uint32_t)(client->id >> 32),
			          (uint32_t)(client->id >>  0));
			add_intercept_condition(client, buf, priority, modifying, 0);
		}
		pthread_mutex_unlock(&(client->mutex));
	}


	/* Multicast the message. */
	n = mds_message_compose_size(&message);
	fail_if (xbmalloc(msgbuf, n));
	mds_message_compose(&message, msgbuf);
	queue_message_multicast(msgbuf, n / sizeof(char), client);
	msgbuf = NULL;


	/* Send asigned ID. */
	if (assign_id)
		fail_if (assign_and_send_id(client, message_id) < 0);

	return 0;

fail:
	xperror(*argv);
	free(msgbuf);
	return 0;
}