1 /*
2  * Copyright © 2019 Intel Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  *
23  * Authors: Simon Ser <simon.ser@intel.com>
24  */
25 
26 #include "config.h"
27 
28 #include <arpa/inet.h>
29 #include <errno.h>
30 #include <netdb.h>
31 #include <stdbool.h>
32 #include <stdlib.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 
36 #include "igt_chamelium_stream.h"
37 #include "igt_core.h"
38 #include "igt_rc.h"
39 
40 #define STREAM_PORT 9994
41 #define STREAM_VERSION_MAJOR 1
42 #define STREAM_VERSION_MINOR 0
43 
44 enum stream_error {
45 	STREAM_ERROR_NONE = 0,
46 	STREAM_ERROR_COMMAND = 1,
47 	STREAM_ERROR_ARGUMENT = 2,
48 	STREAM_ERROR_EXISTS = 3,
49 	STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP = 4,
50 	STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP = 5,
51 	STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP = 6,
52 	STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP = 7,
53 	STREAM_ERROR_NO_MEM = 8,
54 };
55 
56 enum stream_message_kind {
57 	STREAM_MESSAGE_REQUEST = 0,
58 	STREAM_MESSAGE_RESPONSE = 1,
59 	STREAM_MESSAGE_DATA = 2,
60 };
61 
62 enum stream_message_type {
63 	STREAM_MESSAGE_RESET = 0,
64 	STREAM_MESSAGE_GET_VERSION = 1,
65 	STREAM_MESSAGE_VIDEO_STREAM = 2,
66 	STREAM_MESSAGE_SHRINK_VIDEO = 3,
67 	STREAM_MESSAGE_VIDEO_FRAME = 4,
68 	STREAM_MESSAGE_DUMP_REALTIME_VIDEO = 5,
69 	STREAM_MESSAGE_STOP_DUMP_VIDEO = 6,
70 	STREAM_MESSAGE_DUMP_REALTIME_AUDIO = 7,
71 	STREAM_MESSAGE_STOP_DUMP_AUDIO = 8,
72 };
73 
74 struct chamelium_stream {
75 	char *host;
76 	unsigned int port;
77 
78 	int fd;
79 };
80 
stream_error_str(enum stream_error err)81 static const char *stream_error_str(enum stream_error err)
82 {
83 	switch (err) {
84 	case STREAM_ERROR_NONE:
85 		return "no error";
86 	case STREAM_ERROR_COMMAND:
87 		return "invalid command";
88 	case STREAM_ERROR_ARGUMENT:
89 		return "invalid arguments";
90 	case STREAM_ERROR_EXISTS:
91 		return "dump already started";
92 	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_STOP:
93 		return "video dump stopped after overflow";
94 	case STREAM_ERROR_VIDEO_MEM_OVERFLOW_DROP:
95 		return "video frame dropped after overflow";
96 	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_STOP:
97 		return "audio dump stoppred after overflow";
98 	case STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP:
99 		return "audio page dropped after overflow";
100 	case STREAM_ERROR_NO_MEM:
101 		return "out of memory";
102 	}
103 	return "unknown error";
104 }
105 
106 /**
107  * The Chamelium URL is specified in the configuration file. We need to extract
108  * the host to connect to the stream server.
109  */
parse_url_host(const char * url)110 static char *parse_url_host(const char *url)
111 {
112 	static const char prefix[] = "http://";
113 	char *colon;
114 
115 	if (strstr(url, prefix) != url)
116 		return NULL;
117 	url += strlen(prefix);
118 
119 	colon = strchr(url, ':');
120 	if (!colon)
121 		return NULL;
122 
123 	return strndup(url, colon - url);
124 }
125 
chamelium_stream_read_config(struct chamelium_stream * client)126 static bool chamelium_stream_read_config(struct chamelium_stream *client)
127 {
128 	GError *error = NULL;
129 	gchar *chamelium_url;
130 
131 	if (!igt_key_file) {
132 		igt_warn("No configuration file available for chamelium\n");
133 		return false;
134 	}
135 
136 	chamelium_url = g_key_file_get_string(igt_key_file, "Chamelium", "URL",
137 					      &error);
138 	if (!chamelium_url) {
139 		igt_warn("Couldn't read Chamelium URL from config file: %s\n",
140 			 error->message);
141 		return false;
142 	}
143 
144 	client->host = parse_url_host(chamelium_url);
145 	if (!client->host) {
146 		igt_warn("Invalid Chamelium URL in config file: %s\n",
147 			 chamelium_url);
148 		return false;
149 	}
150 	client->port = STREAM_PORT;
151 
152 	return true;
153 }
154 
chamelium_stream_connect(struct chamelium_stream * client)155 static bool chamelium_stream_connect(struct chamelium_stream *client)
156 {
157 	int ret;
158 	char port_str[16];
159 	struct addrinfo hints = {};
160 	struct addrinfo *results, *ai;
161 	struct timeval tv = {};
162 
163 	igt_debug("Connecting to Chamelium stream server: tcp://%s:%u\n",
164 		  client->host, client->port);
165 
166 	snprintf(port_str, sizeof(port_str), "%u", client->port);
167 
168 	hints.ai_family = AF_UNSPEC;
169 	hints.ai_socktype = SOCK_STREAM;
170 	ret = getaddrinfo(client->host, port_str, &hints, &results);
171 	if (ret != 0) {
172 		igt_warn("getaddrinfo failed: %s\n", gai_strerror(ret));
173 		return false;
174 	}
175 
176 	client->fd = -1;
177 	for (ai = results; ai != NULL; ai = ai->ai_next) {
178 		client->fd = socket(ai->ai_family, ai->ai_socktype,
179 				    ai->ai_protocol);
180 		if (client->fd == -1)
181 			continue;
182 
183 		if (connect(client->fd, ai->ai_addr, ai->ai_addrlen) == -1) {
184 			close(client->fd);
185 			client->fd = -1;
186 			continue;
187 		}
188 
189 		break;
190 	}
191 
192 	freeaddrinfo(results);
193 
194 	if (client->fd < 0) {
195 		igt_warn("Failed to connect to Chamelium stream server\n");
196 		return false;
197 	}
198 
199 	/* Set a read and write timeout of 5 seconds. */
200 	tv.tv_sec = 5;
201 	setsockopt(client->fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
202 	setsockopt(client->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
203 
204 	return true;
205 }
206 
read_whole(int fd,void * buf,size_t buf_len)207 static bool read_whole(int fd, void *buf, size_t buf_len)
208 {
209 	ssize_t ret;
210 	size_t n = 0;
211 	char *ptr;
212 
213 	while (n < buf_len) {
214 		ptr = (char *) buf + n;
215 		ret = read(fd, ptr, buf_len - n);
216 		if (ret < 0) {
217 			igt_warn("read failed: %s\n", strerror(errno));
218 			return false;
219 		} else if (ret == 0) {
220 			igt_warn("short read\n");
221 			return false;
222 		}
223 		n += ret;
224 	}
225 
226 	return true;
227 }
228 
write_whole(int fd,void * buf,size_t buf_len)229 static bool write_whole(int fd, void *buf, size_t buf_len)
230 {
231 	ssize_t ret;
232 	size_t n = 0;
233 	char *ptr;
234 
235 	while (n < buf_len) {
236 		ptr = (char *) buf + n;
237 		ret = write(fd, ptr, buf_len - n);
238 		if (ret < 0) {
239 			igt_warn("write failed: %s\n", strerror(errno));
240 			return false;
241 		} else if (ret == 0) {
242 			igt_warn("short write\n");
243 			return false;
244 		}
245 		n += ret;
246 	}
247 
248 	return true;
249 }
250 
read_and_discard(int fd,size_t len)251 static bool read_and_discard(int fd, size_t len)
252 {
253 	char buf[1024];
254 	size_t n;
255 
256 	while (len > 0) {
257 		n = len;
258 		if (n > sizeof(buf))
259 			n = sizeof(buf);
260 
261 		if (!read_whole(fd, buf, n))
262 			return false;
263 
264 		len -= n;
265 	}
266 
267 	return true;
268 }
269 
270 /** Read a message header from the socket.
271  *
272  * The header is laid out as follows:
273  * - u16: message type
274  * - u16: error code
275  * - u32: message length
276  */
chamelium_stream_read_header(struct chamelium_stream * client,enum stream_message_kind * kind,enum stream_message_type * type,enum stream_error * err,size_t * len)277 static bool chamelium_stream_read_header(struct chamelium_stream *client,
278 					 enum stream_message_kind *kind,
279 					 enum stream_message_type *type,
280 					 enum stream_error *err,
281 					 size_t *len)
282 {
283 	uint16_t _type;
284 	char buf[8];
285 
286 	if (!read_whole(client->fd, buf, sizeof(buf)))
287 		return false;
288 
289 	_type = ntohs(*(uint16_t *) &buf[0]);
290 	*type = _type & 0xFF;
291 	*kind = _type >> 8;
292 	*err = ntohs(*(uint16_t *) &buf[2]);
293 	*len = ntohl(*(uint32_t *) &buf[4]);
294 
295 	return true;
296 }
297 
chamelium_stream_write_header(struct chamelium_stream * client,enum stream_message_type type,enum stream_error err,size_t len)298 static bool chamelium_stream_write_header(struct chamelium_stream *client,
299 					  enum stream_message_type type,
300 					  enum stream_error err,
301 					  size_t len)
302 {
303 	char buf[8];
304 	uint16_t _type;
305 
306 	_type = type | (STREAM_MESSAGE_REQUEST << 8);
307 
308 	*(uint16_t *) &buf[0] = htons(_type);
309 	*(uint16_t *) &buf[2] = htons(err);
310 	*(uint32_t *) &buf[4] = htonl(len);
311 
312 	return write_whole(client->fd, buf, sizeof(buf));
313 }
314 
chamelium_stream_read_response(struct chamelium_stream * client,enum stream_message_type type,void * buf,size_t buf_len)315 static bool chamelium_stream_read_response(struct chamelium_stream *client,
316 					   enum stream_message_type type,
317 					   void *buf, size_t buf_len)
318 {
319 	enum stream_message_kind read_kind;
320 	enum stream_message_type read_type;
321 	enum stream_error read_err;
322 	size_t read_len;
323 
324 	if (!chamelium_stream_read_header(client, &read_kind, &read_type,
325 					  &read_err, &read_len))
326 		return false;
327 
328 	if (read_kind != STREAM_MESSAGE_RESPONSE) {
329 		igt_warn("Expected a response, got kind %d\n", read_kind);
330 		return false;
331 	}
332 	if (read_type != type) {
333 		igt_warn("Expected message type %d, got %d\n",
334 			 type, read_type);
335 		return false;
336 	}
337 	if (read_err != STREAM_ERROR_NONE) {
338 		igt_warn("Received error: %s (%d)\n",
339 			 stream_error_str(read_err), read_err);
340 		return false;
341 	}
342 	if (buf_len != read_len) {
343 		igt_warn("Received invalid message body size "
344 			 "(got %zu bytes, want %zu bytes)\n",
345 			 read_len, buf_len);
346 		return false;
347 	}
348 
349 	return read_whole(client->fd, buf, buf_len);
350 }
351 
chamelium_stream_write_request(struct chamelium_stream * client,enum stream_message_type type,void * buf,size_t buf_len)352 static bool chamelium_stream_write_request(struct chamelium_stream *client,
353 					   enum stream_message_type type,
354 					   void *buf, size_t buf_len)
355 {
356 	if (!chamelium_stream_write_header(client, type, STREAM_ERROR_NONE,
357 					   buf_len))
358 		return false;
359 
360 	if (buf_len == 0)
361 		return true;
362 
363 	return write_whole(client->fd, buf, buf_len);
364 }
365 
chamelium_stream_call(struct chamelium_stream * client,enum stream_message_type type,void * req_buf,size_t req_len,void * resp_buf,size_t resp_len)366 static bool chamelium_stream_call(struct chamelium_stream *client,
367 				  enum stream_message_type type,
368 				  void *req_buf, size_t req_len,
369 				  void *resp_buf, size_t resp_len)
370 {
371 	if (!chamelium_stream_write_request(client, type, req_buf, req_len))
372 		return false;
373 
374 	return chamelium_stream_read_response(client, type, resp_buf, resp_len);
375 }
376 
chamelium_stream_check_version(struct chamelium_stream * client)377 static bool chamelium_stream_check_version(struct chamelium_stream *client)
378 {
379 	char resp[2];
380 	uint8_t major, minor;
381 
382 	if (!chamelium_stream_call(client, STREAM_MESSAGE_GET_VERSION,
383 				   NULL, 0, resp, sizeof(resp)))
384 		return false;
385 
386 	major = resp[0];
387 	minor = resp[1];
388 	if (major != STREAM_VERSION_MAJOR || minor < STREAM_VERSION_MINOR) {
389 		igt_warn("Version mismatch (want %d.%d, got %d.%d)\n",
390 			 STREAM_VERSION_MAJOR, STREAM_VERSION_MINOR,
391 			 major, minor);
392 		return false;
393 	}
394 
395 	return true;
396 }
397 
398 /**
399  * chamelium_stream_dump_realtime_audio:
400  *
401  * Starts audio capture. The caller can then call
402  * #chamelium_stream_receive_realtime_audio to receive audio pages.
403  */
chamelium_stream_dump_realtime_audio(struct chamelium_stream * client,enum chamelium_stream_realtime_mode mode)404 bool chamelium_stream_dump_realtime_audio(struct chamelium_stream *client,
405 					  enum chamelium_stream_realtime_mode mode)
406 {
407 	char req[1];
408 
409 	igt_debug("Starting real-time audio capture\n");
410 
411 	req[0] = mode;
412 	return chamelium_stream_call(client, STREAM_MESSAGE_DUMP_REALTIME_AUDIO,
413 				     req, sizeof(req), NULL, 0);
414 }
415 
416 /**
417  * chamelium_stream_receive_realtime_audio:
418  * @page_count: if non-NULL, will be set to the dumped page number
419  * @buf: must either point to a dynamically allocated memory region or NULL
420  * @buf_len: number of elements of *@buf, for zero if @buf is NULL
421  *
422  * Receives one audio page from the streaming server.
423  *
424  * In "best effort" mode, some pages can be dropped. This can be detected via
425  * the page count.
426  *
427  * buf_len will be set to the size of the page. The caller is responsible for
428  * calling free(3) on *buf.
429  */
chamelium_stream_receive_realtime_audio(struct chamelium_stream * client,size_t * page_count,int32_t ** buf,size_t * buf_len)430 bool chamelium_stream_receive_realtime_audio(struct chamelium_stream *client,
431 					     size_t *page_count,
432 					     int32_t **buf, size_t *buf_len)
433 {
434 	enum stream_message_kind kind;
435 	enum stream_message_type type;
436 	enum stream_error err;
437 	size_t body_len;
438 	char page_count_buf[4];
439 	int32_t *ptr;
440 
441 	while (true) {
442 		if (!chamelium_stream_read_header(client, &kind, &type,
443 						  &err, &body_len))
444 			return false;
445 
446 		if (kind != STREAM_MESSAGE_DATA) {
447 			igt_warn("Expected a data message, got kind %d\n", kind);
448 			return false;
449 		}
450 		if (type != STREAM_MESSAGE_DUMP_REALTIME_AUDIO) {
451 			igt_warn("Expected real-time audio dump message, "
452 				 "got type %d\n", type);
453 			return false;
454 		}
455 
456 		if (err == STREAM_ERROR_NONE)
457 			break;
458 		else if (err != STREAM_ERROR_AUDIO_MEM_OVERFLOW_DROP) {
459 			igt_warn("Received error: %s (%d)\n",
460 				 stream_error_str(err), err);
461 			return false;
462 		}
463 
464 		igt_debug("Dropped an audio page because of an overflow\n");
465 		igt_assert(body_len == 0);
466 	}
467 
468 	igt_assert(body_len >= sizeof(page_count_buf));
469 
470 	if (!read_whole(client->fd, page_count_buf, sizeof(page_count_buf)))
471 		return false;
472 	if (page_count)
473 		*page_count = ntohl(*(uint32_t *) &page_count_buf[0]);
474 	body_len -= sizeof(page_count_buf);
475 
476 	igt_assert(body_len % sizeof(int32_t) == 0);
477 	if (*buf_len * sizeof(int32_t) != body_len) {
478 		ptr = realloc(*buf, body_len);
479 		if (!ptr) {
480 			igt_warn("realloc failed: %s\n", strerror(errno));
481 			return false;
482 		}
483 		*buf = ptr;
484 		*buf_len = body_len / sizeof(int32_t);
485 	}
486 
487 	return read_whole(client->fd, *buf, body_len);
488 }
489 
490 /**
491  * chamelium_stream_stop_realtime_audio:
492  *
493  * Stops real-time audio capture. This also drops any buffered audio pages.
494  * The caller shouldn't call #chamelium_stream_receive_realtime_audio after
495  * stopping audio capture.
496  */
chamelium_stream_stop_realtime_audio(struct chamelium_stream * client)497 bool chamelium_stream_stop_realtime_audio(struct chamelium_stream *client)
498 {
499 	enum stream_message_kind kind;
500 	enum stream_message_type type;
501 	enum stream_error err;
502 	size_t len;
503 
504 	igt_debug("Stopping real-time audio capture\n");
505 
506 	if (!chamelium_stream_write_request(client,
507 					    STREAM_MESSAGE_STOP_DUMP_AUDIO,
508 					    NULL, 0))
509 		return false;
510 
511 	while (true) {
512 		if (!chamelium_stream_read_header(client, &kind, &type,
513 						  &err, &len))
514 			return false;
515 
516 		if (kind == STREAM_MESSAGE_RESPONSE)
517 			break;
518 
519 		if (!read_and_discard(client->fd, len))
520 			return false;
521 	}
522 
523 	if (type != STREAM_MESSAGE_STOP_DUMP_AUDIO) {
524 		igt_warn("Unexpected response type %d\n", type);
525 		return false;
526 	}
527 	if (err != STREAM_ERROR_NONE) {
528 		igt_warn("Received error: %s (%d)\n",
529 			 stream_error_str(err), err);
530 		return false;
531 	}
532 	if (len != 0) {
533 		igt_warn("Expected an empty response, got %zu bytes\n", len);
534 		return false;
535 	}
536 
537 	return true;
538 }
539 
540 /**
541  * chamelium_stream_init:
542  *
543  * Connects to the Chamelium streaming server.
544  */
chamelium_stream_init(void)545 struct chamelium_stream *chamelium_stream_init(void)
546 {
547 	struct chamelium_stream *client;
548 
549 	client = calloc(1, sizeof(*client));
550 
551 	if (!chamelium_stream_read_config(client))
552 		goto error_client;
553 	if (!chamelium_stream_connect(client))
554 		goto error_client;
555 	if (!chamelium_stream_check_version(client))
556 		goto error_fd;
557 
558 	return client;
559 
560 error_fd:
561 	close(client->fd);
562 error_client:
563 	free(client);
564 	return NULL;
565 }
566 
chamelium_stream_deinit(struct chamelium_stream * client)567 void chamelium_stream_deinit(struct chamelium_stream *client)
568 {
569 	if (close(client->fd) != 0)
570 		igt_warn("close failed: %s\n", strerror(errno));
571 	free(client);
572 }
573