Esempio n. 1
0
class VirtualDevice(QObject):
    """
    Wrapper class for providing sending methods for grc and native devices

    """
    started = pyqtSignal()
    stopped = pyqtSignal()
    sender_needs_restart = pyqtSignal()

    fatal_error_occurred = pyqtSignal(str)
    ready_for_action = pyqtSignal()

    data_received = pyqtSignal(
        np.ndarray)  # for direct demodulation in sniffer

    continuous_send_msg = "Continuous send mode is not supported for GNU Radio backend. " \
                          "You can change the configured device backend in options."

    def __init__(self,
                 backend_handler,
                 name: str,
                 mode: Mode,
                 freq=None,
                 sample_rate=None,
                 bandwidth=None,
                 gain=None,
                 if_gain=None,
                 baseband_gain=None,
                 samples_to_send=None,
                 device_ip=None,
                 sending_repeats=1,
                 parent=None,
                 resume_on_full_receive_buffer=False,
                 raw_mode=True,
                 portnumber=1234):
        super().__init__(parent)
        self.name = name
        self.mode = mode
        self.backend_handler = backend_handler

        freq = config.DEFAULT_FREQUENCY if freq is None else freq
        sample_rate = config.DEFAULT_SAMPLE_RATE if sample_rate is None else sample_rate
        bandwidth = config.DEFAULT_BANDWIDTH if bandwidth is None else bandwidth
        gain = config.DEFAULT_GAIN if gain is None else gain
        if_gain = config.DEFAULT_IF_GAIN if if_gain is None else if_gain
        baseband_gain = config.DEFAULT_BB_GAIN if baseband_gain is None else baseband_gain

        resume_on_full_receive_buffer = self.mode == Mode.spectrum or resume_on_full_receive_buffer

        if self.name == NetworkSDRInterfacePlugin.NETWORK_SDR_NAME:
            self.backend = Backends.network
        else:
            try:
                self.backend = self.backend_handler.device_backends[
                    name.lower()].selected_backend
            except KeyError:
                logger.warning("Invalid device name: {0}".format(name))
                self.backend = Backends.none
                self.__dev = None
                return

        if self.backend == Backends.grc:
            if mode == Mode.receive:
                from urh.dev.gr.ReceiverThread import ReceiverThread
                self.__dev = ReceiverThread(
                    freq,
                    sample_rate,
                    bandwidth,
                    gain,
                    if_gain,
                    baseband_gain,
                    parent=parent,
                    resume_on_full_receive_buffer=resume_on_full_receive_buffer
                )
            elif mode == Mode.send:
                from urh.dev.gr.SenderThread import SenderThread
                self.__dev = SenderThread(freq,
                                          sample_rate,
                                          bandwidth,
                                          gain,
                                          if_gain,
                                          baseband_gain,
                                          parent=parent)
                self.__dev.data = samples_to_send
                self.__dev.samples_per_transmission = len(
                    samples_to_send) if samples_to_send is not None else 2**15
            elif mode == Mode.spectrum:
                from urh.dev.gr.SpectrumThread import SpectrumThread
                self.__dev = SpectrumThread(freq,
                                            sample_rate,
                                            bandwidth,
                                            gain,
                                            if_gain,
                                            baseband_gain,
                                            parent=parent)
            else:
                raise ValueError("Unknown mode")
            self.__dev.device = name
            self.__dev.started.connect(self.emit_started_signal)
            self.__dev.stopped.connect(self.emit_stopped_signal)
            self.__dev.sender_needs_restart.connect(
                self.emit_sender_needs_restart)
        elif self.backend == Backends.native:
            name = self.name.lower()
            if name in map(str.lower, BackendHandler.DEVICE_NAMES):
                if name == "hackrf":
                    from urh.dev.native.HackRF import HackRF
                    self.__dev = HackRF(freq, sample_rate, bandwidth, gain,
                                        if_gain, baseband_gain,
                                        resume_on_full_receive_buffer)
                elif name.replace("-", "") == "rtlsdr":
                    from urh.dev.native.RTLSDR import RTLSDR
                    self.__dev = RTLSDR(freq,
                                        gain,
                                        sample_rate,
                                        device_number=0,
                                        resume_on_full_receive_buffer=
                                        resume_on_full_receive_buffer)
                elif name.replace("-", "") == "rtltcp":
                    from urh.dev.native.RTLSDRTCP import RTLSDRTCP
                    self.__dev = RTLSDRTCP(freq,
                                           gain,
                                           sample_rate,
                                           bandwidth,
                                           device_number=0,
                                           resume_on_full_receive_buffer=
                                           resume_on_full_receive_buffer)
                elif name == "limesdr":
                    from urh.dev.native.LimeSDR import LimeSDR
                    self.__dev = LimeSDR(freq,
                                         gain,
                                         sample_rate,
                                         bandwidth,
                                         gain,
                                         resume_on_full_receive_buffer=
                                         resume_on_full_receive_buffer)
                elif name.startswith("airspy"):
                    from urh.dev.native.AirSpy import AirSpy
                    self.__dev = AirSpy(freq,
                                        sample_rate,
                                        bandwidth,
                                        gain,
                                        if_gain,
                                        baseband_gain,
                                        resume_on_full_receive_buffer=
                                        resume_on_full_receive_buffer)
                elif name.startswith("usrp"):
                    from urh.dev.native.USRP import USRP
                    self.__dev = USRP(freq,
                                      gain,
                                      sample_rate,
                                      bandwidth,
                                      gain,
                                      resume_on_full_receive_buffer=
                                      resume_on_full_receive_buffer)
                elif name.startswith("sdrplay"):
                    from urh.dev.native.SDRPlay import SDRPlay
                    self.__dev = SDRPlay(freq,
                                         gain,
                                         bandwidth,
                                         gain,
                                         if_gain=if_gain,
                                         resume_on_full_receive_buffer=
                                         resume_on_full_receive_buffer)
                elif name == "soundcard":
                    from urh.dev.native.SoundCard import SoundCard
                    self.__dev = SoundCard(sample_rate,
                                           resume_on_full_receive_buffer=
                                           resume_on_full_receive_buffer)
                else:
                    raise NotImplementedError(
                        "Native Backend for {0} not yet implemented".format(
                            name))

            elif name == "test":
                # For Unittests Only
                self.__dev = Device(freq, sample_rate, bandwidth, gain,
                                    if_gain, baseband_gain,
                                    resume_on_full_receive_buffer)
            else:
                raise ValueError("Unknown device name {0}".format(name))
            self.__dev.portnumber = portnumber
            self.__dev.device_ip = device_ip
            if mode == Mode.send:
                self.__dev.init_send_parameters(samples_to_send,
                                                sending_repeats)
        elif self.backend == Backends.network:
            self.__dev = NetworkSDRInterfacePlugin(
                raw_mode=raw_mode,
                resume_on_full_receive_buffer=resume_on_full_receive_buffer,
                spectrum=self.mode == Mode.spectrum,
                sending=self.mode == Mode.send)
            self.__dev.send_connection_established.connect(
                self.emit_ready_for_action)
            self.__dev.receive_server_started.connect(
                self.emit_ready_for_action)
            self.__dev.error_occurred.connect(self.emit_fatal_error_occurred)
            self.__dev.samples_to_send = samples_to_send
        elif self.backend == Backends.none:
            self.__dev = None
        else:
            raise ValueError("Unsupported Backend")

        if hasattr(self.__dev, "data_received"):
            self.__dev.data_received.connect(self.data_received.emit)

        if mode == Mode.spectrum:
            self.__dev.is_in_spectrum_mode = True

    @property
    def has_multi_device_support(self):
        return hasattr(
            self.__dev,
            "has_multi_device_support") and self.__dev.has_multi_device_support

    @property
    def device_serial(self):
        if hasattr(self.__dev, "device_serial"):
            return self.__dev.device_serial
        else:
            return None

    @device_serial.setter
    def device_serial(self, value):
        if hasattr(self.__dev, "device_serial"):
            self.__dev.device_serial = value

    @property
    def device_number(self):
        if hasattr(self.__dev, "device_number"):
            return self.__dev.device_number
        else:
            return None

    @device_number.setter
    def device_number(self, value):
        if hasattr(self.__dev, "device_number"):
            self.__dev.device_number = value

    @property
    def bandwidth(self):
        return self.__dev.bandwidth

    @bandwidth.setter
    def bandwidth(self, value):
        self.__dev.bandwidth = value

    @property
    def bandwidth_is_adjustable(self):
        if self.backend == Backends.grc:
            return True
        elif self.backend == Backends.native:
            return self.__dev.bandwidth_is_adjustable
        elif self.backend == Backends.network:
            return True
        else:
            raise ValueError("Unsupported Backend")

    @property
    def frequency(self):
        if self.backend == Backends.grc:
            return self.__dev.freq
        elif self.backend == Backends.native:
            return self.__dev.frequency
        else:
            raise ValueError("Unsupported Backend")

    @frequency.setter
    def frequency(self, value):
        if self.backend == Backends.grc:
            self.__dev.freq = value
        elif self.backend == Backends.native:
            self.__dev.frequency = value
        elif self.backend == Backends.network:
            pass
        else:
            raise ValueError("Unsupported Backend")

    @property
    def num_samples_to_send(self) -> int:
        if self.backend in (Backends.native, Backends.network):
            return self.__dev.num_samples_to_send
        else:
            raise ValueError(self.continuous_send_msg)

    @num_samples_to_send.setter
    def num_samples_to_send(self, value: int):
        if self.backend in (Backends.native, Backends.network):
            self.__dev.num_samples_to_send = value
        else:
            raise ValueError(self.continuous_send_msg)

    @property
    def is_send_continuous(self) -> bool:
        if self.backend in (Backends.native, Backends.network):
            return self.__dev.sending_is_continuous
        else:
            raise ValueError(self.continuous_send_msg)

    @is_send_continuous.setter
    def is_send_continuous(self, value: bool):
        if self.backend in (Backends.native, Backends.network):
            self.__dev.sending_is_continuous = value
        else:
            raise ValueError(self.continuous_send_msg)

    @property
    def is_raw_mode(self) -> bool:
        if self.backend == Backends.network:
            return self.__dev.raw_mode
        else:
            return True

    @property
    def continuous_send_ring_buffer(self):
        if self.backend in (Backends.native, Backends.network):
            return self.__dev.continuous_send_ring_buffer
        else:
            raise ValueError(self.continuous_send_msg)

    @continuous_send_ring_buffer.setter
    def continuous_send_ring_buffer(self, value):
        if self.backend in (Backends.native, Backends.network):
            self.__dev.continuous_send_ring_buffer = value
        else:
            raise ValueError(self.continuous_send_msg)

    @property
    def is_in_spectrum_mode(self):
        if self.backend in (Backends.grc, Backends.native, Backends.network):
            return self.__dev.is_in_spectrum_mode
        else:
            raise ValueError("Unsupported Backend")

    @is_in_spectrum_mode.setter
    def is_in_spectrum_mode(self, value: bool):
        if self.backend in (Backends.grc, Backends.native, Backends.network):
            self.__dev.is_in_spectrum_mode = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def gain(self):
        return self.__dev.gain

    @gain.setter
    def gain(self, value):
        try:
            self.__dev.gain = value
        except AttributeError as e:
            logger.warning(str(e))

    @property
    def if_gain(self):
        try:
            return self.__dev.if_gain
        except AttributeError as e:
            logger.warning(str(e))

    @if_gain.setter
    def if_gain(self, value):
        try:
            self.__dev.if_gain = value
        except AttributeError as e:
            logger.warning(str(e))

    @property
    def baseband_gain(self):
        return self.__dev.baseband_gain

    @baseband_gain.setter
    def baseband_gain(self, value):
        self.__dev.baseband_gain = value

    @property
    def sample_rate(self):
        return self.__dev.sample_rate

    @sample_rate.setter
    def sample_rate(self, value):
        self.__dev.sample_rate = value

    @property
    def channel_index(self) -> int:
        return self.__dev.channel_index

    @channel_index.setter
    def channel_index(self, value: int):
        self.__dev.channel_index = value

    @property
    def antenna_index(self) -> int:
        return self.__dev.antenna_index

    @antenna_index.setter
    def antenna_index(self, value: int):
        self.__dev.antenna_index = value

    @property
    def freq_correction(self):
        return self.__dev.freq_correction

    @freq_correction.setter
    def freq_correction(self, value):
        self.__dev.freq_correction = value

    @property
    def direct_sampling_mode(self) -> int:
        return self.__dev.direct_sampling_mode

    @direct_sampling_mode.setter
    def direct_sampling_mode(self, value):
        self.__dev.direct_sampling_mode = value

    @property
    def emit_data_received_signal(self):
        return self.__dev.emit_data_received_signal

    @emit_data_received_signal.setter
    def emit_data_received_signal(self, value):
        self.__dev.emit_data_received_signal = value

    @property
    def samples_to_send(self):
        if self.backend == Backends.grc:
            return self.__dev.data
        elif self.backend in (Backends.native, Backends.network):
            return self.__dev.samples_to_send
        else:
            raise ValueError("Unsupported Backend")

    @samples_to_send.setter
    def samples_to_send(self, value):
        if self.backend == Backends.grc:
            self.__dev.data = value
        elif self.backend == Backends.native:
            self.__dev.init_send_parameters(value, self.num_sending_repeats)
        elif self.backend == Backends.network:
            self.__dev.samples_to_send = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def ip(self):
        if self.backend == Backends.grc:
            return self.__dev.device_ip
        elif self.backend == Backends.native:
            return self.__dev.device_ip
        else:
            raise ValueError("Unsupported Backend")

    @ip.setter
    def ip(self, value):
        if self.backend == Backends.grc:
            self.__dev.device_ip = value
        elif self.backend == Backends.native:
            self.__dev.device_ip = value
        elif self.backend in (Backends.none, Backends.network):
            pass
        else:
            raise ValueError("Unsupported Backend")

    @property
    def port(self):
        if self.backend in (Backends.grc, Backends.native, Backends.network):
            return self.__dev.port
        else:
            raise ValueError("Unsupported Backend")

    @port.setter
    def port(self, value):
        if self.backend in (Backends.grc, Backends.native, Backends.network):
            self.__dev.port = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def data(self):
        if self.backend == Backends.grc:
            return self.__dev.data
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                return self.__dev.samples_to_send
            else:
                return self.__dev.receive_buffer
        elif self.backend == Backends.network:
            if self.mode == Mode.send:
                raise NotImplementedError("Todo")
            else:
                if self.__dev.raw_mode:
                    return self.__dev.receive_buffer
                else:
                    return self.__dev.received_bits
        else:
            raise ValueError("Unsupported Backend")

    @data.setter
    def data(self, value):
        if self.backend == Backends.grc:
            self.__dev.data = value
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                self.__dev.samples_to_send = value
            else:
                self.__dev.receive_buffer = value
        else:
            logger.warning("{}:{} has no data".format(self.__class__.__name__,
                                                      self.backend.name))

    def free_data(self):
        if self.backend == Backends.grc:
            self.__dev.data = None
        elif self.backend == Backends.native:
            self.__dev.samples_to_send = None
            self.__dev.receive_buffer = None
        elif self.backend == Backends.network:
            self.__dev.free_data()
        elif self.backend == Backends.none:
            pass
        else:
            raise ValueError("Unsupported Backend")

    @property
    def resume_on_full_receive_buffer(self) -> bool:
        return self.__dev.resume_on_full_receive_buffer

    @resume_on_full_receive_buffer.setter
    def resume_on_full_receive_buffer(self, value: bool):
        if value != self.__dev.resume_on_full_receive_buffer:
            self.__dev.resume_on_full_receive_buffer = value
            if self.backend == Backends.native:
                self.__dev.receive_buffer = None
            elif self.backend == Backends.grc:
                self.__dev.data = None

    @property
    def num_sending_repeats(self):
        return self.__dev.sending_repeats

    @num_sending_repeats.setter
    def num_sending_repeats(self, value):
        self.__dev.sending_repeats = value

    @property
    def current_index(self):
        if self.backend == Backends.grc:
            return self.__dev.current_index
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                return self.__dev.current_sent_sample
            else:
                return self.__dev.current_recv_index
        elif self.backend == Backends.network:
            if self.mode == Mode.send:
                return self.__dev.current_sent_sample
            else:
                return self.__dev.current_receive_index
        else:
            raise ValueError("Unsupported Backend")

    @current_index.setter
    def current_index(self, value):
        if self.backend == Backends.grc:
            self.__dev.current_index = value
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                self.__dev.current_sent_sample = value
            else:
                self.__dev.current_recv_index = value
        elif self.backend == Backends.network:
            if self.mode == Mode.send:
                self.__dev.current_sent_sample = value
            else:
                self.__dev.current_receive_index = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def current_iteration(self):
        if self.backend == Backends.grc:
            return self.__dev.current_iteration
        elif self.backend in (Backends.native, Backends.network):
            return self.__dev.current_sending_repeat
        else:
            raise ValueError("Unsupported Backend")

    @current_iteration.setter
    def current_iteration(self, value):
        if self.backend == Backends.grc:
            self.__dev.current_iteration = value
        elif self.backend in (Backends.native, Backends.network):
            self.__dev.current_sending_repeat = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def sending_finished(self):
        if self.backend == Backends.grc:
            return self.__dev.current_iteration is None
        elif self.backend in (Backends.native, Backends.network):
            return self.__dev.sending_finished
        else:
            raise ValueError("Unsupported Backend")

    @property
    def spectrum(self):
        if self.mode == Mode.spectrum:
            if self.backend == Backends.grc:
                return self.__dev.x, self.__dev.y
            elif self.backend == Backends.native or self.backend == Backends.network:
                w = np.abs(np.fft.fft(self.__dev.receive_buffer))
                freqs = np.fft.fftfreq(len(w), 1 / self.sample_rate)
                idx = np.argsort(freqs)
                return freqs[idx].astype(np.float32), w[idx].astype(np.float32)
        else:
            raise ValueError("Spectrum x only available in spectrum mode")

    def start(self):
        if self.backend == Backends.grc:
            self.__dev.setTerminationEnabled(True)
            self.__dev.terminate()
            time.sleep(0.1)
            self.__dev.start(
            )  # Already connected to started signal in constructor
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                self.__dev.start_tx_mode(resume=True)
            else:
                self.__dev.start_rx_mode()

            self.emit_started_signal()
        elif self.backend == Backends.network:
            if self.mode == Mode.receive or self.mode == Mode.spectrum:
                self.__dev.start_tcp_server_for_receiving()
            else:
                self.__dev.start_raw_sending_thread()

            self.emit_started_signal()
        else:
            raise ValueError("Unsupported Backend")

    def stop(self, msg: str):
        if self.backend == Backends.grc:
            self.__dev.stop(msg)  # Already connected to stopped in constructor
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                self.__dev.stop_tx_mode(msg)
            else:
                self.__dev.stop_rx_mode(msg)
            self.emit_stopped_signal()
        elif self.backend == Backends.network:
            self.__dev.stop_tcp_server()
            self.__dev.stop_sending_thread()
            self.emit_stopped_signal()
        elif self.backend == Backends.none:
            pass
        else:
            logger.error("Stop device: Unsupported backend " +
                         str(self.backend))

    def stop_on_error(self, msg: str):
        if self.backend == Backends.grc:
            self.__dev.stop(msg)  # Already connected to stopped in constructor
        elif self.backend == Backends.native:
            self.read_messages()  # Clear errors
            self.__dev.stop_rx_mode("Stop on error")
            self.__dev.stop_tx_mode("Stop on error")
            self.emit_stopped_signal()
        else:
            raise ValueError("Unsupported Backend")

    def cleanup(self):
        if self.backend == Backends.grc:
            if self.mode == Mode.send:
                self.__dev.socket.close()
                time.sleep(0.1)
            self.__dev.quit()
            self.data = None

        elif self.backend == Backends.native:
            self.data = None

        elif self.backend == Backends.none:
            pass

        else:
            raise ValueError("Unsupported Backend")

    def emit_stopped_signal(self):
        self.stopped.emit()

    def emit_started_signal(self):
        self.started.emit()

    def emit_sender_needs_restart(self):
        self.sender_needs_restart.emit()

    def read_messages(self) -> str:
        """
        returns a string of new device messages separated by newlines

        :return:
        """
        if self.backend == Backends.grc:
            errors = self.__dev.read_errors()

            if "FATAL: " in errors:
                self.fatal_error_occurred.emit(
                    errors[errors.index("FATAL: "):])

            return errors
        elif self.backend == Backends.native:
            messages = "\n".join(self.__dev.device_messages)
            self.__dev.device_messages.clear()

            if messages and not messages.endswith("\n"):
                messages += "\n"

            if "successfully started" in messages:
                self.ready_for_action.emit()
            elif "failed to start" in messages:
                self.fatal_error_occurred.emit(
                    messages[messages.index("failed to start"):])

            return messages
        elif self.backend == Backends.network:
            return ""
        else:
            raise ValueError("Unsupported Backend")

    def set_server_port(self, port: int):
        if self.backend == Backends.network:
            self.__dev.server_port = port
        else:
            raise ValueError(
                "Setting port only supported for NetworkSDR Plugin")

    def set_client_port(self, port: int):
        if self.backend == Backends.network:
            self.__dev.client_port = port
        else:
            raise ValueError(
                "Setting port only supported for NetworkSDR Plugin")

    def get_device_list(self):
        if hasattr(self.__dev, "get_device_list"):
            return self.__dev.get_device_list()
        else:
            return []

    def increase_gr_port(self):
        if self.backend == Backends.grc:
            self.__dev.gr_port += 1
            logger.info("Retry with port " + str(self.__dev.gr_port))
        else:
            raise ValueError("Only for GR backend")

    def emit_ready_for_action(self):
        """
        Notify observers that device is successfully initialized
        :return:
        """
        self.ready_for_action.emit()

    def emit_fatal_error_occurred(self, msg: str):
        self.fatal_error_occurred.emit(msg)
Esempio n. 2
0
class VirtualDevice(QObject):
    """
    Wrapper class for providing sending methods for grc and native devices

    """
    started = pyqtSignal()
    stopped = pyqtSignal()
    sender_needs_restart = pyqtSignal()

    fatal_error_occurred = pyqtSignal(str)
    ready_for_action = pyqtSignal()

    data_received = pyqtSignal(np.ndarray)  # for direct demodulation in sniffer

    continuous_send_msg = "Continuous send mode is not supported for GNU Radio backend. " \
                          "You can change the configured device backend in options."

    def __init__(self, backend_handler, name: str, mode: Mode, freq=None, sample_rate=None, bandwidth=None,
                 gain=None, if_gain=None, baseband_gain=None, samples_to_send=None,
                 device_ip=None, sending_repeats=1, parent=None, resume_on_full_receive_buffer=False, raw_mode=True,
                 portnumber=1234):
        super().__init__(parent)
        self.name = name
        self.mode = mode
        self.backend_handler = backend_handler

        freq = config.DEFAULT_FREQUENCY if freq is None else freq
        sample_rate = config.DEFAULT_SAMPLE_RATE if sample_rate is None else sample_rate
        bandwidth = config.DEFAULT_BANDWIDTH if bandwidth is None else bandwidth
        gain = config.DEFAULT_GAIN if gain is None else gain
        if_gain = config.DEFAULT_IF_GAIN if if_gain is None else if_gain
        baseband_gain = config.DEFAULT_BB_GAIN if baseband_gain is None else baseband_gain

        resume_on_full_receive_buffer = self.mode == Mode.spectrum or resume_on_full_receive_buffer

        if self.name == NetworkSDRInterfacePlugin.NETWORK_SDR_NAME:
            self.backend = Backends.network
        else:
            try:
                self.backend = self.backend_handler.device_backends[name.lower()].selected_backend
            except KeyError:
                logger.warning("Invalid device name: {0}".format(name))
                self.backend = Backends.none
                self.__dev = None
                return

        if self.backend == Backends.grc:
            if mode == Mode.receive:
                from urh.dev.gr.ReceiverThread import ReceiverThread
                self.__dev = ReceiverThread(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
                                            parent=parent, resume_on_full_receive_buffer=resume_on_full_receive_buffer)
            elif mode == Mode.send:
                from urh.dev.gr.SenderThread import SenderThread
                self.__dev = SenderThread(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
                                          parent=parent)
                self.__dev.data = samples_to_send
                self.__dev.samples_per_transmission = len(samples_to_send) if samples_to_send is not None else 2 ** 15
            elif mode == Mode.spectrum:
                from urh.dev.gr.SpectrumThread import SpectrumThread
                self.__dev = SpectrumThread(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
                                            parent=parent)
            else:
                raise ValueError("Unknown mode")
            self.__dev.device = name
            self.__dev.started.connect(self.emit_started_signal)
            self.__dev.stopped.connect(self.emit_stopped_signal)
            self.__dev.sender_needs_restart.connect(self.emit_sender_needs_restart)
        elif self.backend == Backends.native:
            name = self.name.lower()
            if name in map(str.lower, BackendHandler.DEVICE_NAMES):
                if name == "hackrf":
                    from urh.dev.native.HackRF import HackRF
                    self.__dev = HackRF(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
                                        resume_on_full_receive_buffer)
                elif name.replace("-", "") == "rtlsdr":
                    from urh.dev.native.RTLSDR import RTLSDR
                    self.__dev = RTLSDR(freq, gain, sample_rate, device_number=0,
                                        resume_on_full_receive_buffer=resume_on_full_receive_buffer)
                elif name.replace("-", "") == "rtltcp":
                    from urh.dev.native.RTLSDRTCP import RTLSDRTCP
                    self.__dev = RTLSDRTCP(freq, gain, sample_rate, bandwidth, device_number=0,
                                           resume_on_full_receive_buffer=resume_on_full_receive_buffer)
                elif name == "limesdr":
                    from urh.dev.native.LimeSDR import LimeSDR
                    self.__dev = LimeSDR(freq, gain, sample_rate, bandwidth, gain,
                                         resume_on_full_receive_buffer=resume_on_full_receive_buffer)
                elif name.startswith("airspy"):
                    from urh.dev.native.AirSpy import AirSpy
                    self.__dev = AirSpy(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
                                        resume_on_full_receive_buffer=resume_on_full_receive_buffer)
                elif name.startswith("usrp"):
                    from urh.dev.native.USRP import USRP
                    self.__dev = USRP(freq, gain, sample_rate, bandwidth, gain,
                                      resume_on_full_receive_buffer=resume_on_full_receive_buffer)
                elif name.startswith("sdrplay"):
                    from urh.dev.native.SDRPlay import SDRPlay
                    self.__dev = SDRPlay(freq, gain, bandwidth, gain, if_gain=if_gain,
                                         resume_on_full_receive_buffer=resume_on_full_receive_buffer)
                elif name == "soundcard":
                    from urh.dev.native.SoundCard import SoundCard
                    self.__dev = SoundCard(sample_rate, resume_on_full_receive_buffer=resume_on_full_receive_buffer)
                else:
                    raise NotImplementedError("Native Backend for {0} not yet implemented".format(name))

            elif name == "test":
                # For Unittests Only
                self.__dev = Device(freq, sample_rate, bandwidth, gain, if_gain, baseband_gain,
                                    resume_on_full_receive_buffer)
            else:
                raise ValueError("Unknown device name {0}".format(name))
            self.__dev.portnumber = portnumber
            self.__dev.device_ip = device_ip
            if mode == Mode.send:
                self.__dev.init_send_parameters(samples_to_send, sending_repeats)
        elif self.backend == Backends.network:
            self.__dev = NetworkSDRInterfacePlugin(raw_mode=raw_mode,
                                                   resume_on_full_receive_buffer=resume_on_full_receive_buffer,
                                                   spectrum=self.mode == Mode.spectrum, sending=self.mode == Mode.send)
            self.__dev.send_connection_established.connect(self.emit_ready_for_action)
            self.__dev.receive_server_started.connect(self.emit_ready_for_action)
            self.__dev.error_occurred.connect(self.emit_fatal_error_occurred)
            self.__dev.samples_to_send = samples_to_send
        elif self.backend == Backends.none:
            self.__dev = None
        else:
            raise ValueError("Unsupported Backend")

        if hasattr(self.__dev, "data_received"):
            self.__dev.data_received.connect(self.data_received.emit)

        if mode == Mode.spectrum:
            self.__dev.is_in_spectrum_mode = True

    @property
    def has_multi_device_support(self):
        return hasattr(self.__dev, "has_multi_device_support") and self.__dev.has_multi_device_support

    @property
    def device_serial(self):
        if hasattr(self.__dev, "device_serial"):
            return self.__dev.device_serial
        else:
            return None

    @device_serial.setter
    def device_serial(self, value):
        if hasattr(self.__dev, "device_serial"):
            self.__dev.device_serial = value

    @property
    def device_number(self):
        if hasattr(self.__dev, "device_number"):
            return self.__dev.device_number
        else:
            return None

    @device_number.setter
    def device_number(self, value):
        if hasattr(self.__dev, "device_number"):
            self.__dev.device_number = value

    @property
    def bandwidth(self):
        return self.__dev.bandwidth

    @bandwidth.setter
    def bandwidth(self, value):
        self.__dev.bandwidth = value

    @property
    def bandwidth_is_adjustable(self):
        if self.backend == Backends.grc:
            return True
        elif self.backend == Backends.native:
            return self.__dev.bandwidth_is_adjustable
        elif self.backend == Backends.network:
            return True
        else:
            raise ValueError("Unsupported Backend")

    @property
    def frequency(self):
        if self.backend == Backends.grc:
            return self.__dev.freq
        elif self.backend == Backends.native:
            return self.__dev.frequency
        else:
            raise ValueError("Unsupported Backend")

    @frequency.setter
    def frequency(self, value):
        if self.backend == Backends.grc:
            self.__dev.freq = value
        elif self.backend == Backends.native:
            self.__dev.frequency = value
        elif self.backend == Backends.network:
            pass
        else:
            raise ValueError("Unsupported Backend")

    @property
    def num_samples_to_send(self) -> int:
        if self.backend in (Backends.native, Backends.network):
            return self.__dev.num_samples_to_send
        else:
            raise ValueError(self.continuous_send_msg)

    @num_samples_to_send.setter
    def num_samples_to_send(self, value: int):
        if self.backend in (Backends.native, Backends.network):
            self.__dev.num_samples_to_send = value
        else:
            raise ValueError(self.continuous_send_msg)

    @property
    def is_send_continuous(self) -> bool:
        if self.backend in (Backends.native, Backends.network):
            return self.__dev.sending_is_continuous
        else:
            raise ValueError(self.continuous_send_msg)

    @is_send_continuous.setter
    def is_send_continuous(self, value: bool):
        if self.backend in (Backends.native, Backends.network):
            self.__dev.sending_is_continuous = value
        else:
            raise ValueError(self.continuous_send_msg)

    @property
    def is_raw_mode(self) -> bool:
        if self.backend == Backends.network:
            return self.__dev.raw_mode
        else:
            return True

    @property
    def continuous_send_ring_buffer(self):
        if self.backend in (Backends.native, Backends.network):
            return self.__dev.continuous_send_ring_buffer
        else:
            raise ValueError(self.continuous_send_msg)

    @continuous_send_ring_buffer.setter
    def continuous_send_ring_buffer(self, value):
        if self.backend in (Backends.native, Backends.network):
            self.__dev.continuous_send_ring_buffer = value
        else:
            raise ValueError(self.continuous_send_msg)

    @property
    def is_in_spectrum_mode(self):
        if self.backend in (Backends.grc, Backends.native, Backends.network):
            return self.__dev.is_in_spectrum_mode
        else:
            raise ValueError("Unsupported Backend")

    @is_in_spectrum_mode.setter
    def is_in_spectrum_mode(self, value: bool):
        if self.backend in (Backends.grc, Backends.native, Backends.network):
            self.__dev.is_in_spectrum_mode = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def gain(self):
        return self.__dev.gain

    @gain.setter
    def gain(self, value):
        try:
            self.__dev.gain = value
        except AttributeError as e:
            logger.warning(str(e))

    @property
    def if_gain(self):
        try:
            return self.__dev.if_gain
        except AttributeError as e:
            logger.warning(str(e))

    @if_gain.setter
    def if_gain(self, value):
        try:
            self.__dev.if_gain = value
        except AttributeError as e:
            logger.warning(str(e))

    @property
    def baseband_gain(self):
        return self.__dev.baseband_gain

    @baseband_gain.setter
    def baseband_gain(self, value):
        self.__dev.baseband_gain = value

    @property
    def sample_rate(self):
        return self.__dev.sample_rate

    @sample_rate.setter
    def sample_rate(self, value):
        self.__dev.sample_rate = value

    @property
    def channel_index(self) -> int:
        return self.__dev.channel_index

    @channel_index.setter
    def channel_index(self, value: int):
        self.__dev.channel_index = value

    @property
    def antenna_index(self) -> int:
        return self.__dev.antenna_index

    @antenna_index.setter
    def antenna_index(self, value: int):
        self.__dev.antenna_index = value

    @property
    def freq_correction(self):
        return self.__dev.freq_correction

    @freq_correction.setter
    def freq_correction(self, value):
        self.__dev.freq_correction = value

    @property
    def direct_sampling_mode(self) -> int:
        return self.__dev.direct_sampling_mode

    @direct_sampling_mode.setter
    def direct_sampling_mode(self, value):
        self.__dev.direct_sampling_mode = value

    @property
    def emit_data_received_signal(self):
        return self.__dev.emit_data_received_signal

    @emit_data_received_signal.setter
    def emit_data_received_signal(self, value):
        self.__dev.emit_data_received_signal = value

    @property
    def samples_to_send(self):
        if self.backend == Backends.grc:
            return self.__dev.data
        elif self.backend in (Backends.native, Backends.network):
            return self.__dev.samples_to_send
        else:
            raise ValueError("Unsupported Backend")

    @samples_to_send.setter
    def samples_to_send(self, value):
        if self.backend == Backends.grc:
            self.__dev.data = value
        elif self.backend == Backends.native:
            self.__dev.init_send_parameters(value, self.num_sending_repeats)
        elif self.backend == Backends.network:
            self.__dev.samples_to_send = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def ip(self):
        if self.backend == Backends.grc:
            return self.__dev.device_ip
        elif self.backend == Backends.native:
            return self.__dev.device_ip
        else:
            raise ValueError("Unsupported Backend")

    @ip.setter
    def ip(self, value):
        if self.backend == Backends.grc:
            self.__dev.device_ip = value
        elif self.backend == Backends.native:
            self.__dev.device_ip = value
        elif self.backend in (Backends.none, Backends.network):
            pass
        else:
            raise ValueError("Unsupported Backend")

    @property
    def port(self):
        if self.backend in (Backends.grc, Backends.native, Backends.network):
            return self.__dev.port
        else:
            raise ValueError("Unsupported Backend")

    @port.setter
    def port(self, value):
        if self.backend in (Backends.grc, Backends.native, Backends.network):
            self.__dev.port = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def data(self):
        if self.backend == Backends.grc:
            return self.__dev.data
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                return self.__dev.samples_to_send
            else:
                return self.__dev.receive_buffer
        elif self.backend == Backends.network:
            if self.mode == Mode.send:
                raise NotImplementedError("Todo")
            else:
                if self.__dev.raw_mode:
                    return self.__dev.receive_buffer
                else:
                    return self.__dev.received_bits
        else:
            raise ValueError("Unsupported Backend")

    @data.setter
    def data(self, value):
        if self.backend == Backends.grc:
            self.__dev.data = value
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                self.__dev.samples_to_send = value
            else:
                self.__dev.receive_buffer = value
        else:
            logger.warning("{}:{} has no data".format(self.__class__.__name__, self.backend.name))

    def free_data(self):
        if self.backend == Backends.grc:
            self.__dev.data = None
        elif self.backend == Backends.native:
            self.__dev.samples_to_send = None
            self.__dev.receive_buffer = None
        elif self.backend == Backends.network:
            self.__dev.free_data()
        elif self.backend == Backends.none:
            pass
        else:
            raise ValueError("Unsupported Backend")

    @property
    def resume_on_full_receive_buffer(self) -> bool:
        return self.__dev.resume_on_full_receive_buffer

    @resume_on_full_receive_buffer.setter
    def resume_on_full_receive_buffer(self, value: bool):
        if value != self.__dev.resume_on_full_receive_buffer:
            self.__dev.resume_on_full_receive_buffer = value
            if self.backend == Backends.native:
                self.__dev.receive_buffer = None
            elif self.backend == Backends.grc:
                self.__dev.data = None

    @property
    def num_sending_repeats(self):
        return self.__dev.sending_repeats

    @num_sending_repeats.setter
    def num_sending_repeats(self, value):
        self.__dev.sending_repeats = value

    @property
    def current_index(self):
        if self.backend == Backends.grc:
            return self.__dev.current_index
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                return self.__dev.current_sent_sample
            else:
                return self.__dev.current_recv_index
        elif self.backend == Backends.network:
            if self.mode == Mode.send:
                return self.__dev.current_sent_sample
            else:
                return self.__dev.current_receive_index
        else:
            raise ValueError("Unsupported Backend")

    @current_index.setter
    def current_index(self, value):
        if self.backend == Backends.grc:
            self.__dev.current_index = value
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                self.__dev.current_sent_sample = value
            else:
                self.__dev.current_recv_index = value
        elif self.backend == Backends.network:
            if self.mode == Mode.send:
                self.__dev.current_sent_sample = value
            else:
                self.__dev.current_receive_index = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def current_iteration(self):
        if self.backend == Backends.grc:
            return self.__dev.current_iteration
        elif self.backend in (Backends.native, Backends.network):
            return self.__dev.current_sending_repeat
        else:
            raise ValueError("Unsupported Backend")

    @current_iteration.setter
    def current_iteration(self, value):
        if self.backend == Backends.grc:
            self.__dev.current_iteration = value
        elif self.backend in (Backends.native, Backends.network):
            self.__dev.current_sending_repeat = value
        else:
            raise ValueError("Unsupported Backend")

    @property
    def sending_finished(self):
        if self.backend == Backends.grc:
            return self.__dev.current_iteration is None
        elif self.backend in (Backends.native, Backends.network):
            return self.__dev.sending_finished
        else:
            raise ValueError("Unsupported Backend")

    @property
    def spectrum(self):
        if self.mode == Mode.spectrum:
            if self.backend == Backends.grc:
                return self.__dev.x, self.__dev.y
            elif self.backend == Backends.native or self.backend == Backends.network:
                w = np.abs(np.fft.fft(self.__dev.receive_buffer))
                freqs = np.fft.fftfreq(len(w), 1 / self.sample_rate)
                idx = np.argsort(freqs)
                return freqs[idx].astype(np.float32), w[idx].astype(np.float32)
        else:
            raise ValueError("Spectrum x only available in spectrum mode")

    def start(self):
        if self.backend == Backends.grc:
            self.__dev.setTerminationEnabled(True)
            self.__dev.terminate()
            time.sleep(0.1)
            self.__dev.start()  # Already connected to started signal in constructor
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                self.__dev.start_tx_mode(resume=True)
            else:
                self.__dev.start_rx_mode()

            self.emit_started_signal()
        elif self.backend == Backends.network:
            if self.mode == Mode.receive or self.mode == Mode.spectrum:
                self.__dev.start_tcp_server_for_receiving()
            else:
                self.__dev.start_raw_sending_thread()

            self.emit_started_signal()
        else:
            raise ValueError("Unsupported Backend")

    def stop(self, msg: str):
        if self.backend == Backends.grc:
            self.__dev.stop(msg)  # Already connected to stopped in constructor
        elif self.backend == Backends.native:
            if self.mode == Mode.send:
                self.__dev.stop_tx_mode(msg)
            else:
                self.__dev.stop_rx_mode(msg)
            self.emit_stopped_signal()
        elif self.backend == Backends.network:
            self.__dev.stop_tcp_server()
            self.__dev.stop_sending_thread()
            self.emit_stopped_signal()
        elif self.backend == Backends.none:
            pass
        else:
            logger.error("Stop device: Unsupported backend " + str(self.backend))

    def stop_on_error(self, msg: str):
        if self.backend == Backends.grc:
            self.__dev.stop(msg)  # Already connected to stopped in constructor
        elif self.backend == Backends.native:
            self.read_messages()  # Clear errors
            self.__dev.stop_rx_mode("Stop on error")
            self.__dev.stop_tx_mode("Stop on error")
            self.emit_stopped_signal()
        else:
            raise ValueError("Unsupported Backend")

    def cleanup(self):
        if self.backend == Backends.grc:
            if self.mode == Mode.send:
                self.__dev.socket.close()
                time.sleep(0.1)
            self.__dev.quit()
            self.data = None

        elif self.backend == Backends.native:
            self.data = None

        elif self.backend == Backends.none:
            pass

        else:
            raise ValueError("Unsupported Backend")

    def emit_stopped_signal(self):
        self.stopped.emit()

    def emit_started_signal(self):
        self.started.emit()

    def emit_sender_needs_restart(self):
        self.sender_needs_restart.emit()

    def read_messages(self) -> str:
        """
        returns a string of new device messages separated by newlines

        :return:
        """
        if self.backend == Backends.grc:
            errors = self.__dev.read_errors()

            if "FATAL: " in errors:
                self.fatal_error_occurred.emit(errors[errors.index("FATAL: "):])

            return errors
        elif self.backend == Backends.native:
            messages = "\n".join(self.__dev.device_messages)
            self.__dev.device_messages.clear()

            if messages and not messages.endswith("\n"):
                messages += "\n"

            if "successfully started" in messages:
                self.ready_for_action.emit()
            elif "failed to start" in messages:
                self.fatal_error_occurred.emit(messages[messages.index("failed to start"):])

            return messages
        elif self.backend == Backends.network:
            return ""
        else:
            raise ValueError("Unsupported Backend")

    def set_server_port(self, port: int):
        if self.backend == Backends.network:
            self.__dev.server_port = port
        else:
            raise ValueError("Setting port only supported for NetworkSDR Plugin")

    def set_client_port(self, port: int):
        if self.backend == Backends.network:
            self.__dev.client_port = port
        else:
            raise ValueError("Setting port only supported for NetworkSDR Plugin")

    def get_device_list(self):
        if hasattr(self.__dev, "get_device_list"):
            return self.__dev.get_device_list()
        else:
            return []

    def increase_gr_port(self):
        if self.backend == Backends.grc:
            self.__dev.gr_port += 1
            logger.info("Retry with port " + str(self.__dev.gr_port))
        else:
            raise ValueError("Only for GR backend")

    def emit_ready_for_action(self):
        """
        Notify observers that device is successfully initialized
        :return:
        """
        self.ready_for_action.emit()

    def emit_fatal_error_occurred(self, msg: str):
        self.fatal_error_occurred.emit(msg)
Esempio n. 3
0
class GeneratorTabController(QWidget):
    def __init__(self,
                 compare_frame_controller: CompareFrameController,
                 project_manager: ProjectManager,
                 parent=None):
        super().__init__(parent)
        self.ui = Ui_GeneratorTab()
        self.ui.setupUi(self)

        self.ui.treeProtocols.setHeaderHidden(True)
        self.tree_model = GeneratorTreeModel(compare_frame_controller)
        self.tree_model.set_root_item(
            compare_frame_controller.proto_tree_model.rootItem)
        self.tree_model.controller = self
        self.ui.treeProtocols.setModel(self.tree_model)

        self.has_default_modulation = True

        self.table_model = GeneratorTableModel(
            compare_frame_controller.proto_tree_model.rootItem,
            [Modulator("Modulation")], compare_frame_controller.decodings)
        self.table_model.controller = self
        self.ui.tableMessages.setModel(self.table_model)

        self.label_list_model = GeneratorListModel(None)
        self.ui.listViewProtoLabels.setModel(self.label_list_model)

        self.network_sdr_button_orig_tooltip = self.ui.btnNetworkSDRSend.toolTip(
        )
        self.set_network_sdr_send_button_visibility()
        self.set_rfcat_button_visibility()
        self.network_sdr_plugin = NetworkSDRInterfacePlugin()
        self.rfcat_plugin = RfCatPlugin()
        self.init_rfcat_plugin()

        self.refresh_modulators()
        self.on_selected_modulation_changed()
        self.set_fuzzing_ui_status()
        self.project_manager = project_manager
        self.ui.prBarGeneration.hide()
        self.create_connects(compare_frame_controller)

    @property
    def selected_message_index(self) -> int:
        min_row, _, _, _ = self.ui.tableMessages.selection_range()
        return min_row  #

    @property
    def selected_message(self) -> Message:
        selected_msg_index = self.selected_message_index
        if selected_msg_index == -1 or selected_msg_index >= len(
                self.table_model.protocol.messages):
            return None

        return self.table_model.protocol.messages[selected_msg_index]

    @property
    def active_groups(self):
        return self.tree_model.groups

    @property
    def modulators(self):
        return self.table_model.protocol.modulators

    @property
    def total_modulated_samples(self) -> int:
        return sum(
            int(
                len(msg.encoded_bits) *
                self.modulators[msg.modulator_indx].samples_per_bit +
                msg.pause) for msg in self.table_model.protocol.messages)

    @modulators.setter
    def modulators(self, value):
        assert type(value) == list
        self.table_model.protocol.modulators = value

    def create_connects(self, compare_frame_controller):
        compare_frame_controller.proto_tree_model.modelReset.connect(
            self.refresh_tree)
        compare_frame_controller.participant_changed.connect(
            self.table_model.refresh_vertical_header)
        self.ui.btnEditModulation.clicked.connect(self.show_modulation_dialog)
        self.ui.cBoxModulations.currentIndexChanged.connect(
            self.on_selected_modulation_changed)
        self.ui.tableMessages.selectionModel().selectionChanged.connect(
            self.on_table_selection_changed)
        self.ui.tableMessages.encodings_updated.connect(
            self.on_table_selection_changed)
        self.table_model.undo_stack.indexChanged.connect(
            self.on_undo_stack_index_changed)
        self.table_model.protocol.qt_signals.line_duplicated.connect(
            self.refresh_pause_list)
        self.table_model.protocol.qt_signals.fuzzing_started.connect(
            self.on_fuzzing_started)
        self.table_model.protocol.qt_signals.current_fuzzing_message_changed.connect(
            self.on_current_fuzzing_message_changed)
        self.table_model.protocol.qt_signals.fuzzing_finished.connect(
            self.on_fuzzing_finished)
        self.label_list_model.protolabel_fuzzing_status_changed.connect(
            self.set_fuzzing_ui_status)
        self.ui.cbViewType.currentIndexChanged.connect(
            self.on_view_type_changed)
        self.ui.btnSend.clicked.connect(self.on_btn_send_clicked)
        self.ui.btnSave.clicked.connect(self.on_btn_save_clicked)

        self.project_manager.project_updated.connect(self.on_project_updated)

        self.label_list_model.protolabel_removed.connect(
            self.handle_proto_label_removed)

        self.ui.lWPauses.item_edit_clicked.connect(self.edit_pause_item)
        self.ui.lWPauses.itemSelectionChanged.connect(
            self.on_lWpauses_selection_changed)
        self.ui.lWPauses.lost_focus.connect(self.on_lWPauses_lost_focus)
        self.ui.lWPauses.doubleClicked.connect(self.on_lWPauses_double_clicked)
        self.ui.btnGenerate.clicked.connect(self.generate_file)
        self.label_list_model.protolabel_fuzzing_status_changed.connect(
            self.handle_plabel_fuzzing_state_changed)
        self.ui.btnFuzz.clicked.connect(self.on_btn_fuzzing_clicked)
        self.ui.tableMessages.create_fuzzing_label_clicked.connect(
            self.create_fuzzing_label)
        self.ui.tableMessages.edit_fuzzing_label_clicked.connect(
            self.show_fuzzing_dialog)
        self.ui.listViewProtoLabels.selection_changed.connect(
            self.handle_label_selection_changed)
        self.ui.listViewProtoLabels.edit_on_item_triggered.connect(
            self.show_fuzzing_dialog)

        self.ui.btnNetworkSDRSend.clicked.connect(
            self.on_btn_network_sdr_clicked)
        self.ui.btnRfCatSend.clicked.connect(self.on_btn_rfcat_clicked)

        self.network_sdr_plugin.sending_status_changed.connect(
            self.on_network_sdr_sending_status_changed)
        self.network_sdr_plugin.sending_stop_requested.connect(
            self.on_network_sdr_sending_stop_requested)
        self.network_sdr_plugin.current_send_message_changed.connect(
            self.on_send_message_changed)

    @pyqtSlot()
    def refresh_tree(self):
        self.tree_model.beginResetModel()
        self.tree_model.endResetModel()
        self.ui.treeProtocols.expandAll()

    @pyqtSlot()
    def refresh_table(self):
        self.table_model.update()
        self.ui.tableMessages.resize_columns()
        is_data_there = self.table_model.display_data is not None and len(
            self.table_model.display_data) > 0
        self.ui.btnSend.setEnabled(is_data_there)
        self.ui.btnGenerate.setEnabled(is_data_there)

    @pyqtSlot()
    def refresh_label_list(self):
        self.label_list_model.message = self.selected_message
        self.label_list_model.update()

    @property
    def generator_undo_stack(self) -> QUndoStack:
        return self.table_model.undo_stack

    @pyqtSlot()
    def on_selected_modulation_changed(self):
        cur_ind = self.ui.cBoxModulations.currentIndex()
        min_row, max_row, _, _ = self.ui.tableMessages.selection_range()
        if min_row > -1:
            # Modulation für Selektierte Blöcke setzen
            for row in range(min_row, max_row + 1):
                try:
                    self.table_model.protocol.messages[
                        row].modulator_indx = cur_ind
                except IndexError:
                    continue

        self.show_modulation_info()

    def refresh_modulators(self):
        current_index = 0
        if type(self.sender()) == ModulatorDialogController:
            current_index = self.sender(
            ).ui.comboBoxCustomModulations.currentIndex()
        self.ui.cBoxModulations.clear()
        for modulator in self.modulators:
            self.ui.cBoxModulations.addItem(modulator.name)

        self.ui.cBoxModulations.setCurrentIndex(current_index)

    def show_modulation_info(self):
        show = not self.has_default_modulation or self.modulators[
            0] != Modulator("Modulation")

        if not show:
            self.ui.btnEditModulation.setStyleSheet("background: orange")
            font = QFont()
            font.setBold(True)
            self.ui.btnEditModulation.setFont(font)
        else:
            self.ui.btnEditModulation.setStyleSheet("")
            self.ui.btnEditModulation.setFont(QFont())

        cur_ind = self.ui.cBoxModulations.currentIndex()
        cur_mod = self.modulators[cur_ind]
        self.ui.lCarrierFreqValue.setText(cur_mod.carrier_frequency_str)
        self.ui.lCarrierPhaseValue.setText(cur_mod.carrier_phase_str)
        self.ui.lBitLenValue.setText(cur_mod.bit_len_str)
        self.ui.lSampleRateValue.setText(cur_mod.sample_rate_str)
        mod_type = cur_mod.modulation_type_str
        self.ui.lModTypeValue.setText(mod_type)
        if mod_type == "ASK":
            prefix = "Amplitude"
        elif mod_type == "PSK":
            prefix = "Phase"
        elif mod_type in ("FSK", "GFSK"):
            prefix = "Frequency"
        else:
            prefix = "Unknown Modulation Type (This should not happen...)"

        self.ui.lParamForZero.setText(prefix + " for 0:")
        self.ui.lParamForZeroValue.setText(cur_mod.param_for_zero_str)
        self.ui.lParamForOne.setText(prefix + " for 1:")
        self.ui.lParamForOneValue.setText(cur_mod.param_for_one_str)

    def prepare_modulation_dialog(
            self) -> (ModulatorDialogController, Message):
        preselected_index = self.ui.cBoxModulations.currentIndex()

        min_row, max_row, start, end = self.ui.tableMessages.selection_range()
        if min_row > -1:
            try:
                selected_message = self.table_model.protocol.messages[min_row]
                preselected_index = selected_message.modulator_indx
            except IndexError:
                selected_message = Message([True, False, True, False], 0, [],
                                           MessageType("empty"))
        else:
            selected_message = Message([True, False, True, False], 0, [],
                                       MessageType("empty"))
            if len(self.table_model.protocol.messages) > 0:
                selected_message.bit_len = self.table_model.protocol.messages[
                    0].bit_len

        for m in self.modulators:
            m.default_sample_rate = self.project_manager.device_conf[
                "sample_rate"]

        modulator_dialog = ModulatorDialogController(self.modulators,
                                                     parent=self.parent())
        modulator_dialog.ui.treeViewSignals.setModel(self.tree_model)
        modulator_dialog.ui.treeViewSignals.expandAll()
        modulator_dialog.ui.comboBoxCustomModulations.setCurrentIndex(
            preselected_index)

        modulator_dialog.finished.connect(self.refresh_modulators)
        modulator_dialog.finished.connect(self.refresh_pause_list)

        return modulator_dialog, selected_message

    def initialize_modulation_dialog(self, bits: str,
                                     dialog: ModulatorDialogController):
        dialog.on_modulation_type_changed(
        )  # for drawing modulated signal initially
        dialog.original_bits = bits
        dialog.ui.linEdDataBits.setText(bits)
        dialog.ui.gVOriginalSignal.signal_tree_root = self.tree_model.rootItem
        dialog.draw_original_signal()
        dialog.ui.gVModulated.show_full_scene(reinitialize=True)
        dialog.ui.gVData.show_full_scene(reinitialize=True)
        dialog.ui.gVData.auto_fit_view()
        dialog.ui.gVCarrier.show_full_scene(reinitialize=True)
        dialog.ui.gVCarrier.auto_fit_view()

    def init_rfcat_plugin(self):
        self.set_rfcat_button_visibility()
        self.rfcat_plugin = RfCatPlugin()
        self.rfcat_plugin.current_send_message_changed.connect(
            self.on_send_message_changed)
        self.ui.btnRfCatSend.setEnabled(self.rfcat_plugin.rfcat_is_found)

    @pyqtSlot()
    def on_undo_stack_index_changed(self):
        self.refresh_table()
        self.refresh_pause_list()
        self.refresh_label_list()
        self.refresh_estimated_time()
        self.set_fuzzing_ui_status()

    @pyqtSlot()
    def show_modulation_dialog(self):
        modulator_dialog, message = self.prepare_modulation_dialog()
        modulator_dialog.showMaximized()

        self.initialize_modulation_dialog(message.encoded_bits_str[0:10],
                                          modulator_dialog)
        self.has_default_modulation = False

    @pyqtSlot()
    def on_table_selection_changed(self):
        min_row, max_row, start, end = self.ui.tableMessages.selection_range()

        if min_row == -1:
            self.ui.lEncodingValue.setText("-")  #
            self.ui.lEncodingValue.setToolTip("")
            self.label_list_model.message = None
            return

        container = self.table_model.protocol
        message = container.messages[min_row]
        self.label_list_model.message = message
        decoder_name = message.decoder.name
        metrics = QFontMetrics(self.ui.lEncodingValue.font())
        elidedName = metrics.elidedText(decoder_name, Qt.ElideRight,
                                        self.ui.lEncodingValue.width())
        self.ui.lEncodingValue.setText(elidedName)
        self.ui.lEncodingValue.setToolTip(decoder_name)
        self.ui.cBoxModulations.blockSignals(True)
        self.ui.cBoxModulations.setCurrentIndex(message.modulator_indx)
        self.show_modulation_info()
        self.ui.cBoxModulations.blockSignals(False)

    @pyqtSlot(int)
    def edit_pause_item(self, index: int):
        message = self.table_model.protocol.messages[index]
        cur_len = message.pause
        new_len, ok = QInputDialog.getInt(self,
                                          self.tr("Enter new Pause Length"),
                                          self.tr("Pause Length:"), cur_len, 0)
        if ok:
            message.pause = new_len
            self.refresh_pause_list()

    @pyqtSlot()
    def on_lWPauses_double_clicked(self):
        sel_indexes = [
            index.row() for index in self.ui.lWPauses.selectedIndexes()
        ]
        if len(sel_indexes) > 0:
            self.edit_pause_item(sel_indexes[0])

    @pyqtSlot()
    def refresh_pause_list(self):
        self.ui.lWPauses.clear()
        fmt_str = "Pause ({1:d}-{2:d}) <{0:d} samples ({3})>"
        for i, pause in enumerate(self.table_model.protocol.pauses):
            sr = self.modulators[self.table_model.protocol.messages[i].
                                 modulator_indx].sample_rate
            item = fmt_str.format(pause, i + 1, i + 2,
                                  Formatter.science_time(pause / sr))
            self.ui.lWPauses.addItem(item)

    @pyqtSlot()
    def on_lWpauses_selection_changed(self):
        rows = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
        if len(rows) == 0:
            return
        self.ui.tableMessages.show_pause_active = True
        self.ui.tableMessages.pause_row = rows[0]
        self.ui.tableMessages.viewport().update()
        self.ui.tableMessages.scrollTo(self.table_model.index(rows[0], 0))

    @pyqtSlot()
    def on_lWPauses_lost_focus(self):
        self.ui.tableMessages.show_pause_active = False
        self.ui.tableMessages.viewport().update()

    @pyqtSlot()
    def generate_file(self):
        try:
            total_samples = self.total_modulated_samples
            buffer = self.prepare_modulation_buffer(total_samples,
                                                    show_error=False)
            if buffer is None:
                Errors.generic_error(
                    self.tr("File too big"),
                    self.tr("This file would get too big to save."))
                self.unsetCursor()
                return
            modulated_samples = self.modulate_data(buffer)
            FileOperator.save_data_dialog("", modulated_samples, parent=self)
        except Exception as e:
            Errors.generic_error(self.tr("Failed to generate data"), str(e),
                                 traceback.format_exc())
            self.unsetCursor()

    def prepare_modulation_buffer(self,
                                  total_samples: int,
                                  show_error=True) -> np.ndarray:
        memory_size_for_buffer = total_samples * 8
        logger.debug("Allocating {0:.2f}MB for modulated samples".format(
            memory_size_for_buffer / (1024**2)))
        try:
            return np.zeros(total_samples, dtype=np.complex64)
        except MemoryError:
            if show_error:
                Errors.not_enough_ram_for_sending_precache(
                    memory_size_for_buffer)
            return None

    def modulate_data(self, buffer: np.ndarray) -> np.ndarray:
        """
        
        :param buffer: Buffer in which the modulated data shall be written, initialized with zeros
        :return: 
        """
        self.ui.prBarGeneration.show()
        self.ui.prBarGeneration.setValue(0)
        self.ui.prBarGeneration.setMaximum(self.table_model.row_count)

        pos = 0
        for i in range(0, self.table_model.row_count):
            message = self.table_model.protocol.messages[i]
            modulator = self.modulators[message.modulator_indx]
            # We do not need to modulate the pause extra, as result is already initialized with zeros
            modulator.modulate(start=pos, data=message.encoded_bits, pause=0)
            buffer[pos:pos + len(modulator.modulated_samples
                                 )] = modulator.modulated_samples
            pos += len(modulator.modulated_samples) + message.pause
            self.ui.prBarGeneration.setValue(i + 1)
            QApplication.instance().processEvents()

        self.ui.prBarGeneration.hide()
        return buffer

    @pyqtSlot(int)
    def show_fuzzing_dialog(self, label_index: int):
        view = self.ui.cbViewType.currentIndex()

        if self.label_list_model.message is not None:
            msg_index = self.table_model.protocol.messages.index(
                self.label_list_model.message)
            fdc = FuzzingDialogController(protocol=self.table_model.protocol,
                                          label_index=label_index,
                                          msg_index=msg_index,
                                          proto_view=view,
                                          parent=self)
            fdc.show()
            fdc.finished.connect(self.refresh_label_list)
            fdc.finished.connect(self.refresh_table)
            fdc.finished.connect(self.set_fuzzing_ui_status)

    @pyqtSlot()
    def handle_plabel_fuzzing_state_changed(self):
        self.refresh_table()
        self.label_list_model.update()

    @pyqtSlot(ProtocolLabel)
    def handle_proto_label_removed(self, plabel: ProtocolLabel):
        self.refresh_label_list()
        self.refresh_table()
        self.set_fuzzing_ui_status()

    @pyqtSlot()
    def on_btn_fuzzing_clicked(self):
        fuz_mode = "Successive"
        if self.ui.rbConcurrent.isChecked():
            fuz_mode = "Concurrent"
        elif self.ui.rBExhaustive.isChecked():
            fuz_mode = "Exhaustive"

        self.setCursor(Qt.WaitCursor)
        fuzz_action = Fuzz(self.table_model.protocol, fuz_mode)
        self.table_model.undo_stack.push(fuzz_action)
        self.unsetCursor()

    @pyqtSlot()
    def set_fuzzing_ui_status(self):
        btn_was_enabled = self.ui.btnFuzz.isEnabled()
        fuzz_active = any(lbl.active_fuzzing
                          for msg in self.table_model.protocol.messages
                          for lbl in msg.message_type)
        self.ui.btnFuzz.setEnabled(fuzz_active)
        if self.ui.btnFuzz.isEnabled() and not btn_was_enabled:
            font = self.ui.btnFuzz.font()
            font.setBold(True)
            self.ui.btnFuzz.setFont(font)
        else:
            font = self.ui.btnFuzz.font()
            font.setBold(False)
            self.ui.btnFuzz.setFont(font)
            self.ui.btnFuzz.setStyleSheet("")

        has_same_message = self.table_model.protocol.multiple_fuzz_labels_per_message
        self.ui.rBSuccessive.setEnabled(has_same_message)
        self.ui.rBExhaustive.setEnabled(has_same_message)
        self.ui.rbConcurrent.setEnabled(has_same_message)

    def refresh_existing_encodings(self, encodings_from_file):
        """
        Refresh existing encodings for messages, when encoding was changed by user in dialog

        :return:
        """
        update = False

        for msg in self.table_model.protocol.messages:
            i = next((i for i, d in enumerate(encodings_from_file)
                      if d.name == msg.decoder.name), 0)
            if msg.decoder != encodings_from_file[i]:
                update = True
                msg.decoder = encodings_from_file[i]
                msg.clear_decoded_bits()
                msg.clear_encoded_bits()

        if update:
            self.refresh_table()
            self.refresh_estimated_time()

    @pyqtSlot()
    def refresh_estimated_time(self):
        c = self.table_model.protocol
        if c.num_messages == 0:
            self.ui.lEstimatedTime.setText("Estimated Time: ")
            return

        avg_msg_len = numpy.mean([len(msg.encoded_bits) for msg in c.messages])
        avg_bit_len = numpy.mean([m.samples_per_bit for m in self.modulators])
        avg_sample_rate = numpy.mean([m.sample_rate for m in self.modulators])
        pause_samples = sum(c.pauses)
        nsamples = c.num_messages * avg_msg_len * avg_bit_len + pause_samples

        self.ui.lEstimatedTime.setText(
            locale.format_string("Estimated Time: %.04f seconds",
                                 nsamples / avg_sample_rate))

    @pyqtSlot(int, int, int)
    def create_fuzzing_label(self, msg_index: int, start: int, end: int):
        con = self.table_model.protocol
        start, end = con.convert_range(start, end - 1,
                                       self.ui.cbViewType.currentIndex(), 0,
                                       False, msg_index)
        lbl = con.create_fuzzing_label(start, end, msg_index)
        self.show_fuzzing_dialog(con.protocol_labels.index(lbl))

    @pyqtSlot()
    def handle_label_selection_changed(self):
        rows = [
            index.row()
            for index in self.ui.listViewProtoLabels.selectedIndexes()
        ]
        if len(rows) == 0:
            return

        maxrow = numpy.max(rows)

        try:
            label = self.table_model.protocol.protocol_labels[maxrow]
        except IndexError:
            return
        if label.show and self.selected_message:
            start, end = self.selected_message.get_label_range(
                lbl=label, view=self.table_model.proto_view, decode=False)
            indx = self.table_model.index(0, int((start + end) / 2))
            self.ui.tableMessages.scrollTo(indx)

    @pyqtSlot()
    def on_view_type_changed(self):
        self.setCursor(Qt.WaitCursor)
        self.table_model.proto_view = self.ui.cbViewType.currentIndex()
        self.ui.tableMessages.resize_columns()
        self.unsetCursor()

    @pyqtSlot()
    def on_btn_send_clicked(self):
        try:
            total_samples = self.total_modulated_samples
            buffer = self.prepare_modulation_buffer(total_samples)
            if buffer is not None:
                modulated_data = self.modulate_data(buffer)
            else:
                # Enter continuous mode
                modulated_data = None

            try:
                if modulated_data is not None:
                    try:
                        dialog = SendDialogController(
                            self.project_manager,
                            modulated_data=modulated_data,
                            parent=self)
                    except MemoryError:
                        # Not enough memory for device buffer so we need to create a continuous send dialog
                        del modulated_data
                        Errors.not_enough_ram_for_sending_precache(None)
                        dialog = ContinuousSendDialogController(
                            self.project_manager,
                            self.table_model.protocol.messages,
                            self.modulators,
                            total_samples,
                            parent=self)
                else:
                    dialog = ContinuousSendDialogController(
                        self.project_manager,
                        self.table_model.protocol.messages,
                        self.modulators,
                        total_samples,
                        parent=self)
            except OSError as e:
                logger.error(repr(e))
                return
            if dialog.has_empty_device_list:
                Errors.no_device()
                dialog.close()
                return

            dialog.recording_parameters.connect(
                self.project_manager.set_recording_parameters)
            dialog.show()
            dialog.graphics_view.show_full_scene(reinitialize=True)
        except Exception as e:
            Errors.generic_error(self.tr("Failed to generate data"), str(e),
                                 traceback.format_exc())
            self.unsetCursor()

    @pyqtSlot()
    def on_btn_save_clicked(self):
        filename = FileOperator.get_save_file_name("profile.fuzz",
                                                   caption="Save fuzz profile")
        if filename:
            self.table_model.protocol.to_xml_file(filename)

    def load_from_file(self, filename: str):
        try:
            self.table_model.protocol.from_xml_file(filename)
            self.refresh_pause_list()
            self.refresh_estimated_time()
            self.refresh_modulators()
            self.show_modulation_info()
            self.refresh_table()
            self.set_fuzzing_ui_status()
        except:
            logger.error(
                "You done something wrong to the xml fuzzing profile.")

    @pyqtSlot()
    def on_project_updated(self):
        self.table_model.participants = self.project_manager.participants
        self.table_model.refresh_vertical_header()

    def set_network_sdr_send_button_visibility(self):
        is_plugin_enabled = PluginManager().is_plugin_enabled(
            "NetworkSDRInterface")
        self.ui.btnNetworkSDRSend.setVisible(is_plugin_enabled)

    def set_rfcat_button_visibility(self):
        is_plugin_enabled = PluginManager().is_plugin_enabled("RfCat")
        self.ui.btnRfCatSend.setVisible(is_plugin_enabled)

    @pyqtSlot()
    def on_btn_network_sdr_clicked(self):
        if not self.network_sdr_plugin.is_sending:
            messages = self.table_model.protocol.messages
            sample_rates = [
                self.modulators[msg.modulator_indx].sample_rate
                for msg in messages
            ]
            self.network_sdr_plugin.start_message_sending_thread(
                messages, sample_rates)
        else:
            self.network_sdr_plugin.stop_sending_thread()

    @pyqtSlot(bool)
    def on_network_sdr_sending_status_changed(self, is_sending: bool):
        self.ui.btnNetworkSDRSend.setChecked(is_sending)
        self.ui.btnNetworkSDRSend.setEnabled(True)
        self.ui.btnNetworkSDRSend.setToolTip(
            "Sending in progress" if is_sending else self.
            network_sdr_button_orig_tooltip)
        if not is_sending:
            self.ui.tableMessages.clearSelection()

    @pyqtSlot()
    def on_network_sdr_sending_stop_requested(self):
        self.ui.btnNetworkSDRSend.setToolTip("Stopping sending")
        self.ui.btnNetworkSDRSend.setEnabled(False)

    @pyqtSlot(int)
    def on_send_message_changed(self, message_index: int):
        self.ui.tableMessages.selectRow(message_index)

    @pyqtSlot()
    def on_btn_rfcat_clicked(self):
        if not self.rfcat_plugin.is_sending:
            messages = self.table_model.protocol.messages
            sample_rates = [
                self.modulators[msg.modulator_indx].sample_rate
                for msg in messages
            ]
            self.rfcat_plugin.start_message_sending_thread(
                messages, sample_rates, self.modulators, self.project_manager)
        else:
            self.rfcat_plugin.stop_sending_thread()

    @pyqtSlot(int)
    def on_fuzzing_started(self, num_values: int):
        self.ui.stackedWidgetFuzzing.setCurrentWidget(
            self.ui.pageFuzzingProgressBar)
        self.ui.progressBarFuzzing.setMaximum(num_values)
        self.ui.progressBarFuzzing.setValue(0)
        QApplication.instance().processEvents()

    @pyqtSlot()
    def on_fuzzing_finished(self):
        self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingUI)

    @pyqtSlot(int)
    def on_current_fuzzing_message_changed(self, current_message: int):
        self.ui.progressBarFuzzing.setValue(current_message)
        QApplication.instance().processEvents()
Esempio n. 4
0
class GeneratorTabController(QWidget):
    def __init__(self, compare_frame_controller: CompareFrameController, project_manager: ProjectManager, parent=None):
        super().__init__(parent)
        self.ui = Ui_GeneratorTab()
        self.ui.setupUi(self)
        util.set_splitter_stylesheet(self.ui.splitter)

        self.project_manager = project_manager

        self.ui.treeProtocols.setHeaderHidden(True)
        self.tree_model = GeneratorTreeModel(compare_frame_controller)
        self.tree_model.set_root_item(compare_frame_controller.proto_tree_model.rootItem)
        self.tree_model.controller = self
        self.ui.treeProtocols.setModel(self.tree_model)

        self.table_model = GeneratorTableModel(compare_frame_controller.proto_tree_model.rootItem,
                                               compare_frame_controller.decodings)
        self.table_model.controller = self
        self.ui.tableMessages.setModel(self.table_model)

        self.label_list_model = GeneratorListModel(None)
        self.ui.listViewProtoLabels.setModel(self.label_list_model)

        self.network_sdr_button_orig_tooltip = self.ui.btnNetworkSDRSend.toolTip()
        self.set_network_sdr_send_button_visibility()
        self.set_rfcat_button_visibility()
        self.network_sdr_plugin = NetworkSDRInterfacePlugin()
        self.rfcat_plugin = RfCatPlugin()
        self.init_rfcat_plugin()

        self.modulation_msg_indices = []

        self.refresh_modulators()
        self.on_selected_modulation_changed()
        self.set_fuzzing_ui_status()
        self.ui.prBarGeneration.hide()
        self.create_connects(compare_frame_controller)

        self.set_modulation_profile_status()

    def __get_modulator_of_message(self, message: Message) -> Modulator:
        if message.modulator_index > len(self.modulators) - 1:
            message.modulator_index = 0
        return self.modulators[message.modulator_index]

    @property
    def selected_message_index(self) -> int:
        min_row, _, _, _ = self.ui.tableMessages.selection_range()
        return min_row  #

    @property
    def selected_message(self) -> Message:
        selected_msg_index = self.selected_message_index
        if selected_msg_index == -1 or selected_msg_index >= len(self.table_model.protocol.messages):
            return None

        return self.table_model.protocol.messages[selected_msg_index]

    @property
    def active_groups(self):
        return self.tree_model.groups

    @property
    def modulators(self):
        return self.project_manager.modulators

    @property
    def total_modulated_samples(self) -> int:
        return sum(int(len(msg.encoded_bits) * self.__get_modulator_of_message(msg).samples_per_bit + msg.pause)
                   for msg in self.table_model.protocol.messages)

    @modulators.setter
    def modulators(self, value):
        assert type(value) == list
        self.project_manager.modulators = value

    def create_connects(self, compare_frame_controller):
        compare_frame_controller.proto_tree_model.modelReset.connect(self.refresh_tree)
        compare_frame_controller.participant_changed.connect(self.table_model.refresh_vertical_header)
        self.ui.btnEditModulation.clicked.connect(self.show_modulation_dialog)
        self.ui.cBoxModulations.currentIndexChanged.connect(self.on_selected_modulation_changed)
        self.ui.tableMessages.selectionModel().selectionChanged.connect(self.on_table_selection_changed)
        self.ui.tableMessages.encodings_updated.connect(self.on_table_selection_changed)
        self.table_model.undo_stack.indexChanged.connect(self.on_undo_stack_index_changed)
        self.table_model.protocol.qt_signals.line_duplicated.connect(self.refresh_pause_list)
        self.table_model.protocol.qt_signals.fuzzing_started.connect(self.on_fuzzing_started)
        self.table_model.protocol.qt_signals.current_fuzzing_message_changed.connect(
            self.on_current_fuzzing_message_changed)
        self.table_model.protocol.qt_signals.fuzzing_finished.connect(self.on_fuzzing_finished)
        self.table_model.first_protocol_added.connect(self.on_first_protocol_added)
        self.label_list_model.protolabel_fuzzing_status_changed.connect(self.set_fuzzing_ui_status)
        self.ui.cbViewType.currentIndexChanged.connect(self.on_view_type_changed)
        self.ui.btnSend.clicked.connect(self.on_btn_send_clicked)
        self.ui.btnSave.clicked.connect(self.on_btn_save_clicked)
        self.ui.btnOpen.clicked.connect(self.on_btn_open_clicked)

        self.project_manager.project_updated.connect(self.on_project_updated)

        self.table_model.vertical_header_color_status_changed.connect(
            self.ui.tableMessages.on_vertical_header_color_status_changed)

        self.label_list_model.protolabel_removed.connect(self.handle_proto_label_removed)

        self.ui.lWPauses.item_edit_clicked.connect(self.edit_pause_item)
        self.ui.lWPauses.edit_all_items_clicked.connect(self.edit_all_pause_items)
        self.ui.lWPauses.itemSelectionChanged.connect(self.on_lWpauses_selection_changed)
        self.ui.lWPauses.lost_focus.connect(self.on_lWPauses_lost_focus)
        self.ui.lWPauses.doubleClicked.connect(self.on_lWPauses_double_clicked)
        self.ui.btnGenerate.clicked.connect(self.generate_file)
        self.label_list_model.protolabel_fuzzing_status_changed.connect(self.handle_plabel_fuzzing_state_changed)
        self.ui.btnFuzz.clicked.connect(self.on_btn_fuzzing_clicked)
        self.ui.tableMessages.create_label_triggered.connect(self.create_fuzzing_label)
        self.ui.tableMessages.edit_label_triggered.connect(self.show_fuzzing_dialog)
        self.ui.listViewProtoLabels.selection_changed.connect(self.handle_label_selection_changed)
        self.ui.listViewProtoLabels.edit_on_item_triggered.connect(self.show_fuzzing_dialog)

        self.ui.btnNetworkSDRSend.clicked.connect(self.on_btn_network_sdr_clicked)
        self.ui.btnRfCatSend.clicked.connect(self.on_btn_rfcat_clicked)

        self.network_sdr_plugin.sending_status_changed.connect(self.on_network_sdr_sending_status_changed)
        self.network_sdr_plugin.sending_stop_requested.connect(self.on_network_sdr_sending_stop_requested)
        self.network_sdr_plugin.current_send_message_changed.connect(self.on_send_message_changed)

    @pyqtSlot()
    def refresh_tree(self):
        self.tree_model.beginResetModel()
        self.tree_model.endResetModel()
        self.ui.treeProtocols.expandAll()

    @pyqtSlot()
    def refresh_table(self):
        self.table_model.update()
        self.ui.tableMessages.resize_columns()
        is_data_there = self.table_model.display_data is not None and len(self.table_model.display_data) > 0
        self.ui.btnSend.setEnabled(is_data_there)
        self.ui.btnGenerate.setEnabled(is_data_there)

    @pyqtSlot()
    def refresh_label_list(self):
        self.label_list_model.message = self.selected_message
        self.label_list_model.update()

    @property
    def generator_undo_stack(self) -> QUndoStack:
        return self.table_model.undo_stack

    @pyqtSlot()
    def on_selected_modulation_changed(self):
        cur_ind = self.ui.cBoxModulations.currentIndex()
        min_row, max_row, _, _ = self.ui.tableMessages.selection_range()
        if min_row > -1:
            # set modulation for selected messages
            for row in range(min_row, max_row + 1):
                try:
                    self.table_model.protocol.messages[row].modulator_index = cur_ind
                except IndexError:
                    continue

        self.show_modulation_info()

    def refresh_modulators(self):
        current_index = 0
        if type(self.sender()) == ModulatorDialog:
            current_index = self.sender().ui.comboBoxCustomModulations.currentIndex()
        self.ui.cBoxModulations.clear()
        for modulator in self.modulators:
            self.ui.cBoxModulations.addItem(modulator.name)

        self.ui.cBoxModulations.setCurrentIndex(current_index)

    def bootstrap_modulator(self, protocol: ProtocolAnalyzer):
        """
        Set initial parameters for default modulator if it was not edited by user previously
        :return:
        """
        if len(self.modulators) != 1 or len(self.table_model.protocol.messages) == 0:
            return

        modulator = self.modulators[0]
        modulator.samples_per_bit = protocol.messages[0].bit_len

        if protocol.signal:
            modulator.sample_rate = protocol.signal.sample_rate
            modulator.modulation_type = protocol.signal.modulation_type
            auto_freq = modulator.estimate_carrier_frequency(protocol.signal, protocol)
            if auto_freq is not None and auto_freq != 0:
                modulator.carrier_freq_hz = auto_freq

        self.show_modulation_info()

    def show_modulation_info(self):
        cur_ind = self.ui.cBoxModulations.currentIndex()
        cur_mod = self.modulators[cur_ind]
        self.ui.lCarrierFreqValue.setText(cur_mod.carrier_frequency_str)
        self.ui.lCarrierPhaseValue.setText(cur_mod.carrier_phase_str)
        self.ui.lBitLenValue.setText(cur_mod.bit_len_str)
        self.ui.lSampleRateValue.setText(cur_mod.sample_rate_str)
        mod_type = cur_mod.modulation_type_str
        self.ui.lModTypeValue.setText(mod_type)
        if mod_type == "ASK":
            prefix = "Amplitude"
        elif mod_type == "PSK":
            prefix = "Phase"
        elif mod_type in ("FSK", "GFSK"):
            prefix = "Frequency"
        else:
            prefix = "Unknown Modulation Type (This should not happen...)"

        self.ui.lParamForZero.setText(prefix + " for 0:")
        self.ui.lParamForZeroValue.setText(cur_mod.param_for_zero_str)
        self.ui.lParamForOne.setText(prefix + " for 1:")
        self.ui.lParamForOneValue.setText(cur_mod.param_for_one_str)

    def prepare_modulation_dialog(self) -> (ModulatorDialog, Message):
        preselected_index = self.ui.cBoxModulations.currentIndex()

        min_row, max_row, start, end = self.ui.tableMessages.selection_range()
        if min_row > -1:
            try:
                selected_message = self.table_model.protocol.messages[min_row]
                preselected_index = selected_message.modulator_index
            except IndexError:
                selected_message = Message([1, 0, 1, 0, 1, 0, 1, 0], 0, [], MessageType("empty"))
        else:
            selected_message = Message([1, 0, 1, 0, 1, 0, 1, 0], 0, [], MessageType("empty"))
            if len(self.table_model.protocol.messages) > 0:
                selected_message.bit_len = self.table_model.protocol.messages[0].bit_len

        for m in self.modulators:
            m.default_sample_rate = self.project_manager.device_conf["sample_rate"]

        modulator_dialog = ModulatorDialog(self.modulators, tree_model=self.tree_model, parent=self.parent())
        modulator_dialog.ui.comboBoxCustomModulations.setCurrentIndex(preselected_index)

        modulator_dialog.finished.connect(self.refresh_modulators)
        modulator_dialog.finished.connect(self.refresh_pause_list)

        return modulator_dialog, selected_message

    def set_modulation_profile_status(self):
        visible = constants.SETTINGS.value("multiple_modulations", False, bool)
        self.ui.cBoxModulations.setVisible(visible)

    def init_rfcat_plugin(self):
        self.set_rfcat_button_visibility()
        self.rfcat_plugin = RfCatPlugin()
        self.rfcat_plugin.current_send_message_changed.connect(self.on_send_message_changed)
        self.ui.btnRfCatSend.setEnabled(self.rfcat_plugin.rfcat_is_found)

    @pyqtSlot()
    def on_undo_stack_index_changed(self):
        self.refresh_table()
        self.refresh_pause_list()
        self.refresh_label_list()
        self.refresh_estimated_time()
        self.set_fuzzing_ui_status()

    @pyqtSlot()
    def show_modulation_dialog(self):
        modulator_dialog, message = self.prepare_modulation_dialog()
        modulator_dialog.showMaximized()

        modulator_dialog.initialize(message.encoded_bits_str[0:10])
        self.project_manager.modulation_was_edited = True

    @pyqtSlot()
    def on_table_selection_changed(self):
        min_row, max_row, start, end = self.ui.tableMessages.selection_range()

        if min_row == -1:
            self.ui.lEncodingValue.setText("-")  #
            self.ui.lEncodingValue.setToolTip("")
            self.label_list_model.message = None
            return

        container = self.table_model.protocol
        message = container.messages[min_row]
        self.label_list_model.message = message
        decoder_name = message.decoder.name
        metrics = QFontMetrics(self.ui.lEncodingValue.font())
        elidedName = metrics.elidedText(decoder_name, Qt.ElideRight, self.ui.lEncodingValue.width())
        self.ui.lEncodingValue.setText(elidedName)
        self.ui.lEncodingValue.setToolTip(decoder_name)
        self.ui.cBoxModulations.blockSignals(True)
        self.ui.cBoxModulations.setCurrentIndex(message.modulator_index)
        self.show_modulation_info()
        self.ui.cBoxModulations.blockSignals(False)

    @pyqtSlot(int)
    def edit_pause_item(self, index: int):
        message = self.table_model.protocol.messages[index]
        cur_len = message.pause
        new_len, ok = QInputDialog.getInt(self, self.tr("Enter new Pause Length"),
                                          self.tr("Pause Length:"), cur_len, 0)
        if ok:
            message.pause = new_len
            self.refresh_pause_list()

    @pyqtSlot()
    def edit_all_pause_items(self):
        message = self.table_model.protocol.messages[0]
        cur_len = message.pause
        new_len, ok = QInputDialog.getInt(self, self.tr("Enter new Pause Length"),
                                          self.tr("Pause Length:"), cur_len, 0)
        if ok:
            for message in self.table_model.protocol.messages:
                message.pause = new_len

            self.refresh_pause_list()

    @pyqtSlot()
    def on_lWPauses_double_clicked(self):
        sel_indexes = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
        if len(sel_indexes) > 0:
            self.edit_pause_item(sel_indexes[0])

    @pyqtSlot()
    def refresh_pause_list(self):
        self.ui.lWPauses.clear()

        fmt_str = "Pause ({1:d}-{2:d}) <{0:d} samples ({3})>"
        for i, pause in enumerate(self.table_model.protocol.pauses):
            sr = self.__get_modulator_of_message(self.table_model.protocol.messages[i]).sample_rate
            item = fmt_str.format(pause, i + 1, i + 2, Formatter.science_time(pause / sr))
            self.ui.lWPauses.addItem(item)

        self.refresh_estimated_time()

    @pyqtSlot()
    def on_lWpauses_selection_changed(self):
        rows = [index.row() for index in self.ui.lWPauses.selectedIndexes()]
        if len(rows) == 0:
            return
        self.ui.tableMessages.show_pause_active = True
        self.ui.tableMessages.pause_row = rows[0]
        self.ui.tableMessages.viewport().update()
        self.ui.tableMessages.scrollTo(self.table_model.index(rows[0], 0))

    @pyqtSlot()
    def on_lWPauses_lost_focus(self):
        self.ui.tableMessages.show_pause_active = False
        self.ui.tableMessages.viewport().update()

    @pyqtSlot()
    def generate_file(self):
        try:
            total_samples = self.total_modulated_samples
            buffer = self.prepare_modulation_buffer(total_samples, show_error=False)
            if buffer is None:
                Errors.generic_error(self.tr("File too big"), self.tr("This file would get too big to save."))
                self.unsetCursor()
                return
            modulated_samples = self.modulate_data(buffer)
            try:
                sample_rate = self.modulators[0].sample_rate
            except Exception as e:
                logger.exception(e)
                sample_rate = 1e6
            FileOperator.save_data_dialog("generated.complex", modulated_samples, sample_rate=sample_rate, parent=self)
        except Exception as e:
            Errors.generic_error(self.tr("Failed to generate data"), str(e), traceback.format_exc())
            self.unsetCursor()

    def prepare_modulation_buffer(self, total_samples: int, show_error=True) -> np.ndarray:
        memory_size_for_buffer = total_samples * 8
        logger.debug("Allocating {0:.2f}MB for modulated samples".format(memory_size_for_buffer / (1024 ** 2)))
        try:
            # allocate it three times as we need the same amount for the sending process
            np.zeros(3*total_samples, dtype=np.complex64)
        except MemoryError:
            # will go into continuous mode in this case
            if show_error:
                Errors.not_enough_ram_for_sending_precache(3*memory_size_for_buffer)
            return None

        return np.zeros(total_samples, dtype=np.complex64)

    def modulate_data(self, buffer: np.ndarray) -> np.ndarray:
        """
        
        :param buffer: Buffer in which the modulated data shall be written, initialized with zeros
        :return: 
        """
        self.ui.prBarGeneration.show()
        self.ui.prBarGeneration.setValue(0)
        self.ui.prBarGeneration.setMaximum(self.table_model.row_count)
        self.modulation_msg_indices.clear()

        pos = 0
        for i in range(0, self.table_model.row_count):
            message = self.table_model.protocol.messages[i]
            modulator = self.__get_modulator_of_message(message)
            # We do not need to modulate the pause extra, as result is already initialized with zeros
            modulated = modulator.modulate(start=0, data=message.encoded_bits, pause=0)
            buffer[pos:pos + len(modulated)] = modulated
            pos += len(modulated) + message.pause
            self.modulation_msg_indices.append(pos)
            self.ui.prBarGeneration.setValue(i + 1)
            QApplication.instance().processEvents()

        self.ui.prBarGeneration.hide()
        return buffer

    @pyqtSlot(int)
    def show_fuzzing_dialog(self, label_index: int):
        view = self.ui.cbViewType.currentIndex()

        if self.label_list_model.message is not None:
            msg_index = self.table_model.protocol.messages.index(self.label_list_model.message)
            fdc = FuzzingDialog(protocol=self.table_model.protocol, label_index=label_index,
                                msg_index=msg_index, proto_view=view, parent=self)
            fdc.show()
            fdc.finished.connect(self.refresh_label_list)
            fdc.finished.connect(self.refresh_table)
            fdc.finished.connect(self.set_fuzzing_ui_status)

    @pyqtSlot()
    def handle_plabel_fuzzing_state_changed(self):
        self.refresh_table()
        self.label_list_model.update()

    @pyqtSlot(ProtocolLabel)
    def handle_proto_label_removed(self, plabel: ProtocolLabel):
        self.refresh_label_list()
        self.refresh_table()
        self.set_fuzzing_ui_status()

    @pyqtSlot()
    def on_btn_fuzzing_clicked(self):
        fuz_mode = "Successive"
        if self.ui.rbConcurrent.isChecked():
            fuz_mode = "Concurrent"
        elif self.ui.rBExhaustive.isChecked():
            fuz_mode = "Exhaustive"

        self.setCursor(Qt.WaitCursor)
        fuzz_action = Fuzz(self.table_model.protocol, fuz_mode)
        self.table_model.undo_stack.push(fuzz_action)
        for row in fuzz_action.added_message_indices:
            self.table_model.update_checksums_for_row(row)
        self.unsetCursor()
        self.ui.tableMessages.setFocus()

    @pyqtSlot()
    def set_fuzzing_ui_status(self):
        btn_was_enabled = self.ui.btnFuzz.isEnabled()
        fuzz_active = any(lbl.active_fuzzing for msg in self.table_model.protocol.messages for lbl in msg.message_type)
        self.ui.btnFuzz.setEnabled(fuzz_active)
        if self.ui.btnFuzz.isEnabled() and not btn_was_enabled:
            font = self.ui.btnFuzz.font()
            font.setBold(True)
            self.ui.btnFuzz.setFont(font)
        else:
            font = self.ui.btnFuzz.font()
            font.setBold(False)
            self.ui.btnFuzz.setFont(font)
            self.ui.btnFuzz.setStyleSheet("")

        has_same_message = self.table_model.protocol.multiple_fuzz_labels_per_message
        self.ui.rBSuccessive.setEnabled(has_same_message)
        self.ui.rBExhaustive.setEnabled(has_same_message)
        self.ui.rbConcurrent.setEnabled(has_same_message)

    def refresh_existing_encodings(self, encodings_from_file):
        """
        Refresh existing encodings for messages, when encoding was changed by user in dialog

        :return:
        """
        update = False

        for msg in self.table_model.protocol.messages:
            i = next((i for i, d in enumerate(encodings_from_file) if d.name == msg.decoder.name), 0)
            if msg.decoder != encodings_from_file[i]:
                update = True
                msg.decoder = encodings_from_file[i]
                msg.clear_decoded_bits()
                msg.clear_encoded_bits()

        if update:
            self.refresh_table()
            self.refresh_estimated_time()

    @pyqtSlot()
    def refresh_estimated_time(self):
        c = self.table_model.protocol
        if c.num_messages == 0:
            self.ui.lEstimatedTime.setText("Estimated Time: ")
            return

        avg_msg_len = numpy.mean([len(msg.encoded_bits) for msg in c.messages])
        avg_bit_len = numpy.mean([m.samples_per_bit for m in self.modulators])
        avg_sample_rate = numpy.mean([m.sample_rate for m in self.modulators])
        pause_samples = sum(c.pauses)
        nsamples = c.num_messages * avg_msg_len * avg_bit_len + pause_samples

        self.ui.lEstimatedTime.setText(
            locale.format_string("Estimated Time: %.04f seconds", nsamples / avg_sample_rate))

    @pyqtSlot(int, int, int)
    def create_fuzzing_label(self, msg_index: int, start: int, end: int):
        con = self.table_model.protocol
        start, end = con.convert_range(start, end - 1, self.ui.cbViewType.currentIndex(), 0, False, msg_index)
        lbl = con.create_fuzzing_label(start, end, msg_index)
        self.show_fuzzing_dialog(con.protocol_labels.index(lbl))

    @pyqtSlot()
    def handle_label_selection_changed(self):
        rows = [index.row() for index in self.ui.listViewProtoLabels.selectedIndexes()]
        if len(rows) == 0:
            return

        maxrow = numpy.max(rows)

        try:
            label = self.table_model.protocol.protocol_labels[maxrow]
        except IndexError:
            return
        if label.show and self.selected_message:
            start, end = self.selected_message.get_label_range(lbl=label, view=self.table_model.proto_view,
                                                               decode=False)
            indx = self.table_model.index(0, int((start + end) / 2))
            self.ui.tableMessages.scrollTo(indx)

    @pyqtSlot()
    def on_view_type_changed(self):
        self.setCursor(Qt.WaitCursor)
        self.table_model.proto_view = self.ui.cbViewType.currentIndex()
        self.ui.tableMessages.resize_columns()
        self.unsetCursor()

    @pyqtSlot()
    def on_btn_send_clicked(self):
        try:
            total_samples = self.total_modulated_samples
            buffer = self.prepare_modulation_buffer(total_samples)
            if buffer is not None:
                modulated_data = self.modulate_data(buffer)
            else:
                # Enter continuous mode
                modulated_data = None

            try:
                if modulated_data is not None:
                    try:
                        dialog = SendDialog(self.project_manager, modulated_data=modulated_data,
                                            modulation_msg_indices=self.modulation_msg_indices, parent=self)
                    except MemoryError:
                        # Not enough memory for device buffer so we need to create a continuous send dialog
                        del modulated_data
                        Errors.not_enough_ram_for_sending_precache(None)
                        dialog = ContinuousSendDialog(self.project_manager,
                                                      self.table_model.protocol.messages,
                                                      self.modulators, total_samples, parent=self)
                else:
                    dialog = ContinuousSendDialog(self.project_manager, self.table_model.protocol.messages,
                                                  self.modulators, total_samples, parent=self)
            except OSError as e:
                logger.exception(e)
                return
            if dialog.has_empty_device_list:
                Errors.no_device()
                dialog.close()
                return

            dialog.device_parameters_changed.connect(self.project_manager.set_device_parameters)
            dialog.show()
            dialog.graphics_view.show_full_scene(reinitialize=True)
        except Exception as e:
            Errors.generic_error(self.tr("Failed to generate data"), str(e), traceback.format_exc())
            self.unsetCursor()

    @pyqtSlot()
    def on_btn_save_clicked(self):
        filename = FileOperator.get_save_file_name("profile.fuzz.xml", caption="Save fuzz profile")
        if filename:
            self.table_model.protocol.to_xml_file(filename,
                                                  decoders=self.project_manager.decodings,
                                                  participants=self.project_manager.participants,
                                                  modulators=self.modulators)

    @pyqtSlot()
    def on_btn_open_clicked(self):
        dialog = FileOperator.get_open_dialog(directory_mode=False, parent=self, name_filter="fuzz")
        if dialog.exec_():
            for filename in dialog.selectedFiles():
                self.load_from_file(filename)

    def load_from_file(self, filename: str):
        try:
            self.modulators = ProjectManager.read_modulators_from_file(filename)
            self.table_model.protocol.from_xml_file(filename)
            self.refresh_pause_list()
            self.refresh_estimated_time()
            self.refresh_modulators()
            self.show_modulation_info()
            self.refresh_table()
            self.set_fuzzing_ui_status()
        except:
            logger.error("You done something wrong to the xml fuzzing profile.")

    @pyqtSlot()
    def on_project_updated(self):
        self.table_model.refresh_vertical_header()

    def set_network_sdr_send_button_visibility(self):
        is_plugin_enabled = PluginManager().is_plugin_enabled("NetworkSDRInterface")
        self.ui.btnNetworkSDRSend.setVisible(is_plugin_enabled)

    def set_rfcat_button_visibility(self):
        is_plugin_enabled = PluginManager().is_plugin_enabled("RfCat")
        self.ui.btnRfCatSend.setVisible(is_plugin_enabled)

    @pyqtSlot()
    def on_btn_network_sdr_clicked(self):
        if not self.network_sdr_plugin.is_sending:
            messages = self.table_model.protocol.messages
            sample_rates = [self.__get_modulator_of_message(msg).sample_rate for msg in messages]
            self.network_sdr_plugin.start_message_sending_thread(messages, sample_rates)
        else:
            self.network_sdr_plugin.stop_sending_thread()

    @pyqtSlot(bool)
    def on_network_sdr_sending_status_changed(self, is_sending: bool):
        self.ui.btnNetworkSDRSend.setChecked(is_sending)
        self.ui.btnNetworkSDRSend.setEnabled(True)
        self.ui.btnNetworkSDRSend.setToolTip(
            "Sending in progress" if is_sending else self.network_sdr_button_orig_tooltip)
        if not is_sending:
            self.ui.tableMessages.clearSelection()

    @pyqtSlot()
    def on_network_sdr_sending_stop_requested(self):
        self.ui.btnNetworkSDRSend.setToolTip("Stopping sending")
        self.ui.btnNetworkSDRSend.setEnabled(False)

    @pyqtSlot(int)
    def on_send_message_changed(self, message_index: int):
        self.ui.tableMessages.selectRow(message_index)

    @pyqtSlot()
    def on_btn_rfcat_clicked(self):
        if not self.rfcat_plugin.is_sending:
            messages = self.table_model.protocol.messages
            sample_rates = [self.__get_modulator_of_message(msg).sample_rate for msg in messages]
            self.rfcat_plugin.start_message_sending_thread(messages, sample_rates, self.modulators,
                                                           self.project_manager)
        else:
            self.rfcat_plugin.stop_sending_thread()

    @pyqtSlot(int)
    def on_fuzzing_started(self, num_values: int):
        self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingProgressBar)
        self.ui.progressBarFuzzing.setMaximum(num_values)
        self.ui.progressBarFuzzing.setValue(0)
        QApplication.instance().processEvents()

    @pyqtSlot()
    def on_fuzzing_finished(self):
        self.ui.stackedWidgetFuzzing.setCurrentWidget(self.ui.pageFuzzingUI)
        # Calculate Checksums for Fuzzed Messages
        self.setCursor(Qt.WaitCursor)

        self.unsetCursor()

    @pyqtSlot(int)
    def on_current_fuzzing_message_changed(self, current_message: int):
        self.ui.progressBarFuzzing.setValue(current_message)
        QApplication.instance().processEvents()

    @pyqtSlot(ProtocolAnalyzer)
    def on_first_protocol_added(self, protocol: ProtocolAnalyzer):
        if not self.project_manager.modulation_was_edited:
            self.bootstrap_modulator(protocol)