aes67-daemon/daemon/rtsp_client.cpp
Andrea Bondavalli e9dd44fdb4 Minor changes
2020-04-10 13:01:36 -07:00

178 lines
5.8 KiB
C++

//
// rtsp_client.cpp
//
// Copyright (c) 2019 2020 Andrea Bondavalli. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include <httplib.h>
#include <boost/algorithm/string.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iomanip>
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#include <chrono>
#include "log.hpp"
#include "utils.hpp"
#include "rtsp_client.hpp"
using namespace boost::asio;
using namespace boost::asio::ip;
using namespace boost::algorithm;
struct RtspResponse {
uint32_t cseq;
std::string content_type;
uint64_t content_length;
std::string body;
};
RtspResponse read_response(tcp::iostream& s, uint16_t max_length) {
RtspResponse res;
std::string header;
/*
RTSP/1.0 200 OK
CSeq: 312
Date: 23 Jan 1997 15:35:06 GMT
Content-Type: application/sdp
Content-Length: 376
*/
try {
while (std::getline(s, header) && header != "" && header != "\r") {
to_lower(header);
trim(header);
if (header.rfind("cseq:", 0) != std::string::npos) {
res.cseq = std::stoi(header.substr(5));
} else if (header.rfind("content-type:", 0) != std::string::npos) {
res.content_type = header.substr(13);
trim(res.content_type);
} else if (header.rfind("content-length:", 0) != std::string::npos) {
res.content_length = std::stoi(header.substr(15));
}
}
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "rtsp_client:: invalid response header, "
<< "cannot perform number conversion";
}
// read up to max_length
if (res.content_length > 0 && res.content_length < max_length) {
res.body.reserve(res.content_length);
std::copy_n(std::istreambuf_iterator(s), res.content_length,
std::back_inserter(res.body));
}
return res;
}
std::pair<bool, RTSPSSource> RTSPClient::describe(const std::string& path,
const std::string& address,
const std::string& port) {
RTSPSSource rs;
bool success{false};
try {
tcp::iostream s;
#if BOOST_VERSION < 106700
s.expires_from_now(boost::posix_time::seconds(client_timeout));
#else
s.expires_from_now(std::chrono::seconds(client_timeout));
#endif
BOOST_LOG_TRIVIAL(debug) << "rtsp_client:: connecting to "
<< "rtsp://" << address << ":" << port << path;
s.connect(address, port.length() ? port : dft_port);
if (!s) {
BOOST_LOG_TRIVIAL(warning)
<< "rtsp_client:: unable to connect to " << address << ":" << port;
return std::make_pair(success, rs);
}
uint16_t cseq = seq_number++;
s << "DESCRIBE rtsp://" << address << ":" << port
<< httplib::detail::encode_url(path) << " RTSP/1.0\r\n";
s << "CSeq: " << cseq << "\r\n";
s << "User-Agent: aes67-daemon\r\n";
s << "Accept: application/sdp\r\n\r\n";
// By default, the stream is tied with itself. This means that the stream
// automatically flush the buffered output before attempting a read. It is
// not necessary not explicitly flush the stream at this point.
// Check that response is OK.
std::string rtsp_version;
s >> rtsp_version;
unsigned int status_code;
s >> status_code;
std::string status_message;
std::getline(s, status_message);
if (!s || rtsp_version.substr(0, 5) != "RTSP/") {
BOOST_LOG_TRIVIAL(error) << "rtsp_client:: invalid response from "
<< "rtsp://" << address << ":" << port << path;
return std::make_pair(success, rs);
}
if (status_code != 200) {
BOOST_LOG_TRIVIAL(error) << "rtsp_client:: response with status code "
<< status_code << " from "
<< "rtsp://" << address << ":" << port << path;
return std::make_pair(success, rs);
}
auto res = read_response(s, max_body_length);
if (res.content_type.rfind("application/sdp", 0) == std::string::npos) {
BOOST_LOG_TRIVIAL(error) << "rtsp_client:: unsupported content-type "
<< res.content_type << " from "
<< "rtsp://" << address << ":" << port << path;
return std::make_pair(success, rs);
}
if (res.cseq != cseq) {
BOOST_LOG_TRIVIAL(error)
<< "rtsp_client:: invalid response sequence " << res.cseq << " from "
<< "rtsp://" << address << ":" << port << path;
return std::make_pair(success, rs);
}
std::stringstream ss;
ss << "rtsp:" << std::hex
<< crc16(reinterpret_cast<const uint8_t*>(res.body.c_str()),
res.body.length())
<< std::hex << ip::address_v4::from_string(address.c_str()).to_ulong();
rs.id = ss.str();
rs.source = "mDNS";
rs.address = address;
rs.sdp = std::move(res.body);
BOOST_LOG_TRIVIAL(info) << "rtsp_client:: describe completed "
<< "rtsp://" << address << ":" << port << path;
success = true;
} catch (std::exception& e) {
BOOST_LOG_TRIVIAL(warning)
<< "rtsp_client:: error with "
<< "rtsp://" << address << ":" << port << path << ": " << e.what();
}
return std::make_pair(success, rs);
}