#include "libdddvb.h" #include "dddvb.h" #include "tools.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MMI_STATE_CLOSED 0 #define MMI_STATE_OPEN 1 #define MMI_STATE_ENQ 2 #define MMI_STATE_MENU 3 int set_nonblock(int fd) { int fl = fcntl(fd, F_GETFL); if (fl < 0) return fl; return fcntl(fd, F_SETFL, fl | O_NONBLOCK); } int streamsock(const char *port, int family, struct sockaddr *sadr) { int one=1, sock; struct addrinfo *ais, *ai, hints = { .ai_flags = AI_PASSIVE, .ai_family = family, .ai_socktype = SOCK_STREAM, .ai_protocol = 0, .ai_addrlen = 0, .ai_addr = NULL, .ai_canonname = NULL, .ai_next = NULL, }; if (getaddrinfo(NULL, port, &hints, &ais) < 0) return -1; for (ai = ais; ai; ai = ai->ai_next) { sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock == -1) continue; if (!setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) && !bind(sock, ai->ai_addr, ai->ai_addrlen)) { *sadr = *ai->ai_addr; break; } close(sock); sock = -1; } freeaddrinfo(ais); return sock; } int unixsock(const char *path) { unlink(path); struct sockaddr_un sa; size_t hlen = offsetof(struct sockaddr_un, sun_path); size_t pathlen = strlen(path); if (pathlen > sizeof(sa.sun_path)) return(-1); memset(&sa, 0, hlen); sa.sun_family = AF_UNIX; if (pathlen > 0) memcpy(sa.sun_path, path, pathlen); int sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock == -1) return(-1); if (bind(sock, (struct sockaddr *) &sa, (socklen_t) (hlen + pathlen)) == -1) { close(sock); return(-1); } return(sock); } static int ai_callback(void *arg, uint8_t slot_id, uint16_t session_number, uint8_t application_type, uint16_t application_manufacturer, uint16_t manufacturer_code, uint8_t menu_string_length, uint8_t *menu_string) { struct dddvb_ca *ca = arg; dbgprintf(DEBUG_CA, "Application type: %02x\n", application_type); dbgprintf(DEBUG_CA, "Application manufacturer: %04x\n", application_manufacturer); dbgprintf(DEBUG_CA, "Manufacturer code: %04x\n", manufacturer_code); dbgprintf(DEBUG_CA, "Menu string: %.*s\n", menu_string_length, menu_string); return 0; } static int ca_info_callback(void *arg, uint8_t slot_id, uint16_t snum, uint32_t id_count, uint16_t *ids) { struct dddvb_ca *ca = arg; uint32_t i; dbgprintf(DEBUG_CA, "CAM supports the following ca system ids:\n"); for (i = 0; i < id_count; i++) { dbgprintf(DEBUG_CA, " 0x%04x\n", ids[i]); } ca->resource_ready = 1; return 0; } #if 0 static int handle_pmt(struct dvbca *ca, uint8_t *buf, int size) { int listmgmt = CA_LIST_MANAGEMENT_ONLY; uint8_t capmt[4096]; struct section *section = section_codec(buf, size); struct section_ext *section_ext = section_ext_decode(section, 0); struct mpeg_pmt_section *pmt = mpeg_pmt_section_codec(section_ext); dbgprintf(DEBUG_CA, "handle pmt\n"); if (section_ext->version_number == ca->ca_pmt_version && ca->pmt == ca->pmt_old) return; if (ca->pmt != ca->pmt_old) { ca->pmt_old = ca->pmt; ca->sentpmt = 0; } if (ca->resource_ready) { ca->data_pmt_version = pmt->head.version_number; if (ca->sentpmt) { listmgmt = CA_LIST_MANAGEMENT_UPDATE; //return; } ca->sentpmt = 1; dbgprintf(DEBUG_CA, "set ca_pmt\n"); if ((size = en50221_ca_format_pmt(pmt, capmt, sizeof(capmt), ca->moveca, listmgmt, CA_PMT_CMD_ID_OK_DESCRAMBLING)) < 0) { dbgprintf(DEBUG_CA, "Failed to format PMT\n"); return -1; } if (en50221_app_ca_pmt(ca->stdcam->ca_resource, ca->stdcam->ca_session_number, capmt, size)) { dbgprintf(DEBUG_CA, "Failed to send PMT\n"); return -1; } } } #endif #if 0 static void handle_tdt(struct dddvb_ca *ca) { struct section *section; struct dvb_tdt_section *tdt; uint8_t sec[4096]; time_t dvb_time; int len; if (ca->stdcam == NULL) return; if (ca->stdcam->dvbtime == NULL) return; len = getsec(ca->input, 0x14, 0, 0x70, sec); if (len < 0) return; dbgprintf(DEBUG_CA, "got tdt\n"); section = section_codec(sec, len); if (section == NULL) return; tdt = dvb_tdt_section_codec(section); if (tdt == NULL) return; dvb_time = dvbdate_to_unixtime(tdt->utc_time); dbgprintf(DEBUG_CA, "set dvbtime\n"); if (ca->stdcam->dvbtime) ca->stdcam->dvbtime(ca->stdcam, dvb_time); } #endif static int handle_pmts(struct dddvb_ca *ca) { int listmgmt = CA_LIST_MANAGEMENT_ONLY; uint8_t sec[4096], capmt[4096]; struct section *section; struct section_ext *section_ext; struct mpeg_pmt_section *pmt; int i, size, num, len; if (!ca->resource_ready) return 0; dbgprintf(DEBUG_CA, "handle pmts\n"); for (i = num = 0; i < MAX_PMT; i++) if (ca->pmt[i]) num++; for (i = 0; i < num; i++) { len = -1;//getsec(ca->input, ca->pmt[i] & 0xffff, ca->pmt[i] >> 16, 2, sec); if (len < 0) continue; section = section_codec(sec, len); section_ext = section_ext_decode(section, 0); pmt = mpeg_pmt_section_codec(section_ext); ca->ca_pmt_version[i] = section_ext->version_number; if (ca->sentpmt) { //return 0; listmgmt = CA_LIST_MANAGEMENT_UPDATE; } else { listmgmt = CA_LIST_MANAGEMENT_ONLY; if (num > 1) { listmgmt = CA_LIST_MANAGEMENT_MORE; if (i == 0) listmgmt = CA_LIST_MANAGEMENT_FIRST; if (i == num - 1) listmgmt = CA_LIST_MANAGEMENT_LAST; } } dbgprintf(DEBUG_CA, "set ca_pmt\n"); if ((size = en50221_ca_format_pmt(pmt, capmt, sizeof(capmt), ca->moveca, listmgmt, CA_PMT_CMD_ID_OK_DESCRAMBLING)) < 0) { dbgprintf(DEBUG_CA, "Failed to format PMT\n"); return -1; } //dump(capmt, size); if (en50221_app_ca_pmt(ca->stdcam->ca_resource, ca->stdcam->ca_session_number, capmt, size)) { dbgprintf(DEBUG_CA, "Failed to send PMT\n"); return -1; } } if (num) ca->sentpmt = 1; return 0; } static int set_pmts(struct dddvb_ca *ca, uint8_t **pmts) { int listmgmt = CA_LIST_MANAGEMENT_ONLY; uint8_t sec[4096], capmt[4096]; struct section *section; struct section_ext *section_ext; struct mpeg_pmt_section *pmt; int i, size, num, len; if (!ca->resource_ready) return -EBUSY; for (i = 0; i < MAX_PMT; i++) if (!pmts[i]) break; num = i; dbgprintf(DEBUG_CA, "handle %d pmts\n", num); for (i = 0; i < num; i++) { memcpy(sec, pmts[i], 3); len = ((sec[1] & 0x0f) << 8) | sec[2]; len += 3; memcpy(sec, pmts[i], len); section = section_codec(sec, len); section_ext = section_ext_decode(section, 0); pmt = mpeg_pmt_section_codec(section_ext); ca->ca_pmt_version[i] = section_ext->version_number; if (ca->sentpmt) { //return 0; listmgmt = CA_LIST_MANAGEMENT_UPDATE; } else { listmgmt = CA_LIST_MANAGEMENT_ONLY; if (num > 1) { listmgmt = CA_LIST_MANAGEMENT_MORE; if (i == 0) listmgmt = CA_LIST_MANAGEMENT_FIRST; if (i == num - 1) listmgmt = CA_LIST_MANAGEMENT_LAST; } } dbgprintf(DEBUG_CA, "set ca_pmt\n"); if ((size = en50221_ca_format_pmt(pmt, capmt, sizeof(capmt), ca->moveca, listmgmt, CA_PMT_CMD_ID_OK_DESCRAMBLING)) < 0) { dbgprintf(DEBUG_CA, "Failed to format PMT\n"); return -1; } //dump(capmt, size); if (en50221_app_ca_pmt(ca->stdcam->ca_resource, ca->stdcam->ca_session_number, capmt, size)) { dbgprintf(DEBUG_CA, "Failed to send PMT\n"); return -1; } } if (num) ca->sentpmt = 1; return 0; } static void proc_csock_msg(struct dddvb_ca *ca, uint8_t *buf, int len) { if (*buf == '\r') { return; } else if (*buf == '\n') { switch(ca->mmi_state) { case MMI_STATE_CLOSED: case MMI_STATE_OPEN: if ((ca->mmi_bufp == 0) && (ca->resource_ready)) { en50221_app_ai_entermenu(ca->stdcam->ai_resource, ca->stdcam->ai_session_number); } break; case MMI_STATE_ENQ: if (ca->mmi_bufp == 0) { en50221_app_mmi_answ(ca->stdcam->mmi_resource, ca->stdcam->mmi_session_number, MMI_ANSW_ID_CANCEL, NULL, 0); } else { en50221_app_mmi_answ(ca->stdcam->mmi_resource, ca->stdcam->mmi_session_number, MMI_ANSW_ID_ANSWER, ca->mmi_buf, ca->mmi_bufp); } ca->mmi_state = MMI_STATE_OPEN; break; case MMI_STATE_MENU: ca->mmi_buf[ca->mmi_bufp] = 0; en50221_app_mmi_menu_answ(ca->stdcam->mmi_resource, ca->stdcam->mmi_session_number, atoi(ca->mmi_buf)); ca->mmi_state = MMI_STATE_OPEN; break; } ca->mmi_bufp = 0; } else { if (ca->mmi_bufp < (sizeof(ca->mmi_buf) - 1)) { ca->mmi_buf[ca->mmi_bufp++] = *buf; } } } static int proc_csock(struct dddvb_ca *ca) { uint8_t buf[1024]; int len, i, res; if (ca->stdcam == NULL) return -1; while ((len = recv(ca->sock, buf, 1, 0)) >= 0) { if (len == 0) goto release; if (len < 0) { if (errno != EAGAIN) goto release; return 0; } proc_csock_msg(ca, buf, len); } return 0; release: close(ca->sock); ca->sock = -1; return -1; } static void handle_ci(struct dddvb_ca *ca) { uint8_t sec[4096]; uint32_t pmt_count, tdt_count; int len; int sock, i; struct sockaddr sadr; char port[6], path[32]; if (ca->dd->cam_family == 1) { snprintf(port, sizeof(port), "%u", (uint16_t) (8888 + ca->nr)); sock = streamsock(port, AF_INET, &sadr); } else if (ca->dd->cam_family == 2) { snprintf(path, sizeof(path), "/var/run/resiplayer/cam%u", ca->nr); sock = unixsock(path); } else { sock = -1; } if (listen(sock, 4) < 0) { dbgprintf(DEBUG_CA, "listen error"); return; } ca->sock = -1; while (1) {//!ca->exit) { struct timeval timeout; uint32_t count = 0; int num; int mfd; fd_set fds; timeout.tv_sec = 0; timeout.tv_usec = 200000; FD_ZERO(&fds); if (ca->sock < 0) { FD_SET(sock, &fds); num = select(sock + 1, &fds, NULL, NULL, &timeout); } else { FD_SET(ca->sock, &fds); num = select(ca->sock + 1, &fds, NULL, NULL, &timeout); } if (num > 0) { if (ca->sock < 0) { if (FD_ISSET(sock, &fds)) { socklen_t len; struct sockaddr cadr; ca->sock = accept(sock, &cadr, &len); if (ca->sock >= 0) { set_nonblock(ca->sock); } } } else { if (FD_ISSET(ca->sock, &fds)) { proc_csock(ca); } } } pthread_mutex_lock(&ca->mutex); if (!ca->state) { pthread_mutex_unlock(&ca->mutex); continue; } if (ca->setpmt) { dbgprintf(DEBUG_CA, "got new PMT %08x\n", ca->pmt_new); memcpy(ca->pmt, ca->pmt_new, sizeof(ca->pmt)); memset(ca->pmt_old, 0, sizeof(ca->pmt_old)); for (i = 0; i < MAX_PMT; i++) ca->ca_pmt_version[i] = -1; ca->sentpmt = 0; ca->setpmt = 0; pmt_count = 0; tdt_count = 0; } pthread_mutex_unlock(&ca->mutex); /* if (!ca->sentpmt) handle_pmts(ca); else { pmt_count++; if (pmt_count == 10) { //handle_pmts(ca); pmt_count = 0; } } */ tdt_count++; if (tdt_count == 10) { //handle_tdt(ca); tdt_count = 0; } } } int set_pmt(struct dddvb_ca *ca, uint32_t *pmt) { dbgprintf(DEBUG_CA, "set_pmt %08x %08x %08x\n", pmt[0], pmt[1], pmt[2]); pthread_mutex_lock(&ca->mutex); ca->setpmt = 1; memcpy(ca->pmt_new, pmt, sizeof(ca->pmt_new)); pthread_mutex_unlock(&ca->mutex); return 0; } static void ci_poll(struct dddvb_ca *ca) { while (!ca->dd->exit) { ca->stdcam->poll(ca->stdcam); } } static int mmi_close_callback(void *arg, uint8_t slot_id, uint16_t snum, uint8_t cmd_id, uint8_t delay) { struct dddvb_ca *ca = arg; ca->mmi_state = MMI_STATE_CLOSED; if (ca->dd->cam_proto == 2) sendstringx(ca->sock, "CLOSE", 0, NULL); return 0; } static int mmi_display_control_callback(void *arg, uint8_t slot_id, uint16_t snum, uint8_t cmd_id, uint8_t mmi_mode) { struct dddvb_ca *ca = arg; struct en50221_app_mmi_display_reply_details reply; if (cmd_id != MMI_DISPLAY_CONTROL_CMD_ID_SET_MMI_MODE) { en50221_app_mmi_display_reply(ca->stdcam->mmi_resource, snum, MMI_DISPLAY_REPLY_ID_UNKNOWN_CMD_ID, &reply); return 0; } // we only support high level mode if (mmi_mode != MMI_MODE_HIGH_LEVEL) { en50221_app_mmi_display_reply(ca->stdcam->mmi_resource, snum, MMI_DISPLAY_REPLY_ID_UNKNOWN_MMI_MODE, &reply); return 0; } reply.u.mode_ack.mmi_mode = mmi_mode; en50221_app_mmi_display_reply(ca->stdcam->mmi_resource, snum, MMI_DISPLAY_REPLY_ID_MMI_MODE_ACK, &reply); ca->mmi_state = MMI_STATE_OPEN; if (ca->dd->cam_proto == 2) sendstringx(ca->sock, "OPEN", 0, NULL); return 0; } static int mmi_enq_callback(void *arg, uint8_t slot_id, uint16_t snum, uint8_t blind_answer, uint8_t expected_answer_length, uint8_t *text, uint32_t text_size) { struct dddvb_ca *ca = arg; if (ca->sock >= 0) { if (ca->dd->cam_proto == 1) sendstring(ca->sock, "%.*s: ", text_size, text); if (ca->dd->cam_proto == 2) sendstringx(ca->sock, "ENQ", text_size, text); } //mmi_enq_blind = blind_answer; //mmi_enq_length = expected_answer_length; ca->mmi_state = MMI_STATE_ENQ; return 0; } static int mmi_menu_callback(void *arg, uint8_t slot_id, uint16_t snum, struct en50221_app_mmi_text *title, struct en50221_app_mmi_text *sub_title, struct en50221_app_mmi_text *bottom, uint32_t item_count, struct en50221_app_mmi_text *items, uint32_t item_raw_length, uint8_t *items_raw) { uint32_t i; struct dddvb_ca *ca = arg; if ((ca->sock >= 0) && (ca->dd->cam_proto == 1)) { if (title->text_length) sendstring(ca->sock, "%.*s\n", title->text_length, title->text); if (sub_title->text_length) sendstring(ca->sock, "%.*s\n", sub_title->text_length, sub_title->text); for (i = 0; i < item_count; i++) sendstring(ca->sock, "%i. %.*s\n", i + 1, items[i].text_length, items[i].text); if (bottom->text_length) sendstring(ca->sock, "%.*s\n", bottom->text_length, bottom->text); } if (ca->dd->cam_proto == 2) { sendstringx(ca->sock, "MENU", title->text_length, title->text); sendstringx(ca->sock, "MSUB", sub_title->text_length, sub_title->text); for (i = 0; i < item_count; i++) sendstringx(ca->sock, "ITEM", items[i].text_length, items[i].text); sendstringx(ca->sock, "MEND", bottom->text_length, bottom->text); } ca->mmi_state = MMI_STATE_MENU; return 0; } static int init_ca_stack(struct dddvb_ca *ca) { ca->tl = en50221_tl_create(1, 16); if (ca->tl == NULL) { dbgprintf(DEBUG_CA, "Failed to create transport layer\n"); return -1; } ca->sl = en50221_sl_create(ca->tl, 16); if (ca->sl == NULL) { dbgprintf(DEBUG_CA, "Failed to create session layer\n"); en50221_tl_destroy(ca->tl); return -1; } ca->stdcam = en50221_stdcam_llci_create(ca->fd, 0, ca->tl, ca->sl); if (!ca->stdcam) { dbgprintf(DEBUG_CA, "Failed to create stdcam\n"); en50221_sl_destroy(ca->sl); en50221_tl_destroy(ca->tl); return -1; } if (ca->stdcam->ai_resource) { en50221_app_ai_register_callback(ca->stdcam->ai_resource, ai_callback, ca); } if (ca->stdcam->ca_resource) { en50221_app_ca_register_info_callback(ca->stdcam->ca_resource, ca_info_callback, ca); } if (ca->stdcam->mmi_resource) { en50221_app_mmi_register_close_callback(ca->stdcam->mmi_resource, mmi_close_callback, ca); en50221_app_mmi_register_display_control_callback(ca->stdcam->mmi_resource, mmi_display_control_callback, ca); en50221_app_mmi_register_enq_callback(ca->stdcam->mmi_resource, mmi_enq_callback, ca); en50221_app_mmi_register_menu_callback(ca->stdcam->mmi_resource, mmi_menu_callback, ca); en50221_app_mmi_register_list_callback(ca->stdcam->mmi_resource, mmi_menu_callback, ca); } else { dbgprintf(DEBUG_CA, "CAM Menus are not supported by this interface hardware\n"); } return 0; } static int init_ca(struct dddvb *dd, int a, int f, int fd) { struct dddvb_ca *ca; char fname[80]; ca = &dd->dvbca[dd->dvbca_num]; ca->dd = dd; ca->anum = a; ca->fnum = f; ca->nr = dd->dvbca_num + 1; ca->fd = fd; pthread_mutex_init(&ca->mutex, 0); init_ca_stack(ca); pthread_create(&ca->poll_pt, NULL, (void *) ci_poll, ca); pthread_create(&ca->pt, NULL, (void *) handle_ci, ca); sprintf(fname, "/dev/dvb/adapter%d/ci%d", a, f); ca->ci_wfd = open(fname, O_WRONLY); ca->ci_rfd = open(fname, O_RDONLY | O_NONBLOCK); dd->dvbca_num++; return 0; } int dddvb_ca_write(struct dddvb *dd, uint32_t nr, uint8_t *buf, uint32_t len) { struct dddvb_ca *ca = &dd->dvbca[nr]; return write(ca->ci_wfd, buf, len); } int dddvb_ca_read(struct dddvb *dd, uint32_t nr, uint8_t *buf, uint32_t len) { struct dddvb_ca *ca = &dd->dvbca[nr]; return read(ca->ci_rfd, buf, len); } int dddvb_ca_set_pmts(struct dddvb *dd, uint32_t nr, uint8_t **pmts) { struct dddvb_ca *ca = &dd->dvbca[nr]; dbgprintf(DEBUG_CA, "ca_set_pmt\n"); return set_pmts(ca, pmts); } int scan_dvbca(struct dddvb *dd) { int a, f, fd; char fname[80]; for (a = 0; a < 16; a++) { for (f = 0; f < 16; f++) { sprintf(fname, "/dev/dvb/adapter%d/ca%d", a, f); fd = open(fname, O_RDWR); if (fd >= 0) { init_ca(dd, a, f, fd); //close(fd); } } } dbgprintf(DEBUG_CA, "Found %d CA interfaces\n", dd->dvbca_num); }