1import locale
2
3import numpy
4import numpy as np
5from PyQt5.QtCore import Qt, pyqtSlot
6from PyQt5.QtGui import QFontMetrics
7from PyQt5.QtWidgets import QInputDialog, QWidget, QUndoStack, QApplication
8
9from urh import settings
10from urh.controller.CompareFrameController import CompareFrameController
11from urh.controller.dialogs.ContinuousSendDialog import ContinuousSendDialog
12from urh.controller.dialogs.FuzzingDialog import FuzzingDialog
13from urh.controller.dialogs.ModulatorDialog import ModulatorDialog
14from urh.controller.dialogs.SendDialog import SendDialog
15from urh.models.GeneratorListModel import GeneratorListModel
16from urh.models.GeneratorTableModel import GeneratorTableModel
17from urh.models.GeneratorTreeModel import GeneratorTreeModel
18from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
19from urh.plugins.PluginManager import PluginManager
20from urh.plugins.RfCat.RfCatPlugin import RfCatPlugin
21from urh.signalprocessing.IQArray import IQArray
22from urh.signalprocessing.Message import Message
23from urh.signalprocessing.MessageType import MessageType
24from urh.signalprocessing.Modulator import Modulator
25from urh.signalprocessing.ProtocoLabel import ProtocolLabel
26from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
27from urh.ui.actions.Fuzz import Fuzz
28from urh.ui.ui_generator import Ui_GeneratorTab
29from urh.util import FileOperator, util
30from urh.util.Errors import Errors
31from urh.util.Formatter import Formatter
32from urh.util.Logger import logger
33from urh.util.ProjectManager import ProjectManager
34
35
36class GeneratorTabController(QWidget):
37    def __init__(self, compare_frame_controller: CompareFrameController, project_manager: ProjectManager, parent=None):
38        super().__init__(parent)
39        self.ui = Ui_GeneratorTab()
40        self.ui.setupUi(self)
41        util.set_splitter_stylesheet(self.ui.splitter)
42
43        self.project_manager = project_manager
44
45        self.ui.treeProtocols.setHeaderHidden(True)
46        self.tree_model = GeneratorTreeModel(compare_frame_controller)
47        self.tree_model.set_root_item(compare_frame_controller.proto_tree_model.rootItem)
48        self.tree_model.controller = self
49        self.ui.treeProtocols.setModel(self.tree_model)
50
51        self.table_model = GeneratorTableModel(compare_frame_controller.proto_tree_model.rootItem,
52                                               compare_frame_controller.decodings)
53        self.table_model.controller = self
54        self.ui.tableMessages.setModel(self.table_model)
55
56        self.label_list_model = GeneratorListModel(None)
57        self.ui.listViewProtoLabels.setModel(self.label_list_model)
58
59        self.network_sdr_button_orig_tooltip = self.ui.btnNetworkSDRSend.toolTip()
60        self.set_network_sdr_send_button_visibility()
61        self.set_rfcat_button_visibility()
62        self.network_sdr_plugin = NetworkSDRInterfacePlugin()
63        self.rfcat_plugin = RfCatPlugin()
64        self.init_rfcat_plugin()
65
66        self.modulation_msg_indices = []
67
68        self.refresh_modulators()
69        self.on_selected_modulation_changed()
70        self.set_fuzzing_ui_status()
71        self.ui.prBarGeneration.hide()
72        self.create_connects(compare_frame_controller)
73
74        self.set_modulation_profile_status()
75
76    def __get_modulator_of_message(self, message: Message) -> Modulator:
77        if message.modulator_index > len(self.modulators) - 1:
78            message.modulator_index = 0
79        return self.modulators[message.modulator_index]
80
81    @property
82    def selected_message_index(self) -> int:
83        min_row, _, _, _ = self.ui.tableMessages.selection_range()
84        return min_row  #
85
86    @property
87    def selected_message(self) -> Message:
88        selected_msg_index = self.selected_message_index
89        if selected_msg_index == -1 or selected_msg_index >= len(self.table_model.protocol.messages):
90            return None
91
92        return self.table_model.protocol.messages[selected_msg_index]
93
94    @property
95    def active_groups(self):
96        return self.tree_model.groups
97
98    @property
99    def modulators(self):
100        return self.project_manager.modulators
101
102    @property
103    def total_modulated_samples(self) -> int:
104        return sum(int(len(msg.encoded_bits) * self.__get_modulator_of_message(msg).samples_per_symbol + msg.pause)
105                   for msg in self.table_model.protocol.messages)
106
107    @modulators.setter
108    def modulators(self, value):
109        assert type(value) == list
110        self.project_manager.modulators = value
111
112    def create_connects(self, compare_frame_controller):
113        compare_frame_controller.proto_tree_model.modelReset.connect(self.refresh_tree)
114        compare_frame_controller.participant_changed.connect(self.table_model.refresh_vertical_header)
115        self.ui.btnEditModulation.clicked.connect(self.show_modulation_dialog)
116        self.ui.cBoxModulations.currentIndexChanged.connect(self.on_selected_modulation_changed)
117        self.ui.tableMessages.selectionModel().selectionChanged.connect(self.on_table_selection_changed)
118        self.ui.tableMessages.encodings_updated.connect(self.on_table_selection_changed)
119        self.table_model.undo_stack.indexChanged.connect(self.on_undo_stack_index_changed)
120        self.table_model.protocol.qt_signals.line_duplicated.connect(self.refresh_pause_list)
121        self.table_model.protocol.qt_signals.fuzzing_started.connect(self.on_fuzzing_started)
122        self.table_model.protocol.qt_signals.current_fuzzing_message_changed.connect(
123            self.on_current_fuzzing_message_changed)
124        self.table_model.protocol.qt_signals.fuzzing_finished.connect(self.on_fuzzing_finished)
125        self.table_model.first_protocol_added.connect(self.on_first_protocol_added)
126        self.label_list_model.protolabel_fuzzing_status_changed.connect(self.set_fuzzing_ui_status)
127        self.ui.cbViewType.currentIndexChanged.connect(self.on_view_type_changed)
128        self.ui.btnSend.clicked.connect(self.on_btn_send_clicked)
129        self.ui.btnSave.clicked.connect(self.on_btn_save_clicked)
130        self.ui.btnOpen.clicked.connect(self.on_btn_open_clicked)
131
132        self.project_manager.project_updated.connect(self.on_project_updated)
133
134        self.table_model.vertical_header_color_status_changed.connect(
135            self.ui.tableMessages.on_vertical_header_color_status_changed)
136
137        self.label_list_model.protolabel_removed.connect(self.handle_proto_label_removed)
138
139        self.ui.lWPauses.item_edit_clicked.connect(self.edit_pause_item)
140        self.ui.lWPauses.edit_all_items_clicked.connect(self.edit_all_pause_items)
141        self.ui.lWPauses.itemSelectionChanged.connect(self.on_lWpauses_selection_changed)
142        self.ui.lWPauses.lost_focus.connect(self.on_lWPauses_lost_focus)
143        self.ui.lWPauses.doubleClicked.connect(self.on_lWPauses_double_clicked)
144        self.ui.btnGenerate.clicked.connect(self.generate_file)
145        self.label_list_model.protolabel_fuzzing_status_changed.connect(self.handle_plabel_fuzzing_state_changed)
146        self.ui.btnFuzz.clicked.connect(self.on_btn_fuzzing_clicked)
147        self.ui.tableMessages.create_label_triggered.connect(self.create_fuzzing_label)
148        self.ui.tableMessages.edit_label_triggered.connect(self.show_fuzzing_dialog)
149        self.ui.listViewProtoLabels.selection_changed.connect(self.handle_label_selection_changed)
150        self.ui.listViewProtoLabels.edit_on_item_triggered.connect(self.show_fuzzing_dialog)
151
152        self.ui.btnNetworkSDRSend.clicked.connect(self.on_btn_network_sdr_clicked)
153        self.ui.btnRfCatSend.clicked.connect(self.on_btn_rfcat_clicked)
154
155        self.network_sdr_plugin.sending_status_changed.connect(self.on_network_sdr_sending_status_changed)
156        self.network_sdr_plugin.sending_stop_requested.connect(self.on_network_sdr_sending_stop_requested)
157        self.network_sdr_plugin.current_send_message_changed.connect(self.on_send_message_changed)
158
159    @pyqtSlot()
160    def refresh_tree(self):
161        self.tree_model.beginResetModel()
162        self.tree_model.endResetModel()
163        self.ui.treeProtocols.expandAll()
164
165    @pyqtSlot()
166    def refresh_table(self):
167        self.table_model.update()
168        self.ui.tableMessages.resize_columns()
169        is_data_there = self.table_model.display_data is not None and len(self.table_model.display_data) > 0
170        self.ui.btnSend.setEnabled(is_data_there)
171        self.ui.btnGenerate.setEnabled(is_data_there)
172
173    @pyqtSlot()
174    def refresh_label_list(self):
175        self.label_list_model.message = self.selected_message
176        self.label_list_model.update()
177
178    @property
179    def generator_undo_stack(self) -> QUndoStack:
180        return self.table_model.undo_stack
181
182    @pyqtSlot()
183    def on_selected_modulation_changed(self):
184        cur_ind = self.ui.cBoxModulations.currentIndex()
185        min_row, max_row, _, _ = self.ui.tableMessages.selection_range()
186        if min_row > -1:
187            # set modulation for selected messages
188            for row in range(min_row, max_row + 1):
189                try:
190                    self.table_model.protocol.messages[row].modulator_index = cur_ind
191                except IndexError:
192                    continue
193
194        self.show_modulation_info()
195
196    def refresh_modulators(self):
197        current_index = 0
198        if type(self.sender()) == ModulatorDialog:
199            current_index = self.sender().ui.comboBoxCustomModulations.currentIndex()
200        self.ui.cBoxModulations.clear()
201        for modulator in self.modulators:
202            self.ui.cBoxModulations.addItem(modulator.name)
203
204        self.ui.cBoxModulations.setCurrentIndex(current_index)
205
206    def bootstrap_modulator(self, protocol: ProtocolAnalyzer):
207        """
208        Set initial parameters for default modulator if it was not edited by user previously
209        :return:
210        """
211        if len(self.modulators) != 1 or len(self.table_model.protocol.messages) == 0:
212            return
213
214        modulator = self.modulators[0]
215        modulator.samples_per_symbol = protocol.messages[0].samples_per_symbol
216        modulator.bits_per_symbol = protocol.messages[0].bits_per_symbol
217
218        if protocol.signal:
219            modulator.sample_rate = protocol.signal.sample_rate
220            modulator.modulation_type = protocol.signal.modulation_type
221            auto_freq = modulator.estimate_carrier_frequency(protocol.signal, protocol)
222            if auto_freq is not None and auto_freq != 0:
223                modulator.carrier_freq_hz = auto_freq
224
225        modulator.parameters = modulator.get_default_parameters()
226        self.show_modulation_info()
227
228    def show_modulation_info(self):
229        cur_ind = self.ui.cBoxModulations.currentIndex()
230        mod = self.modulators[cur_ind]
231        self.ui.lCarrierFreqValue.setText(mod.carrier_frequency_str)
232        self.ui.lCarrierPhaseValue.setText(mod.carrier_phase_str)
233        self.ui.lBitLenValue.setText(mod.samples_per_symbol_str)
234        self.ui.lSampleRateValue.setText(mod.sample_rate_str)
235        mod_type = mod.modulation_type
236        self.ui.lModTypeValue.setText(mod_type)
237
238        self.ui.lParamCaption.setText(mod.parameter_type_str)
239        self.ui.labelParameterValues.setText(mod.parameters_string)
240        self.ui.labelBitsPerSymbol.setText(str(mod.bits_per_symbol))
241
242    def prepare_modulation_dialog(self) -> (ModulatorDialog, Message):
243        preselected_index = self.ui.cBoxModulations.currentIndex()
244
245        min_row, max_row, start, end = self.ui.tableMessages.selection_range()
246        if min_row > -1:
247            try:
248                selected_message = self.table_model.protocol.messages[min_row]
249                preselected_index = selected_message.modulator_index
250            except IndexError:
251                selected_message = Message([1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], 0, [], MessageType("empty"))
252        else:
253            selected_message = Message([1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0], 0, [], MessageType("empty"))
254            if len(self.table_model.protocol.messages) > 0:
255                selected_message.samples_per_symbol = self.table_model.protocol.messages[0].samples_per_symbol
256
257        for m in self.modulators:
258            m.default_sample_rate = self.project_manager.device_conf["sample_rate"]
259
260        modulator_dialog = ModulatorDialog(self.modulators, tree_model=self.tree_model, parent=self.parent())
261        modulator_dialog.ui.comboBoxCustomModulations.setCurrentIndex(preselected_index)
262
263        modulator_dialog.finished.connect(self.refresh_modulators)
264        modulator_dialog.finished.connect(self.refresh_pause_list)
265
266        return modulator_dialog, selected_message
267
268    def set_modulation_profile_status(self):
269        visible = settings.read("multiple_modulations", False, bool)
270        self.ui.cBoxModulations.setVisible(visible)
271
272    def init_rfcat_plugin(self):
273        self.set_rfcat_button_visibility()
274        self.rfcat_plugin = RfCatPlugin()
275        self.rfcat_plugin.current_send_message_changed.connect(self.on_send_message_changed)
276        self.ui.btnRfCatSend.setEnabled(self.rfcat_plugin.rfcat_is_found)
277
278    @pyqtSlot()
279    def on_undo_stack_index_changed(self):
280        self.refresh_table()
281        self.refresh_pause_list()
282        self.refresh_label_list()
283        self.refresh_estimated_time()
284        self.set_fuzzing_ui_status()
285
286    @pyqtSlot()
287    def show_modulation_dialog(self):
288        modulator_dialog, message = self.prepare_modulation_dialog()
289        modulator_dialog.showMaximized()
290
291        modulator_dialog.initialize(message.encoded_bits_str[0:16])
292        self.project_manager.modulation_was_edited = True
293
294    @pyqtSlot()
295    def on_table_selection_changed(self):
296        min_row, max_row, start, end = self.ui.tableMessages.selection_range()
297
298        if min_row == -1:
299            self.ui.lEncodingValue.setText("-")  #
300            self.ui.lEncodingValue.setToolTip("")
301            self.label_list_model.message = None
302            return
303
304        container = self.table_model.protocol
305        message = container.messages[min_row]
306        self.label_list_model.message = message
307        decoder_name = message.decoder.name
308        metrics = QFontMetrics(self.ui.lEncodingValue.font())
309        elidedName = metrics.elidedText(decoder_name, Qt.ElideRight, self.ui.lEncodingValue.width())
310        self.ui.lEncodingValue.setText(elidedName)
311        self.ui.lEncodingValue.setToolTip(decoder_name)
312        self.ui.cBoxModulations.blockSignals(True)
313        self.ui.cBoxModulations.setCurrentIndex(message.modulator_index)
314        self.show_modulation_info()
315        self.ui.cBoxModulations.blockSignals(False)
316
317    @pyqtSlot(int)
318    def edit_pause_item(self, index: int):
319        message = self.table_model.protocol.messages[index]
320        cur_len = message.pause
321        new_len, ok = QInputDialog.getInt(self, self.tr("Enter new Pause Length"),
322                                          self.tr("Pause Length:"), cur_len, 0)
323        if ok:
324            message.pause = new_len
325            self.refresh_pause_list()
326
327    @pyqtSlot()
328    def edit_all_pause_items(self):
329        message = self.table_model.protocol.messages[0]
330        cur_len = message.pause
331        new_len, ok = QInputDialog.getInt(self, self.tr("Enter new Pause Length"),
332                                          self.tr("Pause Length:"), cur_len, 0)
333        if ok:
334            for message in self.table_model.protocol.messages:
335                message.pause = new_len
336
337            self.refresh_pause_list()
338
339    @pyqtSlot()
340    def on_lWPauses_double_clicked(self):
341        sel_indexes = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
342        if len(sel_indexes) > 0:
343            self.edit_pause_item(sel_indexes[0])
344
345    @pyqtSlot()
346    def refresh_pause_list(self):
347        self.ui.lWPauses.clear()
348
349        fmt_str = "Pause ({1:d}-{2:d}) <{0:d} samples ({3})>"
350        for i, pause in enumerate(self.table_model.protocol.pauses):
351            sr = self.__get_modulator_of_message(self.table_model.protocol.messages[i]).sample_rate
352            item = fmt_str.format(pause, i + 1, i + 2, Formatter.science_time(pause / sr))
353            self.ui.lWPauses.addItem(item)
354
355        self.refresh_estimated_time()
356
357    @pyqtSlot()
358    def on_lWpauses_selection_changed(self):
359        rows = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
360        if len(rows) == 0:
361            return
362        self.ui.tableMessages.show_pause_active = True
363        self.ui.tableMessages.pause_row = rows[0]
364        self.ui.tableMessages.viewport().update()
365        self.ui.tableMessages.scrollTo(self.table_model.index(rows[0], 0))
366
367    @pyqtSlot()
368    def on_lWPauses_lost_focus(self):
369        self.ui.tableMessages.show_pause_active = False
370        self.ui.tableMessages.viewport().update()
371
372    @pyqtSlot()
373    def generate_file(self):
374        try:
375            total_samples = self.total_modulated_samples
376            buffer = self.prepare_modulation_buffer(total_samples, show_error=False)
377            if buffer is None:
378                Errors.generic_error(self.tr("File too big"), self.tr("This file would get too big to save."))
379                self.unsetCursor()
380                return
381            modulated_samples = self.modulate_data(buffer)
382            try:
383                sample_rate = self.modulators[0].sample_rate
384            except Exception as e:
385                logger.exception(e)
386                sample_rate = 1e6
387            FileOperator.ask_signal_file_name_and_save("generated", modulated_samples, sample_rate=sample_rate, parent=self)
388        except Exception as e:
389            Errors.exception(e)
390            self.unsetCursor()
391
392    def prepare_modulation_buffer(self, total_samples: int, show_error=True) -> IQArray:
393        dtype = Modulator.get_dtype()
394        n = 2 if dtype == np.int8 else 4 if dtype == np.int16 else 8
395
396        memory_size_for_buffer = total_samples * n
397        logger.debug("Allocating {0:.2f}MB for modulated samples".format(memory_size_for_buffer / (1024 ** 2)))
398        try:
399            # allocate it three times as we need the same amount for the sending process
400            IQArray(None, dtype=dtype, n=3*total_samples)
401        except MemoryError:
402            # will go into continuous mode in this case
403            if show_error:
404                Errors.not_enough_ram_for_sending_precache(3*memory_size_for_buffer)
405            return None
406
407        return IQArray(None, dtype=dtype, n=total_samples)
408
409    def modulate_data(self, buffer: IQArray) -> IQArray:
410        """
411
412        :param buffer: Buffer in which the modulated data shall be written, initialized with zeros
413        :return:
414        """
415        self.ui.prBarGeneration.show()
416        self.ui.prBarGeneration.setValue(0)
417        self.ui.prBarGeneration.setMaximum(self.table_model.row_count)
418        self.modulation_msg_indices.clear()
419
420        pos = 0
421        for i in range(0, self.table_model.row_count):
422            message = self.table_model.protocol.messages[i]
423            modulator = self.__get_modulator_of_message(message)
424            # We do not need to modulate the pause extra, as result is already initialized with zeros
425            modulated = modulator.modulate(start=0, data=message.encoded_bits, pause=0)
426            buffer[pos:pos + len(modulated)] = modulated
427            pos += len(modulated) + message.pause
428            self.modulation_msg_indices.append(pos)
429            self.ui.prBarGeneration.setValue(i + 1)
430            QApplication.instance().processEvents()
431
432        self.ui.prBarGeneration.hide()
433        return buffer
434
435    @pyqtSlot(int)
436    def show_fuzzing_dialog(self, label_index: int):
437        view = self.ui.cbViewType.currentIndex()
438
439        if self.label_list_model.message is not None:
440            msg_index = self.table_model.protocol.messages.index(self.label_list_model.message)
441            fdc = FuzzingDialog(protocol=self.table_model.protocol, label_index=label_index,
442                                msg_index=msg_index, proto_view=view, parent=self)
443            fdc.show()
444            fdc.finished.connect(self.on_fuzzing_dialog_finished)
445
446    @pyqtSlot()
447    def on_fuzzing_dialog_finished(self):
448        self.refresh_label_list()
449        self.refresh_table()
450        self.set_fuzzing_ui_status()
451        self.ui.tabWidget.setCurrentIndex(2)
452
453    @pyqtSlot()
454    def handle_plabel_fuzzing_state_changed(self):
455        self.refresh_table()
456        self.label_list_model.update()
457
458    @pyqtSlot(ProtocolLabel)
459    def handle_proto_label_removed(self, plabel: ProtocolLabel):
460        self.refresh_label_list()
461        self.refresh_table()
462        self.set_fuzzing_ui_status()
463
464    @pyqtSlot()
465    def on_btn_fuzzing_clicked(self):
466        fuz_mode = "Successive"
467        if self.ui.rbConcurrent.isChecked():
468            fuz_mode = "Concurrent"
469        elif self.ui.rBExhaustive.isChecked():
470            fuz_mode = "Exhaustive"
471
472        self.setCursor(Qt.WaitCursor)
473        fuzz_action = Fuzz(self.table_model.protocol, fuz_mode)
474        self.table_model.undo_stack.push(fuzz_action)
475        for row in fuzz_action.added_message_indices:
476            self.table_model.update_checksums_for_row(row)
477        self.unsetCursor()
478        self.ui.tableMessages.setFocus()
479
480    @pyqtSlot()
481    def set_fuzzing_ui_status(self):
482        btn_was_enabled = self.ui.btnFuzz.isEnabled()
483        fuzz_active = any(lbl.active_fuzzing for msg in self.table_model.protocol.messages for lbl in msg.message_type)
484        self.ui.btnFuzz.setEnabled(fuzz_active)
485        if self.ui.btnFuzz.isEnabled() and not btn_was_enabled:
486            font = self.ui.btnFuzz.font()
487            font.setBold(True)
488            self.ui.btnFuzz.setFont(font)
489        else:
490            font = self.ui.btnFuzz.font()
491            font.setBold(False)
492            self.ui.btnFuzz.setFont(font)
493            self.ui.btnFuzz.setStyleSheet("")
494
495        has_same_message = self.table_model.protocol.multiple_fuzz_labels_per_message
496        self.ui.rBSuccessive.setEnabled(has_same_message)
497        self.ui.rBExhaustive.setEnabled(has_same_message)
498        self.ui.rbConcurrent.setEnabled(has_same_message)
499
500    def refresh_existing_encodings(self, encodings_from_file):
501        """
502        Refresh existing encodings for messages, when encoding was changed by user in dialog
503
504        :return:
505        """
506        update = False
507
508        for msg in self.table_model.protocol.messages:
509            i = next((i for i, d in enumerate(encodings_from_file) if d.name == msg.decoder.name), 0)
510            if msg.decoder != encodings_from_file[i]:
511                update = True
512                msg.decoder = encodings_from_file[i]
513                msg.clear_decoded_bits()
514                msg.clear_encoded_bits()
515
516        if update:
517            self.refresh_table()
518            self.refresh_estimated_time()
519
520    @pyqtSlot()
521    def refresh_estimated_time(self):
522        c = self.table_model.protocol
523        if c.num_messages == 0:
524            self.ui.lEstimatedTime.setText("Estimated Time: ")
525            return
526
527        avg_msg_len = numpy.mean([len(msg.encoded_bits) for msg in c.messages])
528        avg_samples_per_symbol = numpy.mean([m.samples_per_symbol for m in self.modulators])
529        avg_sample_rate = numpy.mean([m.sample_rate for m in self.modulators])
530        pause_samples = sum(c.pauses)
531        nsamples = c.num_messages * avg_msg_len * avg_samples_per_symbol + pause_samples
532
533        self.ui.lEstimatedTime.setText(
534            locale.format_string("Estimated Time: %.04f seconds", nsamples / avg_sample_rate))
535
536    @pyqtSlot(int, int, int)
537    def create_fuzzing_label(self, msg_index: int, start: int, end: int):
538        con = self.table_model.protocol
539        start, end = con.convert_range(start, end - 1, self.ui.cbViewType.currentIndex(), 0, False, msg_index)
540        lbl = con.create_fuzzing_label(start, end, msg_index)
541        self.show_fuzzing_dialog(con.protocol_labels.index(lbl))
542
543    @pyqtSlot()
544    def handle_label_selection_changed(self):
545        rows = [index.row() for index in self.ui.listViewProtoLabels.selectedIndexes()]
546        if len(rows) == 0:
547            return
548
549        maxrow = numpy.max(rows)
550
551        try:
552            label = self.table_model.protocol.protocol_labels[maxrow]
553        except IndexError:
554            return
555        if label.show and self.selected_message:
556            start, end = self.selected_message.get_label_range(lbl=label, view=self.table_model.proto_view,
557                                                               decode=False)
558            indx = self.table_model.index(0, int((start + end) / 2))
559            self.ui.tableMessages.scrollTo(indx)
560
561    @pyqtSlot()
562    def on_view_type_changed(self):
563        self.setCursor(Qt.WaitCursor)
564        self.table_model.proto_view = self.ui.cbViewType.currentIndex()
565        self.ui.tableMessages.resize_columns()
566        self.unsetCursor()
567
568    @pyqtSlot()
569    def on_btn_send_clicked(self):
570        try:
571            total_samples = self.total_modulated_samples
572            buffer = self.prepare_modulation_buffer(total_samples)
573            if buffer is not None:
574                modulated_data = self.modulate_data(buffer)
575            else:
576                # Enter continuous mode
577                modulated_data = None
578
579            try:
580                if modulated_data is not None:
581                    try:
582                        dialog = SendDialog(self.project_manager, modulated_data=modulated_data,
583                                            modulation_msg_indices=self.modulation_msg_indices, parent=self)
584                    except MemoryError:
585                        # Not enough memory for device buffer so we need to create a continuous send dialog
586                        del modulated_data
587                        Errors.not_enough_ram_for_sending_precache(None)
588                        dialog = ContinuousSendDialog(self.project_manager,
589                                                      self.table_model.protocol.messages,
590                                                      self.modulators, total_samples, parent=self)
591                else:
592                    dialog = ContinuousSendDialog(self.project_manager, self.table_model.protocol.messages,
593                                                  self.modulators, total_samples, parent=self)
594            except OSError as e:
595                logger.exception(e)
596                return
597            if dialog.has_empty_device_list:
598                Errors.no_device()
599                dialog.close()
600                return
601
602            dialog.device_parameters_changed.connect(self.project_manager.set_device_parameters)
603            dialog.show()
604            dialog.graphics_view.show_full_scene(reinitialize=True)
605        except Exception as e:
606            Errors.exception(e)
607            self.unsetCursor()
608
609    @pyqtSlot()
610    def on_btn_save_clicked(self):
611        filename = FileOperator.ask_save_file_name("profile.fuzz.xml", caption="Save fuzzing profile")
612        if filename:
613            self.table_model.protocol.to_xml_file(filename,
614                                                  decoders=self.project_manager.decodings,
615                                                  participants=self.project_manager.participants,
616                                                  modulators=self.modulators)
617
618    @pyqtSlot()
619    def on_btn_open_clicked(self):
620        dialog = FileOperator.get_open_dialog(directory_mode=False, parent=self, name_filter="fuzz")
621        if dialog.exec_():
622            for filename in dialog.selectedFiles():
623                self.load_from_file(filename)
624
625    def load_from_file(self, filename: str):
626        try:
627            self.modulators = ProjectManager.read_modulators_from_file(filename)
628            self.table_model.protocol.from_xml_file(filename)
629            self.refresh_pause_list()
630            self.refresh_estimated_time()
631            self.refresh_modulators()
632            self.show_modulation_info()
633            self.refresh_table()
634            self.set_fuzzing_ui_status()
635        except:
636            logger.error("You done something wrong to the xml fuzzing profile.")
637
638    @pyqtSlot()
639    def on_project_updated(self):
640        self.table_model.refresh_vertical_header()
641
642    def set_network_sdr_send_button_visibility(self):
643        is_plugin_enabled = PluginManager().is_plugin_enabled("NetworkSDRInterface")
644        self.ui.btnNetworkSDRSend.setVisible(is_plugin_enabled)
645
646    def set_rfcat_button_visibility(self):
647        is_plugin_enabled = PluginManager().is_plugin_enabled("RfCat")
648        self.ui.btnRfCatSend.setVisible(is_plugin_enabled)
649
650    @pyqtSlot()
651    def on_btn_network_sdr_clicked(self):
652        if not self.network_sdr_plugin.is_sending:
653            messages = self.table_model.protocol.messages
654            sample_rates = [self.__get_modulator_of_message(msg).sample_rate for msg in messages]
655            self.network_sdr_plugin.start_message_sending_thread(messages, sample_rates)
656        else:
657            self.network_sdr_plugin.stop_sending_thread()
658
659    @pyqtSlot(bool)
660    def on_network_sdr_sending_status_changed(self, is_sending: bool):
661        self.ui.btnNetworkSDRSend.setChecked(is_sending)
662        self.ui.btnNetworkSDRSend.setEnabled(True)
663        self.ui.btnNetworkSDRSend.setToolTip(
664            "Sending in progress" if is_sending else self.network_sdr_button_orig_tooltip)
665        if not is_sending:
666            self.ui.tableMessages.clearSelection()
667
668    @pyqtSlot()
669    def on_network_sdr_sending_stop_requested(self):
670        self.ui.btnNetworkSDRSend.setToolTip("Stopping sending")
671        self.ui.btnNetworkSDRSend.setEnabled(False)
672
673    @pyqtSlot(int)
674    def on_send_message_changed(self, message_index: int):
675        self.ui.tableMessages.selectRow(message_index)
676
677    @pyqtSlot()
678    def on_btn_rfcat_clicked(self):
679        if not self.rfcat_plugin.is_sending:
680            messages = self.table_model.protocol.messages
681            sample_rates = [self.__get_modulator_of_message(msg).sample_rate for msg in messages]
682            self.rfcat_plugin.start_message_sending_thread(messages, sample_rates, self.modulators,
683                                                           self.project_manager)
684        else:
685            self.rfcat_plugin.stop_sending_thread()
686
687    @pyqtSlot(int)
688    def on_fuzzing_started(self, num_values: int):
689        self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingProgressBar)
690        self.ui.progressBarFuzzing.setMaximum(num_values)
691        self.ui.progressBarFuzzing.setValue(0)
692        QApplication.instance().processEvents()
693
694    @pyqtSlot()
695    def on_fuzzing_finished(self):
696        self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingUI)
697        # Calculate Checksums for Fuzzed Messages
698        self.setCursor(Qt.WaitCursor)
699
700        self.unsetCursor()
701
702    @pyqtSlot(int)
703    def on_current_fuzzing_message_changed(self, current_message: int):
704        self.ui.progressBarFuzzing.setValue(current_message)
705        QApplication.instance().processEvents()
706
707    @pyqtSlot(ProtocolAnalyzer)
708    def on_first_protocol_added(self, protocol: ProtocolAnalyzer):
709        if not self.project_manager.modulation_was_edited:
710            self.bootstrap_modulator(protocol)
711