1 /*- 2 * Copyright (c) 2020 Hans Petter Selasky 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26 #include <sys/types.h> 27 #include <sys/queue.h> 28 #include <sys/ioctl.h> 29 #include <sys/socket.h> 30 #include <sys/endian.h> 31 #include <sys/uio.h> 32 #include <sys/soundcard.h> 33 34 #include <stdio.h> 35 #include <stdint.h> 36 #include <stdbool.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <fcntl.h> 40 #include <unistd.h> 41 #include <err.h> 42 #include <errno.h> 43 #include <poll.h> 44 #include <sysexits.h> 45 46 #include <netdb.h> 47 #include <netinet/in.h> 48 #include <netinet/tcp.h> 49 50 #include <net/if.h> 51 #include <net/if_vlan_var.h> 52 #include <net/bpf.h> 53 54 #include <arpa/inet.h> 55 56 #include <pthread.h> 57 58 #include "int.h" 59 60 #define VOSS_HTTPD_BIND_MAX 8 61 #define VOSS_HTTPD_MAX_STREAM_TIME (60 * 60 * 3) /* seconds */ 62 63 struct http_state { 64 int fd; 65 uint64_t ts; 66 }; 67 68 struct rtp_raw_packet { 69 struct { 70 uint32_t padding; 71 uint8_t dhost[6]; 72 uint8_t shost[6]; 73 uint16_t ether_type; 74 } __packed eth; 75 struct { 76 uint8_t hl_ver; 77 uint8_t tos; 78 uint16_t len; 79 uint16_t ident; 80 uint16_t offset; 81 uint8_t ttl; 82 uint8_t protocol; 83 uint16_t chksum; 84 union { 85 uint32_t sourceip; 86 uint16_t source16[2]; 87 }; 88 union { 89 uint32_t destip; 90 uint16_t dest16[2]; 91 }; 92 } __packed ip; 93 struct { 94 uint16_t srcport; 95 uint16_t dstport; 96 uint16_t len; 97 uint16_t chksum; 98 } __packed udp; 99 union { 100 uint8_t header8[12]; 101 uint16_t header16[6]; 102 uint32_t header32[3]; 103 } __packed rtp; 104 105 } __packed; 106 107 static const char * 108 voss_httpd_bind_rtp(vclient_t *pvc, const char *ifname, int *pfd) 109 { 110 const char *perr = NULL; 111 struct vlanreq vr = {}; 112 struct ifreq ifr = {}; 113 int fd; 114 115 fd = socket(AF_LOCAL, SOCK_DGRAM, 0); 116 if (fd < 0) { 117 perr = "Cannot open raw RTP socket"; 118 goto done; 119 } 120 121 strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); 122 ifr.ifr_data = (void *)&vr; 123 124 if (ioctl(fd, SIOCGETVLAN, &ifr) == 0) 125 pvc->profile->http.rtp_vlanid = vr.vlr_tag; 126 else 127 pvc->profile->http.rtp_vlanid = 0; 128 129 close(fd); 130 131 ifr.ifr_data = NULL; 132 133 *pfd = fd = open("/dev/bpf", O_RDWR); 134 if (fd < 0) { 135 perr = "Cannot open BPF device"; 136 goto done; 137 } 138 139 if (ioctl(fd, BIOCSETIF, &ifr) != 0) { 140 perr = "Cannot bind BPF device to network interface"; 141 goto done; 142 } 143 done: 144 if (perr != NULL && fd > -1) 145 close(fd); 146 return (perr); 147 } 148 149 static uint16_t 150 voss_ipv4_csum(const void *vptr, size_t count) 151 { 152 const uint16_t *ptr = vptr; 153 uint32_t sum = 0; 154 155 while (count--) 156 sum += *ptr++; 157 158 sum = (sum >> 16) + (sum & 0xffff); 159 sum += (sum >> 16); 160 161 return (~sum); 162 } 163 164 static uint16_t 165 voss_udp_csum(uint32_t sum, const void *vhdr, size_t count, 166 const uint16_t *ptr, size_t length) 167 { 168 const uint16_t *hdr = vhdr; 169 170 while (count--) 171 sum += *hdr++; 172 173 while (length > 1) { 174 sum += *ptr++; 175 length -= 2; 176 } 177 178 if (length & 1) 179 sum += *__DECONST(uint8_t *, ptr); 180 181 sum = (sum >> 16) + (sum & 0xffff); 182 sum += (sum >> 16); 183 184 return (~sum); 185 } 186 187 static void 188 voss_httpd_send_rtp_sub(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts) 189 { 190 struct rtp_raw_packet pkt = {}; 191 struct iovec iov[2]; 192 size_t total_ip; 193 uint16_t port = atoi(pvc->profile->http.rtp_port); 194 size_t x; 195 196 /* NOTE: BPF filter will insert VLAN header for us */ 197 memset(pkt.eth.dhost, 255, sizeof(pkt.eth.dhost)); 198 memset(pkt.eth.shost, 1, sizeof(pkt.eth.shost)); 199 pkt.eth.ether_type = htobe16(0x0800); 200 total_ip = sizeof(pkt.ip) + sizeof(pkt.udp) + sizeof(pkt.rtp) + len; 201 202 iov[0].iov_base = pkt.eth.dhost; 203 iov[0].iov_len = 14 + total_ip - len; 204 205 iov[1].iov_base = alloca(len); 206 iov[1].iov_len = len; 207 208 /* byte swap data - WAV files are 16-bit little endian */ 209 for (x = 0; x != (len / 2); x++) 210 ((uint16_t *)iov[1].iov_base)[x] = bswap16(((uint16_t *)ptr)[x]); 211 212 pkt.ip.hl_ver = 0x45; 213 pkt.ip.len = htobe16(total_ip); 214 pkt.ip.ttl = 8; 215 pkt.ip.protocol = 17; /* UDP */ 216 pkt.ip.sourceip = 0x01010101U; 217 pkt.ip.destip = htobe32((239 << 24) + (255 << 16) + (1 << 0)); 218 pkt.ip.chksum = voss_ipv4_csum((void *)&pkt.ip, sizeof(pkt.ip) / 2); 219 220 pkt.udp.srcport = htobe16(port); 221 pkt.udp.dstport = htobe16(port); 222 pkt.udp.len = htobe16(total_ip - sizeof(pkt.ip)); 223 224 pkt.rtp.header8[0] = (2 << 6); 225 pkt.rtp.header8[1] = ((pvc->channels == 2) ? 10 : 11) | 0x80; 226 227 pkt.rtp.header16[1] = htobe16(pvc->profile->http.rtp_seqnum); 228 pkt.rtp.header32[1] = htobe32(ts); 229 pkt.rtp.header32[2] = htobe32(0); 230 231 pkt.udp.chksum = voss_udp_csum(pkt.ip.dest16[0] + pkt.ip.dest16[1] + 232 pkt.ip.source16[0] + pkt.ip.source16[1] + 0x1100 + pkt.udp.len, 233 (void *)&pkt.udp, sizeof(pkt.udp) / 2 + sizeof(pkt.rtp) / 2, 234 iov[1].iov_base, iov[1].iov_len); 235 236 pvc->profile->http.rtp_seqnum++; 237 pvc->profile->http.rtp_ts += len / (2 * pvc->channels); 238 239 (void)writev(fd, iov, 2); 240 } 241 242 static void 243 voss_httpd_send_rtp(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts) 244 { 245 const uint32_t mod = pvc->channels * vclient_sample_bytes(pvc); 246 const uint32_t max = 1420 - (1420 % mod); 247 248 while (len >= max) { 249 voss_httpd_send_rtp_sub(pvc, fd, ptr, max, ts); 250 len -= max; 251 ptr = (uint8_t *)ptr + max; 252 } 253 254 if (len != 0) 255 voss_httpd_send_rtp_sub(pvc, fd, ptr, len, ts); 256 } 257 258 static size_t 259 voss_httpd_usage(vclient_t *pvc) 260 { 261 size_t usage = 0; 262 size_t x; 263 264 for (x = 0; x < pvc->profile->http.nstate; x++) 265 usage += (pvc->profile->http.state[x].fd != -1); 266 return (usage); 267 } 268 269 static char * 270 voss_httpd_read_line(FILE *io, char *linebuffer, size_t linelen) 271 { 272 char buffer[2]; 273 size_t size = 0; 274 275 if (fread(buffer, 1, 2, io) != 2) 276 return (NULL); 277 278 while (1) { 279 if (buffer[0] == '\r' && buffer[1] == '\n') 280 break; 281 if (size == (linelen - 1)) 282 return (NULL); 283 linebuffer[size++] = buffer[0]; 284 buffer[0] = buffer[1]; 285 if (fread(buffer + 1, 1, 1, io) != 1) 286 return (NULL); 287 } 288 linebuffer[size++] = 0; 289 290 return (linebuffer); 291 } 292 293 static int 294 voss_http_generate_wav_header(vclient_t *pvc, FILE *io, 295 uintmax_t r_start, uintmax_t r_end, bool is_partial) 296 { 297 uint8_t buffer[256]; 298 uint8_t *ptr; 299 uintmax_t dummy_len; 300 uintmax_t delta; 301 size_t mod; 302 size_t len; 303 size_t buflen; 304 305 ptr = buffer; 306 mod = pvc->channels * vclient_sample_bytes(pvc); 307 308 if (mod == 0 || sizeof(buffer) < (44 + mod - 1)) 309 return (-1); 310 311 /* align to next sample */ 312 len = 44 + mod - 1; 313 len -= len % mod; 314 315 buflen = len; 316 317 /* clear block */ 318 memset(ptr, 0, len); 319 320 /* fill out data header */ 321 ptr[len - 8] = 'd'; 322 ptr[len - 7] = 'a'; 323 ptr[len - 6] = 't'; 324 ptr[len - 5] = 'a'; 325 326 /* magic for unspecified length */ 327 ptr[len - 4] = 0x00; 328 ptr[len - 3] = 0xF0; 329 ptr[len - 2] = 0xFF; 330 ptr[len - 1] = 0x7F; 331 332 /* fill out header */ 333 *ptr++ = 'R'; 334 *ptr++ = 'I'; 335 *ptr++ = 'F'; 336 *ptr++ = 'F'; 337 338 /* total chunk size - unknown */ 339 340 *ptr++ = 0; 341 *ptr++ = 0; 342 *ptr++ = 0; 343 *ptr++ = 0; 344 345 *ptr++ = 'W'; 346 *ptr++ = 'A'; 347 *ptr++ = 'V'; 348 *ptr++ = 'E'; 349 *ptr++ = 'f'; 350 *ptr++ = 'm'; 351 *ptr++ = 't'; 352 *ptr++ = ' '; 353 354 /* make sure header fits in PCM block */ 355 len -= 28; 356 357 *ptr++ = len; 358 *ptr++ = len >> 8; 359 *ptr++ = len >> 16; 360 *ptr++ = len >> 24; 361 362 /* audioformat = PCM */ 363 364 *ptr++ = 0x01; 365 *ptr++ = 0x00; 366 367 /* number of channels */ 368 369 len = pvc->channels; 370 371 *ptr++ = len; 372 *ptr++ = len >> 8; 373 374 /* sample rate */ 375 376 len = pvc->sample_rate; 377 378 *ptr++ = len; 379 *ptr++ = len >> 8; 380 *ptr++ = len >> 16; 381 *ptr++ = len >> 24; 382 383 /* byte rate */ 384 385 len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc); 386 387 *ptr++ = len; 388 *ptr++ = len >> 8; 389 *ptr++ = len >> 16; 390 *ptr++ = len >> 24; 391 392 /* block align */ 393 394 len = pvc->channels * vclient_sample_bytes(pvc); 395 396 *ptr++ = len; 397 *ptr++ = len >> 8; 398 399 /* bits per sample */ 400 401 len = vclient_sample_bytes(pvc) * 8; 402 403 *ptr++ = len; 404 *ptr++ = len >> 8; 405 406 /* check if alignment is correct */ 407 if (r_start >= buflen && (r_start % mod) != 0) 408 return (2); 409 410 dummy_len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc); 411 dummy_len *= VOSS_HTTPD_MAX_STREAM_TIME; 412 413 /* fixup end */ 414 if (r_end >= dummy_len) 415 r_end = dummy_len - 1; 416 417 delta = r_end - r_start + 1; 418 419 if (is_partial) { 420 fprintf(io, "HTTP/1.1 206 Partial Content\r\n" 421 "Content-Type: audio/wav\r\n" 422 "Server: virtual_oss/1.0\r\n" 423 "Cache-Control: no-cache, no-store\r\n" 424 "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" 425 "Connection: Close\r\n" 426 "Content-Range: bytes %ju-%ju/%ju\r\n" 427 "Content-Length: %ju\r\n" 428 "\r\n", r_start, r_end, dummy_len, delta); 429 } else { 430 fprintf(io, "HTTP/1.0 200 OK\r\n" 431 "Content-Type: audio/wav\r\n" 432 "Server: virtual_oss/1.0\r\n" 433 "Cache-Control: no-cache, no-store\r\n" 434 "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" 435 "Connection: Close\r\n" 436 "Content-Length: %ju\r\n" 437 "\r\n", dummy_len); 438 } 439 440 /* check if we should insert a header */ 441 if (r_start < buflen) { 442 buflen -= r_start; 443 if (buflen > delta) 444 buflen = delta; 445 /* send data */ 446 if (fwrite(buffer + r_start, buflen, 1, io) != 1) 447 return (-1); 448 /* check if all data was read */ 449 if (buflen == delta) 450 return (1); 451 } 452 return (0); 453 } 454 455 static void 456 voss_httpd_handle_connection(vclient_t *pvc, int fd, const struct sockaddr_in *sa) 457 { 458 char linebuffer[2048]; 459 uintmax_t r_start = 0; 460 uintmax_t r_end = -1ULL; 461 bool is_partial = false; 462 char *line; 463 FILE *io; 464 size_t x; 465 int page; 466 467 io = fdopen(fd, "r+"); 468 if (io == NULL) 469 goto done; 470 471 page = -1; 472 473 /* dump HTTP request header */ 474 while (1) { 475 line = voss_httpd_read_line(io, linebuffer, sizeof(linebuffer)); 476 if (line == NULL) 477 goto done; 478 if (line[0] == 0) 479 break; 480 if (page < 0 && (strstr(line, "GET / ") == line || 481 strstr(line, "GET /index.html") == line)) { 482 page = 0; 483 } else if (page < 0 && strstr(line, "GET /stream.wav") == line) { 484 page = 1; 485 } else if (page < 0 && strstr(line, "GET /stream.m3u") == line) { 486 page = 2; 487 } else if (strstr(line, "Range: bytes=") == line && 488 sscanf(line, "Range: bytes=%ju-%ju", &r_start, &r_end) >= 1) { 489 is_partial = true; 490 } 491 } 492 493 switch (page) { 494 case 0: 495 x = voss_httpd_usage(pvc); 496 497 fprintf(io, "HTTP/1.0 200 OK\r\n" 498 "Content-Type: text/html\r\n" 499 "Server: virtual_oss/1.0\r\n" 500 "Cache-Control: no-cache, no-store\r\n" 501 "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" 502 "\r\n" 503 "<html><head><title>Welcome to live streaming</title>" 504 "<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />" 505 "<meta http-equiv=\"Pragma\" content=\"no-cache\" />" 506 "<meta http-equiv=\"Expires\" content=\"0\" />" 507 "</head>" 508 "<body>" 509 "<h1>Live HD stream</h1>" 510 "<br>" 511 "<br>" 512 "<h2>Alternative 1 (recommended)</h2>" 513 "<ol type=\"1\">" 514 "<li>Install <a href=\"https://www.videolan.org\">VideoLanClient (VLC)</a>, from App- or Play-store free of charge</li>" 515 "<li>Open VLC and select Network Stream</li>" 516 "<li>Enter, copy or share this network address to VLC: <a href=\"http://%s:%s/stream.m3u\">http://%s:%s/stream.m3u</a></li>" 517 "</ol>" 518 "<br>" 519 "<br>" 520 "<h2>Alternative 2 (on your own)</h2>" 521 "<br>" 522 "<br>" 523 "<audio id=\"audio\" controls=\"true\" src=\"stream.wav\" preload=\"none\"></audio>" 524 "<br>" 525 "<br>", 526 pvc->profile->http.host, pvc->profile->http.port, 527 pvc->profile->http.host, pvc->profile->http.port); 528 529 if (x == pvc->profile->http.nstate) 530 fprintf(io, "<h2>There are currently no free slots (%zu active). Try again later!</h2>", x); 531 else 532 fprintf(io, "<h2>There are %zu free slots (%zu active)</h2>", pvc->profile->http.nstate - x, x); 533 534 fprintf(io, "</body></html>"); 535 break; 536 case 1: 537 for (x = 0; x < pvc->profile->http.nstate; x++) { 538 if (pvc->profile->http.state[x].fd >= 0) 539 continue; 540 switch (voss_http_generate_wav_header(pvc, io, r_start, r_end, is_partial)) { 541 static const int enable = 1; 542 543 case 0: 544 fflush(io); 545 fdclose(io, NULL); 546 if (ioctl(fd, FIONBIO, &enable) != 0) { 547 close(fd); 548 return; 549 } 550 pvc->profile->http.state[x].ts = 551 virtual_oss_timestamp() - 1000000000ULL; 552 pvc->profile->http.state[x].fd = fd; 553 return; 554 case 1: 555 fclose(io); 556 return; 557 case 2: 558 fprintf(io, "HTTP/1.1 416 Range Not Satisfiable\r\n" 559 "Server: virtual_oss/1.0\r\n" 560 "\r\n"); 561 goto done; 562 default: 563 goto done; 564 } 565 } 566 fprintf(io, "HTTP/1.0 503 Out of Resources\r\n" 567 "Server: virtual_oss/1.0\r\n" 568 "\r\n"); 569 break; 570 case 2: 571 fprintf(io, "HTTP/1.0 200 OK\r\n" 572 "Content-Type: audio/mpegurl\r\n" 573 "Server: virtual_oss/1.0\r\n" 574 "Cache-Control: no-cache, no-store\r\n" 575 "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" 576 "\r\n"); 577 if (sa->sin_family == AF_INET && pvc->profile->http.rtp_port != NULL) { 578 fprintf(io, "rtp://239.255.0.1:%s\r\n", pvc->profile->http.rtp_port); 579 } else { 580 fprintf(io, "http://%s:%s/stream.wav\r\n", 581 pvc->profile->http.host, pvc->profile->http.port); 582 } 583 break; 584 default: 585 fprintf(io, "HTTP/1.0 404 Not Found\r\n" 586 "Content-Type: text/html\r\n" 587 "Server: virtual_oss/1.0\r\n" 588 "\r\n" 589 "<html><head><title>virtual_oss</title></head>" 590 "<body>" 591 "<h1>Invalid page requested! " 592 "<a HREF=\"index.html\">Click here to go back</a>.</h1><br>" 593 "</body>" 594 "</html>"); 595 break; 596 } 597 done: 598 if (io != NULL) 599 fclose(io); 600 else 601 close(fd); 602 } 603 604 static int 605 voss_httpd_do_listen(vclient_t *pvc, const char *host, const char *port, 606 struct pollfd *pfd, int num_sock, int buffer) 607 { 608 static const struct timeval timeout = {.tv_sec = 1}; 609 struct addrinfo hints = {}; 610 struct addrinfo *res; 611 struct addrinfo *res0; 612 int error; 613 int flag; 614 int s; 615 int ns = 0; 616 617 hints.ai_family = AF_UNSPEC; 618 hints.ai_socktype = SOCK_STREAM; 619 hints.ai_protocol = IPPROTO_TCP; 620 hints.ai_flags = AI_PASSIVE; 621 622 if ((error = getaddrinfo(host, port, &hints, &res))) 623 return (-1); 624 625 res0 = res; 626 627 do { 628 if ((s = socket(res0->ai_family, res0->ai_socktype, 629 res0->ai_protocol)) < 0) 630 continue; 631 632 flag = 1; 633 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &flag, (int)sizeof(flag)); 634 setsockopt(s, SOL_SOCKET, SO_SNDBUF, &buffer, (int)sizeof(buffer)); 635 setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffer, (int)sizeof(buffer)); 636 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, (int)sizeof(timeout)); 637 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, (int)sizeof(timeout)); 638 639 if (bind(s, res0->ai_addr, res0->ai_addrlen) == 0) { 640 if (listen(s, pvc->profile->http.nstate) == 0) { 641 if (ns < num_sock) { 642 pfd[ns++].fd = s; 643 continue; 644 } 645 close(s); 646 break; 647 } 648 } 649 close(s); 650 } while ((res0 = res0->ai_next) != NULL); 651 652 freeaddrinfo(res); 653 654 return (ns); 655 } 656 657 static size_t 658 voss_httpd_buflimit(vclient_t *pvc) 659 { 660 /* don't buffer more than 250ms */ 661 return ((pvc->sample_rate / 4) * 662 pvc->channels * vclient_sample_bytes(pvc)); 663 }; 664 665 static void 666 voss_httpd_server(vclient_t *pvc) 667 { 668 const size_t bufferlimit = voss_httpd_buflimit(pvc); 669 const char *host = pvc->profile->http.host; 670 const char *port = pvc->profile->http.port; 671 struct sockaddr sa = {}; 672 struct pollfd fds[VOSS_HTTPD_BIND_MAX] = {}; 673 int nfd; 674 675 nfd = voss_httpd_do_listen(pvc, host, port, fds, VOSS_HTTPD_BIND_MAX, bufferlimit); 676 if (nfd < 1) { 677 errx(EX_SOFTWARE, "Could not bind to " 678 "'%s' and '%s'", host, port); 679 } 680 681 while (1) { 682 struct sockaddr_in si; 683 int ns = nfd; 684 int c; 685 int f; 686 687 for (c = 0; c != ns; c++) { 688 fds[c].events = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | 689 POLLERR | POLLHUP | POLLNVAL); 690 fds[c].revents = 0; 691 } 692 if (poll(fds, ns, -1) < 0) 693 errx(EX_SOFTWARE, "Polling failed"); 694 695 for (c = 0; c != ns; c++) { 696 socklen_t socklen = sizeof(sa); 697 698 if (fds[c].revents == 0) 699 continue; 700 f = accept(fds[c].fd, &sa, &socklen); 701 if (f < 0) 702 continue; 703 memcpy(&si, &sa, sizeof(sa)); 704 voss_httpd_handle_connection(pvc, f, &si); 705 } 706 } 707 } 708 709 static void 710 voss_httpd_streamer(vclient_t *pvc) 711 { 712 const size_t bufferlimit = voss_httpd_buflimit(pvc); 713 uint8_t *ptr; 714 size_t len; 715 uint64_t ts; 716 size_t x; 717 718 atomic_lock(); 719 while (1) { 720 if (vclient_export_read_locked(pvc) != 0) { 721 atomic_wait(); 722 continue; 723 } 724 vring_get_read(&pvc->rx_ring[1], &ptr, &len); 725 if (len == 0) { 726 /* try to avoid ring wraps */ 727 vring_reset(&pvc->rx_ring[1]); 728 atomic_wait(); 729 continue; 730 } 731 atomic_unlock(); 732 733 ts = virtual_oss_timestamp(); 734 735 /* check if we should send RTP data, if any */ 736 if (pvc->profile->http.rtp_fd > -1) { 737 voss_httpd_send_rtp(pvc, pvc->profile->http.rtp_fd, 738 ptr, len, pvc->profile->http.rtp_ts); 739 } 740 741 /* send HTTP data, if any */ 742 for (x = 0; x < pvc->profile->http.nstate; x++) { 743 int fd = pvc->profile->http.state[x].fd; 744 uint64_t delta = ts - pvc->profile->http.state[x].ts; 745 uint8_t buf[1]; 746 int write_len; 747 748 if (fd < 0) { 749 /* do nothing */ 750 } else if (delta >= (8ULL * 1000000000ULL)) { 751 /* no data for 8 seconds - terminate */ 752 pvc->profile->http.state[x].fd = -1; 753 close(fd); 754 } else if (read(fd, buf, sizeof(buf)) != -1 || errno != EWOULDBLOCK) { 755 pvc->profile->http.state[x].fd = -1; 756 close(fd); 757 } else if (ioctl(fd, FIONWRITE, &write_len) < 0) { 758 pvc->profile->http.state[x].fd = -1; 759 close(fd); 760 } else if ((ssize_t)(bufferlimit - write_len) < (ssize_t)len) { 761 /* do nothing */ 762 } else if (write(fd, ptr, len) != (ssize_t)len) { 763 pvc->profile->http.state[x].fd = -1; 764 close(fd); 765 } else { 766 /* update timestamp */ 767 pvc->profile->http.state[x].ts = ts; 768 } 769 } 770 771 atomic_lock(); 772 vring_inc_read(&pvc->rx_ring[1], len); 773 } 774 } 775 776 const char * 777 voss_httpd_start(vprofile_t *pvp) 778 { 779 vclient_t *pvc; 780 pthread_t td; 781 int error; 782 size_t x; 783 784 if (pvp->http.host == NULL || pvp->http.port == NULL || pvp->http.nstate == 0) 785 return (NULL); 786 787 pvp->http.state = malloc(sizeof(pvp->http.state[0]) * pvp->http.nstate); 788 if (pvp->http.state == NULL) 789 return ("Could not allocate HTTP states"); 790 791 for (x = 0; x != pvp->http.nstate; x++) { 792 pvp->http.state[x].fd = -1; 793 pvp->http.state[x].ts = 0; 794 } 795 796 pvc = vclient_alloc(); 797 if (pvc == NULL) 798 return ("Could not allocate client for HTTP server"); 799 800 pvc->profile = pvp; 801 802 if (pvp->http.rtp_ifname != NULL) { 803 const char *perr; 804 805 if (pvc->channels > 2) 806 return ("RTP only supports 44.1kHz, 1 or 2 channels at 16-bit depth"); 807 808 /* bind to UDP port */ 809 perr = voss_httpd_bind_rtp(pvc, pvp->http.rtp_ifname, 810 &pvp->http.rtp_fd); 811 if (perr != NULL) 812 return (perr); 813 814 /* setup buffers */ 815 error = vclient_setup_buffers(pvc, 0, 0, 816 pvp->channels, AFMT_S16_LE, 44100); 817 } else { 818 pvp->http.rtp_fd = -1; 819 820 /* setup buffers */ 821 error = vclient_setup_buffers(pvc, 0, 0, pvp->channels, 822 vclient_get_default_fmt(pvp, VTYPE_WAV_HDR), 823 voss_dsp_sample_rate); 824 } 825 826 if (error != 0) { 827 vclient_free(pvc); 828 return ("Could not allocate buffers for HTTP server"); 829 } 830 831 /* trigger enabled */ 832 pvc->rx_enabled = 1; 833 834 pvc->type = VTYPE_OSS_DAT; 835 836 atomic_lock(); 837 TAILQ_INSERT_TAIL(&pvp->head, pvc, entry); 838 atomic_unlock(); 839 840 if (pthread_create(&td, NULL, (void *)&voss_httpd_server, pvc)) 841 return ("Could not create HTTP daemon thread"); 842 if (pthread_create(&td, NULL, (void *)&voss_httpd_streamer, pvc)) 843 return ("Could not create HTTP streamer thread"); 844 845 return (NULL); 846 } 847