From 600ed9972ea0d390e9a44af34eb0167284b52cce Mon Sep 17 00:00:00 2001 From: phil <me@filou.se> Date: Wed, 21 Apr 2021 14:54:14 +0200 Subject: [PATCH] working rav-publish (to be tested) --- README.md | 25 ++-- scripts/html-index.sh | 23 ++++ src/include/aes67/utils/rtsp-srv.h | 5 +- src/utils/rav-publish/rav-publish.c | 207 ++++++++++++++++++++++++++-- src/utils/rtsp-srv.c | 102 +++++++++++--- 5 files changed, 317 insertions(+), 45 deletions(-) create mode 100755 scripts/html-index.sh diff --git a/README.md b/README.md index a0d58d9..a26efc0 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ https://github.com/tschiemer/aes67 - [x] [sdp-gen](#sdp-gen): generate SDP - RTSP/HTTP - [x] [rtsp-describe](#rtsp-describe): retrieve SDP from RTSP service - - [ ] rtsp/http combo server? + - [ ] ~~rtsp/http combo server?~~ -> [rav-publish](#rav-publish) - RAVENNA - [ ] ~~RAV2SAP~~ -> [sapd](#sapd) - [x] [rav-lookup](#rav-lookup): browse for sessions/devices @@ -63,16 +63,10 @@ https://github.com/tschiemer/aes67 - Support - mDNS (abstraction for mdns service) - [x] dns-sd - - [x] discovery - - [x] registration - - [ ] avahi - - [ ] dns-sd compat layer - - [ ] RTSP describe - - [x] retrieve SDP from server - - [ ] serve SDPs on request + - [x] avahi (to be tested further) + - [x] RTSP describe client + server - - + ## In a Nutshell @@ -454,10 +448,15 @@ By default sets up a mini-RTSP server serving the given session descriptions. Options: -h,-? Outputs this info -v Some status output to STDERR - -p <rtsp-port> Port of RTSP server. - --host <host> Host of target device (by default will assume self). - --ip <ip> IPv4/6 address of target device (create an record for host). + -p, --port <rtsp-port> Port of RTSP server. + --host <host> Host of target device (by default will assume self; if given will try to use originator IPv4/6 from SDP file). + --ip <ip> (Override) IPv4/6 address of target device (create an record for host). --no-rtsp Do not start a RTSP server. + --http <root> Start a http server with given root dir (implies RTSP server) + --http-index <index-file> Index file to serve from directories (default index.html) +Examples +./rav-publish -p 9191 --no-rtsp my-session.sdp another-session.sdp +scripts/html-index.sh sdp.d >> sdp.d/index.html && ./rav-publish -p 9191 --http sdp.d sdp.d/*.sdp ``` To quickly register a (test) ravenna session with name `Hello my pretty` on the localhost's port 9191: diff --git a/scripts/html-index.sh b/scripts/html-index.sh new file mode 100755 index 0000000..6e88cfc --- /dev/null +++ b/scripts/html-index.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +usage(){ + echo "Usage: $0 <root-directory>" + echo "Outputs simple HTML index file starting from <root-directory>" +} + +if [ $# == 0 ]; then + usage + exit 0; +fi + +ROOT=$1 + +IFS=$'\n' + +i=0 +echo "<!DOCTYPE html><html><body><ul>" +for filepath in `ls -a "$ROOT" | cat`; do + path=`basename "$filepath"` + echo " <li><a href='$path'>$path</a></li>" +done +echo "</ul></body></html>" \ No newline at end of file diff --git a/src/include/aes67/utils/rtsp-srv.h b/src/include/aes67/utils/rtsp-srv.h index 96dd235..4cb1c19 100644 --- a/src/include/aes67/utils/rtsp-srv.h +++ b/src/include/aes67/utils/rtsp-srv.h @@ -61,6 +61,7 @@ enum aes67_rtsp_srv_proto { }; enum aes67_rtsp_srv_method { + aes67_rtsp_srv_method_undefined = 0, aes67_rtsp_srv_method_options = 1, aes67_rtsp_srv_method_describe = 2, aes67_rtsp_srv_method_get = 4, @@ -134,6 +135,8 @@ struct aes67_rtsp_srv { u16_t sent; u16_t len; u8_t data[AES67_RTSP_SRV_TXBUFSIZE]; + void * response_state; + bool more; } res; // response }; @@ -148,7 +151,7 @@ void aes67_rtsp_srv_blocking(struct aes67_rtsp_srv * srv, bool blocking); void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv); u16_t aes67_rtsp_srv_sdp_getter(struct aes67_rtsp_srv * srv, void * sdpref, u8_t * buf, u16_t maxlen); -void aes67_rtsp_srv_http_handler(struct aes67_rtsp_srv * srv, const enum aes67_rtsp_srv_method method, const char * uri, const u8_t urilen, u8_t * buf, u16_t * len, u16_t maxlen, void * response_data); +void aes67_rtsp_srv_http_handler(struct aes67_rtsp_srv * srv, const enum aes67_rtsp_srv_method method, char * uri, u8_t urilen, u8_t * buf, u16_t * len, u16_t maxlen, bool * more, void ** response_state); struct aes67_rtsp_srv_resource * aes67_rtsp_srv_sdp_add(struct aes67_rtsp_srv * srv, const char * uri, const u8_t urilen, void * sdpref); void aes67_rtsp_srv_sdp_remove(struct aes67_rtsp_srv * srv, void * sdpref); diff --git a/src/utils/rav-publish/rav-publish.c b/src/utils/rav-publish/rav-publish.c index bfd3ff4..59b1b02 100644 --- a/src/utils/rav-publish/rav-publish.c +++ b/src/utils/rav-publish/rav-publish.c @@ -57,6 +57,7 @@ static struct { u32_t ttl; bool http; char * http_root; + char * http_index; } opts = { .verbose = false, .rtsp = true, @@ -65,7 +66,8 @@ static struct { .port = 0, .ttl = 100, .http = false, - .http_root = NULL + .http_root = NULL, + .http_index = "index.html" }; static volatile bool keep_running; @@ -90,7 +92,11 @@ static void help(FILE * fd) "\t --ip <ip>\t (Override) IPv4/6 address of target device (create an record for host).\n" "\t --no-rtsp\t Do not start a RTSP server.\n" "\t --http <root>\t Start a http server with given root dir (implies RTSP server)\n" - , argv0); + "\t --http-index <index-file> Index file to serve from directories (default index.html)\n" + "Examples\n" + "%s -p 9191 --no-rtsp my-session.sdp another-session.sdp\n" + "scripts/html-index.sh sdp.d >> sdp.d/index.html && %s -p 9191 --http sdp.d sdp.d/*.sdp\n" + , argv0, argv0, argv0); } @@ -255,6 +261,11 @@ static int rtsp_setup() return EXIT_FAILURE; } + fprintf(stderr, "Started RTSP%s server on port %hu\n", opts.http ? "/HTTP" : "", opts.port); + if (opts.http){ + fprintf(stderr, "HTTP root = %s\n", opts.http_root); + } + return EXIT_SUCCESS; } @@ -295,13 +306,176 @@ u16_t aes67_rtsp_srv_sdp_getter(struct aes67_rtsp_srv * srv, void * sdpref, u8_t return len + sdpres->len; } +void aes67_rtsp_srv_http_handler(struct aes67_rtsp_srv * srv, const enum aes67_rtsp_srv_method method, char * uri, u8_t urilen, u8_t * buf, u16_t * len, u16_t maxlen, bool * more, void ** response_state) +{ + static struct { + int fd; + off_t filesize; + off_t read; + } state = { + .fd = -1 + }; + + // if data is set, this is from a previous call but the file to be transmitted was to big for the internal buffer to send at once. + // possibly the + if (*response_state){ + + // premature termination + if (state.fd != -1){ + close(state.fd); + return; + } + + // continue passing file contents to server + + ssize_t r = read(state.fd, buf + *len, maxlen); + + if (r > 0){ + *len = r; + state.read += r; + } + + *more = state.read < state.filesize; + + if (!*more){ + close(state.fd); + state.fd = -1; + } + + return; + } + + + uri[urilen] = '\0'; +// fprintf(stderr, "HTTP request for [%s]\n", uri); + + char uri_decoded[256]; + + uri_decode(uri, urilen, uri_decoded); + + + // basic safety check + if (strstr(uri_decoded, "..") != NULL){ + printf("404 1\n"); + *len = snprintf((char*)buf, maxlen, + "HTTP/1.1 404 Not Found\r\n" + "Connection: close\r\n" + "\r\n" + ); + return; + } + +// u16_t l = 0; + +// if (urilen > 4 && memcmp(uri, "/api", 4) == 0){ +// +// // this is mor a proof of concept when trying to implement a restful api +// *len = snprintf((char*)buf, maxlen, +// "HTTP/1.1 200 OK\r\n" +// "Connection: close\r\n" +// "Content-Type: application/json\r\n" +// "\r\n" +// "{code: 200, msg: \"proof of concept\"}" +// ); +// return; +// +// } + + char path[256]; + + snprintf(path, sizeof(path), "%s%s", opts.http_root, uri_decoded); + +// printf("file = [%s]\n", path); + + // check file existence + if (access(path, F_OK) != 0){ + fprintf(stderr, "ERROR 404 2\n"); + *len = snprintf((char*)buf, maxlen, + "HTTP/1.1 404 Not Found\r\n" + "Connection: close\r\n" + "\r\n" + ); + return; + } + + struct stat st; + + //get file stat + if (stat(path, &st)){ + fprintf(stderr, "ERROR 500 1\n"); + *len = snprintf((char*)buf, maxlen, + "HTTP/1.1 500 Internal Server Error\r\n" + "Connection: close\r\n" + "\r\n" + ); + return; + } + + // forbid directories (no listing) + if ((st.st_mode & S_IFMT) == S_IFDIR){ + // check if there is an index file + + snprintf(path, sizeof(path), "%s%s/%s", opts.http_root, uri, opts.http_index); + + if (access(path, F_OK) || stat(path, &st) || (st.st_mode & S_IFMT) != S_IFREG){ + + fprintf(stderr, "ERROR 403 1\n"); + *len = snprintf((char*)buf, maxlen, + "HTTP/1.1 403 Forbidden\r\n" + "Connection: close\r\n" + "\r\n" + ); + return; + } + } + + state.fd = open(path, O_RDONLY); + if (state.fd == -1){ + fprintf(stderr, "ERROR 500 2 \n"); + *len = snprintf((char*)buf, maxlen, + "HTTP/1.1 500 Internal Server Error\r\n" + "Connection: close\r\n" + "\r\n" + ); + return; + } + + state.filesize = st.st_size; + + *len = snprintf((char*)buf, maxlen, + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Content-Length: %lld\r\n" + "\r\n" + , st.st_size + ); + + state.read = 0; + + ssize_t remaining = maxlen - *len; + ssize_t r = read(state.fd, buf + *len, remaining); + + if (r > 0){ + *len += r; + state.read += r; + } + + *more = state.read < state.filesize; + + if (!*more){ + close(state.fd); + state.fd = -1; + } + + *response_state = &state; +} + static int load_sdpres(char * fname, size_t maxlen) { if (access(fname, F_OK) != 0){ fprintf(stderr, "ERROR file exists? %s\n", fname); return EXIT_FAILURE; } - struct stat st; if (stat(fname, &st)){ @@ -402,6 +576,7 @@ int main(int argc, char * argv[]) {"no-rtsp", no_argument, 0, 3 }, {"ttl", required_argument, 0, 4}, {"http", required_argument, 0, 5}, + {"http-index", required_argument, 0, 6}, {0, 0, 0, 0 } }; @@ -449,21 +624,31 @@ int main(int argc, char * argv[]) } break; - case 5: - if (access(optarg, F_OK) != 0){ + case 5: { + if (access(optarg, F_OK) != 0) { fprintf(stderr, "ERROR http root does not exists? %s\n", optarg); return EXIT_FAILURE; } + + struct stat st; + + if (stat(optarg, &st)){ + fprintf(stderr, "ERROR failed to get filesize %s\n", optarg); + return EXIT_FAILURE; + } + if ((st.st_mode & S_IFMT) != S_IFDIR){ + fprintf(stderr, "ERROR http root is not a directory: %s\n", optarg); + return EXIT_FAILURE; + } + opts.http = true; opts.http_root = optarg; + break; + } -// struct stat st; - -// if (stat(fname, &st)){ -// fprintf(stderr, "ERROR failed to get filesize %s\n", fname); -// return EXIT_FAILURE; -// } + case 6: + opts.http_index = optarg; break; case '?': diff --git a/src/utils/rtsp-srv.c b/src/utils/rtsp-srv.c index 33c68d8..291778c 100644 --- a/src/utils/rtsp-srv.c +++ b/src/utils/rtsp-srv.c @@ -244,7 +244,7 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) if (c == '\n'){ // basic sanity check - if (srv->req.data_len < 32){ + if (srv->req.data_len < 15){ close(srv->client_sockfd); srv->client_sockfd = -1; srv->state = aes67_rtsp_srv_state_listening; @@ -257,6 +257,8 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) u8_t * s = &srv->req.data[srv->req.data_len - CR - sizeof("HTTP/1.0")]; +// printf("[%s] [%s]\n", srv->req.data, s); + if (s[0] == 'H' && s[1] == 'T' && s[2] == 'T' && @@ -264,7 +266,6 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) s[4] == '/' && s[6] == '.' ){ - if (!srv->http_enabled){ close(srv->client_sockfd); srv->client_sockfd = -1; @@ -297,7 +298,9 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) srv->req.version.minor = s[7] - '0'; s = srv->req.data; - fprintf(stderr, "[%s]\n", s); + + srv->req.data[srv->req.data_len] = '\0'; + fprintf(stderr, "REQUEST %s", s); if (srv->req.proto == aes67_rtsp_srv_proto_rtsp && s[0] == 'D' && @@ -372,7 +375,7 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) srv->req.urilen = &srv->req.data[srv->req.data_len - CR - sizeof(" HTTP/1.0")] - srv->req.uri; srv->req.uri[srv->req.urilen] = '\0'; - printf("uri[%d] = [%s]\n", srv->req.urilen, srv->req.uri); +// printf("uri[%d] = [%s]\n", srv->req.urilen, srv->req.uri); // if rtsp, discard scheme and host if (srv->req.proto == aes67_rtsp_srv_proto_rtsp){ @@ -404,7 +407,16 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) srv->req.urilen -= delim - srv->req.uri; srv->req.uri = delim; } - printf("uri[%d] = [%s]\n", srv->req.urilen, srv->req.uri); + else if (srv->req.proto == aes67_rtsp_srv_proto_http){ + // sanity check + if (srv->req.uri[0] != '/'){ + close(srv->client_sockfd); + srv->client_sockfd = -1; + srv->state = aes67_rtsp_srv_state_listening; + return; + } + } +// printf("uri[%d] = [%s]\n", srv->req.urilen, srv->req.uri); // set line start srv->req.line = &srv->req.data[srv->req.data_len]; @@ -437,7 +449,7 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) close(srv->client_sockfd); srv->client_sockfd = -1; srv->state = aes67_rtsp_srv_state_listening; - printf("closed!\n"); +// printf("closed!\n"); return; } else if (r == 1) { srv->req.data[srv->req.data_len++] = c; @@ -447,7 +459,7 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) if (c == '\n') { // end of header? ([CR]NL) - if (srv->req.llen == 1 + CR && (!CR || srv->req.line[0] == '\r') && srv->req.line[1] == '\n') { + if (srv->req.llen == 1 + CR && (!CR || srv->req.line[0] == '\r') && srv->req.line[CR] == '\n') { srv->req.header_len = srv->req.data_len; // break to start reading body break; @@ -549,7 +561,7 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) if (srv->state == aes67_rtsp_srv_state_processing){ if (srv->req.proto == aes67_rtsp_srv_proto_rtsp){ - fprintf(stderr, "is rtsp\n"); +// fprintf(stderr, "is rtsp\n"); u16_t status_code = AES67_RTSP_STATUS_INTERNAL_ERROR; struct aes67_rtsp_srv_resource * res = NULL; @@ -557,7 +569,6 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) switch (srv->req.method){ case aes67_rtsp_srv_method_options: - fprintf(stderr, "method = options\n"); status_code = AES67_RTSP_STATUS_OK; break; @@ -674,22 +685,75 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) } // proto == aes67_rtsp_srv_proto_rtsp else if (srv->req.proto == aes67_rtsp_srv_proto_http){ - fprintf(stderr, "is http\n"); + + srv->res.more = false; + srv->res.len = 0; + srv->res.sent = 0; + srv->res.response_state = NULL; + + // NULL-terminate just to make simpler for processor (note, we're altering the buffer hereby, obviously..) +// srv->req.uri[srv->req.urilen] = '\0'; + + aes67_rtsp_srv_http_handler(srv, srv->req.method, (char*)srv->req.uri, srv->req.urilen, srv->res.data, &srv->res.len, AES67_RTSP_SRV_TXBUFSIZE, &srv->res.more, &srv->res.response_state); + + if (srv->res.len == 0){ +// fprintf(stderr, "no http data, closing\n"); + + close(srv->client_sockfd); + srv->client_sockfd = -1; + srv->state = aes67_rtsp_srv_state_listening; + } else { + srv->state = aes67_rtsp_srv_state_sending; + } + } // proto == aes67_rtsp_srv_proto_http } // state == aes67_rtsp_srv_state_processing if (srv->state == aes67_rtsp_srv_state_sending){ if (srv->res.sent < srv->res.len){ - fprintf(stderr, "Sending %d bytes..\n", srv->res.len - srv->res.sent); - write(srv->client_sockfd, srv->res.data + srv->res.sent, srv->res.len - srv->res.sent); +// fprintf(stderr, "Sending %d bytes..\n", srv->res.len - srv->res.sent); + + int r = write(srv->client_sockfd, srv->res.data + srv->res.sent, srv->res.len - srv->res.sent); + + if (r > 0){ + srv->res.sent += r; - srv->res.sent = srv->res.len; + // just some sanity check because I'm not sure how this behaves in real life + assert(r == srv->res.len); + + srv->res.len = 0; + + // if http with more data to send, call handler again + if (srv->req.proto == aes67_rtsp_srv_proto_http && srv->res.more) { + aes67_rtsp_srv_http_handler(srv, srv->req.method, (char *) srv->req.uri, srv->req.urilen, srv->res.data, + &srv->res.len, AES67_RTSP_SRV_TXBUFSIZE, &srv->res.more, + &srv->res.response_state); + } + + // no more data, terminate + if (srv->res.len == 0){ + + close(srv->client_sockfd); + srv->client_sockfd = -1; + + srv->state = aes67_rtsp_srv_state_listening; + } + } + // if socket closed + else if (r == 0){ + + if (srv->req.proto == aes67_rtsp_srv_proto_http){ + aes67_rtsp_srv_http_handler(srv, aes67_rtsp_srv_method_undefined, NULL, 0, NULL, NULL, 0, &srv->res.more, &srv->res.response_state); + } + + close(srv->client_sockfd); + srv->client_sockfd = -1; + + srv->state = aes67_rtsp_srv_state_listening; + } - srv->state = aes67_rtsp_srv_state_listening; - close(srv->client_sockfd); - srv->client_sockfd = -1; } } //state == aes67_rtsp_srv_state_sending @@ -698,13 +762,11 @@ void aes67_rtsp_srv_process(struct aes67_rtsp_srv * srv) WEAK_FUN u16_t aes67_rtsp_srv_sdp_getter(struct aes67_rtsp_srv * srv, void * sdpref, u8_t * buf, u16_t maxlen) { assert(false); - - return AES67_RTSP_STATUS_INTERNAL_ERROR; } -WEAK_FUN void aes67_rtsp_srv_http_handler(struct aes67_rtsp_srv * srv, const enum aes67_rtsp_srv_method method, const char * uri, const u8_t urilen, u8_t * buf, u16_t * len, u16_t maxlen, void * response_data) +WEAK_FUN void aes67_rtsp_srv_http_handler(struct aes67_rtsp_srv * srv, const enum aes67_rtsp_srv_method method, char * uri, u8_t urilen, u8_t * buf, u16_t * len, u16_t maxlen, bool * more, void ** response_state) { - + assert(false); } -- GitLab