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