def update_sdt_names(self, _sdt_msg, _service_provider, _service_name): # must start with x474011 (pid for SDT is x0011 # update the full msg length 2 bytes with first 4 bits as xF at position 7-8 (includes crc) # update descriptor length lower 12 bits at position 20-21 (remaining bytes - crc) # x48 service descriptor tag # 1 byte length of service provider # byte string of the service provider # 1 byte length of service name # byte string of the service name # crc if _sdt_msg[:3] != b'\x47\x40\x11': self.logger.info('WRONG ATSC MSG {}'.format(bytes(_sdt_msg[:20]).hex())) return _sdt_msg descr = b'\x01' \ + utils.set_str(_service_provider, False) \ + utils.set_str(_service_name, False) descr = b'\x48' + utils.set_u8(len(descr)) + descr msg = _sdt_msg[8:20] + utils.set_u8(len(descr)) + descr length = utils.set_u16(len(msg) + 4 + 0xF000) msg = ATSC_SERVICE_DESCR_TABLE_TAG + length + msg crc = self.gen_crc_mpeg(msg) msg = _sdt_msg[:5] + msg + crc msg = msg.ljust(len(_sdt_msg), b'\xFF') return msg
def datagram_received(self, _data, _host_port): """Handle a received multicast datagram.""" (host, port) = _host_port if self.config['hdhomerun']['udp_netmask'] is None: is_allowed = True else: net = IPv4Network(self.config['hdhomerun']['udp_netmask']) is_allowed = IPv4Address(host) in net if is_allowed: self.logger.debug('UDP: from {}:{}'.format(host, port)) try: (frame_type, msg_len, device_type, sub_dt_len, sub_dt, device_id, sub_did_len, sub_did) = \ struct.unpack('>HHBBIBBI', _data[0:-4]) (crc, ) = struct.unpack('<I', _data[-4:]) except ValueError as err: self.logger.error('UDP: {}'.format(err)) return if frame_type != HDHOMERUN_TYPE_DISCOVER_REQ: self.logger.error( 'UDP: Unknown from type = {}'.format(frame_type)) else: msg_type = bytes.fromhex('0003') header = bytes.fromhex('010400000001') device_id = bytes.fromhex('0204' + self.config['hdhomerun']['hdhr_id']) base_url = 'http://' + \ self.config['main']['plex_accessible_ip'] + \ ':' + str(self.config['main']['web_admin_port']) base_url_msg = b'\x2a' + utils.set_str(base_url.encode(), False) tuner_count = b'\x10\x01' + utils.set_u8( self.config['main']['tuner_count']) lineup_url = base_url + '/lineup.json' lineup_url = b'\x27' + utils.set_str(lineup_url.encode(), False) msg = header + device_id + base_url_msg + tuner_count + lineup_url msg_len = utils.set_u16(len(msg)) response = msg_type + msg_len + msg x = zlib.crc32(response) crc = struct.pack('<I', x) response += crc self.logger.debug('UDP Response={} {}'.format( _host_port, response)) self.sock_multicast.sendto(response, _host_port)
def gen_err_response(self, _frame_type, _tag, _text): # This is a tag type of HDHOMERUN_ERROR_MESSAGE # does not include the crc msg = msgs[_tag].format(*_text).encode() tag = utils.set_u8(HDHOMERUN_ERROR_MESSAGE) err_resp = utils.set_str(msg, True) msg_len = utils.set_u16(len(tag) + len(err_resp)) response = _frame_type + msg_len + tag + err_resp return response
def gen_multiple_string_structure(self, _names): # Table 6.39 Multiple String Structure # event titles, long channel names, the ETT messages, and RRT text items # allows for upto 255 character strings # 8bit = array size # for each name # 3byte ISO_639_language_string = 'eng' # 1byte segments = normally 0x01 # for each segment # 1byte compression = 0x00 (not compression) # 1byte mode = 0x00 (used with unicode 2 byte letters, assume ASCII) # 1byte length in bytes = calculated # string in byte format. 1 byte per character msg = utils.set_u8(len(_names)) for name in _names: lang = self.gen_lang(b'eng') segment_len = utils.set_u8(1) compress_mode = utils.set_u16(0) name_bytes = utils.set_str(name.encode(), False) msg += lang + segment_len + compress_mode + name_bytes return msg
def create_getset_response(self, _req_dict, _address): (host, port) = _address frame_type = utils.set_u16(HDHOMERUN_TYPE_GETSET_RSP) name = _req_dict[HDHOMERUN_GETSET_NAME] name_str = name.decode('utf-8') if HDHOMERUN_GETSET_VALUE in _req_dict.keys(): value = _req_dict[HDHOMERUN_GETSET_VALUE] else: value = None if name == b'/sys/model': # required to id the device name_resp = utils.set_u8(HDHOMERUN_GETSET_NAME) + utils.set_str( name, True) value_resp = utils.set_u8(HDHOMERUN_GETSET_VALUE) + utils.set_str( b'hdhomerun4_atsc', True) msg_len = utils.set_u16(len(name_resp) + len(value_resp)) response = frame_type + msg_len + name_resp + value_resp x = zlib.crc32(response) crc = struct.pack('<I', x) response += crc return response elif name_str.startswith('/tuner'): tuner_index = int(name_str[6]) if name_str.endswith('/lockkey'): self.logger.error( 'TCP: NOT IMPLEMENTED GETSET LOCKKEY MSG REQUEST: {} '. format(_req_dict)) response = self.gen_err_response(frame_type, 'lockedErrMsg', [host]) x = zlib.crc32(response) crc = struct.pack('<I', x) response += crc return response elif name_str.endswith('/status'): tuner_status = self.tuners[tuner_index]['status'] if tuner_status == 'Scan': response = self.gen_err_response(frame_type, 'scanErrMsg', [host]) else: value_resp = utils.set_u8(HDHOMERUN_GETSET_VALUE) \ + utils.set_str(tuner_status_msg[tuner_status], True) name_resp = utils.set_u8( HDHOMERUN_GETSET_NAME) + utils.set_str(name, True) msg_len = utils.set_u16(len(name_resp) + len(value_resp)) response = frame_type + msg_len + name_resp + value_resp x = zlib.crc32(response) crc = struct.pack('<I', x) response += crc return response elif name_str.endswith('/vchannel'): tuner_status = self.tuners[tuner_index]['status'] if tuner_status == 'Stream': value_resp = utils.set_u8(HDHOMERUN_GETSET_VALUE) \ + utils.set_str(self.tuners[tuner_index]['channel'].encode(), True) else: value_resp = utils.set_u8(HDHOMERUN_GETSET_VALUE) \ + utils.set_str('none', True) name_resp = utils.set_u8( HDHOMERUN_GETSET_NAME) + utils.set_str(name, True) msg_len = utils.set_u16(len(name_resp) + len(value_resp)) response = frame_type + msg_len + name_resp + value_resp x = zlib.crc32(response) crc = struct.pack('<I', x) response += crc return response else: self.logger.error( 'TCP: NOT IMPLEMENTED GETSET MSG REQUEST: {} '.format( _req_dict)) return None else: self.logger.error( 'TCP: 3 UNKNOWN GETSET MSG REQUEST: {} '.format(_req_dict)) return None