pvr.octonet/src/Socket.cpp

723 lines
15 KiB
C++

/*
* Copyright (C) 2005-2011 Team XBMC
* http://www.xbmc.org
*
* 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 2 of the License, or
* (at your option) 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 "libXBMC_addon.h"
#include <string>
#include "p8-platform/os.h"
#include "client.h"
#include "Socket.h"
#include <cstdio>
using namespace std;
using namespace ADDON;
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)
{
_sd = INVALID_SOCKET;
_family = family;
_domain = domain;
_type = type;
_protocol = protocol;
_port = 0;
memset (&_sockaddr, 0, sizeof( _sockaddr ) );
}
Socket::Socket()
{
// Default constructor, default settings
_sd = INVALID_SOCKET;
_family = af_inet;
_domain = pf_inet;
_type = sock_stream;
_protocol = tcp;
_port = 0;
memset (&_sockaddr, 0, sizeof( _sockaddr ) );
}
Socket::~Socket()
{
close();
osCleanup();
}
bool Socket::setHostname(const std::string& host)
{
_hostname = host;
return true;
}
bool Socket::close()
{
if (is_valid())
{
if (_sd != SOCKET_ERROR)
closesocket(_sd);
_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();
}
_sd = socket(_family, _type, _protocol);
_port = port;
_sockaddr.sin_family = (sa_family_t) _family;
_sockaddr.sin_addr.s_addr = INADDR_ANY; //listen to all
_sockaddr.sin_port = htons( _port );
int bind_return = ::bind(_sd, (sockaddr*)(&_sockaddr), sizeof(_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 (_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( _sockaddr );
new_socket._sd = ::accept(_sd, const_cast<sockaddr*>( (const sockaddr*) &_sockaddr), &addr_length );
#ifdef TARGET_WINDOWS
if (new_socket._sd == INVALID_SOCKET)
#else
if (new_socket._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(_sd, &set_w);
FD_SET(_sd, &set_e);
result = select(FD_SETSIZE, &set_w, NULL, &set_e, &tv);
if (result < 0)
{
libKodi->Log(LOG_ERROR, "Socket::send - select failed");
close();
return 0;
}
if (FD_ISSET(_sd, &set_w))
{
libKodi->Log(LOG_ERROR, "Socket::send - failed to send data");
close();
return 0;
}
int status = ::send(_sd, data, len, 0 );
if (status == -1)
{
errormessage( getLastError(), "Socket::send");
libKodi->Log(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(_sd, data, size, 0, (const struct sockaddr*) &_sockaddr, sizeof( _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 = NULL;
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(_sd, &set_r);
FD_SET(_sd, &set_e);
int result = select(FD_SETSIZE, &set_r, NULL, &set_e, &timeout);
if (result < 0)
{
libKodi->Log(LOG_DEBUG, "%s: select failed", __FUNCTION__);
errormessage(getLastError(), __FUNCTION__);
close();
return false;
}
if (result == 0)
{
if (retries != 0)
{
libKodi->Log(LOG_DEBUG, "%s: timeout waiting for response, retrying... (%i)", __FUNCTION__, retries);
retries--;
continue;
} else {
libKodi->Log(LOG_DEBUG, "%s: timeout waiting for response. Aborting after 10 retries.", __FUNCTION__);
return false;
}
}
result = recv(_sd, buffer, sizeof(buffer) - 1, 0);
if (result < 0)
{
libKodi->Log(LOG_DEBUG, "%s: recv failed", __FUNCTION__);
errormessage(getLastError(), __FUNCTION__);
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(_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(_sd, data, buffersize, 0, from, fromlen);
return status;
}
bool Socket::connect ( const std::string& host, const unsigned short port )
{
close();
if ( !setHostname( host ) )
{
libKodi->Log(LOG_ERROR, "Socket::setHostname(%s) failed.\n", host.c_str());
return false;
}
_port = port;
char strPort[15];
snprintf(strPort, 15, "%hu", port);
struct addrinfo hints;
struct addrinfo* result = NULL;
struct addrinfo *address = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_family = _family;
hints.ai_socktype = _type;
hints.ai_protocol = _protocol;
int retval = getaddrinfo(host.c_str(), strPort, &hints, &result);
if (retval != 0)
{
errormessage(getLastError(), "Socket::connect");
return false;
}
for (address = result; address != NULL; address = address->ai_next)
{
// Create the socket
_sd = socket(address->ai_family, address->ai_socktype, address->ai_protocol);
if (_sd == INVALID_SOCKET)
{
errormessage(getLastError(), "Socket::create");
continue;
}
int status = ::connect(_sd, address->ai_addr, address->ai_addrlen);
if (status == SOCKET_ERROR)
{
close();
continue;
}
// We have a conection
break;
}
freeaddrinfo(result);
if (address == NULL)
{
libKodi->Log(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(_hostname, _port);
}
bool Socket::is_valid() const
{
return (_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(_sd, FIONBIO, &iMode) == -1)
{
libKodi->Log(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 = NULL;
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";
}
libKodi->Log(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),&_wsaData) != 0)
{
return false;
}
WORD wVersionRequested = MAKEWORD(2,2);
// check version
if (_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(_sd, F_GETFL);
if ( opts < 0 )
{
return false;
}
if ( b )
opts = ( opts | O_NONBLOCK );
else
opts = ( opts & ~O_NONBLOCK );
if(fcntl (_sd , F_SETFL, opts) == -1)
{
libKodi->Log(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 = NULL;
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;
}
libKodi->Log(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