mirror of
				https://github.com/DigitalDevices/pvr.octonet.git
				synced 2025-03-01 10:53:09 +00:00 
			
		
		
		
	Change add-on to use inputstream.ffmpegdirect which also enables timeshifting
This commit is contained in:
		@@ -10,8 +10,6 @@
 | 
			
		||||
 | 
			
		||||
#include "OctonetData.h"
 | 
			
		||||
 | 
			
		||||
#include "rtsp_client.hpp"
 | 
			
		||||
 | 
			
		||||
#include <json/json.h>
 | 
			
		||||
#include <kodi/Filesystem.h>
 | 
			
		||||
#include <kodi/General.h>
 | 
			
		||||
@@ -23,10 +21,12 @@
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
OctonetData::OctonetData(const std::string& octonetAddress,
 | 
			
		||||
                         bool enableTimeshift,
 | 
			
		||||
                         const kodi::addon::IInstanceInfo& instance)
 | 
			
		||||
  : kodi::addon::CInstancePVRClient(instance)
 | 
			
		||||
{
 | 
			
		||||
  m_serverAddress = octonetAddress;
 | 
			
		||||
  m_enableTimeshift = enableTimeshift;
 | 
			
		||||
  m_channels.clear();
 | 
			
		||||
  m_groups.clear();
 | 
			
		||||
  m_lastEpgLoad = 0;
 | 
			
		||||
@@ -273,6 +273,25 @@ PVR_ERROR OctonetData::GetChannels(bool radio, kodi::addon::PVRChannelsResultSet
 | 
			
		||||
  return PVR_ERROR_NO_ERROR;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PVR_ERROR OctonetData::GetChannelStreamProperties(const kodi::addon::PVRChannel& channelinfo, std::vector<kodi::addon::PVRStreamProperty>& properties)
 | 
			
		||||
{
 | 
			
		||||
  properties.emplace_back(PVR_STREAM_PROPERTY_INPUTSTREAM, "inputstream.ffmpegdirect");
 | 
			
		||||
  properties.emplace_back("inputstream.ffmpegdirect.is_realtime_stream", "true");
 | 
			
		||||
  properties.emplace_back("inputstream.ffmpegdirect.open_mode", "ffmpeg");
 | 
			
		||||
  if (m_enableTimeshift)
 | 
			
		||||
  {
 | 
			
		||||
    // This property is required to support timeshifting for Radio channels
 | 
			
		||||
    properties.emplace_back("inputstream-player", "videodefaultplayer");
 | 
			
		||||
    properties.emplace_back("inputstream.ffmpegdirect.stream_mode", "timeshift");
 | 
			
		||||
  }
 | 
			
		||||
  properties.emplace_back(PVR_STREAM_PROPERTY_MIMETYPE, "video/x-mpegts");
 | 
			
		||||
  properties.emplace_back(PVR_STREAM_PROPERTY_STREAMURL, GetUrl(channelinfo.GetUniqueId()));
 | 
			
		||||
 | 
			
		||||
  kodi::Log(ADDON_LOG_INFO, "Playing channel - name: %s, url: %s, and using inputstream.ffmpegdirect", GetName(channelinfo.GetUniqueId()).c_str(), GetUrl(channelinfo.GetUniqueId()).c_str());
 | 
			
		||||
 | 
			
		||||
  return PVR_ERROR_NO_ERROR;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PVR_ERROR OctonetData::GetEPGForChannel(int channelUid,
 | 
			
		||||
                                        time_t start,
 | 
			
		||||
                                        time_t end,
 | 
			
		||||
@@ -426,20 +445,3 @@ OctonetGroup* OctonetData::FindGroup(const std::string& name)
 | 
			
		||||
  return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* PVR stream handling */
 | 
			
		||||
/* entirely unused, as we use standard RTSP+TS mux, which can be handlded by
 | 
			
		||||
 * Kodi core */
 | 
			
		||||
bool OctonetData::OpenLiveStream(const kodi::addon::PVRChannel& channelinfo)
 | 
			
		||||
{
 | 
			
		||||
  return rtsp_open(GetName(channelinfo.GetUniqueId()), GetUrl(channelinfo.GetUniqueId()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int OctonetData::ReadLiveStream(unsigned char* pBuffer, unsigned int iBufferSize)
 | 
			
		||||
{
 | 
			
		||||
  return rtsp_read(pBuffer, iBufferSize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OctonetData::CloseLiveStream()
 | 
			
		||||
{
 | 
			
		||||
  rtsp_close();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,7 @@ class ATTR_DLL_LOCAL OctonetData : public kodi::addon::CInstancePVRClient
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  OctonetData(const std::string& octonetAddress,
 | 
			
		||||
              bool enableTimeshift,
 | 
			
		||||
              const kodi::addon::IInstanceInfo& instance);
 | 
			
		||||
  ~OctonetData() override;
 | 
			
		||||
 | 
			
		||||
@@ -66,16 +67,13 @@ public:
 | 
			
		||||
  PVR_ERROR GetChannelGroups(bool radio, kodi::addon::PVRChannelGroupsResultSet& results) override;
 | 
			
		||||
  PVR_ERROR GetChannelGroupMembers(const kodi::addon::PVRChannelGroup& group,
 | 
			
		||||
                                   kodi::addon::PVRChannelGroupMembersResultSet& results) override;
 | 
			
		||||
  PVR_ERROR GetChannelStreamProperties(const kodi::addon::PVRChannel& channel, std::vector<kodi::addon::PVRStreamProperty>& properties) override;
 | 
			
		||||
 | 
			
		||||
  PVR_ERROR GetEPGForChannel(int channelUid,
 | 
			
		||||
                             time_t start,
 | 
			
		||||
                             time_t end,
 | 
			
		||||
                             kodi::addon::PVREPGTagsResultSet& results) override;
 | 
			
		||||
 | 
			
		||||
  bool OpenLiveStream(const kodi::addon::PVRChannel& channelinfo) override;
 | 
			
		||||
  int ReadLiveStream(unsigned char* buffer, unsigned int size) override;
 | 
			
		||||
  void CloseLiveStream() override;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
  const std::string& GetUrl(int id) const;
 | 
			
		||||
  const std::string& GetName(int id) const;
 | 
			
		||||
@@ -89,6 +87,7 @@ protected:
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  std::string m_serverAddress;
 | 
			
		||||
  bool m_enableTimeshift = false;
 | 
			
		||||
  std::vector<OctonetChannel> m_channels;
 | 
			
		||||
  std::vector<OctonetGroup> m_groups;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										728
									
								
								src/Socket.cpp
									
									
									
									
									
								
							
							
						
						
									
										728
									
								
								src/Socket.cpp
									
									
									
									
									
								
							@@ -1,728 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
 | 
			
		||||
 *
 | 
			
		||||
 *  SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 *  See LICENSE.md for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "Socket.h"
 | 
			
		||||
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <kodi/General.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
namespace OCTO
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
/* Master defines for client control */
 | 
			
		||||
#define RECEIVE_TIMEOUT 6 //sec
 | 
			
		||||
 | 
			
		||||
Socket::Socket(const enum SocketFamily family,
 | 
			
		||||
               const enum SocketDomain domain,
 | 
			
		||||
               const enum SocketType type,
 | 
			
		||||
               const enum SocketProtocol protocol)
 | 
			
		||||
{
 | 
			
		||||
  m_sd = INVALID_SOCKET;
 | 
			
		||||
  m_family = family;
 | 
			
		||||
  m_domain = domain;
 | 
			
		||||
  m_type = type;
 | 
			
		||||
  m_protocol = protocol;
 | 
			
		||||
  m_port = 0;
 | 
			
		||||
  memset(&m_sockaddr, 0, sizeof(m_sockaddr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Socket::Socket()
 | 
			
		||||
{
 | 
			
		||||
  // Default constructor, default settings
 | 
			
		||||
  m_sd = INVALID_SOCKET;
 | 
			
		||||
  m_family = af_inet;
 | 
			
		||||
  m_domain = pf_inet;
 | 
			
		||||
  m_type = sock_stream;
 | 
			
		||||
  m_protocol = tcp;
 | 
			
		||||
  m_port = 0;
 | 
			
		||||
  memset(&m_sockaddr, 0, sizeof(m_sockaddr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Socket::~Socket()
 | 
			
		||||
{
 | 
			
		||||
  close();
 | 
			
		||||
  osCleanup();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Socket::setHostname(const std::string& host)
 | 
			
		||||
{
 | 
			
		||||
  m_hostname = host;
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Socket::close()
 | 
			
		||||
{
 | 
			
		||||
  if (is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    if (m_sd != SOCKET_ERROR)
 | 
			
		||||
      closesocket(m_sd);
 | 
			
		||||
    m_sd = INVALID_SOCKET;
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Socket::create()
 | 
			
		||||
{
 | 
			
		||||
  close();
 | 
			
		||||
 | 
			
		||||
  if (!osInit())
 | 
			
		||||
  {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool Socket::bind(const unsigned short port)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
  if (is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    close();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  m_sd = socket(m_family, m_type, m_protocol);
 | 
			
		||||
  m_port = port;
 | 
			
		||||
  m_sockaddr.sin_family = (sa_family_t)m_family;
 | 
			
		||||
  m_sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all
 | 
			
		||||
  m_sockaddr.sin_port = htons(m_port);
 | 
			
		||||
 | 
			
		||||
  int bind_return = ::bind(m_sd, (sockaddr*)(&m_sockaddr), sizeof(m_sockaddr));
 | 
			
		||||
 | 
			
		||||
  if (bind_return == -1)
 | 
			
		||||
  {
 | 
			
		||||
    errormessage(getLastError(), "Socket::bind");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool Socket::listen() const
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
  if (!is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int listen_return = ::listen(m_sd, SOMAXCONN);
 | 
			
		||||
  //This is defined as 5 in winsock.h, and 0x7FFFFFFF in winsock2.h.
 | 
			
		||||
  //linux 128//MAXCONNECTIONS =1
 | 
			
		||||
 | 
			
		||||
  if (listen_return == -1)
 | 
			
		||||
  {
 | 
			
		||||
    errormessage(getLastError(), "Socket::listen");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool Socket::accept(Socket& new_socket) const
 | 
			
		||||
{
 | 
			
		||||
  if (!is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  socklen_t addr_length = sizeof(m_sockaddr);
 | 
			
		||||
  new_socket.m_sd =
 | 
			
		||||
      ::accept(m_sd, const_cast<sockaddr*>((const sockaddr*)&m_sockaddr), &addr_length);
 | 
			
		||||
 | 
			
		||||
#ifdef TARGET_WINDOWS
 | 
			
		||||
  if (new_socket.m_sd == INVALID_SOCKET)
 | 
			
		||||
#else
 | 
			
		||||
  if (new_socket.m_sd <= 0)
 | 
			
		||||
#endif
 | 
			
		||||
  {
 | 
			
		||||
    errormessage(getLastError(), "Socket::accept");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int Socket::send(const std::string& data)
 | 
			
		||||
{
 | 
			
		||||
  return Socket::send((const char*)data.c_str(), (const unsigned int)data.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int Socket::send(const char* data, const unsigned int len)
 | 
			
		||||
{
 | 
			
		||||
  fd_set set_w, set_e;
 | 
			
		||||
  struct timeval tv;
 | 
			
		||||
  int result;
 | 
			
		||||
 | 
			
		||||
  if (!is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // fill with new data
 | 
			
		||||
  tv.tv_sec = 0;
 | 
			
		||||
  tv.tv_usec = 0;
 | 
			
		||||
 | 
			
		||||
  FD_ZERO(&set_w);
 | 
			
		||||
  FD_ZERO(&set_e);
 | 
			
		||||
  FD_SET(m_sd, &set_w);
 | 
			
		||||
  FD_SET(m_sd, &set_e);
 | 
			
		||||
 | 
			
		||||
  result = select(FD_SETSIZE, &set_w, nullptr, &set_e, &tv);
 | 
			
		||||
 | 
			
		||||
  if (result < 0)
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Socket::send  - select failed");
 | 
			
		||||
    close();
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  if (FD_ISSET(m_sd, &set_w))
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Socket::send  - failed to send data");
 | 
			
		||||
    close();
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  int status = ::send(m_sd, data, len, 0);
 | 
			
		||||
 | 
			
		||||
  if (status == -1)
 | 
			
		||||
  {
 | 
			
		||||
    errormessage(getLastError(), "Socket::send");
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Socket::send  - failed to send data");
 | 
			
		||||
    close();
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int Socket::sendto(const char* data, unsigned int size, bool sendcompletebuffer)
 | 
			
		||||
{
 | 
			
		||||
  int sentbytes = 0;
 | 
			
		||||
  int i;
 | 
			
		||||
 | 
			
		||||
  do
 | 
			
		||||
  {
 | 
			
		||||
    i = ::sendto(m_sd, data, size, 0, (const struct sockaddr*)&m_sockaddr, sizeof(m_sockaddr));
 | 
			
		||||
 | 
			
		||||
    if (i <= 0)
 | 
			
		||||
    {
 | 
			
		||||
      errormessage(getLastError(), "Socket::sendto");
 | 
			
		||||
      osCleanup();
 | 
			
		||||
      return i;
 | 
			
		||||
    }
 | 
			
		||||
    sentbytes += i;
 | 
			
		||||
  } while ((sentbytes < (int)size) && (sendcompletebuffer == true));
 | 
			
		||||
 | 
			
		||||
  return i;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int Socket::receive(std::string& data, unsigned int minpacketsize) const
 | 
			
		||||
{
 | 
			
		||||
  char* buf = nullptr;
 | 
			
		||||
  int status = 0;
 | 
			
		||||
 | 
			
		||||
  if (!is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  buf = new char[minpacketsize + 1];
 | 
			
		||||
  memset(buf, 0, minpacketsize + 1);
 | 
			
		||||
 | 
			
		||||
  status = receive(buf, minpacketsize, minpacketsize);
 | 
			
		||||
 | 
			
		||||
  data = buf;
 | 
			
		||||
 | 
			
		||||
  delete[] buf;
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//Receive until error or \n
 | 
			
		||||
bool Socket::ReadLine(string& line)
 | 
			
		||||
{
 | 
			
		||||
  fd_set set_r, set_e;
 | 
			
		||||
  timeval timeout;
 | 
			
		||||
  int retries = 6;
 | 
			
		||||
  char buffer[2048];
 | 
			
		||||
 | 
			
		||||
  if (!is_valid())
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  while (true)
 | 
			
		||||
  {
 | 
			
		||||
    size_t pos1 = line.find("\r\n", 0);
 | 
			
		||||
    if (pos1 != std::string::npos)
 | 
			
		||||
    {
 | 
			
		||||
      line.erase(pos1, string::npos);
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    timeout.tv_sec = RECEIVE_TIMEOUT;
 | 
			
		||||
    timeout.tv_usec = 0;
 | 
			
		||||
 | 
			
		||||
    // fill with new data
 | 
			
		||||
    FD_ZERO(&set_r);
 | 
			
		||||
    FD_ZERO(&set_e);
 | 
			
		||||
    FD_SET(m_sd, &set_r);
 | 
			
		||||
    FD_SET(m_sd, &set_e);
 | 
			
		||||
    int result = select(FD_SETSIZE, &set_r, nullptr, &set_e, &timeout);
 | 
			
		||||
 | 
			
		||||
    if (result < 0)
 | 
			
		||||
    {
 | 
			
		||||
      kodi::Log(ADDON_LOG_DEBUG, "%s: select failed", __func__);
 | 
			
		||||
      errormessage(getLastError(), __func__);
 | 
			
		||||
      close();
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (result == 0)
 | 
			
		||||
    {
 | 
			
		||||
      if (retries != 0)
 | 
			
		||||
      {
 | 
			
		||||
        kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __func__,
 | 
			
		||||
                  retries);
 | 
			
		||||
        retries--;
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      else
 | 
			
		||||
      {
 | 
			
		||||
        kodi::Log(ADDON_LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.",
 | 
			
		||||
                  __func__);
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    result = recv(m_sd, buffer, sizeof(buffer) - 1, 0);
 | 
			
		||||
    if (result < 0)
 | 
			
		||||
    {
 | 
			
		||||
      kodi::Log(ADDON_LOG_DEBUG, "%s: recv failed", __func__);
 | 
			
		||||
      errormessage(getLastError(), __func__);
 | 
			
		||||
      close();
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    buffer[result] = 0;
 | 
			
		||||
 | 
			
		||||
    line.append(buffer);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int Socket::receive(std::string& data) const
 | 
			
		||||
{
 | 
			
		||||
  char buf[MAXRECV + 1];
 | 
			
		||||
  int status = 0;
 | 
			
		||||
 | 
			
		||||
  if (!is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  memset(buf, 0, MAXRECV + 1);
 | 
			
		||||
  status = receive(buf, MAXRECV, 0);
 | 
			
		||||
  data = buf;
 | 
			
		||||
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Socket::receive(char* data,
 | 
			
		||||
                    const unsigned int buffersize,
 | 
			
		||||
                    const unsigned int minpacketsize) const
 | 
			
		||||
{
 | 
			
		||||
  unsigned int receivedsize = 0;
 | 
			
		||||
 | 
			
		||||
  if (!is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  while ((receivedsize <= minpacketsize) && (receivedsize < buffersize))
 | 
			
		||||
  {
 | 
			
		||||
    int status = ::recv(m_sd, data + receivedsize, (buffersize - receivedsize), 0);
 | 
			
		||||
 | 
			
		||||
    if (status == SOCKET_ERROR)
 | 
			
		||||
    {
 | 
			
		||||
      errormessage(getLastError(), "Socket::receive");
 | 
			
		||||
      return status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    receivedsize += status;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return receivedsize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int Socket::recvfrom(char* data,
 | 
			
		||||
                     const int buffersize,
 | 
			
		||||
                     struct sockaddr* from,
 | 
			
		||||
                     socklen_t* fromlen) const
 | 
			
		||||
{
 | 
			
		||||
  int status = ::recvfrom(m_sd, data, buffersize, 0, from, fromlen);
 | 
			
		||||
 | 
			
		||||
  return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bool Socket::connect(const std::string& host, const unsigned short port)
 | 
			
		||||
{
 | 
			
		||||
  close();
 | 
			
		||||
 | 
			
		||||
  if (!setHostname(host))
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str());
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  m_port = port;
 | 
			
		||||
 | 
			
		||||
  char strPort[15];
 | 
			
		||||
  snprintf(strPort, 15, "%hu", port);
 | 
			
		||||
 | 
			
		||||
  struct addrinfo hints;
 | 
			
		||||
  struct addrinfo* result = nullptr;
 | 
			
		||||
  struct addrinfo* address = nullptr;
 | 
			
		||||
  memset(&hints, 0, sizeof(hints));
 | 
			
		||||
  hints.ai_family = m_family;
 | 
			
		||||
  hints.ai_socktype = m_type;
 | 
			
		||||
  hints.ai_protocol = m_protocol;
 | 
			
		||||
 | 
			
		||||
  int retval = getaddrinfo(host.c_str(), strPort, &hints, &result);
 | 
			
		||||
  if (retval != 0)
 | 
			
		||||
  {
 | 
			
		||||
    errormessage(getLastError(), "Socket::connect");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (address = result; address != nullptr; address = address->ai_next)
 | 
			
		||||
  {
 | 
			
		||||
    // Create the socket
 | 
			
		||||
    m_sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
 | 
			
		||||
 | 
			
		||||
    if (m_sd == INVALID_SOCKET)
 | 
			
		||||
    {
 | 
			
		||||
      errormessage(getLastError(), "Socket::create");
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int status = ::connect(m_sd, address->ai_addr, address->ai_addrlen);
 | 
			
		||||
    if (status == SOCKET_ERROR)
 | 
			
		||||
    {
 | 
			
		||||
      close();
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // We have a conection
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  freeaddrinfo(result);
 | 
			
		||||
 | 
			
		||||
  if (address == nullptr)
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Socket::connect %s:%u\n", host.c_str(), port);
 | 
			
		||||
    errormessage(getLastError(), "Socket::connect");
 | 
			
		||||
    close();
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Socket::reconnect()
 | 
			
		||||
{
 | 
			
		||||
  if (is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return connect(m_hostname, m_port);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Socket::is_valid() const
 | 
			
		||||
{
 | 
			
		||||
  return (m_sd != INVALID_SOCKET);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#if defined(TARGET_WINDOWS)
 | 
			
		||||
bool Socket::set_non_blocking(const bool b)
 | 
			
		||||
{
 | 
			
		||||
  u_long iMode;
 | 
			
		||||
 | 
			
		||||
  if (b)
 | 
			
		||||
    iMode = 1; // enable non_blocking
 | 
			
		||||
  else
 | 
			
		||||
    iMode = 0; // disable non_blocking
 | 
			
		||||
 | 
			
		||||
  if (ioctlsocket(m_sd, FIONBIO, &iMode) == -1)
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Socket::set_non_blocking - Can't set socket condition to: %i",
 | 
			
		||||
              iMode);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Socket::errormessage(int errnum, const char* functionname) const
 | 
			
		||||
{
 | 
			
		||||
  const char* errmsg = nullptr;
 | 
			
		||||
 | 
			
		||||
  switch (errnum)
 | 
			
		||||
  {
 | 
			
		||||
    case WSANOTINITIALISED:
 | 
			
		||||
      errmsg = "A successful WSAStartup call must occur before using this function.";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAENETDOWN:
 | 
			
		||||
      errmsg = "The network subsystem or the associated service provider has failed";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSA_NOT_ENOUGH_MEMORY:
 | 
			
		||||
      errmsg = "Insufficient memory available";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSA_INVALID_PARAMETER:
 | 
			
		||||
      errmsg = "One or more parameters are invalid";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSA_OPERATION_ABORTED:
 | 
			
		||||
      errmsg = "Overlapped operation aborted";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEINTR:
 | 
			
		||||
      errmsg = "Interrupted function call";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEBADF:
 | 
			
		||||
      errmsg = "File handle is not valid";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEACCES:
 | 
			
		||||
      errmsg = "Permission denied";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEFAULT:
 | 
			
		||||
      errmsg = "Bad address";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEINVAL:
 | 
			
		||||
      errmsg = "Invalid argument";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAENOTSOCK:
 | 
			
		||||
      errmsg = "Socket operation on nonsocket";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEDESTADDRREQ:
 | 
			
		||||
      errmsg = "Destination address required";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEMSGSIZE:
 | 
			
		||||
      errmsg = "Message too long";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEPROTOTYPE:
 | 
			
		||||
      errmsg = "Protocol wrong type for socket";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAENOPROTOOPT:
 | 
			
		||||
      errmsg = "Bad protocol option";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEPFNOSUPPORT:
 | 
			
		||||
      errmsg = "Protocol family not supported";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEAFNOSUPPORT:
 | 
			
		||||
      errmsg = "Address family not supported by protocol family";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEADDRINUSE:
 | 
			
		||||
      errmsg = "Address already in use";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAECONNRESET:
 | 
			
		||||
      errmsg = "Connection reset by peer";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAHOST_NOT_FOUND:
 | 
			
		||||
      errmsg = "Authoritative answer host not found";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSATRY_AGAIN:
 | 
			
		||||
      errmsg = "Nonauthoritative host not found, or server failure";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAEISCONN:
 | 
			
		||||
      errmsg = "Socket is already connected";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAETIMEDOUT:
 | 
			
		||||
      errmsg = "Connection timed out";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSAECONNREFUSED:
 | 
			
		||||
      errmsg = "Connection refused";
 | 
			
		||||
      break;
 | 
			
		||||
    case WSANO_DATA:
 | 
			
		||||
      errmsg = "Valid name, no data record of requested type";
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      errmsg = "WSA Error";
 | 
			
		||||
  }
 | 
			
		||||
  kodi::Log(ADDON_LOG_ERROR, "%s: (Winsock error=%i) %s\n", functionname, errnum, errmsg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Socket::getLastError() const
 | 
			
		||||
{
 | 
			
		||||
  return WSAGetLastError();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Socket::win_usage_count = 0; //Declared static in Socket class
 | 
			
		||||
 | 
			
		||||
bool Socket::osInit()
 | 
			
		||||
{
 | 
			
		||||
  win_usage_count++;
 | 
			
		||||
  // initialize winsock:
 | 
			
		||||
  if (WSAStartup(MAKEWORD(2, 2), &m_wsaData) != 0)
 | 
			
		||||
  {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  WORD wVersionRequested = MAKEWORD(2, 2);
 | 
			
		||||
 | 
			
		||||
  // check version
 | 
			
		||||
  if (m_wsaData.wVersion != wVersionRequested)
 | 
			
		||||
  {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Socket::osCleanup()
 | 
			
		||||
{
 | 
			
		||||
  win_usage_count--;
 | 
			
		||||
  if (win_usage_count == 0)
 | 
			
		||||
  {
 | 
			
		||||
    WSACleanup();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
 | 
			
		||||
bool Socket::set_non_blocking(const bool b)
 | 
			
		||||
{
 | 
			
		||||
  int opts;
 | 
			
		||||
 | 
			
		||||
  opts = fcntl(m_sd, F_GETFL);
 | 
			
		||||
 | 
			
		||||
  if (opts < 0)
 | 
			
		||||
  {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (b)
 | 
			
		||||
    opts = (opts | O_NONBLOCK);
 | 
			
		||||
  else
 | 
			
		||||
    opts = (opts & ~O_NONBLOCK);
 | 
			
		||||
 | 
			
		||||
  if (fcntl(m_sd, F_SETFL, opts) == -1)
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Socket::set_non_blocking - Can't set socket flags to: %i", opts);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Socket::errormessage(int errnum, const char* functionname) const
 | 
			
		||||
{
 | 
			
		||||
  const char* errmsg = nullptr;
 | 
			
		||||
 | 
			
		||||
  switch (errnum)
 | 
			
		||||
  {
 | 
			
		||||
    case EAGAIN: //same as EWOULDBLOCK
 | 
			
		||||
      errmsg = "EAGAIN: The socket is marked non-blocking and the requested operation would block";
 | 
			
		||||
      break;
 | 
			
		||||
    case EBADF:
 | 
			
		||||
      errmsg = "EBADF: An invalid descriptor was specified";
 | 
			
		||||
      break;
 | 
			
		||||
    case ECONNRESET:
 | 
			
		||||
      errmsg = "ECONNRESET: Connection reset by peer";
 | 
			
		||||
      break;
 | 
			
		||||
    case EDESTADDRREQ:
 | 
			
		||||
      errmsg = "EDESTADDRREQ: The socket is not in connection mode and no peer address is set";
 | 
			
		||||
      break;
 | 
			
		||||
    case EFAULT:
 | 
			
		||||
      errmsg = "EFAULT: An invalid userspace address was specified for a parameter";
 | 
			
		||||
      break;
 | 
			
		||||
    case EINTR:
 | 
			
		||||
      errmsg = "EINTR: A signal occurred before data was transmitted";
 | 
			
		||||
      break;
 | 
			
		||||
    case EINVAL:
 | 
			
		||||
      errmsg = "EINVAL: Invalid argument passed";
 | 
			
		||||
      break;
 | 
			
		||||
    case ENOTSOCK:
 | 
			
		||||
      errmsg = "ENOTSOCK: The argument is not a valid socket";
 | 
			
		||||
      break;
 | 
			
		||||
    case EMSGSIZE:
 | 
			
		||||
      errmsg = "EMSGSIZE: The socket requires that message be sent atomically, and the size of the "
 | 
			
		||||
               "message to be sent made this impossible";
 | 
			
		||||
      break;
 | 
			
		||||
    case ENOBUFS:
 | 
			
		||||
      errmsg = "ENOBUFS: The output queue for a network interface was full";
 | 
			
		||||
      break;
 | 
			
		||||
    case ENOMEM:
 | 
			
		||||
      errmsg = "ENOMEM: No memory available";
 | 
			
		||||
      break;
 | 
			
		||||
    case EPIPE:
 | 
			
		||||
      errmsg = "EPIPE: The local end has been shut down on a connection oriented socket";
 | 
			
		||||
      break;
 | 
			
		||||
    case EPROTONOSUPPORT:
 | 
			
		||||
      errmsg = "EPROTONOSUPPORT: The protocol type or the specified protocol is not supported "
 | 
			
		||||
               "within this domain";
 | 
			
		||||
      break;
 | 
			
		||||
    case EAFNOSUPPORT:
 | 
			
		||||
      errmsg = "EAFNOSUPPORT: The implementation does not support the specified address family";
 | 
			
		||||
      break;
 | 
			
		||||
    case ENFILE:
 | 
			
		||||
      errmsg = "ENFILE: Not enough kernel memory to allocate a new socket structure";
 | 
			
		||||
      break;
 | 
			
		||||
    case EMFILE:
 | 
			
		||||
      errmsg = "EMFILE: Process file table overflow";
 | 
			
		||||
      break;
 | 
			
		||||
    case EACCES:
 | 
			
		||||
      errmsg =
 | 
			
		||||
          "EACCES: Permission to create a socket of the specified type and/or protocol is denied";
 | 
			
		||||
      break;
 | 
			
		||||
    case ECONNREFUSED:
 | 
			
		||||
      errmsg = "ECONNREFUSED: A remote host refused to allow the network connection (typically "
 | 
			
		||||
               "because it is not running the requested service)";
 | 
			
		||||
      break;
 | 
			
		||||
    case ENOTCONN:
 | 
			
		||||
      errmsg = "ENOTCONN: The socket is associated with a connection-oriented protocol and has not "
 | 
			
		||||
               "been connected";
 | 
			
		||||
      break;
 | 
			
		||||
    //case E:
 | 
			
		||||
    //	errmsg = "";
 | 
			
		||||
    //	break;
 | 
			
		||||
    default:
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  kodi::Log(ADDON_LOG_ERROR, "%s: (errno=%i) %s\n", functionname, errnum, errmsg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int Socket::getLastError() const
 | 
			
		||||
{
 | 
			
		||||
  return errno;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Socket::osInit()
 | 
			
		||||
{
 | 
			
		||||
  // Not needed for Linux
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Socket::osCleanup()
 | 
			
		||||
{
 | 
			
		||||
  // Not needed for Linux
 | 
			
		||||
}
 | 
			
		||||
#endif //TARGET_WINDOWS || TARGET_LINUX || TARGET_DARWIN || TARGET_FREEBSD
 | 
			
		||||
 | 
			
		||||
} //namespace OCTO
 | 
			
		||||
							
								
								
									
										284
									
								
								src/Socket.h
									
									
									
									
									
								
							
							
						
						
									
										284
									
								
								src/Socket.h
									
									
									
									
									
								
							@@ -1,284 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
 | 
			
		||||
 *
 | 
			
		||||
 *  SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 *  See LICENSE.md for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
//Include platform specific datatypes, header files, defines and constants:
 | 
			
		||||
#if defined TARGET_WINDOWS
 | 
			
		||||
#define WIN32_LEAN_AND_MEAN // Enable LEAN_AND_MEAN support
 | 
			
		||||
#pragma warning(disable : 4005) // Disable "warning C4005: '_WINSOCKAPI_' : macro redefinition"
 | 
			
		||||
#include <WS2tcpip.h>
 | 
			
		||||
#include <winsock2.h>
 | 
			
		||||
#pragma warning(default : 4005)
 | 
			
		||||
#include <windows.h>
 | 
			
		||||
 | 
			
		||||
#ifndef NI_MAXHOST
 | 
			
		||||
#define NI_MAXHOST 1025
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef socklen_t
 | 
			
		||||
typedef int socklen_t;
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef ipaddr_t
 | 
			
		||||
typedef unsigned long ipaddr_t;
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef port_t
 | 
			
		||||
typedef unsigned short port_t;
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef sa_family_t
 | 
			
		||||
#define sa_family_t ADDRESS_FAMILY
 | 
			
		||||
#endif
 | 
			
		||||
#elif defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
 | 
			
		||||
#ifdef SOCKADDR_IN
 | 
			
		||||
#undef SOCKADDR_IN
 | 
			
		||||
#endif
 | 
			
		||||
#include <arpa/inet.h> /* for inet_pton */
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <fcntl.h>
 | 
			
		||||
#include <netdb.h> /* for gethostbyname */
 | 
			
		||||
#include <netinet/in.h> /* for htons */
 | 
			
		||||
#include <sys/socket.h> /* for socket,connect */
 | 
			
		||||
#include <sys/types.h> /* for socket,connect */
 | 
			
		||||
#include <sys/un.h> /* for Unix socket */
 | 
			
		||||
#include <unistd.h> /* for read, write, close */
 | 
			
		||||
 | 
			
		||||
typedef int SOCKET;
 | 
			
		||||
typedef sockaddr SOCKADDR;
 | 
			
		||||
typedef sockaddr_in SOCKADDR_IN;
 | 
			
		||||
#ifndef INVALID_SOCKET
 | 
			
		||||
#define INVALID_SOCKET (-1)
 | 
			
		||||
#endif
 | 
			
		||||
#define SOCKET_ERROR (-1)
 | 
			
		||||
 | 
			
		||||
#define closesocket(sd) ::close(sd)
 | 
			
		||||
#else
 | 
			
		||||
#error Platform specific socket support is not yet available on this platform!
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
namespace OCTO
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
#define MAXCONNECTIONS 1 ///< Maximum number of pending connections before "Connection refused"
 | 
			
		||||
#define MAXRECV 1500 ///< Maximum packet size
 | 
			
		||||
 | 
			
		||||
enum SocketFamily
 | 
			
		||||
{
 | 
			
		||||
  af_unspec = AF_UNSPEC,
 | 
			
		||||
  af_inet = AF_INET,
 | 
			
		||||
  af_inet6 = AF_INET6
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum SocketDomain
 | 
			
		||||
{
 | 
			
		||||
#if defined TARGET_LINUX || defined TARGET_DARWIN || defined TARGET_FREEBSD
 | 
			
		||||
  pf_unix = PF_UNIX,
 | 
			
		||||
  pf_local = PF_LOCAL,
 | 
			
		||||
#endif
 | 
			
		||||
  pf_inet = PF_INET
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum SocketType
 | 
			
		||||
{
 | 
			
		||||
  sock_stream = SOCK_STREAM,
 | 
			
		||||
  sock_dgram = SOCK_DGRAM
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum SocketProtocol
 | 
			
		||||
{
 | 
			
		||||
  tcp = IPPROTO_TCP,
 | 
			
		||||
  udp = IPPROTO_UDP
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Socket
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
  /*!
 | 
			
		||||
   * An unconnected socket may be created directly on the local
 | 
			
		||||
   * machine. The socket type (SOCK_STREAM, SOCK_DGRAM) and
 | 
			
		||||
   * protocol may also be specified.
 | 
			
		||||
   * If the socket cannot be created, an exception is thrown.
 | 
			
		||||
   *
 | 
			
		||||
   * \param family Socket family (IPv4 or IPv6)
 | 
			
		||||
   * \param domain The domain parameter specifies a communications domain within which communication will take place;
 | 
			
		||||
   * this selects the protocol family which should be used.
 | 
			
		||||
   * \param type base type and protocol family of the socket.
 | 
			
		||||
   * \param protocol specific protocol to apply.
 | 
			
		||||
   */
 | 
			
		||||
  Socket(const enum SocketFamily family,
 | 
			
		||||
         const enum SocketDomain domain,
 | 
			
		||||
         const enum SocketType type,
 | 
			
		||||
         const enum SocketProtocol protocol = tcp);
 | 
			
		||||
  Socket(void);
 | 
			
		||||
  virtual ~Socket();
 | 
			
		||||
 | 
			
		||||
  //Socket settings
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket setFamily
 | 
			
		||||
   * \param family    Can be af_inet or af_inet6. Default: af_inet
 | 
			
		||||
   */
 | 
			
		||||
  void setFamily(const enum SocketFamily family) { m_family = family; };
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket setDomain
 | 
			
		||||
   * \param domain    Can be pf_unix, pf_local, pf_inet or pf_inet6. Default: pf_inet
 | 
			
		||||
   */
 | 
			
		||||
  void setDomain(const enum SocketDomain domain) { m_domain = domain; };
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket setType
 | 
			
		||||
   * \param type    Can be sock_stream or sock_dgram. Default: sock_stream.
 | 
			
		||||
   */
 | 
			
		||||
  void setType(const enum SocketType type) { m_type = type; };
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket setProtocol
 | 
			
		||||
   * \param protocol    Can be tcp or udp. Default: tcp.
 | 
			
		||||
   */
 | 
			
		||||
  void setProtocol(const enum SocketProtocol protocol) { m_protocol = protocol; };
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket setPort
 | 
			
		||||
   * \param port    port number for socket communication
 | 
			
		||||
   */
 | 
			
		||||
  void setPort(const unsigned short port) { m_sockaddr.sin_port = htons(port); };
 | 
			
		||||
 | 
			
		||||
  bool setHostname(const std::string& host);
 | 
			
		||||
 | 
			
		||||
  // Server initialization
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket create
 | 
			
		||||
   * Create a new socket
 | 
			
		||||
   * \return     True if succesful
 | 
			
		||||
   */
 | 
			
		||||
  bool create();
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket close
 | 
			
		||||
   * Close the socket
 | 
			
		||||
   * \return     True if succesful
 | 
			
		||||
   */
 | 
			
		||||
  bool close();
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket bind
 | 
			
		||||
   */
 | 
			
		||||
  bool bind(const unsigned short port);
 | 
			
		||||
  bool listen() const;
 | 
			
		||||
  bool accept(Socket& socket) const;
 | 
			
		||||
 | 
			
		||||
  // Client initialization
 | 
			
		||||
  bool connect(const std::string& host, const unsigned short port);
 | 
			
		||||
 | 
			
		||||
  bool reconnect();
 | 
			
		||||
 | 
			
		||||
  // Data Transmission
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket send function
 | 
			
		||||
   *
 | 
			
		||||
   * \param data    Reference to a std::string with the data to transmit
 | 
			
		||||
   * \return    Number of bytes send or -1 in case of an error
 | 
			
		||||
   */
 | 
			
		||||
  int send(const std::string& data);
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket send function
 | 
			
		||||
   *
 | 
			
		||||
   * \param data    Pointer to a character array of size 'size' with the data to transmit
 | 
			
		||||
   * \param size    Length of the data to transmit
 | 
			
		||||
   * \return    Number of bytes send or -1 in case of an error
 | 
			
		||||
   */
 | 
			
		||||
  int send(const char* data, const unsigned int size);
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket sendto function
 | 
			
		||||
   *
 | 
			
		||||
   * \param data    Reference to a std::string with the data to transmit
 | 
			
		||||
   * \param size    Length of the data to transmit
 | 
			
		||||
   * \param sendcompletebuffer    If 'true': do not return until the complete buffer is transmitted
 | 
			
		||||
   * \return    Number of bytes send or -1 in case of an error
 | 
			
		||||
   */
 | 
			
		||||
  int sendto(const char* data, unsigned int size, bool sendcompletebuffer = false);
 | 
			
		||||
  // Data Receive
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket receive function
 | 
			
		||||
   *
 | 
			
		||||
   * \param data    Reference to a std::string for storage of the received data.
 | 
			
		||||
   * \param minpacketsize    The minimum number of bytes that should be received before returning from this function
 | 
			
		||||
   * \return    Number of bytes received or SOCKET_ERROR
 | 
			
		||||
   */
 | 
			
		||||
  int receive(std::string& data, unsigned int minpacketsize) const;
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket receive function
 | 
			
		||||
   *
 | 
			
		||||
   * \param data    Reference to a std::string for storage of the received data.
 | 
			
		||||
   * \return    Number of bytes received or SOCKET_ERROR
 | 
			
		||||
   */
 | 
			
		||||
  int receive(std::string& data) const;
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket receive function
 | 
			
		||||
   *
 | 
			
		||||
   * \param data    Pointer to a character array of size buffersize. Used to store the received data.
 | 
			
		||||
   * \param buffersize    Size of the 'data' buffer
 | 
			
		||||
   * \param minpacketsize    Specifies the minimum number of bytes that need to be received before returning
 | 
			
		||||
   * \return    Number of bytes received or SOCKET_ERROR
 | 
			
		||||
   */
 | 
			
		||||
  int receive(char* data, const unsigned int buffersize, const unsigned int minpacketsize) const;
 | 
			
		||||
 | 
			
		||||
  /*!
 | 
			
		||||
   * Socket recvfrom function
 | 
			
		||||
   *
 | 
			
		||||
   * \param data    Pointer to a character array of size buffersize. Used to store the received data.
 | 
			
		||||
   * \param buffersize    Size of the 'data' buffer
 | 
			
		||||
   * \param from        Optional: pointer to a sockaddr struct that will get the address from which the data is received
 | 
			
		||||
   * \param fromlen    Optional, only required if 'from' is given: length of from struct
 | 
			
		||||
   * \return    Number of bytes received or SOCKET_ERROR
 | 
			
		||||
   */
 | 
			
		||||
  int recvfrom(char* data,
 | 
			
		||||
               const int buffersize,
 | 
			
		||||
               struct sockaddr* from = nullptr,
 | 
			
		||||
               socklen_t* fromlen = nullptr) const;
 | 
			
		||||
 | 
			
		||||
  bool set_non_blocking(const bool);
 | 
			
		||||
 | 
			
		||||
  bool ReadLine(std::string& line);
 | 
			
		||||
 | 
			
		||||
  bool is_valid() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
  SOCKET m_sd; ///< Socket Descriptor
 | 
			
		||||
  SOCKADDR_IN m_sockaddr; ///< Socket Address
 | 
			
		||||
  //struct addrinfo* m_addrinfo;         ///< Socket address info
 | 
			
		||||
  std::string m_hostname; ///< Hostname
 | 
			
		||||
  unsigned short m_port; ///< Port number
 | 
			
		||||
 | 
			
		||||
  enum SocketFamily m_family; ///< Socket Address Family
 | 
			
		||||
  enum SocketProtocol m_protocol; ///< Socket Protocol
 | 
			
		||||
  enum SocketType m_type; ///< Socket Type
 | 
			
		||||
  enum SocketDomain m_domain; ///< Socket domain
 | 
			
		||||
 | 
			
		||||
#ifdef TARGET_WINDOWS
 | 
			
		||||
  WSADATA m_wsaData; ///< Windows Socket data
 | 
			
		||||
  static int
 | 
			
		||||
      win_usage_count; ///< Internal Windows usage counter used to prevent a global WSACleanup when more than one Socket object is used
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  void errormessage(int errornum, const char* functionname = nullptr) const;
 | 
			
		||||
  int getLastError(void) const;
 | 
			
		||||
  bool osInit();
 | 
			
		||||
  void osCleanup();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} //namespace OCTO
 | 
			
		||||
@@ -29,8 +29,9 @@ ADDON_STATUS COctonetAddon::CreateInstance(const kodi::addon::IInstanceInfo& ins
 | 
			
		||||
 | 
			
		||||
    /* IP or hostname of the octonet to be connected to */
 | 
			
		||||
    std::string octonetAddress = kodi::addon::GetSettingString("octonetAddress");
 | 
			
		||||
    bool enableTimeshift = kodi::addon::GetSettingBoolean("timeshiftEnabled");
 | 
			
		||||
 | 
			
		||||
    OctonetData* usedInstance = new OctonetData(octonetAddress, instance);
 | 
			
		||||
    OctonetData* usedInstance = new OctonetData(octonetAddress, enableTimeshift, instance);
 | 
			
		||||
    hdl = usedInstance;
 | 
			
		||||
 | 
			
		||||
    m_usedInstances.emplace(instance.GetID(), usedInstance);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,548 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
 | 
			
		||||
 *
 | 
			
		||||
 *  SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 *  See LICENSE.md for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include "rtsp_client.hpp"
 | 
			
		||||
 | 
			
		||||
#include "Socket.h"
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cctype>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
#if defined(_WIN32) || defined(_WIN64)
 | 
			
		||||
#define strtok_r strtok_s
 | 
			
		||||
#define strncasecmp _strnicmp
 | 
			
		||||
 | 
			
		||||
int vasprintf(char** sptr, char* fmt, va_list argv)
 | 
			
		||||
{
 | 
			
		||||
  int wanted = vsnprintf(*sptr = nullptr, 0, fmt, argv);
 | 
			
		||||
  if ((wanted < 0) || ((*sptr = (char*)malloc(1 + wanted)) == nullptr))
 | 
			
		||||
    return -1;
 | 
			
		||||
  return vsprintf(*sptr, fmt, argv);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int asprintf(char** sptr, char* fmt, ...)
 | 
			
		||||
{
 | 
			
		||||
  int retval;
 | 
			
		||||
  va_list argv;
 | 
			
		||||
  va_start(argv, fmt);
 | 
			
		||||
  retval = vasprintf(sptr, fmt, argv);
 | 
			
		||||
  va_end(argv);
 | 
			
		||||
  return retval;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#define RTSP_DEFAULT_PORT 554
 | 
			
		||||
#define RTSP_RECEIVE_BUFFER 2048
 | 
			
		||||
#define RTP_HEADER_SIZE 12
 | 
			
		||||
#define VLEN 100
 | 
			
		||||
#define KEEPALIVE_INTERVAL 60
 | 
			
		||||
#define KEEPALIVE_MARGIN 5
 | 
			
		||||
#define UDP_ADDRESS_LEN 16
 | 
			
		||||
#define RTCP_BUFFER_SIZE 1024
 | 
			
		||||
 | 
			
		||||
using namespace std;
 | 
			
		||||
using namespace OCTO;
 | 
			
		||||
 | 
			
		||||
enum rtsp_state
 | 
			
		||||
{
 | 
			
		||||
  RTSP_IDLE,
 | 
			
		||||
  RTSP_DESCRIBE,
 | 
			
		||||
  RTSP_SETUP,
 | 
			
		||||
  RTSP_PLAY,
 | 
			
		||||
  RTSP_RUNNING
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum rtsp_result
 | 
			
		||||
{
 | 
			
		||||
  RTSP_RESULT_OK = 200,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct rtsp_client
 | 
			
		||||
{
 | 
			
		||||
  char* content_base;
 | 
			
		||||
  char* control;
 | 
			
		||||
  char session_id[64];
 | 
			
		||||
  uint16_t stream_id;
 | 
			
		||||
  int keepalive_interval;
 | 
			
		||||
 | 
			
		||||
  char udp_address[UDP_ADDRESS_LEN];
 | 
			
		||||
  uint16_t udp_port;
 | 
			
		||||
 | 
			
		||||
  Socket tcp_sock;
 | 
			
		||||
  Socket udp_sock;
 | 
			
		||||
  Socket rtcp_sock;
 | 
			
		||||
 | 
			
		||||
  enum rtsp_state state;
 | 
			
		||||
  int cseq;
 | 
			
		||||
 | 
			
		||||
  size_t fifo_size;
 | 
			
		||||
  uint16_t last_seq_nr;
 | 
			
		||||
 | 
			
		||||
  string name;
 | 
			
		||||
  int level;
 | 
			
		||||
  int quality;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct url
 | 
			
		||||
{
 | 
			
		||||
  string protocol;
 | 
			
		||||
  string host;
 | 
			
		||||
  int port;
 | 
			
		||||
  string path;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct rtcp_app
 | 
			
		||||
{
 | 
			
		||||
  uint8_t subtype;
 | 
			
		||||
  uint8_t pt;
 | 
			
		||||
  uint16_t len;
 | 
			
		||||
  uint32_t ssrc;
 | 
			
		||||
  char name[4];
 | 
			
		||||
  uint16_t identifier;
 | 
			
		||||
  uint16_t string_len;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static rtsp_client* rtsp = nullptr;
 | 
			
		||||
 | 
			
		||||
static url parse_url(const std::string& str)
 | 
			
		||||
{
 | 
			
		||||
  static const string prot_end = "://";
 | 
			
		||||
  static const string host_end = "/";
 | 
			
		||||
  url result;
 | 
			
		||||
 | 
			
		||||
  string::const_iterator begin = str.begin();
 | 
			
		||||
  string::const_iterator end = search(begin, str.end(), prot_end.begin(), prot_end.end());
 | 
			
		||||
  result.protocol.reserve(distance(begin, end));
 | 
			
		||||
  transform(begin, end, back_inserter(result.protocol), ::tolower);
 | 
			
		||||
  advance(end, prot_end.size());
 | 
			
		||||
  begin = end;
 | 
			
		||||
 | 
			
		||||
  end = search(begin, str.end(), host_end.begin(), host_end.end());
 | 
			
		||||
  result.host.reserve(distance(begin, end));
 | 
			
		||||
  transform(begin, end, back_inserter(result.host), ::tolower);
 | 
			
		||||
  advance(end, host_end.size());
 | 
			
		||||
  begin = end;
 | 
			
		||||
 | 
			
		||||
  result.port = RTSP_DEFAULT_PORT;
 | 
			
		||||
 | 
			
		||||
  result.path.reserve(distance(begin, str.end()));
 | 
			
		||||
  transform(begin, str.end(), back_inserter(result.path), ::tolower);
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void split_string(const string& s, char delim, vector<string>& elems)
 | 
			
		||||
{
 | 
			
		||||
  stringstream ss;
 | 
			
		||||
  ss.str(s);
 | 
			
		||||
 | 
			
		||||
  string item;
 | 
			
		||||
  while (getline(ss, item, delim))
 | 
			
		||||
  {
 | 
			
		||||
    elems.push_back(item);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int tcp_sock_read_line(string& line)
 | 
			
		||||
{
 | 
			
		||||
  static string buf;
 | 
			
		||||
 | 
			
		||||
  while (true)
 | 
			
		||||
  {
 | 
			
		||||
    string::size_type pos = buf.find("\r\n");
 | 
			
		||||
    if (pos != string::npos)
 | 
			
		||||
    {
 | 
			
		||||
      line = buf.substr(0, pos);
 | 
			
		||||
      buf.erase(0, pos + 2);
 | 
			
		||||
      return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    char tmp_buf[2048];
 | 
			
		||||
    int size = rtsp->tcp_sock.receive(tmp_buf, sizeof(tmp_buf), 1);
 | 
			
		||||
    if (size <= 0)
 | 
			
		||||
    {
 | 
			
		||||
      return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    buf.append(&tmp_buf[0], &tmp_buf[size]);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static string compose_url(const url& u)
 | 
			
		||||
{
 | 
			
		||||
  stringstream res;
 | 
			
		||||
  res << u.protocol << "://" << u.host;
 | 
			
		||||
  if (u.port > 0)
 | 
			
		||||
    res << ":" << u.port;
 | 
			
		||||
  res << "/" << u.path;
 | 
			
		||||
 | 
			
		||||
  return res.str();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void parse_session(char* request_line, char* session, unsigned max, int* timeout)
 | 
			
		||||
{
 | 
			
		||||
  char* state;
 | 
			
		||||
  char* tok;
 | 
			
		||||
 | 
			
		||||
  tok = strtok_r(request_line, ";", &state);
 | 
			
		||||
  if (tok == nullptr)
 | 
			
		||||
    return;
 | 
			
		||||
  strncpy(session, tok, min(strlen(tok), (size_t)(max - 1)));
 | 
			
		||||
 | 
			
		||||
  while ((tok = strtok_r(nullptr, ";", &state)) != nullptr)
 | 
			
		||||
  {
 | 
			
		||||
    if (strncmp(tok, "timeout=", 8) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      *timeout = atoi(tok + 8);
 | 
			
		||||
      if (*timeout > 5)
 | 
			
		||||
        *timeout -= KEEPALIVE_MARGIN;
 | 
			
		||||
      else if (*timeout > 0)
 | 
			
		||||
        *timeout = 1;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int parse_port(char* str, uint16_t* port)
 | 
			
		||||
{
 | 
			
		||||
  int p = atoi(str);
 | 
			
		||||
  if (p < 0 || p > UINT16_MAX)
 | 
			
		||||
    return -1;
 | 
			
		||||
 | 
			
		||||
  *port = p;
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int parse_transport(char* request_line)
 | 
			
		||||
{
 | 
			
		||||
  char* state;
 | 
			
		||||
  char* tok;
 | 
			
		||||
  int err;
 | 
			
		||||
 | 
			
		||||
  tok = strtok_r(request_line, ";", &state);
 | 
			
		||||
  if (tok == nullptr || strncmp(tok, "RTP/AVP", 7) != 0)
 | 
			
		||||
    return -1;
 | 
			
		||||
 | 
			
		||||
  tok = strtok_r(nullptr, ";", &state);
 | 
			
		||||
  if (tok == nullptr || strncmp(tok, "multicast", 9) != 0)
 | 
			
		||||
    return 0;
 | 
			
		||||
 | 
			
		||||
  while ((tok = strtok_r(nullptr, ";", &state)) != nullptr)
 | 
			
		||||
  {
 | 
			
		||||
    if (strncmp(tok, "destination=", 12) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      strncpy(rtsp->udp_address, tok + 12, min(strlen(tok + 12), (size_t)(UDP_ADDRESS_LEN - 1)));
 | 
			
		||||
    }
 | 
			
		||||
    else if (strncmp(tok, "port=", 5) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      char port[6];
 | 
			
		||||
      char* end;
 | 
			
		||||
 | 
			
		||||
      memset(port, 0x00, 6);
 | 
			
		||||
      strncpy(port, tok + 5, min(strlen(tok + 5), (size_t)5));
 | 
			
		||||
      if ((end = strstr(port, "-")) != nullptr)
 | 
			
		||||
        *end = '\0';
 | 
			
		||||
      err = parse_port(port, &rtsp->udp_port);
 | 
			
		||||
      if (err)
 | 
			
		||||
        return err;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#define skip_whitespace(x) \
 | 
			
		||||
  while (*x == ' ') \
 | 
			
		||||
  x++
 | 
			
		||||
static enum rtsp_result rtsp_handle()
 | 
			
		||||
{
 | 
			
		||||
  uint8_t buffer[512];
 | 
			
		||||
  int rtsp_result = 0;
 | 
			
		||||
  bool have_header = false;
 | 
			
		||||
  size_t content_length = 0;
 | 
			
		||||
  size_t read = 0;
 | 
			
		||||
  char *in, *val;
 | 
			
		||||
  string in_str;
 | 
			
		||||
 | 
			
		||||
  /* Parse header */
 | 
			
		||||
  while (!have_header)
 | 
			
		||||
  {
 | 
			
		||||
    if (tcp_sock_read_line(in_str) < 0)
 | 
			
		||||
      break;
 | 
			
		||||
    in = const_cast<char*>(in_str.c_str());
 | 
			
		||||
 | 
			
		||||
    if (strncmp(in, "RTSP/1.0 ", 9) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      rtsp_result = atoi(in + 9);
 | 
			
		||||
    }
 | 
			
		||||
    else if (strncmp(in, "Content-Base:", 13) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      free(rtsp->content_base);
 | 
			
		||||
 | 
			
		||||
      val = in + 13;
 | 
			
		||||
      skip_whitespace(val);
 | 
			
		||||
 | 
			
		||||
      rtsp->content_base = strdup(val);
 | 
			
		||||
    }
 | 
			
		||||
    else if (strncmp(in, "Content-Length:", 15) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      val = in + 16;
 | 
			
		||||
      skip_whitespace(val);
 | 
			
		||||
 | 
			
		||||
      content_length = atoi(val);
 | 
			
		||||
    }
 | 
			
		||||
    else if (strncmp("Session:", in, 8) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      val = in + 8;
 | 
			
		||||
      skip_whitespace(val);
 | 
			
		||||
 | 
			
		||||
      parse_session(val, rtsp->session_id, 64, &rtsp->keepalive_interval);
 | 
			
		||||
    }
 | 
			
		||||
    else if (strncmp("Transport:", in, 10) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      val = in + 10;
 | 
			
		||||
      skip_whitespace(val);
 | 
			
		||||
 | 
			
		||||
      if (parse_transport(val) != 0)
 | 
			
		||||
      {
 | 
			
		||||
        rtsp_result = -1;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else if (strncmp("com.ses.streamID:", in, 17) == 0)
 | 
			
		||||
    {
 | 
			
		||||
      val = in + 17;
 | 
			
		||||
      skip_whitespace(val);
 | 
			
		||||
 | 
			
		||||
      rtsp->stream_id = atoi(val);
 | 
			
		||||
    }
 | 
			
		||||
    else if (in[0] == '\0')
 | 
			
		||||
    {
 | 
			
		||||
      have_header = true;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Discard further content */
 | 
			
		||||
  while (content_length > 0 && (read = rtsp->tcp_sock.receive((char*)buffer, sizeof(buffer),
 | 
			
		||||
                                                              min(sizeof(buffer), content_length))))
 | 
			
		||||
    content_length -= read;
 | 
			
		||||
 | 
			
		||||
  return (enum rtsp_result)rtsp_result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool rtsp_open(const string& name, const string& url_str)
 | 
			
		||||
{
 | 
			
		||||
  string setup_url_str;
 | 
			
		||||
  const char* psz_setup_url;
 | 
			
		||||
  stringstream setup_ss;
 | 
			
		||||
  stringstream play_ss;
 | 
			
		||||
  url setup_url;
 | 
			
		||||
 | 
			
		||||
  rtsp_close();
 | 
			
		||||
  rtsp = new rtsp_client();
 | 
			
		||||
  if (rtsp == nullptr)
 | 
			
		||||
    return false;
 | 
			
		||||
 | 
			
		||||
  rtsp->name = name;
 | 
			
		||||
  rtsp->level = 0;
 | 
			
		||||
  rtsp->quality = 0;
 | 
			
		||||
 | 
			
		||||
  kodi::Log(ADDON_LOG_DEBUG, "try to open '%s'", url_str.c_str());
 | 
			
		||||
 | 
			
		||||
  url dst = parse_url(url_str);
 | 
			
		||||
  kodi::Log(ADDON_LOG_DEBUG, "connect to host '%s'", dst.host.c_str());
 | 
			
		||||
 | 
			
		||||
  if (!rtsp->tcp_sock.connect(dst.host, dst.port))
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Failed to connect to RTSP server %s:%d", dst.host.c_str(),
 | 
			
		||||
              dst.port);
 | 
			
		||||
    goto error;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: tcp keep alive?
 | 
			
		||||
 | 
			
		||||
  if (asprintf(&rtsp->content_base, "rtsp://%s:%d/", dst.host.c_str(), dst.port) < 0)
 | 
			
		||||
  {
 | 
			
		||||
    rtsp->content_base = nullptr;
 | 
			
		||||
    goto error;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rtsp->last_seq_nr = 0;
 | 
			
		||||
  rtsp->keepalive_interval = (KEEPALIVE_INTERVAL - KEEPALIVE_MARGIN);
 | 
			
		||||
 | 
			
		||||
  setup_url = dst;
 | 
			
		||||
 | 
			
		||||
  // reverse the satip protocol trick, as SAT>IP believes to be RTSP
 | 
			
		||||
  if (!strncasecmp(setup_url.protocol.c_str(), "satip", 5))
 | 
			
		||||
  {
 | 
			
		||||
    setup_url.protocol = "rtsp";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setup_url_str = compose_url(setup_url);
 | 
			
		||||
  psz_setup_url = setup_url_str.c_str();
 | 
			
		||||
 | 
			
		||||
  // TODO: Find available port
 | 
			
		||||
  rtsp->udp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
 | 
			
		||||
  rtsp->udp_port = 6785;
 | 
			
		||||
  if (!rtsp->udp_sock.bind(rtsp->udp_port))
 | 
			
		||||
  {
 | 
			
		||||
    goto error;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setup_ss << "SETUP " << setup_url_str << " RTSP/1.0\r\n";
 | 
			
		||||
  setup_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
 | 
			
		||||
  setup_ss << "Transport: RTP/AVP;unicast;client_port=" << rtsp->udp_port << "-"
 | 
			
		||||
           << (rtsp->udp_port + 1) << "\r\n\r\n";
 | 
			
		||||
  rtsp->tcp_sock.send(setup_ss.str());
 | 
			
		||||
 | 
			
		||||
  if (rtsp_handle() != RTSP_RESULT_OK)
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Failed to setup RTSP session");
 | 
			
		||||
    goto error;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (asprintf(&rtsp->control, "%sstream=%d", rtsp->content_base, rtsp->stream_id) < 0)
 | 
			
		||||
  {
 | 
			
		||||
    rtsp->control = nullptr;
 | 
			
		||||
    goto error;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  play_ss << "PLAY " << rtsp->control << " RTSP/1.0\r\n";
 | 
			
		||||
  play_ss << "CSeq: " << rtsp->cseq++ << "\r\n";
 | 
			
		||||
  play_ss << "Session: " << rtsp->session_id << "\r\n\r\n";
 | 
			
		||||
  rtsp->tcp_sock.send(play_ss.str());
 | 
			
		||||
 | 
			
		||||
  if (rtsp_handle() != RTSP_RESULT_OK)
 | 
			
		||||
  {
 | 
			
		||||
    kodi::Log(ADDON_LOG_ERROR, "Failed to play RTSP session");
 | 
			
		||||
    goto error;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rtsp->rtcp_sock = Socket(af_inet, pf_inet, sock_dgram, udp);
 | 
			
		||||
  if (!rtsp->rtcp_sock.bind(rtsp->udp_port + 1))
 | 
			
		||||
  {
 | 
			
		||||
    goto error;
 | 
			
		||||
  }
 | 
			
		||||
  if (!rtsp->rtcp_sock.set_non_blocking(true))
 | 
			
		||||
  {
 | 
			
		||||
    goto error;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
 | 
			
		||||
error:
 | 
			
		||||
  rtsp_close();
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void parse_rtcp(const char* buf, int size)
 | 
			
		||||
{
 | 
			
		||||
  int offset = 0;
 | 
			
		||||
  while (size > 4)
 | 
			
		||||
  {
 | 
			
		||||
    const rtcp_app* app = reinterpret_cast<const rtcp_app*>(buf + offset);
 | 
			
		||||
    uint16_t len = 4 * (ntohs(app->len) + 1);
 | 
			
		||||
 | 
			
		||||
    if ((app->pt != 204) || (memcmp(app->name, "SES1", 4) != 0))
 | 
			
		||||
    {
 | 
			
		||||
      size -= len;
 | 
			
		||||
      offset += len;
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    uint16_t string_len = ntohs(app->string_len);
 | 
			
		||||
    string app_data(&buf[offset + sizeof(rtcp_app)], string_len);
 | 
			
		||||
 | 
			
		||||
    vector<string> elems;
 | 
			
		||||
    split_string(app_data, ';', elems);
 | 
			
		||||
    if (elems.size() != 4)
 | 
			
		||||
    {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    vector<string> tuner;
 | 
			
		||||
    split_string(elems[2], ',', tuner);
 | 
			
		||||
    if (tuner.size() < 4)
 | 
			
		||||
    {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    rtsp->level = atoi(tuner[1].c_str());
 | 
			
		||||
    rtsp->quality = atoi(tuner[3].c_str());
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int rtsp_read(void* buf, unsigned buf_size)
 | 
			
		||||
{
 | 
			
		||||
  sockaddr addr;
 | 
			
		||||
  socklen_t addr_len = sizeof(addr);
 | 
			
		||||
  int ret = rtsp->udp_sock.recvfrom((char*)buf, buf_size, (sockaddr*)&addr, &addr_len);
 | 
			
		||||
 | 
			
		||||
  char rtcp_buf[RTCP_BUFFER_SIZE];
 | 
			
		||||
  int rtcp_len = rtsp->rtcp_sock.recvfrom(rtcp_buf, RTCP_BUFFER_SIZE, (sockaddr*)&addr, &addr_len);
 | 
			
		||||
  parse_rtcp(rtcp_buf, rtcp_len);
 | 
			
		||||
 | 
			
		||||
  // TODO: check ip
 | 
			
		||||
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void rtsp_teardown()
 | 
			
		||||
{
 | 
			
		||||
  if (!rtsp->tcp_sock.is_valid())
 | 
			
		||||
  {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (rtsp->session_id[0] > 0)
 | 
			
		||||
  {
 | 
			
		||||
    char* msg;
 | 
			
		||||
    int len;
 | 
			
		||||
    stringstream ss;
 | 
			
		||||
 | 
			
		||||
    rtsp->udp_sock.close();
 | 
			
		||||
 | 
			
		||||
    ss << "TEARDOWN " << rtsp->control << " RTSP/1.0\r\n";
 | 
			
		||||
    ss << "CSeq: " << rtsp->cseq++ << "\r\n";
 | 
			
		||||
    ss << "Session: " << rtsp->session_id << "\r\n\r\n";
 | 
			
		||||
    rtsp->tcp_sock.send(ss.str());
 | 
			
		||||
 | 
			
		||||
    if (rtsp_handle() != RTSP_RESULT_OK)
 | 
			
		||||
    {
 | 
			
		||||
      kodi::Log(ADDON_LOG_ERROR, "Failed to teardown RTSP session");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void rtsp_close()
 | 
			
		||||
{
 | 
			
		||||
  if (rtsp)
 | 
			
		||||
  {
 | 
			
		||||
    rtsp_teardown();
 | 
			
		||||
    rtsp->tcp_sock.close();
 | 
			
		||||
    rtsp->udp_sock.close();
 | 
			
		||||
    rtsp->rtcp_sock.close();
 | 
			
		||||
    delete rtsp;
 | 
			
		||||
    rtsp = nullptr;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status)
 | 
			
		||||
{
 | 
			
		||||
  if (rtsp)
 | 
			
		||||
  {
 | 
			
		||||
    signal_status.SetAdapterName(rtsp->name);
 | 
			
		||||
    signal_status.SetSNR(0x1111 * rtsp->quality);
 | 
			
		||||
    signal_status.SetSignal(0x101 * rtsp->level);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  Copyright (C) 2005-2021 Team Kodi (https://kodi.tv)
 | 
			
		||||
 *
 | 
			
		||||
 *  SPDX-License-Identifier: GPL-2.0-or-later
 | 
			
		||||
 *  See LICENSE.md for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <kodi/addon-instance/pvr/Channels.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
bool rtsp_open(const std::string& name, const std::string& url_str);
 | 
			
		||||
void rtsp_close();
 | 
			
		||||
int rtsp_read(void* buf, unsigned buf_size);
 | 
			
		||||
void rtsp_fill_signal_status(kodi::addon::PVRSignalStatus& signal_status);
 | 
			
		||||
		Reference in New Issue
	
	Block a user