def test_add_remove_stream(self): # protect RTP tx_session = Session(policy=Policy( key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345)) protected = tx_session.protect(RTP) self.assertEqual(len(protected), 182) # add stream and unprotect RTP rx_session = Session() rx_session.add_stream(Policy( key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345)) unprotected = rx_session.unprotect(protected) self.assertEqual(len(unprotected), 172) self.assertEqual(unprotected, RTP) # remove stream rx_session.remove_stream(12345) # try removing stream again with self.assertRaises(Error) as cm: rx_session.remove_stream(12345) self.assertEqual(str(cm.exception), 'no appropriate context found')
class srtp_hex2hex_coder(hex_coder): ''' Allow record srtp/rtp stream ''' def __init__(self, override_payload_offset=None, rtp_offset=0, srtpkey=None, resrtpkey=None, verbose=False, payload_type=111, filter_ssrc=None): #super(ogg_opus_coder, self).__init__(verbose=verbose) hex_coder.__init__(self, verbose=verbose) # setup rtp parameters self.override_payload_offset = override_payload_offset self.rtp_offset = rtp_offset self.payload_type = payload_type # setup srtp parameters self.srtpkey = srtpkey self.resrtpkey = resrtpkey self.srtp_session = None self.ssrc = None self.filter_ssrc = filter_ssrc def record_rtp_packet(self, packet, is_last=False): if not packet: print("No packet") return False if self.verbose: print("Dumping packet ", packet) rtp_raw_full = None try: rtp_raw_full = bytes.fromhex(''.join(packet[self.rtp_offset:])) except: print("Failed to get hex", packet) return False if len(rtp_raw_full) < RTP_FIXED_HEADER: print("udp payload is too small") return False if self.verbose: print(self.rtp_offset, packet[self.rtp_offset - 1], packet[self.rtp_offset], packet[self.rtp_offset + 1]) # decode RTP Header rtp_raw_header = rtp_raw_full[:RTP_FIXED_HEADER] if self.verbose: print(rtp_raw_header.hex()) rtp = RTP_HEADER._make(struct.unpack(RTP_HEADER_FMT, rtp_raw_header)) if self.verbose: print(rtp) if rtp.PAYLOAD_TYPE & 0b10000000: # strip marker bit from payload rtp = RTP_HEADER(FIRST=rtp.FIRST, PAYLOAD_TYPE=rtp.PAYLOAD_TYPE & 0b01111111, SEQUENCE_NUMBER=rtp.SEQUENCE_NUMBER, TIMESTAMP=rtp.TIMESTAMP, SSRC=rtp.SSRC) print('stripped marker bit', rtp) # Filter the RTP with v=2 if (rtp.FIRST & 0b11000000) != 0b10000000: print("Not an RTP") return False rtp_exten = rtp.FIRST & 0b10000 rtp_exten_length = 0 rtp_csrc = rtp.FIRST & 0b1111 # calculate rtp header length calc_rtp_header_len = RTP_FIXED_HEADER + rtp_csrc * 4 if rtp_exten: exten_start = RTP_FIXED_HEADER + rtp_csrc * 4 exten_raw = rtp_raw_full[exten_start:exten_start + 4] if len(exten_raw) != 4: print("Skipping malformed RTP") return False rtp_exten_profile, rtp_exten_length = struct.unpack( '>HH', exten_raw) calc_rtp_header_len += 4 + rtp_exten_length * 4 if self.verbose: print("calc_rtp_header_len", calc_rtp_header_len) if self.override_payload_offset: calc_rtp_header_len = self.override_payload_offset - self.rtp_offset # Filter opus if self.payload_type and rtp.PAYLOAD_TYPE != self.payload_type: print( "Skipping payload {rtp_payload_type} while {opus_payload_type} expected." .format(rtp_payload_type=rtp.PAYLOAD_TYPE, opus_payload_type=self.payload_type)) return False if self.filter_ssrc and self.filter_ssrc != rtp.SSRC: print( "Skipping ssrc={rtp_ssrc} while {filter_ssrc} expected".format( rtp_ssrc=rtp.SSRC, filter_ssrc=self.filter_ssrc)) return False if len(rtp_raw_full) < (calc_rtp_header_len + 1): print("Empty payload") return False if self.srtpkey: if self.ssrc != rtp.SSRC: #if not self.srtp_session: # self.srtp_session = Session() self.srtp_session = Session() print("using key [%s]" % self.srtpkey) srtpkey = base64.b64decode(self.srtpkey) plc = Policy(key=srtpkey, ssrc_value=rtp.SSRC, ssrc_type=Policy.SSRC_ANY_INBOUND) print(plc) self.srtp_session.add_stream(plc) self.ssrc = rtp.SSRC try: rtp_raw_full = self.srtp_session.unprotect(rtp_raw_full) except: print("decrypt fail seq={sequence}, ssrc={ssrc}".format( sequence=rtp.SEQUENCE_NUMBER, ssrc=rtp.SSRC)) ''' if self.resrtpkey: srtpkey = base64.b64decode(self.resrtpkey) plc = Policy(key=srtpkey,ssrc_value=rtp.SSRC,ssrc_type=Policy.SSRC_ANY_INBOUND) self.srtp_session.add_stream(plc) print("Using restrpkey here from next packet") ''' return False self.write_udp( bytes.fromhex(''.join(packet[:self.rtp_offset])) + rtp_raw_full) return True def record_rtp_file(self, infile, outfile, hexstring=False): ''' Convert an RTP hexdump into a recorded file ''' self.start_file(outfile) if hexstring: with open(infile, 'r') as rtp_fd: packet_counter = 0 success_counter = 0 for xline in rtp_fd: xline = xline.strip() packet = [ xline[idx:idx + 2] for idx in range(0, len(xline), +2) ] packet_counter += 1 if self.record_rtp_packet(packet): success_counter += 1 else: re_valid_hex = re.compile( r'^[0-9A-Fa-f]{1,8}\s{1,4}[0-9A-Fa-f]{2}') with open(infile, 'r') as rtp_fd: packet_counter = 0 success_counter = 0 packet = [] for xline in rtp_fd: if not xline or not re_valid_hex.match(xline): if packet: packet_counter += 1 if self.record_rtp_packet(packet): success_counter += 1 packet = [] else: content = xline.split() #print(len(content)) content.pop(0) # skip the segment column packet.extend(content) if packet: packet_counter += 1 if self.record_rtp_packet(packet, is_last=True): success_counter += 1 print("Written %d out of %d packets" % (success_counter, packet_counter)) self.end_file()
class srtp_ogg_opus_coder(ogg_opus_coder): ''' Allow record srtp/rtp stream ''' def __init__(self, override_payload_offset=None, rtp_offset=0, srtpkey=None, resrtpkey=None, verbose=False, payload_type=111, filter_ssrc=None): #super(ogg_opus_coder, self).__init__(verbose=verbose) ogg_opus_coder.__init__(self, verbose=verbose) # setup rtp parameters self.override_payload_offset = override_payload_offset self.rtp_offset = rtp_offset self.payload_type = payload_type # setup srtp parameters self.srtpkey = srtpkey self.resrtpkey = resrtpkey self.srtp_session = None self.ssrc = None self.filter_ssrc = filter_ssrc def record_rtp_packet(self, packet, is_last=False): assert self.ogg is not None if not packet: print("No packet") return False if self.verbose: print("Dumping packet ", packet) rtp_raw_full = None try: rtp_raw_full = bytes.fromhex(''.join(packet[self.rtp_offset:])) except: print("Failed to get hex", packet) return False if len(rtp_raw_full) < RTP_FIXED_HEADER: print("udp payload is too small") return False if self.verbose: print(self.rtp_offset, packet[self.rtp_offset - 1], packet[self.rtp_offset], packet[self.rtp_offset + 1]) # decode RTP Header rtp_raw_header = rtp_raw_full[:RTP_FIXED_HEADER] if self.verbose: print(rtp_raw_header.hex()) rtp = RTP_HEADER._make(struct.unpack(RTP_HEADER_FMT, rtp_raw_header)) if self.verbose: print(rtp) # Filter the RTP with v=2 if (rtp.FIRST & 0b11000000) != 0b10000000: print("Not an RTP") return False rtp_exten = rtp.FIRST & 0b10000 rtp_exten_length = 0 rtp_csrc = rtp.FIRST & 0b1111 # calculate rtp header length calc_rtp_header_len = RTP_FIXED_HEADER + rtp_csrc * 4 if rtp_exten: exten_start = RTP_FIXED_HEADER + rtp_csrc * 4 exten_raw = rtp_raw_full[exten_start:exten_start + 4] if len(exten_raw) != 4: print("Skipping malformed RTP") return False rtp_exten_profile, rtp_exten_length = struct.unpack( '>HH', exten_raw) calc_rtp_header_len += 4 + rtp_exten_length * 4 if self.verbose: print("calc_rtp_header_len", calc_rtp_header_len) if self.override_payload_offset: calc_rtp_header_len = self.override_payload_offset - self.rtp_offset # Filter opus if self.payload_type and rtp.PAYLOAD_TYPE != self.payload_type: print( "Skipping payload {rtp_payload_type} while {opus_payload_type} expected." .format(rtp_payload_type=rtp.PAYLOAD_TYPE, opus_payload_type=self.payload_type)) return False if self.filter_ssrc and self.filter_ssrc != rtp.SSRC: print( "Skipping ssrc={rtp_ssrc} while {filter_ssrc} expected".format( rtp_ssrc=rtp.SSRC, filter_ssrc=self.filter_ssrc)) return False if len(rtp_raw_full) < (calc_rtp_header_len + 1): print("Empty payload") return False if self.srtpkey: if self.ssrc != rtp.SSRC: if not self.srtp_session: self.srtp_session = Session() print("using key [%s]" % self.srtpkey) srtpkey = base64.b64decode(self.srtpkey) plc = Policy(key=srtpkey, ssrc_value=rtp.SSRC, ssrc_type=Policy.SSRC_ANY_INBOUND) print(plc) self.srtp_session.add_stream(plc) self.ssrc = rtp.SSRC try: rtp_raw_full = self.srtp_session.unprotect(rtp_raw_full) except: print("decrypt fail seq={sequence}, ssrc={ssrc}".format( sequence=rtp.SEQUENCE_NUMBER, ssrc=rtp.SSRC)) ''' if self.resrtpkey: srtpkey = base64.b64decode(self.resrtpkey) plc = Policy(key=srtpkey,ssrc_value=rtp.SSRC,ssrc_type=Policy.SSRC_ANY_INBOUND) self.srtp_session.add_stream(plc) print("Using restrpkey here from next packet") ''' return False # Add bitstream header if self.ogg.get_curr_bitstream() != rtp.SSRC: self.write_stream_header(rtp.SSRC) self.write_stream_comment('hex_to_opus', [str(rtp)]) # rtp_payload = rtp_raw_full[RTP_FIXED_HEADER:] rtp_payload = rtp_raw_full[calc_rtp_header_len:] self.ogg.write_page( rtp_payload, is_data=True, is_last=is_last, ptime=20, pageno=rtp.SEQUENCE_NUMBER) # By default the ptime=20 return True def record_rtp_file(self, infile, outfile): ''' Convert an RTP hexdump into a recorded file ''' self.start_file(outfile) re_valid_hex = re.compile(r'^[0-9A-Fa-f]{1,8}\s{1,4}[0-9A-Fa-f]{2}') with open(infile, 'r') as rtp_fd: packet_counter = 0 success_counter = 0 packet = [] for xline in rtp_fd: if not xline or not re_valid_hex.match(xline): if packet: packet_counter += 1 if self.record_rtp_packet(packet): success_counter += 1 packet = [] else: content = xline.split() #print(len(content)) content.pop(0) # skip the segment column packet.extend(content) if packet: self.record_rtp_packet(packet, is_last=True) print("Written %d out of %d packets" % (success_counter, packet_counter)) self.end_file() def hexdump(self, content): counter = 0 output = [] for segment in range((len(content) >> 4) + 1): segment_out = [] segment_out.append('%06x' % counter) for offset in range(0, 16): pos = (segment << 4) + offset if pos >= len(content): break # avoid overflow segment_out.append('%02x' % content[pos]) counter += 1 output.append(' '.join(segment_out)) output.append('\n') return '\n'.join(output) def playback_as_srtp_stream(self, infile, outfile, starting_sequence=0): ''' Read an ogg file and create rtp/srtp in hex form ''' self.ogg.reset(open(infile, 'rb')) with open(outfile, 'w') as hex_fd: page_counter = 0 sequence_counter = starting_sequence for header, content in self.ogg: print(header) if 0 == page_counter: opus_identity_head = OPUS_IDENTITY_HEADER._make( struct.unpack(OPUS_IDENTITY_HEADER_FMT, content)) print(opus_identity_head) #elif 1 == page_counter: # opus_comment_head = OPUS_COMMENT_HEADER._make(struct.unpack(OPUS_COMMENT_HEADER_FMT, content)) # print(opus_comment_head) else: print(' '.join([hex(x) for x in content])) if page_counter > 1: # show the TOC byte #toc = content[0] #config = (toc >> 3) #s = toc & 0b100 #s = s >> 2 #num_frames = toc & 0b11 #print("config %d, s %d, c/frames %d" % (config, s, num_frames)) # make an RTP packet rtp = RTP_HEADER(0x80, self.payload_type, sequence_counter, header.GRANULE_POS, header.BITSTREAM) rtp_raw_full = struct.pack(RTP_HEADER_FMT, * rtp) + content if self.srtpkey: if self.ssrc != rtp.SSRC: print("using key [%s]" % self.srtpkey) srtpkey = base64.b64decode(self.srtpkey) plc = Policy( key=srtpkey, ssrc_value=rtp.SSRC, ssrc_type=Policy.SSRC_ANY_OUTBOUND) print(plc) self.srtp_session = Session(policy=plc) self.ssrc = rtp.SSRC try: rtp_raw_full = self.srtp_session.protect( rtp_raw_full) except: print( "encrypt fail seq={sequence}, ssrc={ssrc}". format(sequence=rtp.SEQUENCE_NUMBER, ssrc=rtp.SSRC)) hex_fd.write(self.hexdump(rtp_raw_full)) if sequence_counter == MAX_RTP_SEQUENCE_NUM: sequence_counter = 0 else: sequence_counter += 1 page_counter += 1 print("Read %d pages" % page_counter)