/* * Copyright (C) 2005-2013 Team XBMC * http://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, 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 XBMC; see the file COPYING. If not, see * . * */ #include "system.h" #include "HTSPSession.h" #include "URL.h" #include "video/VideoInfoTag.h" #include "FileItem.h" #include "utils/log.h" #ifdef _MSC_VER #ifdef _XBOX #include #else #include #endif #define SHUT_RDWR SD_BOTH #define ETIMEDOUT WSAETIMEDOUT #else #include #include #endif extern "C" { #include "lib/libhts/net.h" #include "lib/libhts/htsmsg.h" #include "lib/libhts/htsmsg_binary.h" #include "lib/libhts/sha1.h" } struct SContentType { unsigned id; const char* genre; }; static const SContentType g_dvb_content_group[] = { { 0x1, "Movie/Drama" } , { 0x2, "News/Current Affairs" } , { 0x3, "Show/Game show" } , { 0x4, "Sports" } , { 0x5, "Children's/Youth" } , { 0x6, "Music/Ballet/Dance" } , { 0x7, "Arts/Culture (without music)" } , { 0x8, "Social/Political issues/Economics" } , { 0x9, "Childrens/Youth Education/Science/Factual" } , { 0xa, "Leisure hobbies" } , { 0xb, "Misc" } , { 0xf, "Unknown" } }; static const SContentType g_dvb_content_type[] = { // movie/drama { 0x11, "Detective/Thriller" } , { 0x12, "Adventure/Western/War" } , { 0x13, "Science Fiction/Fantasy/Horror" } , { 0x14, "Comedy" } , { 0x15, "Soap/Melodrama/Folkloric" } , { 0x16, "Romance" } , { 0x17, "Serious/ClassicalReligion/Historical" } , { 0x18, "Adult Movie/Drama" } // news/current affairs , { 0x21, "News/Weather Report" } , { 0x22, "Magazine" } , { 0x23, "Documentary" } , { 0x24, "Discussion/Interview/Debate" } // show/game show , { 0x31, "Game show/Quiz/Contest" } , { 0x32, "Variety" } , { 0x33, "Talk" } // sports , { 0x41, "Special Event (Olympics/World cup/...)" } , { 0x42, "Magazine" } , { 0x43, "Football/Soccer" } , { 0x44, "Tennis/Squash" } , { 0x45, "Team sports (excluding football)" } , { 0x46, "Athletics" } , { 0x47, "Motor Sport" } , { 0x48, "Water Sport" } , { 0x49, "Winter Sports" } , { 0x4a, "Equestrian" } , { 0x4b, "Martial sports" } // childrens/youth , { 0x51, "Pre-school" } , { 0x52, "Entertainment (6 to 14 year-olds)" } , { 0x53, "Entertainment (10 to 16 year-olds)" } , { 0x54, "Informational/Educational/Schools" } , { 0x55, "Cartoons/Puppets" } // music/ballet/dance , { 0x61, "Rock/Pop" } , { 0x62, "Serious music/Classical Music" } , { 0x63, "Folk/Traditional music" } , { 0x64, "Jazz" } , { 0x65, "Musical/Opera" } , { 0x66, "Ballet" } // arts/culture , { 0x71, "Performing Arts" } , { 0x72, "Fine Arts" } , { 0x73, "Religion" } , { 0x74, "Popular Culture/Tradital Arts" } , { 0x75, "Literature" } , { 0x76, "Film/Cinema" } , { 0x77, "Experimental Film/Video" } , { 0x78, "Broadcasting/Press" } , { 0x79, "New Media" } , { 0x7a, "Magazine" } , { 0x7b, "Fashion" } // social/political/economic , { 0x81, "Magazine/Report/Domentary" } , { 0x82, "Economics/Social Advisory" } , { 0x83, "Remarkable People" } // children's youth: educational/science/factual , { 0x91, "Nature/Animals/Environment" } , { 0x92, "Technology/Natural sciences" } , { 0x93, "Medicine/Physiology/Psychology" } , { 0x94, "Foreign Countries/Expeditions" } , { 0x95, "Social/Spiritual Sciences" } , { 0x96, "Further Education" } , { 0x97, "Languages" } // leisure hobbies , { 0xa1, "Tourism/Travel" } , { 0xa2, "Handicraft" } , { 0xa3, "Motoring" } , { 0xa4, "Fitness & Health" } , { 0xa5, "Cooking" } , { 0xa6, "Advertisement/Shopping" } , { 0xa7, "Gardening" } // misc , { 0xb0, "Original Language" } , { 0xb1, "Black and White" } , { 0xb2, "Unpublished" } , { 0xb3, "Live Broadcast" } }; using namespace std; using namespace HTSP; string CHTSPSession::GetGenre(unsigned type) { // look for full content for(int i = 0; i < sizeof(g_dvb_content_type) / sizeof(g_dvb_content_type[0]); i++) { if(g_dvb_content_type[i].id == type) return g_dvb_content_type[i].genre; } // look for group type = (type >> 4) & 0xf; for(int i = 0; i < sizeof(g_dvb_content_group) / sizeof(g_dvb_content_group[0]); i++) { if(g_dvb_content_group[i].id == type) return g_dvb_content_group[i].genre; } return ""; } CHTSPSession::CHTSPSession() : m_fd(INVALID_SOCKET) , m_seq(0) , m_challenge(NULL) , m_challenge_len(0) , m_queue_size(1000) { } CHTSPSession::~CHTSPSession() { Close(); } void CHTSPSession::Abort() { shutdown(m_fd, SHUT_RDWR); } void CHTSPSession::Close() { if(m_fd != INVALID_SOCKET) { closesocket(m_fd); m_fd = INVALID_SOCKET; } if(m_challenge) { free(m_challenge); m_challenge = NULL; m_challenge_len = 0; } } bool CHTSPSession::Connect(const std::string& hostname, int port) { char errbuf[1024]; int errlen = sizeof(errbuf); htsmsg_t *m; const char *method, *server, *version; const void * chall = NULL; size_t chall_len = 0; int32_t proto = 0; if(port == 0) port = 9982; m_fd = htsp_tcp_connect(hostname.c_str() , port , errbuf, errlen, 3000); if(m_fd == INVALID_SOCKET) { CLog::Log(LOGERROR, "CHTSPSession::Open - failed to connect to server (%s)\n", errbuf); return false; } // send hello m = htsmsg_create_map(); htsmsg_add_str(m, "method", "hello"); htsmsg_add_str(m, "clientname", "XBMC Media Center"); htsmsg_add_u32(m, "htspversion", 1); // read welcome if((m = ReadResult(m)) == NULL) { CLog::Log(LOGERROR, "CHTSPSession::Open - failed to read greeting from server"); return false; } method = htsmsg_get_str(m, "method"); htsmsg_get_s32(m, "htspversion", &proto); server = htsmsg_get_str(m, "servername"); version = htsmsg_get_str(m, "serverversion"); htsmsg_get_bin(m, "challenge", &chall, &chall_len); CLog::Log(LOGDEBUG, "CHTSPSession::Open - connected to server: [%s], version: [%s], proto: %d" , server ? server : "", version ? version : "", proto); m_protocol = proto; if(chall && chall_len) { m_challenge = malloc(chall_len); m_challenge_len = chall_len; memcpy(m_challenge, chall, chall_len); } htsmsg_destroy(m); return true; } bool CHTSPSession::Auth(const std::string& username, const std::string& password) { htsmsg_t *m = htsmsg_create_map(); htsmsg_add_str(m, "method" , "authenticate"); htsmsg_add_str(m, "username", username.c_str()); if(password != "" && m_challenge) { struct HTSSHA1* shactx = (struct HTSSHA1*) malloc(hts_sha1_size); uint8_t d[20]; hts_sha1_init(shactx); hts_sha1_update(shactx , (const uint8_t *)password.c_str() , password.length()); hts_sha1_update(shactx , (const uint8_t *)m_challenge , m_challenge_len); hts_sha1_final(shactx, d); htsmsg_add_bin(m, "digest", d, 20); free(shactx); } return ReadSuccess(m, false, "get reply from authentication with server"); } htsmsg_t* CHTSPSession::ReadMessage(int timeout) { void* buf; uint32_t l; int x; if(m_queue.size()) { htsmsg_t* m = m_queue.front(); m_queue.pop_front(); return m; } x = htsp_tcp_read_timeout(m_fd, &l, 4, timeout); if(x == ETIMEDOUT) return htsmsg_create_map(); if(x) { CLog::Log(LOGERROR, "CHTSPSession::ReadMessage - Failed to read packet size (%d)\n", x); return NULL; } l = ntohl(l); if(l == 0) return htsmsg_create_map(); buf = malloc(l); x = htsp_tcp_read(m_fd, buf, l); if(x) { CLog::Log(LOGERROR, "CHTSPSession::ReadMessage - Failed to read packet (%d)\n", x); free(buf); return NULL; } return htsmsg_binary_deserialize(buf, l, buf); /* consumes 'buf' */ } bool CHTSPSession::SendMessage(htsmsg_t* m) { void* buf; size_t len; if(htsmsg_binary_serialize(m, &buf, &len, -1) < 0) { htsmsg_destroy(m); return false; } htsmsg_destroy(m); if(send(m_fd, (char*)buf, len, 0) < 0) { free(buf); return false; } free(buf); return true; } htsmsg_t* CHTSPSession::ReadResult(htsmsg_t* m, bool sequence) { if(sequence) htsmsg_add_u32(m, "seq", ++m_seq); if(!SendMessage(m)) return NULL; std::deque queue; m_queue.swap(queue); while((m = ReadMessage())) { uint32_t seq; if(!sequence) break; if(!htsmsg_get_u32(m, "seq", &seq) && seq == m_seq) break; queue.push_back(m); if(queue.size() >= m_queue_size) { CLog::Log(LOGERROR, "CDVDInputStreamHTSP::ReadResult - maximum queue size (%u) reached", m_queue_size); m_queue.swap(queue); return NULL; } } m_queue.swap(queue); const char* error; if(m && (error = htsmsg_get_str(m, "error"))) { CLog::Log(LOGERROR, "CDVDInputStreamHTSP::ReadResult - error (%s)", error); htsmsg_destroy(m); return NULL; } uint32_t noaccess; if(m && !htsmsg_get_u32(m, "noaccess", &noaccess) && noaccess) { CLog::Log(LOGERROR, "CDVDInputStreamHTSP::ReadResult - access denied (%d)", noaccess); htsmsg_destroy(m); return NULL; } return m; } bool CHTSPSession::ReadSuccess(htsmsg_t* m, bool sequence, std::string action) { if((m = ReadResult(m, sequence)) == NULL) { CLog::Log(LOGDEBUG, "CDVDInputStreamHTSP::ReadSuccess - failed to %s", action.c_str()); return false; } htsmsg_destroy(m); return true; } bool CHTSPSession::SendSubscribe(int subscription, int channel) { htsmsg_t *m = htsmsg_create_map(); htsmsg_add_str(m, "method" , "subscribe"); htsmsg_add_s32(m, "channelId" , channel); htsmsg_add_s32(m, "subscriptionId", subscription); return ReadSuccess(m, true, "subscribe to channel"); } bool CHTSPSession::SendUnsubscribe(int subscription) { htsmsg_t *m = htsmsg_create_map(); htsmsg_add_str(m, "method" , "unsubscribe"); htsmsg_add_s32(m, "subscriptionId", subscription); return ReadSuccess(m, true, "unsubscribe from channel"); } bool CHTSPSession::SendEnableAsync() { htsmsg_t *m = htsmsg_create_map(); htsmsg_add_str(m, "method", "enableAsyncMetadata"); return ReadSuccess(m, true, "enableAsyncMetadata failed"); } bool CHTSPSession::GetEvent(SEvent& event, uint32_t id) { if(id == 0) { event.Clear(); return false; } htsmsg_t *msg = htsmsg_create_map(); htsmsg_add_str(msg, "method", "getEvent"); htsmsg_add_u32(msg, "eventId", id); if((msg = ReadResult(msg, true)) == NULL) { CLog::Log(LOGDEBUG, "CHTSPSession::GetEvent - failed to get event %d", id); return false; } return ParseEvent(msg, id, event); } bool CHTSPSession::ParseEvent(htsmsg_t* msg, uint32_t id, SEvent &event) { uint32_t start, stop, next, content; const char *title, *desc; if( htsmsg_get_u32(msg, "start", &start) || htsmsg_get_u32(msg, "stop" , &stop) || (title = htsmsg_get_str(msg, "title")) == NULL) { CLog::Log(LOGDEBUG, "CHTSPSession::ParseEvent - malformed event"); htsmsg_print(msg); htsmsg_destroy(msg); return false; } event.Clear(); event.id = id; event.start = start; event.stop = stop; event.title = title; if((desc = htsmsg_get_str(msg, "description"))) event.descs = desc; if(htsmsg_get_u32(msg, "nextEventId", &next)) event.next = 0; else event.next = next; if(htsmsg_get_u32(msg, "contentType", &content) == 0) event.content = content; CLog::Log(LOGDEBUG, "CHTSPSession::ParseEvent - id:%u, title:'%s', desc:'%s', start:%u, stop:%u, next:%u, content:%u" , event.id , event.title.c_str() , event.descs.c_str() , event.start , event.stop , event.next) , event.content; return true; } void CHTSPSession::ParseChannelUpdate(htsmsg_t* msg, SChannels &channels) { uint32_t id, event = 0, num = 0; const char *name, *icon; if(htsmsg_get_u32(msg, "channelId", &id)) { CLog::Log(LOGERROR, "CHTSPSession::ParseChannelUpdate - malformed message received"); htsmsg_print(msg); return; } SChannel &channel = channels[id]; channel.id = id; if(htsmsg_get_u32(msg, "eventId", &event) == 0) channel.event = event; if((name = htsmsg_get_str(msg, "channelName"))) channel.name = name; if((icon = htsmsg_get_str(msg, "channelIcon"))) channel.icon = icon; if(htsmsg_get_u32(msg, "channelNumber", &num) == 0) { if(num == 0) channel.num = id + 1000; else channel.num = num; } else channel.num = id; // fallback older servers htsmsg_t *tags; if((tags = htsmsg_get_list(msg, "tags"))) { channel.tags.clear(); htsmsg_field_t *f; HTSMSG_FOREACH(f, tags) { if(f->hmf_type != HMF_S64) continue; channel.tags.push_back((int)f->hmf_s64); } } CLog::Log(LOGDEBUG, "CHTSPSession::ParseChannelUpdate - id:%u, name:'%s', icon:'%s', event:%u" , id, name ? name : "(null)", icon ? icon : "(null)", event); } void CHTSPSession::ParseChannelRemove(htsmsg_t* msg, SChannels &channels) { uint32_t id; if(htsmsg_get_u32(msg, "channelId", &id)) { CLog::Log(LOGERROR, "CDVDInputStreamHTSP::ParseChannelRemove - malformed message received"); htsmsg_print(msg); return; } CLog::Log(LOGDEBUG, "CHTSPSession::ParseChannelRemove - id:%u", id); channels.erase(id); } void CHTSPSession::ParseTagUpdate(htsmsg_t* msg, STags &tags) { uint32_t id; const char *name, *icon; if(htsmsg_get_u32(msg, "tagId", &id)) { CLog::Log(LOGERROR, "CHTSPSession::ParseTagUpdate - malformed message received"); htsmsg_print(msg); return; } STag &tag = tags[id]; tag.id = id; if((icon = htsmsg_get_str(msg, "tagIcon"))) tag.icon = icon; if((name = htsmsg_get_str(msg, "tagName"))) tag.name = name; htsmsg_t *channels; if((channels = htsmsg_get_list(msg, "members"))) { tag.channels.clear(); htsmsg_field_t *f; HTSMSG_FOREACH(f, channels) { if(f->hmf_type != HMF_S64) continue; tag.channels.push_back((int)f->hmf_s64); } } CLog::Log(LOGDEBUG, "CHTSPSession::ParseTagUpdate - id:%u, name:'%s', icon:'%s'" , id, name ? name : "(null)", icon ? icon : "(null)"); } void CHTSPSession::ParseTagRemove(htsmsg_t* msg, STags &tags) { uint32_t id; if(htsmsg_get_u32(msg, "tagId", &id)) { CLog::Log(LOGERROR, "CHTSPSession::ParseTagRemove - malformed message received"); htsmsg_print(msg); return; } CLog::Log(LOGDEBUG, "CHTSPSession::ParseTagRemove - id:%u", id); tags.erase(id); } bool CHTSPSession::ParseItem(const SChannel& channel, int tagid, const SEvent& event, CFileItem& item) { CVideoInfoTag* tag = item.GetVideoInfoTag(); CStdString temp; CURL url(item.GetPath()); temp.Format("tags/%d/%d.ts", tagid, channel.id); url.SetFileName(temp); tag->m_iSeason = 0; tag->m_iEpisode = 0; tag->m_iTrack = channel.num; tag->m_strAlbum = channel.name; tag->m_strShowTitle = event.title; tag->m_strPlot = event.descs; tag->m_strStatus = "livetv"; tag->m_strGenre = GetGenre(event.content); tag->m_strTitle = tag->m_strAlbum; if(tag->m_strShowTitle.length() > 0) tag->m_strTitle += " : " + tag->m_strShowTitle; item.SetPath(url.Get()); item.m_strTitle = tag->m_strTitle; item.SetThumbnailImage(channel.icon); item.SetMimeType("video/X-htsp"); item.SetCachedVideoThumb(); return true; } bool CHTSPSession::ParseQueueStatus (htsmsg_t* msg, SQueueStatus &queue) { if(htsmsg_get_u32(msg, "packets", &queue.packets) || htsmsg_get_u32(msg, "bytes", &queue.bytes) || htsmsg_get_u32(msg, "Bdrops", &queue.bdrops) || htsmsg_get_u32(msg, "Pdrops", &queue.pdrops) || htsmsg_get_u32(msg, "Idrops", &queue.idrops)) { CLog::Log(LOGERROR, "CHTSPSession::ParseQueueStatus - malformed message received"); htsmsg_print(msg); return false; } /* delay isn't always transmitted */ if(htsmsg_get_u32(msg, "delay", &queue.delay)) queue.delay = 0; return true; }