aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/virtual_oss/virtual_oss/httpd.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/virtual_oss/virtual_oss/httpd.c')
-rw-r--r--usr.sbin/virtual_oss/virtual_oss/httpd.c846
1 files changed, 846 insertions, 0 deletions
diff --git a/usr.sbin/virtual_oss/virtual_oss/httpd.c b/usr.sbin/virtual_oss/virtual_oss/httpd.c
new file mode 100644
index 000000000000..dc5d6036f39d
--- /dev/null
+++ b/usr.sbin/virtual_oss/virtual_oss/httpd.c
@@ -0,0 +1,846 @@
+/*-
+ * Copyright (c) 2020 Hans Petter Selasky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/endian.h>
+#include <sys/uio.h>
+#include <sys/soundcard.h>
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <poll.h>
+#include <sysexits.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <net/if.h>
+#include <net/if_vlan_var.h>
+#include <net/bpf.h>
+
+#include <arpa/inet.h>
+
+#include <pthread.h>
+
+#include "int.h"
+
+#define VOSS_HTTPD_BIND_MAX 8
+#define VOSS_HTTPD_MAX_STREAM_TIME (60 * 60 * 3) /* seconds */
+
+struct http_state {
+ int fd;
+ uint64_t ts;
+};
+
+struct rtp_raw_packet {
+ struct {
+ uint32_t padding;
+ uint8_t dhost[6];
+ uint8_t shost[6];
+ uint16_t ether_type;
+ } __packed eth;
+ struct {
+ uint8_t hl_ver;
+ uint8_t tos;
+ uint16_t len;
+ uint16_t ident;
+ uint16_t offset;
+ uint8_t ttl;
+ uint8_t protocol;
+ uint16_t chksum;
+ union {
+ uint32_t sourceip;
+ uint16_t source16[2];
+ };
+ union {
+ uint32_t destip;
+ uint16_t dest16[2];
+ };
+ } __packed ip;
+ struct {
+ uint16_t srcport;
+ uint16_t dstport;
+ uint16_t len;
+ uint16_t chksum;
+ } __packed udp;
+ union {
+ uint8_t header8[12];
+ uint16_t header16[6];
+ uint32_t header32[3];
+ } __packed rtp;
+
+} __packed;
+
+static const char *
+voss_httpd_bind_rtp(vclient_t *pvc, const char *ifname, int *pfd)
+{
+ const char *perr = NULL;
+ struct vlanreq vr = {};
+ struct ifreq ifr = {};
+ int fd;
+
+ fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ perr = "Cannot open raw RTP socket";
+ goto done;
+ }
+
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ifr.ifr_data = (void *)&vr;
+
+ if (ioctl(fd, SIOCGETVLAN, &ifr) == 0)
+ pvc->profile->http.rtp_vlanid = vr.vlr_tag;
+ else
+ pvc->profile->http.rtp_vlanid = 0;
+
+ close(fd);
+
+ ifr.ifr_data = NULL;
+
+ *pfd = fd = open("/dev/bpf", O_RDWR);
+ if (fd < 0) {
+ perr = "Cannot open BPF device";
+ goto done;
+ }
+
+ if (ioctl(fd, BIOCSETIF, &ifr) != 0) {
+ perr = "Cannot bind BPF device to network interface";
+ goto done;
+ }
+done:
+ if (perr != NULL && fd > -1)
+ close(fd);
+ return (perr);
+}
+
+static uint16_t
+voss_ipv4_csum(const void *vptr, size_t count)
+{
+ const uint16_t *ptr = vptr;
+ uint32_t sum = 0;
+
+ while (count--)
+ sum += *ptr++;
+
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += (sum >> 16);
+
+ return (~sum);
+}
+
+static uint16_t
+voss_udp_csum(uint32_t sum, const void *vhdr, size_t count,
+ const uint16_t *ptr, size_t length)
+{
+ const uint16_t *hdr = vhdr;
+
+ while (count--)
+ sum += *hdr++;
+
+ while (length > 1) {
+ sum += *ptr++;
+ length -= 2;
+ }
+
+ if (length & 1)
+ sum += *__DECONST(uint8_t *, ptr);
+
+ sum = (sum >> 16) + (sum & 0xffff);
+ sum += (sum >> 16);
+
+ return (~sum);
+}
+
+static void
+voss_httpd_send_rtp_sub(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts)
+{
+ struct rtp_raw_packet pkt = {};
+ struct iovec iov[2];
+ size_t total_ip;
+ uint16_t port = atoi(pvc->profile->http.rtp_port);
+ size_t x;
+
+ /* NOTE: BPF filter will insert VLAN header for us */
+ memset(pkt.eth.dhost, 255, sizeof(pkt.eth.dhost));
+ memset(pkt.eth.shost, 1, sizeof(pkt.eth.shost));
+ pkt.eth.ether_type = htobe16(0x0800);
+ total_ip = sizeof(pkt.ip) + sizeof(pkt.udp) + sizeof(pkt.rtp) + len;
+
+ iov[0].iov_base = pkt.eth.dhost;
+ iov[0].iov_len = 14 + total_ip - len;
+
+ iov[1].iov_base = alloca(len);
+ iov[1].iov_len = len;
+
+ /* byte swap data - WAV files are 16-bit little endian */
+ for (x = 0; x != (len / 2); x++)
+ ((uint16_t *)iov[1].iov_base)[x] = bswap16(((uint16_t *)ptr)[x]);
+
+ pkt.ip.hl_ver = 0x45;
+ pkt.ip.len = htobe16(total_ip);
+ pkt.ip.ttl = 8;
+ pkt.ip.protocol = 17; /* UDP */
+ pkt.ip.sourceip = 0x01010101U;
+ pkt.ip.destip = htobe32((239 << 24) + (255 << 16) + (1 << 0));
+ pkt.ip.chksum = voss_ipv4_csum((void *)&pkt.ip, sizeof(pkt.ip) / 2);
+
+ pkt.udp.srcport = htobe16(port);
+ pkt.udp.dstport = htobe16(port);
+ pkt.udp.len = htobe16(total_ip - sizeof(pkt.ip));
+
+ pkt.rtp.header8[0] = (2 << 6);
+ pkt.rtp.header8[1] = ((pvc->channels == 2) ? 10 : 11) | 0x80;
+
+ pkt.rtp.header16[1] = htobe16(pvc->profile->http.rtp_seqnum);
+ pkt.rtp.header32[1] = htobe32(ts);
+ pkt.rtp.header32[2] = htobe32(0);
+
+ pkt.udp.chksum = voss_udp_csum(pkt.ip.dest16[0] + pkt.ip.dest16[1] +
+ pkt.ip.source16[0] + pkt.ip.source16[1] + 0x1100 + pkt.udp.len,
+ (void *)&pkt.udp, sizeof(pkt.udp) / 2 + sizeof(pkt.rtp) / 2,
+ iov[1].iov_base, iov[1].iov_len);
+
+ pvc->profile->http.rtp_seqnum++;
+ pvc->profile->http.rtp_ts += len / (2 * pvc->channels);
+
+ (void)writev(fd, iov, 2);
+}
+
+static void
+voss_httpd_send_rtp(vclient_t *pvc, int fd, void *ptr, size_t len, uint32_t ts)
+{
+ const uint32_t mod = pvc->channels * vclient_sample_bytes(pvc);
+ const uint32_t max = 1420 - (1420 % mod);
+
+ while (len >= max) {
+ voss_httpd_send_rtp_sub(pvc, fd, ptr, max, ts);
+ len -= max;
+ ptr = (uint8_t *)ptr + max;
+ }
+
+ if (len != 0)
+ voss_httpd_send_rtp_sub(pvc, fd, ptr, len, ts);
+}
+
+static size_t
+voss_httpd_usage(vclient_t *pvc)
+{
+ size_t usage = 0;
+ size_t x;
+
+ for (x = 0; x < pvc->profile->http.nstate; x++)
+ usage += (pvc->profile->http.state[x].fd != -1);
+ return (usage);
+}
+
+static char *
+voss_httpd_read_line(FILE *io, char *linebuffer, size_t linelen)
+{
+ char buffer[2];
+ size_t size = 0;
+
+ if (fread(buffer, 1, 2, io) != 2)
+ return (NULL);
+
+ while (1) {
+ if (buffer[0] == '\r' && buffer[1] == '\n')
+ break;
+ if (size == (linelen - 1))
+ return (NULL);
+ linebuffer[size++] = buffer[0];
+ buffer[0] = buffer[1];
+ if (fread(buffer + 1, 1, 1, io) != 1)
+ return (NULL);
+ }
+ linebuffer[size++] = 0;
+
+ return (linebuffer);
+}
+
+static int
+voss_http_generate_wav_header(vclient_t *pvc, FILE *io,
+ uintmax_t r_start, uintmax_t r_end, bool is_partial)
+{
+ uint8_t buffer[256];
+ uint8_t *ptr;
+ uintmax_t dummy_len;
+ uintmax_t delta;
+ size_t mod;
+ size_t len;
+ size_t buflen;
+
+ ptr = buffer;
+ mod = pvc->channels * vclient_sample_bytes(pvc);
+
+ if (mod == 0 || sizeof(buffer) < (44 + mod - 1))
+ return (-1);
+
+ /* align to next sample */
+ len = 44 + mod - 1;
+ len -= len % mod;
+
+ buflen = len;
+
+ /* clear block */
+ memset(ptr, 0, len);
+
+ /* fill out data header */
+ ptr[len - 8] = 'd';
+ ptr[len - 7] = 'a';
+ ptr[len - 6] = 't';
+ ptr[len - 5] = 'a';
+
+ /* magic for unspecified length */
+ ptr[len - 4] = 0x00;
+ ptr[len - 3] = 0xF0;
+ ptr[len - 2] = 0xFF;
+ ptr[len - 1] = 0x7F;
+
+ /* fill out header */
+ *ptr++ = 'R';
+ *ptr++ = 'I';
+ *ptr++ = 'F';
+ *ptr++ = 'F';
+
+ /* total chunk size - unknown */
+
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+ *ptr++ = 0;
+
+ *ptr++ = 'W';
+ *ptr++ = 'A';
+ *ptr++ = 'V';
+ *ptr++ = 'E';
+ *ptr++ = 'f';
+ *ptr++ = 'm';
+ *ptr++ = 't';
+ *ptr++ = ' ';
+
+ /* make sure header fits in PCM block */
+ len -= 28;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* audioformat = PCM */
+
+ *ptr++ = 0x01;
+ *ptr++ = 0x00;
+
+ /* number of channels */
+
+ len = pvc->channels;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ /* sample rate */
+
+ len = pvc->sample_rate;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* byte rate */
+
+ len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+ *ptr++ = len >> 16;
+ *ptr++ = len >> 24;
+
+ /* block align */
+
+ len = pvc->channels * vclient_sample_bytes(pvc);
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ /* bits per sample */
+
+ len = vclient_sample_bytes(pvc) * 8;
+
+ *ptr++ = len;
+ *ptr++ = len >> 8;
+
+ /* check if alignment is correct */
+ if (r_start >= buflen && (r_start % mod) != 0)
+ return (2);
+
+ dummy_len = pvc->sample_rate * pvc->channels * vclient_sample_bytes(pvc);
+ dummy_len *= VOSS_HTTPD_MAX_STREAM_TIME;
+
+ /* fixup end */
+ if (r_end >= dummy_len)
+ r_end = dummy_len - 1;
+
+ delta = r_end - r_start + 1;
+
+ if (is_partial) {
+ fprintf(io, "HTTP/1.1 206 Partial Content\r\n"
+ "Content-Type: audio/wav\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ "Connection: Close\r\n"
+ "Content-Range: bytes %ju-%ju/%ju\r\n"
+ "Content-Length: %ju\r\n"
+ "\r\n", r_start, r_end, dummy_len, delta);
+ } else {
+ fprintf(io, "HTTP/1.0 200 OK\r\n"
+ "Content-Type: audio/wav\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ "Connection: Close\r\n"
+ "Content-Length: %ju\r\n"
+ "\r\n", dummy_len);
+ }
+
+ /* check if we should insert a header */
+ if (r_start < buflen) {
+ buflen -= r_start;
+ if (buflen > delta)
+ buflen = delta;
+ /* send data */
+ if (fwrite(buffer + r_start, buflen, 1, io) != 1)
+ return (-1);
+ /* check if all data was read */
+ if (buflen == delta)
+ return (1);
+ }
+ return (0);
+}
+
+static void
+voss_httpd_handle_connection(vclient_t *pvc, int fd, const struct sockaddr_in *sa)
+{
+ char linebuffer[2048];
+ uintmax_t r_start = 0;
+ uintmax_t r_end = -1ULL;
+ bool is_partial = false;
+ char *line;
+ FILE *io;
+ size_t x;
+ int page;
+
+ io = fdopen(fd, "r+");
+ if (io == NULL)
+ goto done;
+
+ page = -1;
+
+ /* dump HTTP request header */
+ while (1) {
+ line = voss_httpd_read_line(io, linebuffer, sizeof(linebuffer));
+ if (line == NULL)
+ goto done;
+ if (line[0] == 0)
+ break;
+ if (page < 0 && (strstr(line, "GET / ") == line ||
+ strstr(line, "GET /index.html") == line)) {
+ page = 0;
+ } else if (page < 0 && strstr(line, "GET /stream.wav") == line) {
+ page = 1;
+ } else if (page < 0 && strstr(line, "GET /stream.m3u") == line) {
+ page = 2;
+ } else if (strstr(line, "Range: bytes=") == line &&
+ sscanf(line, "Range: bytes=%ju-%ju", &r_start, &r_end) >= 1) {
+ is_partial = true;
+ }
+ }
+
+ switch (page) {
+ case 0:
+ x = voss_httpd_usage(pvc);
+
+ fprintf(io, "HTTP/1.0 200 OK\r\n"
+ "Content-Type: text/html\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ "\r\n"
+ "<html><head><title>Welcome to live streaming</title>"
+ "<meta http-equiv=\"Cache-Control\" content=\"no-cache, no-store, must-revalidate\" />"
+ "<meta http-equiv=\"Pragma\" content=\"no-cache\" />"
+ "<meta http-equiv=\"Expires\" content=\"0\" />"
+ "</head>"
+ "<body>"
+ "<h1>Live HD stream</h1>"
+ "<br>"
+ "<br>"
+ "<h2>Alternative 1 (recommended)</h2>"
+ "<ol type=\"1\">"
+ "<li>Install <a href=\"https://www.videolan.org\">VideoLanClient (VLC)</a>, from App- or Play-store free of charge</li>"
+ "<li>Open VLC and select Network Stream</li>"
+ "<li>Enter, copy or share this network address to VLC: <a href=\"http://%s:%s/stream.m3u\">http://%s:%s/stream.m3u</a></li>"
+ "</ol>"
+ "<br>"
+ "<br>"
+ "<h2>Alternative 2 (on your own)</h2>"
+ "<br>"
+ "<br>"
+ "<audio id=\"audio\" controls=\"true\" src=\"stream.wav\" preload=\"none\"></audio>"
+ "<br>"
+ "<br>",
+ pvc->profile->http.host, pvc->profile->http.port,
+ pvc->profile->http.host, pvc->profile->http.port);
+
+ if (x == pvc->profile->http.nstate)
+ fprintf(io, "<h2>There are currently no free slots (%zu active). Try again later!</h2>", x);
+ else
+ fprintf(io, "<h2>There are %zu free slots (%zu active)</h2>", pvc->profile->http.nstate - x, x);
+
+ fprintf(io, "</body></html>");
+ break;
+ case 1:
+ for (x = 0; x < pvc->profile->http.nstate; x++) {
+ if (pvc->profile->http.state[x].fd >= 0)
+ continue;
+ switch (voss_http_generate_wav_header(pvc, io, r_start, r_end, is_partial)) {
+ static const int enable = 1;
+
+ case 0:
+ fflush(io);
+ fdclose(io, NULL);
+ if (ioctl(fd, FIONBIO, &enable) != 0) {
+ close(fd);
+ return;
+ }
+ pvc->profile->http.state[x].ts =
+ virtual_oss_timestamp() - 1000000000ULL;
+ pvc->profile->http.state[x].fd = fd;
+ return;
+ case 1:
+ fclose(io);
+ return;
+ case 2:
+ fprintf(io, "HTTP/1.1 416 Range Not Satisfiable\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "\r\n");
+ goto done;
+ default:
+ goto done;
+ }
+ }
+ fprintf(io, "HTTP/1.0 503 Out of Resources\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "\r\n");
+ break;
+ case 2:
+ fprintf(io, "HTTP/1.0 200 OK\r\n"
+ "Content-Type: audio/mpegurl\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "Cache-Control: no-cache, no-store\r\n"
+ "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
+ "\r\n");
+ if (sa->sin_family == AF_INET && pvc->profile->http.rtp_port != NULL) {
+ fprintf(io, "rtp://239.255.0.1:%s\r\n", pvc->profile->http.rtp_port);
+ } else {
+ fprintf(io, "http://%s:%s/stream.wav\r\n",
+ pvc->profile->http.host, pvc->profile->http.port);
+ }
+ break;
+ default:
+ fprintf(io, "HTTP/1.0 404 Not Found\r\n"
+ "Content-Type: text/html\r\n"
+ "Server: virtual_oss/1.0\r\n"
+ "\r\n"
+ "<html><head><title>virtual_oss</title></head>"
+ "<body>"
+ "<h1>Invalid page requested! "
+ "<a HREF=\"index.html\">Click here to go back</a>.</h1><br>"
+ "</body>"
+ "</html>");
+ break;
+ }
+done:
+ if (io != NULL)
+ fclose(io);
+ else
+ close(fd);
+}
+
+static int
+voss_httpd_do_listen(vclient_t *pvc, const char *host, const char *port,
+ struct pollfd *pfd, int num_sock, int buffer)
+{
+ static const struct timeval timeout = {.tv_sec = 1};
+ struct addrinfo hints = {};
+ struct addrinfo *res;
+ struct addrinfo *res0;
+ int error;
+ int flag;
+ int s;
+ int ns = 0;
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_PASSIVE;
+
+ if ((error = getaddrinfo(host, port, &hints, &res)))
+ return (-1);
+
+ res0 = res;
+
+ do {
+ if ((s = socket(res0->ai_family, res0->ai_socktype,
+ res0->ai_protocol)) < 0)
+ continue;
+
+ flag = 1;
+ setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &flag, (int)sizeof(flag));
+ setsockopt(s, SOL_SOCKET, SO_SNDBUF, &buffer, (int)sizeof(buffer));
+ setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buffer, (int)sizeof(buffer));
+ setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, (int)sizeof(timeout));
+ setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, (int)sizeof(timeout));
+
+ if (bind(s, res0->ai_addr, res0->ai_addrlen) == 0) {
+ if (listen(s, pvc->profile->http.nstate) == 0) {
+ if (ns < num_sock) {
+ pfd[ns++].fd = s;
+ continue;
+ }
+ close(s);
+ break;
+ }
+ }
+ close(s);
+ } while ((res0 = res0->ai_next) != NULL);
+
+ freeaddrinfo(res);
+
+ return (ns);
+}
+
+static size_t
+voss_httpd_buflimit(vclient_t *pvc)
+{
+ /* don't buffer more than 250ms */
+ return ((pvc->sample_rate / 4) *
+ pvc->channels * vclient_sample_bytes(pvc));
+};
+
+static void
+voss_httpd_server(vclient_t *pvc)
+{
+ const size_t bufferlimit = voss_httpd_buflimit(pvc);
+ const char *host = pvc->profile->http.host;
+ const char *port = pvc->profile->http.port;
+ struct sockaddr sa = {};
+ struct pollfd fds[VOSS_HTTPD_BIND_MAX] = {};
+ int nfd;
+
+ nfd = voss_httpd_do_listen(pvc, host, port, fds, VOSS_HTTPD_BIND_MAX, bufferlimit);
+ if (nfd < 1) {
+ errx(EX_SOFTWARE, "Could not bind to "
+ "'%s' and '%s'", host, port);
+ }
+
+ while (1) {
+ struct sockaddr_in si;
+ int ns = nfd;
+ int c;
+ int f;
+
+ for (c = 0; c != ns; c++) {
+ fds[c].events = (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI |
+ POLLERR | POLLHUP | POLLNVAL);
+ fds[c].revents = 0;
+ }
+ if (poll(fds, ns, -1) < 0)
+ errx(EX_SOFTWARE, "Polling failed");
+
+ for (c = 0; c != ns; c++) {
+ socklen_t socklen = sizeof(sa);
+
+ if (fds[c].revents == 0)
+ continue;
+ f = accept(fds[c].fd, &sa, &socklen);
+ if (f < 0)
+ continue;
+ memcpy(&si, &sa, sizeof(sa));
+ voss_httpd_handle_connection(pvc, f, &si);
+ }
+ }
+}
+
+static void
+voss_httpd_streamer(vclient_t *pvc)
+{
+ const size_t bufferlimit = voss_httpd_buflimit(pvc);
+ uint8_t *ptr;
+ size_t len;
+ uint64_t ts;
+ size_t x;
+
+ atomic_lock();
+ while (1) {
+ if (vclient_export_read_locked(pvc) != 0) {
+ atomic_wait();
+ continue;
+ }
+ vring_get_read(&pvc->rx_ring[1], &ptr, &len);
+ if (len == 0) {
+ /* try to avoid ring wraps */
+ vring_reset(&pvc->rx_ring[1]);
+ atomic_wait();
+ continue;
+ }
+ atomic_unlock();
+
+ ts = virtual_oss_timestamp();
+
+ /* check if we should send RTP data, if any */
+ if (pvc->profile->http.rtp_fd > -1) {
+ voss_httpd_send_rtp(pvc, pvc->profile->http.rtp_fd,
+ ptr, len, pvc->profile->http.rtp_ts);
+ }
+
+ /* send HTTP data, if any */
+ for (x = 0; x < pvc->profile->http.nstate; x++) {
+ int fd = pvc->profile->http.state[x].fd;
+ uint64_t delta = ts - pvc->profile->http.state[x].ts;
+ uint8_t buf[1];
+ int write_len;
+
+ if (fd < 0) {
+ /* do nothing */
+ } else if (delta >= (8ULL * 1000000000ULL)) {
+ /* no data for 8 seconds - terminate */
+ pvc->profile->http.state[x].fd = -1;
+ close(fd);
+ } else if (read(fd, buf, sizeof(buf)) != -1 || errno != EWOULDBLOCK) {
+ pvc->profile->http.state[x].fd = -1;
+ close(fd);
+ } else if (ioctl(fd, FIONWRITE, &write_len) < 0) {
+ pvc->profile->http.state[x].fd = -1;
+ close(fd);
+ } else if ((ssize_t)(bufferlimit - write_len) < (ssize_t)len) {
+ /* do nothing */
+ } else if (write(fd, ptr, len) != (ssize_t)len) {
+ pvc->profile->http.state[x].fd = -1;
+ close(fd);
+ } else {
+ /* update timestamp */
+ pvc->profile->http.state[x].ts = ts;
+ }
+ }
+
+ atomic_lock();
+ vring_inc_read(&pvc->rx_ring[1], len);
+ }
+}
+
+const char *
+voss_httpd_start(vprofile_t *pvp)
+{
+ vclient_t *pvc;
+ pthread_t td;
+ int error;
+ size_t x;
+
+ if (pvp->http.host == NULL || pvp->http.port == NULL || pvp->http.nstate == 0)
+ return (NULL);
+
+ pvp->http.state = malloc(sizeof(pvp->http.state[0]) * pvp->http.nstate);
+ if (pvp->http.state == NULL)
+ return ("Could not allocate HTTP states");
+
+ for (x = 0; x != pvp->http.nstate; x++) {
+ pvp->http.state[x].fd = -1;
+ pvp->http.state[x].ts = 0;
+ }
+
+ pvc = vclient_alloc();
+ if (pvc == NULL)
+ return ("Could not allocate client for HTTP server");
+
+ pvc->profile = pvp;
+
+ if (pvp->http.rtp_ifname != NULL) {
+ const char *perr;
+
+ if (pvc->channels > 2)
+ return ("RTP only supports 44.1kHz, 1 or 2 channels at 16-bit depth");
+
+ /* bind to UDP port */
+ perr = voss_httpd_bind_rtp(pvc, pvp->http.rtp_ifname,
+ &pvp->http.rtp_fd);
+ if (perr != NULL)
+ return (perr);
+
+ /* setup buffers */
+ error = vclient_setup_buffers(pvc, 0, 0,
+ pvp->channels, AFMT_S16_LE, 44100);
+ } else {
+ pvp->http.rtp_fd = -1;
+
+ /* setup buffers */
+ error = vclient_setup_buffers(pvc, 0, 0, pvp->channels,
+ vclient_get_default_fmt(pvp, VTYPE_WAV_HDR),
+ voss_dsp_sample_rate);
+ }
+
+ if (error != 0) {
+ vclient_free(pvc);
+ return ("Could not allocate buffers for HTTP server");
+ }
+
+ /* trigger enabled */
+ pvc->rx_enabled = 1;
+
+ pvc->type = VTYPE_OSS_DAT;
+
+ atomic_lock();
+ TAILQ_INSERT_TAIL(&pvp->head, pvc, entry);
+ atomic_unlock();
+
+ if (pthread_create(&td, NULL, (void *)&voss_httpd_server, pvc))
+ return ("Could not create HTTP daemon thread");
+ if (pthread_create(&td, NULL, (void *)&voss_httpd_streamer, pvc))
+ return ("Could not create HTTP streamer thread");
+
+ return (NULL);
+}