├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── data ├── CHANGELOG.md ├── Dockerfile ├── azure-pipelines.yml ├── build_icons.py ├── decodings │ ├── enocean_switchtelegram │ ├── enocean_switchtelegram.c │ ├── homematic │ ├── homematic.c │ ├── homematic_complete │ ├── homematic_complete.c │ ├── test │ ├── test.c │ └── test.sh ├── generate_ui.py ├── icons │ ├── appicon.icns │ ├── appicon.ico │ ├── appicon.png │ ├── appicon.svg │ ├── collapse.svg │ ├── decoding.svg │ ├── equals.svg │ ├── equals_qm.svg │ ├── icon.xcf │ ├── lock.svg │ ├── message_type.svg │ ├── modulation.svg │ ├── plus.svg │ ├── sniffer.svg │ ├── spectrum.svg │ ├── splitter_handle_horizontal.svg │ ├── splitter_handle_vertical.svg │ ├── uncollapse.svg │ └── unlock.svg ├── inno.iss ├── pyinstaller_helper.py ├── release.py ├── requirements.txt ├── ui │ ├── advanced_modulation_settings.ui │ ├── analysis.ui │ ├── checksum_options_widget.ui │ ├── csv_wizard.ui │ ├── decoding.ui │ ├── filter_bandwidth_dialog.ui │ ├── filter_dialog.ui │ ├── fuzzing.ui │ ├── generator.ui │ ├── main.ui │ ├── messagetype_options.ui │ ├── modulation.ui │ ├── modulation_parameters_dialog.ui │ ├── modulation_settings_widget.ui │ ├── options.ui │ ├── plugins.ui │ ├── project.ui │ ├── properties_dialog.ui │ ├── send_recv.ui │ ├── send_recv_device_settings.ui │ ├── send_recv_sniff_settings.ui │ ├── signal_details.ui │ ├── signal_frame.ui │ ├── simulator.ui │ ├── simulator_dialog.ui │ ├── tab_interpretation.ui │ └── urh.qrc └── urh.desktop ├── setup.py ├── src └── urh │ ├── __init__.py │ ├── ainterpretation │ ├── AutoInterpretation.py │ ├── Wavelet.py │ └── __init__.py │ ├── awre │ ├── AutoAssigner.py │ ├── CommonRange.py │ ├── FormatFinder.py │ ├── Histogram.py │ ├── MessageTypeBuilder.py │ ├── Preprocessor.py │ ├── ProtocolGenerator.py │ ├── __init__.py │ └── engines │ │ ├── AddressEngine.py │ │ ├── ChecksumEngine.py │ │ ├── Engine.py │ │ ├── LengthEngine.py │ │ ├── SequenceNumberEngine.py │ │ └── __init__.py │ ├── cli │ ├── __init__.py │ └── urh_cli.py │ ├── colormaps.py │ ├── constants.py │ ├── controller │ ├── CompareFrameController.py │ ├── GeneratorTabController.py │ ├── MainController.py │ ├── SignalTabController.py │ ├── SimulatorTabController.py │ ├── __init__.py │ ├── dialogs │ │ ├── AdvancedModulationOptionsDialog.py │ │ ├── CSVImportDialog.py │ │ ├── ContinuousSendDialog.py │ │ ├── DecoderDialog.py │ │ ├── FilterBandwidthDialog.py │ │ ├── FilterDialog.py │ │ ├── FuzzingDialog.py │ │ ├── MessageTypeDialog.py │ │ ├── ModulationParametersDialog.py │ │ ├── ModulatorDialog.py │ │ ├── OptionsDialog.py │ │ ├── ProjectDialog.py │ │ ├── ProtocolLabelDialog.py │ │ ├── ProtocolSniffDialog.py │ │ ├── ReceiveDialog.py │ │ ├── SendDialog.py │ │ ├── SendRecvDialog.py │ │ ├── SignalDetailsDialog.py │ │ ├── SimulatorDialog.py │ │ ├── SpectrumDialogController.py │ │ └── __init__.py │ └── widgets │ │ ├── ChecksumWidget.py │ │ ├── DeviceSettingsWidget.py │ │ ├── ModulationSettingsWidget.py │ │ ├── PluginFrame.py │ │ ├── SignalFrame.py │ │ ├── SniffSettingsWidget.py │ │ └── __init__.py │ ├── cythonext │ ├── .gitignore │ ├── __init__.py │ ├── analyze.py │ ├── auto_interpretation.pyx │ ├── awre_util.pyx │ ├── build.py │ ├── path_creator.pyx │ ├── signal_functions.pyx │ ├── util.pxd │ └── util.pyx │ ├── dev │ ├── BackendHandler.py │ ├── EndlessSender.py │ ├── PCAP.py │ ├── VirtualDevice.py │ ├── __init__.py │ ├── config.py │ ├── gr │ │ ├── AbstractBaseThread.py │ │ ├── ReceiverThread.py │ │ ├── SenderThread.py │ │ ├── SpectrumThread.py │ │ ├── __init__.py │ │ └── scripts │ │ │ ├── Initializer.py │ │ │ ├── InputHandlerThread.py │ │ │ ├── __init__.py │ │ │ ├── airspy_recv.grc │ │ │ ├── airspy_recv.py │ │ │ ├── bladerf_recv.grc │ │ │ ├── bladerf_recv.py │ │ │ ├── bladerf_send.grc │ │ │ ├── bladerf_send.py │ │ │ ├── funcube_recv.grc │ │ │ ├── funcube_recv.py │ │ │ ├── hackrf_recv.grc │ │ │ ├── hackrf_recv.py │ │ │ ├── hackrf_send.grc │ │ │ ├── hackrf_send.py │ │ │ ├── rtl-sdr_recv.py │ │ │ ├── sdrplay_recv.grc │ │ │ ├── sdrplay_recv.py │ │ │ ├── usrp_recv.grc │ │ │ ├── usrp_recv.py │ │ │ ├── usrp_send.grc │ │ │ └── usrp_send.py │ └── native │ │ ├── AirSpy.py │ │ ├── BladeRF.py │ │ ├── Device.py │ │ ├── ExtensionHelper.py │ │ ├── HackRF.py │ │ ├── LimeSDR.py │ │ ├── PlutoSDR.py │ │ ├── RTLSDR.py │ │ ├── RTLSDRTCP.py │ │ ├── SDRPlay.py │ │ ├── SendConfig.py │ │ ├── SoundCard.py │ │ ├── USRP.py │ │ ├── __init__.py │ │ └── lib │ │ ├── .gitignore │ │ ├── __init__.py │ │ ├── airspy.pyx │ │ ├── bladerf.pyx │ │ ├── cairspy.pxd │ │ ├── cbladerf.pxd │ │ ├── chackrf.pxd │ │ ├── climesdr.pxd │ │ ├── cplutosdr.pxd │ │ ├── crtlsdr.pxd │ │ ├── csdrplay.pxd │ │ ├── cusrp.pxd │ │ ├── hackrf.pyx │ │ ├── limesdr.pyx │ │ ├── plutosdr.pyx │ │ ├── rtlsdr.pyx │ │ ├── sdrplay.pyx │ │ └── usrp.pyx │ ├── main.py │ ├── models │ ├── FieldTypeTableModel.py │ ├── FileFilterProxyModel.py │ ├── FileIconProvider.py │ ├── FileSystemModel.py │ ├── FuzzingTableModel.py │ ├── GeneratorListModel.py │ ├── GeneratorTableModel.py │ ├── GeneratorTreeModel.py │ ├── LabelValueTableModel.py │ ├── MessageTypeTableModel.py │ ├── PLabelTableModel.py │ ├── ParticipantLegendListModel.py │ ├── ParticipantListModel.py │ ├── ParticipantTableModel.py │ ├── PluginListModel.py │ ├── ProtocolTableModel.py │ ├── ProtocolTreeItem.py │ ├── ProtocolTreeModel.py │ ├── RulesetTableModel.py │ ├── SimulatorMessageFieldModel.py │ ├── SimulatorMessageTableModel.py │ ├── SimulatorParticipantListModel.py │ ├── TableModel.py │ └── __init__.py │ ├── plugins │ ├── InsertSine │ │ ├── InsertSinePlugin.py │ │ ├── __init__.py │ │ ├── descr.txt │ │ ├── insert_sine_dialog.ui │ │ └── settings.ui │ ├── MessageBreak │ │ ├── MessageBreakAction.py │ │ ├── MessageBreakPlugin.py │ │ ├── __init__.py │ │ ├── descr.txt │ │ └── settings.ui │ ├── NetworkSDRInterface │ │ ├── NetworkSDRInterfacePlugin.py │ │ ├── __init__.py │ │ ├── descr.txt │ │ └── settings.ui │ ├── Plugin.py │ ├── PluginManager.py │ ├── RfCat │ │ ├── RfCatPlugin.py │ │ ├── __init__.py │ │ ├── descr.txt │ │ └── settings.ui │ ├── ZeroHide │ │ ├── ZeroHideAction.py │ │ ├── ZeroHidePlugin.py │ │ ├── __init__.py │ │ ├── descr.txt │ │ └── settings.ui │ └── __init__.py │ ├── signalprocessing │ ├── ChecksumLabel.py │ ├── ContinuousModulator.py │ ├── Encoding.py │ ├── FieldType.py │ ├── Filter.py │ ├── IQArray.py │ ├── Interval.py │ ├── Message.py │ ├── MessageType.py │ ├── Modulator.py │ ├── Participant.py │ ├── ProtocoLabel.py │ ├── ProtocolAnalyzer.py │ ├── ProtocolAnalyzerContainer.py │ ├── ProtocolGroup.py │ ├── ProtocolSniffer.py │ ├── Ruleset.py │ ├── Signal.py │ ├── Spectrogram.py │ └── __init__.py │ ├── simulator │ ├── ActionItem.py │ ├── GraphicsItem.py │ ├── LabelItem.py │ ├── MessageItem.py │ ├── ParticipantItem.py │ ├── RuleItem.py │ ├── Simulator.py │ ├── SimulatorConfiguration.py │ ├── SimulatorCounterAction.py │ ├── SimulatorExpressionParser.py │ ├── SimulatorGotoAction.py │ ├── SimulatorItem.py │ ├── SimulatorMessage.py │ ├── SimulatorProtocolLabel.py │ ├── SimulatorRule.py │ ├── SimulatorSleepAction.py │ ├── SimulatorTriggerCommandAction.py │ ├── Transcript.py │ ├── UnlabeledRangeItem.py │ └── __init__.py │ ├── ui │ ├── ElidedLabel.py │ ├── ExpressionLineEdit.py │ ├── GeneratorListWidget.py │ ├── KillerDoubleSpinBox.py │ ├── ListWidget.py │ ├── RuleExpressionValidator.py │ ├── ScrollArea.py │ ├── SimulatorScene.py │ ├── __init__.py │ ├── actions │ │ ├── ChangeSignalParameter.py │ │ ├── Clear.py │ │ ├── DeleteBitsAndPauses.py │ │ ├── EditSignalAction.py │ │ ├── Fuzz.py │ │ ├── InsertBitsAndPauses.py │ │ ├── InsertColumn.py │ │ └── __init__.py │ ├── delegates │ │ ├── CheckBoxDelegate.py │ │ ├── ComboBoxDelegate.py │ │ ├── KillerSpinBoxDelegate.py │ │ ├── MessageTypeButtonDelegate.py │ │ ├── ProtocolValueDelegate.py │ │ ├── SectionComboBoxDelegate.py │ │ ├── SpinBoxDelegate.py │ │ └── __init__.py │ ├── painting │ │ ├── ContinuousSceneManager.py │ │ ├── FFTSceneManager.py │ │ ├── GridScene.py │ │ ├── HorizontalSelection.py │ │ ├── LabeledArrow.py │ │ ├── LiveSceneManager.py │ │ ├── SceneManager.py │ │ ├── Selection.py │ │ ├── SignalSceneManager.py │ │ ├── SniffSceneManager.py │ │ ├── SpectrogramScene.py │ │ ├── SpectrogramSceneManager.py │ │ ├── VerticalSelection.py │ │ ├── ZoomableScene.py │ │ └── __init__.py │ ├── ui_advanced_modulation_settings.py │ ├── ui_analysis.py │ ├── ui_checksum_options_widget.py │ ├── ui_csv_wizard.py │ ├── ui_decoding.py │ ├── ui_filter_bandwidth_dialog.py │ ├── ui_filter_dialog.py │ ├── ui_fuzzing.py │ ├── ui_generator.py │ ├── ui_main.py │ ├── ui_messagetype_options.py │ ├── ui_modulation.py │ ├── ui_modulation_parameters_dialog.py │ ├── ui_modulation_settings_widget.py │ ├── ui_options.py │ ├── ui_plugins.py │ ├── ui_project.py │ ├── ui_properties_dialog.py │ ├── ui_send_recv.py │ ├── ui_send_recv_device_settings.py │ ├── ui_send_recv_sniff_settings.py │ ├── ui_signal_details.py │ ├── ui_signal_frame.py │ ├── ui_simulator.py │ ├── ui_simulator_dialog.py │ ├── ui_tab_interpretation.py │ ├── urh_rc.py │ ├── views │ │ ├── DirectoryTreeView.py │ │ ├── EditableGraphicView.py │ │ ├── EpicGraphicView.py │ │ ├── FuzzingTableView.py │ │ ├── GeneratorListView.py │ │ ├── GeneratorTableView.py │ │ ├── GeneratorTreeView.py │ │ ├── LabelValueTableView.py │ │ ├── LegendGraphicView.py │ │ ├── LiveGraphicView.py │ │ ├── LoggingGraphicsView.py │ │ ├── MessageTypeTableView.py │ │ ├── ModulatorTreeView.py │ │ ├── ParticipantTableView.py │ │ ├── ProtocolLabelTableView.py │ │ ├── ProtocolTableView.py │ │ ├── ProtocolTreeView.py │ │ ├── SelectableGraphicView.py │ │ ├── SimulatorGraphicsView.py │ │ ├── SimulatorLabelTableView.py │ │ ├── SimulatorMessageTableView.py │ │ ├── SpectrogramGraphicView.py │ │ ├── TableView.py │ │ ├── TextEditProtocolView.py │ │ ├── ZoomAndDropableGraphicView.py │ │ ├── ZoomableGraphicView.py │ │ └── __init__.py │ └── xtra_icons_rc.py │ ├── util │ ├── Errors.py │ ├── FileOperator.py │ ├── Formatter.py │ ├── GenericCRC.py │ ├── HTMLFormatter.py │ ├── Logger.py │ ├── ProjectManager.py │ ├── RingBuffer.py │ ├── SettingsProxy.py │ ├── WSPChecksum.py │ ├── __init__.py │ └── util.py │ └── version.py └── tests ├── .coveragerc ├── PlotTests.py ├── QtTestCase.py ├── SpectrogramTest.py ├── TestExternalDecodings.py ├── TestGeneratorTablePerformance.py ├── TestInstallation.py ├── __init__.py ├── auto_interpretation ├── __init__.py ├── auto_interpretation_test_util.py ├── test_additional_signals.py ├── test_auto_interpretation_integration.py ├── test_bit_length_detection.py ├── test_center_detection.py ├── test_estimate_tolerance.py ├── test_message_segmentation.py ├── test_modulation_detection.py └── test_noise_detection.py ├── awre ├── AWRETestCase.py ├── AWRExperiments.py ├── TestAWREHistograms.py ├── __init__.py ├── test_address_engine.py ├── test_awre_preprocessing.py ├── test_awre_real_protocols.py ├── test_checksum_engine.py ├── test_common_range.py ├── test_format_finder.py ├── test_generated_protocols.py ├── test_length_engine.py ├── test_partially_labeled.py └── test_sequence_number_engine.py ├── cli ├── switch_socket.sh ├── test_cli_logic.py └── test_cli_parsing.py ├── data ├── 35_messages.proto.xml ├── ASK_mod.complex ├── FSK10.complex ├── FSK15.complex ├── TestProjectForCLI.xml ├── ack_frames_with_crc.proto.xml ├── ask.complex ├── ask50.complex ├── ask_short.complex ├── awre_zeroed_crc.txt ├── cc1101.complex ├── code.py ├── constant_bits.txt ├── csvtest.csv ├── decode.py ├── decoded_bits.txt ├── decoded_with_trash.txt ├── elektromaten.coco ├── encode.py ├── enocean.complex ├── enocean_bits.txt ├── esaver.coco ├── external_program_simulator.py ├── four_broken.proto.xml ├── four_participants.proto.xml ├── fsk.complex ├── fsk_live.coco ├── homematic.coco ├── homematic.proto.xml ├── misaligned.txt ├── multi_messages_different_rssi.coco ├── no_preamble24.proto.xml ├── noised_homematic.complex ├── one_address_one_mt.proto.xml ├── only_one_address.proto.xml ├── ook_overshoot.coco ├── protocol.proto.xml ├── protocol_wsp.proto.xml ├── psk_gen_noisy.complex ├── psk_generated.complex ├── pwm.coco ├── rwe.proto.xml ├── steckdose_anlernen.complex ├── testprofile.sim.xml ├── three_channels.complex ├── three_syncs.proto.xml ├── two_participants.coco ├── unaveraged.coco ├── undecoded.txt ├── with_checksum.proto.xml ├── without_ack_random_data.proto.xml ├── without_ack_random_data2.proto.xml ├── wsp.complex └── xavax.coco ├── debug_tests.py ├── device ├── HackRFTests.py ├── TestAirSpy.py ├── TestBladeRF.py ├── TestLimeSDR.py ├── TestPlutoSDR.py ├── TestRTLSDR.py ├── TestRTLSDRPipe.py ├── TestRTLSDRTCP.py ├── TestSDRPlay.py ├── TestSoundCard.py ├── TestUSRP.py └── __init__.py ├── docker ├── __init__.py ├── archlinux │ ├── Dockerfile │ └── install_from_aur.sh ├── centos7 │ └── Dockerfile ├── debian8 │ ├── Dockerfile │ └── install_from_source.sh ├── docker_util.py ├── fedora25 │ └── Dockerfile ├── gentoo │ ├── Dockerfile │ └── run_from_source.sh ├── info.txt ├── kali │ ├── Dockerfile │ └── install_with_pip.sh ├── ubuntu1404 │ ├── Dockerfile │ └── install_with_pip.sh └── ubuntu1604 │ ├── Dockerfile │ └── test_native_backends_installed.sh ├── performance ├── fft.py ├── live_spectrogram.py ├── modulator_performance.py ├── simulator_perfomance.py └── spectrum_analyzer.py ├── test_CRC.py ├── test_advanced_modulation_settings.py ├── test_analysis_tab_GUI.py ├── test_auto_assignments.py ├── test_checksum_widget.py ├── test_continuous_modulator.py ├── test_crc_gui_integration.py ├── test_csv_import_dialog.py ├── test_decoding_gui.py ├── test_demodulations.py ├── test_encoding.py ├── test_file_operator.py ├── test_filter.py ├── test_filter_bandwidth_dialog.py ├── test_fuzzing_dialog.py ├── test_fuzzing_profile.py ├── test_generator.py ├── test_interval.py ├── test_iq_array.py ├── test_labels.py ├── test_maincontroller_gui.py ├── test_message_type_options_gui.py ├── test_messagetype.py ├── test_modulator.py ├── test_modulator_gui.py ├── test_options_gui.py ├── test_plugins.py ├── test_project_manager.py ├── test_protocol_analyzer.py ├── test_protocol_label_dialog.py ├── test_protocol_sniffer.py ├── test_ringbuffer.py ├── test_send_recv_dialog_gui.py ├── test_signal_details_gui.py ├── test_signal_tab_GUI.py ├── test_simulator.py ├── test_simulator_dialog.py ├── test_simulator_tab_gui.py ├── test_spectrogram.py ├── test_util.py └── utils_testing.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6WDFF59DL56Z2'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | ##### Expected Behavior 8 | 9 | 10 | ##### Actual Behavior 11 | 12 | ##### Steps To Reproduce 13 | 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | ##### Screenshots 20 | 21 | 22 | ##### Platform Specifications 23 | 24 | - OS: [e.g. Arch Linux] 25 | - URH version: [e.g. 2.4.2] 26 | - Python version: [e.g. 3.7] 27 | - Installed via [e.g. pip] 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea to enhance URH 4 | 5 | --- 6 | 7 | ##### Is your feature request related to a problem? 8 | 10 | 11 | ##### Describe the solution you'd like 12 | 13 | 14 | ##### Describe alternatives you've considered 15 | 16 | 17 | ##### Additional context 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C extensions 2 | *.so 3 | 4 | .coverage 5 | 6 | .pytest_cache 7 | 8 | # Pycharm 9 | .idea/ 10 | 11 | venv/ 12 | 13 | src/__pycache__/ 14 | src/urh/__pycache__/ 15 | __pycache__/ 16 | build/ 17 | src/urh/awre/__pycache__/ 18 | src/urh/awre/components/__pycache__/ 19 | src/urh/controller/__pycache__/ 20 | src/urh/cythonext/__pycache__/ 21 | src/urh/dev/__pycache__/ 22 | src/urh/dev/gr/__pycache__/ 23 | *.pyc 24 | src/urh/dev/native/__pycache__/ 25 | src/urh/dev/native/lib/__pycache__/ 26 | src/urh/models/__pycache__/ 27 | src/urh/plugins/MessageBreak/__pycache__/ 28 | src/urh/plugins/ZeroHide/__pycache__/ 29 | src/urh/plugins/__pycache__/ 30 | src/urh/signalprocessing/__pycache__/ 31 | src/urh/ui/__pycache__/ 32 | src/urh/ui/actions/__pycache__/ 33 | src/urh/ui/delegates/__pycache__/ 34 | src/urh/ui/views/__pycache__/ 35 | src/urh/util/__pycache__/ 36 | tmp/ 37 | dist/ 38 | src/urh.egg-info/ 39 | .cache/ 40 | tests/data/URHProject.xml 41 | src/urh/cythonext/*.html 42 | src/urh/cythonext/*.c 43 | src/urh/cythonext/*.cpp 44 | 45 | src/urh/dev/native/lib/*.html 46 | src/urh/dev/native/lib/*.c 47 | src/urh/dev/native/lib/*.cpp 48 | 49 | /tests/show_gui 50 | 51 | 52 | 53 | misc/* 54 | -------------------------------------------------------------------------------- /data/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | LABEL maintainer="Johannes.Pohl90@gmail.com" 4 | 5 | ENV TZ=Europe/Berlin 6 | 7 | # Debug QT plugins by exporting QT_DEBUG_PLUGINS=1 before running URH 8 | # To allow docker to connect to X run xhost +local:docker 9 | 10 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \ 11 | && apt-get -qq update \ 12 | && apt-get -qq install software-properties-common \ 13 | && add-apt-repository -y ppa:myriadrf/drivers && apt-get -qq update \ 14 | && apt-get -qq install wget gcc g++ git \ 15 | python-pip python-zmq \ 16 | python3 python3-pip python3-pyaudio python3-pyqt5 python3-numpy python3-zmq python3-psutil \ 17 | fonts-dejavu-core libgles2-mesa libusb-1.0-0 \ 18 | gr-osmosdr libgnuradio-zeromq* \ 19 | libhackrf-dev liblimesuite-dev libbladerf-dev librtlsdr-dev libairspy-dev libuhd-dev libiio-dev \ 20 | && python2 -m pip install pyusb \ 21 | && python3 -m pip install setuptools cython \ 22 | && mkdir /tmp/sdrplay \ 23 | && wget http://www.sdrplay.com/software/SDRplay_RSP_API-Linux-2.13.1.run -O /tmp/sdrplay/sdrplay.run \ 24 | && cd /tmp/sdrplay && bash sdrplay.run --tar xf \ 25 | && cp mirsdrapi-rsp.h /usr/local/include \ 26 | && cp x86_64/libmirsdrapi-rsp.so.2.13 /usr/lib/x86_64-linux-gnu/ \ 27 | && ln -s /usr/lib/x86_64-linux-gnu/libmirsdrapi-rsp.so.2.13 /usr/lib/x86_64-linux-gnu/libmirsdrapi-rsp.so \ 28 | && rm -rf /tmp/sdrplay \ 29 | \ 30 | && cd /tmp && git clone --depth=1 https://github.com/jopohl/urh \ 31 | && cd /tmp/urh \ 32 | && python3 setup.py install \ 33 | && rm -rf /tmp/urh \ 34 | \ 35 | && cd /tmp && git clone --depth=1 https://github.com/atlas0fd00m/rfcat \ 36 | && cd rfcat \ 37 | && python setup.py install \ 38 | && rm -rf ../rfcat \ 39 | \ 40 | && apt-get -qq remove wget gcc g++ git ttf-bitstream-vera \ 41 | && apt-get -qq autoremove \ 42 | && dbus-uuidgen > /var/lib/dbus/machine-id \ 43 | && apt-get -qq clean all \ 44 | && mkdir /tmp/runtime-root \ 45 | && chmod 0700 /tmp/runtime-root 46 | 47 | CMD XDG_RUNTIME_DIR=/tmp/runtime-root urh 48 | -------------------------------------------------------------------------------- /data/decodings/enocean_switchtelegram: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/data/decodings/enocean_switchtelegram -------------------------------------------------------------------------------- /data/decodings/enocean_switchtelegram.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char **argv) 5 | { 6 | int i, len = strlen(argv[2]); 7 | 8 | if (argc>2) 9 | { 10 | if (argv[1][0]=='d') 11 | for(i = 0; i < len; i++) 12 | switch(i % 12) 13 | { 14 | case 0: 15 | case 1: 16 | case 2: 17 | case 4: 18 | case 5: 19 | case 6: 20 | case 8: 21 | case 9: 22 | putchar(argv[2][i]); 23 | break; 24 | default: 25 | break; 26 | } 27 | else 28 | for(i = 0; i < len; i++) 29 | switch(i % 8) 30 | { 31 | case 0: 32 | case 1: 33 | case 3: 34 | case 4: 35 | case 6: 36 | putchar(argv[2][i]); 37 | break; 38 | case 2: 39 | case 5: 40 | putchar(argv[2][i]); 41 | if(argv[2][i] == '0') putchar('1'); 42 | else putchar('0'); 43 | break; 44 | case 7: 45 | putchar(argv[2][i]); 46 | if(i < len - 1) 47 | printf("01"); 48 | } 49 | 50 | } 51 | else printf("Usage: %s \n\td - decode\n\te - encode\n\tbit sequence as string of 0 and 1.\n", argv[0]); 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /data/decodings/homematic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/data/decodings/homematic -------------------------------------------------------------------------------- /data/decodings/homematic_complete: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/data/decodings/homematic_complete -------------------------------------------------------------------------------- /data/decodings/test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/data/decodings/test -------------------------------------------------------------------------------- /data/decodings/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char **argv) 5 | { 6 | int i, count, what; 7 | if(argc>2) 8 | { 9 | if(argv[1][0]=='d') 10 | { 11 | for(i = 0; i < strlen(argv[2]); i++) 12 | if(argv[2][i] == '0') printf("000"); 13 | else printf("11"); 14 | } 15 | else 16 | { 17 | count = 0; 18 | what = -1; 19 | for(i = 0; i < strlen(argv[2]); i++) 20 | { 21 | if(argv[2][i] == '0') 22 | { 23 | if(what == 1) count = 0; 24 | what = 0; 25 | count++; 26 | if(count == 3) 27 | { 28 | putchar('0'); 29 | count = 0; 30 | } 31 | } 32 | else 33 | { 34 | if(what == 0) count = 0; 35 | what = 1; 36 | count++; 37 | if(count == 2) 38 | { 39 | putchar('1'); 40 | count = 0; 41 | } 42 | } 43 | } 44 | } 45 | } 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /data/decodings/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "$1$1" 3 | -------------------------------------------------------------------------------- /data/icons/appicon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/data/icons/appicon.icns -------------------------------------------------------------------------------- /data/icons/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/data/icons/appicon.ico -------------------------------------------------------------------------------- /data/icons/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/data/icons/appicon.png -------------------------------------------------------------------------------- /data/icons/collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 38 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 56 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /data/icons/icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/data/icons/icon.xcf -------------------------------------------------------------------------------- /data/icons/message_type.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 38 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 56 | MT 67 | 68 | 69 | -------------------------------------------------------------------------------- /data/icons/spectrum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 38 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 56 | 60 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /data/icons/uncollapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 38 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 56 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /data/inno.iss: -------------------------------------------------------------------------------- 1 | [Setup] 2 | ; NOTE: The value of AppId uniquely identifies this application. 3 | ; Do not use the same AppId value in installers for other applications. 4 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 5 | AppId={{8A0E36A6-DE56-46B8-BCC3-C16D1BC759CC} 6 | AppName=Universal Radio Hacker 7 | AppVersion={#MyAppVersion} 8 | VersionInfoVersion={#MyAppVersion} 9 | ArchitecturesInstallIn64BitMode=x64 10 | UninstallDisplayName=Universal Radio Hacker 11 | AppPublisher=Johannes Pohl 12 | AppPublisherURL=https://github.com/jopohl/urh 13 | AppSupportURL=https://github.com/jopohl/urh 14 | AppUpdatesURL=https://github.com/jopohl/urh 15 | DefaultDirName={pf}\Universal Radio Hacker 16 | DisableProgramGroupPage=yes 17 | LicenseFile=..\LICENSE 18 | OutputDir=..\dist 19 | OutputBaseFilename=Universal.Radio.Hacker-{#MyAppVersion}-{#Arch} 20 | Compression=lzma2/ultra 21 | SolidCompression=yes 22 | 23 | [Languages] 24 | Name: "english"; MessagesFile: "compiler:Default.isl" 25 | 26 | [Tasks] 27 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 28 | 29 | [Files] 30 | Source: "..\pyinstaller\urh\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 31 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 32 | 33 | [Icons] 34 | Name: "{commonprograms}\Universal Radio Hacker"; Filename: "{app}\urh.exe" 35 | Name: "{commondesktop}\Universal Radio Hacker"; Filename: "{app}\urh.exe"; Tasks: desktopicon 36 | 37 | [Run] 38 | Filename: "{app}\urh.exe"; Description: "{cm:LaunchProgram,Universal Radio Hacker}"; Flags: nowait postinstall skipifsilent 39 | -------------------------------------------------------------------------------- /data/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.9; sys_platform != 'win32' 2 | numpy>=1.9,!=1.16.0; sys_platform == 'win32' 3 | pyqt5!=5.12.3,!=5.13.1; sys_platform != 'win32' and sys_platform != 'linux' 4 | pyqt5!=5.11.1,!=5.11.2,!=5.11.3,!=5.12.3,!=5.13.0; sys_platform == 'win32' 5 | pyqt5!=5.12,!=5.12.1,!=5.12.2,!=5.12.3,!=5.13.0,!=5.13.1,!=5.13.2; sys_platform == 'linux' 6 | psutil 7 | pyzmq 8 | cython 9 | -------------------------------------------------------------------------------- /data/ui/modulation_parameters_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | DialogModulationParameters 4 | 5 | 6 | 7 | 0 8 | 0 9 | 303 10 | 286 11 | 12 | 13 | 14 | Modulation Parameters 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | false 24 | 25 | 26 | 2 27 | 28 | 29 | true 30 | 31 | 32 | false 33 | 34 | 35 | 36 | 37 | 38 | Symbol 39 | 40 | 41 | 42 | 43 | Amplitude 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /data/ui/urh.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | ../icons/appicon.ico 4 | ../icons/decoding.svg 5 | ../icons/equals_qm.svg 6 | ../icons/appicon.png 7 | ../icons/collapse.svg 8 | ../icons/uncollapse.svg 9 | ../icons/equals.svg 10 | ../icons/lock.svg 11 | ../icons/modulation.svg 12 | ../icons/plus.svg 13 | ../icons/sniffer.svg 14 | ../icons/spectrum.svg 15 | ../icons/splitter_handle_horizontal.svg 16 | ../icons/splitter_handle_vertical.svg 17 | ../icons/unlock.svg 18 | ../icons/message_type.svg 19 | 20 | 21 | -------------------------------------------------------------------------------- /data/urh.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=Universal Radio Hacker 4 | Comment=investigate wireless protocols like a boss 5 | Exec=/usr/bin/urh 6 | Icon=urh 7 | Terminal=false 8 | -------------------------------------------------------------------------------- /src/urh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/__init__.py -------------------------------------------------------------------------------- /src/urh/ainterpretation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/ainterpretation/__init__.py -------------------------------------------------------------------------------- /src/urh/awre/AutoAssigner.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from urh.cythonext import util 4 | from urh.signalprocessing.Message import Message 5 | 6 | 7 | def auto_assign_participants(messages, participants): 8 | """ 9 | 10 | :type messages: list of Message 11 | :type participants: list of Participant 12 | :return: 13 | """ 14 | if len(participants) == 0: 15 | return 16 | 17 | if len(participants) == 1: 18 | for message in messages: # type: Message 19 | message.participant = participants[0] 20 | return 21 | 22 | # Try to assign participants based on SRC_ADDRESS label and participant address 23 | for msg in filter(lambda m: m.participant is None, messages): 24 | src_address = msg.get_src_address_from_data() 25 | if src_address: 26 | try: 27 | msg.participant = next(p for p in participants if p.address_hex == src_address) 28 | except StopIteration: 29 | pass 30 | 31 | # Assign remaining participants based on RSSI of messages 32 | rssis = np.array([msg.rssi for msg in messages], dtype=np.float32) 33 | min_rssi, max_rssi = util.minmax(rssis) 34 | center_spacing = (max_rssi - min_rssi) / (len(participants) - 1) 35 | centers = [min_rssi + i * center_spacing for i in range(0, len(participants))] 36 | rssi_assigned_centers = [] 37 | 38 | for rssi in rssis: 39 | center_index = np.argmin(np.abs(rssi - centers)) 40 | rssi_assigned_centers.append(int(center_index)) 41 | 42 | participants.sort(key=lambda participant: participant.relative_rssi) 43 | for message, center_index in zip(messages, rssi_assigned_centers): 44 | if message.participant is None: 45 | message.participant = participants[center_index] 46 | 47 | 48 | def auto_assign_participant_addresses(messages, participants): 49 | """ 50 | 51 | :type messages: list of Message 52 | :type participants: list of Participant 53 | :return: 54 | """ 55 | participants_without_address = [p for p in participants if not p.address_hex] 56 | 57 | if len(participants_without_address) == 0: 58 | return 59 | 60 | for msg in messages: 61 | if msg.participant in participants_without_address: 62 | src_address = msg.get_src_address_from_data() 63 | if src_address: 64 | participants_without_address.remove(msg.participant) 65 | msg.participant.address_hex = src_address 66 | -------------------------------------------------------------------------------- /src/urh/awre/MessageTypeBuilder.py: -------------------------------------------------------------------------------- 1 | from urh.signalprocessing.ChecksumLabel import ChecksumLabel 2 | 3 | from urh.signalprocessing.FieldType import FieldType 4 | from urh.signalprocessing.MessageType import MessageType 5 | from urh.signalprocessing.ProtocoLabel import ProtocolLabel 6 | 7 | 8 | class MessageTypeBuilder(object): 9 | def __init__(self, name: str): 10 | self.name = name 11 | self.message_type = MessageType(name) 12 | 13 | def add_label(self, label_type: FieldType.Function, length: int, name: str=None): 14 | try: 15 | start = self.message_type[-1].end 16 | color_index = self.message_type[-1].color_index + 1 17 | except IndexError: 18 | start, color_index = 0, 0 19 | 20 | if name is None: 21 | name = label_type.value 22 | 23 | lbl = ProtocolLabel(name, start, start+length-1, color_index, field_type=FieldType(label_type.name, label_type)) 24 | self.message_type.append(lbl) 25 | 26 | def add_checksum_label(self, length, checksum, data_start=None, data_end=None, name: str=None): 27 | label_type = FieldType.Function.CHECKSUM 28 | try: 29 | start = self.message_type[-1].end 30 | color_index = self.message_type[-1].color_index + 1 31 | except IndexError: 32 | start, color_index = 0, 0 33 | 34 | if name is None: 35 | name = label_type.value 36 | 37 | if data_start is None: 38 | # End of sync or preamble 39 | sync_label = self.message_type.get_first_label_with_type(FieldType.Function.SYNC) 40 | if sync_label: 41 | data_start = sync_label.end 42 | else: 43 | preamble_label = self.message_type.get_first_label_with_type(FieldType.Function.PREAMBLE) 44 | if preamble_label: 45 | data_start = preamble_label.end 46 | else: 47 | data_start = 0 48 | 49 | if data_end is None: 50 | data_end = start 51 | 52 | lbl = ChecksumLabel(name, start, start+length-1, color_index, field_type=FieldType(label_type.name, label_type)) 53 | lbl.data_ranges = [(data_start, data_end)] 54 | lbl.checksum = checksum 55 | self.message_type.append(lbl) 56 | -------------------------------------------------------------------------------- /src/urh/awre/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/awre/__init__.py -------------------------------------------------------------------------------- /src/urh/awre/engines/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/awre/engines/__init__.py -------------------------------------------------------------------------------- /src/urh/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/cli/__init__.py -------------------------------------------------------------------------------- /src/urh/controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/controller/__init__.py -------------------------------------------------------------------------------- /src/urh/controller/dialogs/AdvancedModulationOptionsDialog.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt 2 | from PyQt5.QtWidgets import QDialog 3 | 4 | from urh.ui.ui_advanced_modulation_settings import Ui_DialogAdvancedModSettings 5 | 6 | 7 | class AdvancedModulationOptionsDialog(QDialog): 8 | pause_threshold_edited = pyqtSignal(int) 9 | message_length_divisor_edited = pyqtSignal(int) 10 | 11 | def __init__(self, pause_threshold: int, message_length_divisor: int, parent=None): 12 | super().__init__(parent) 13 | self.ui = Ui_DialogAdvancedModSettings() 14 | self.ui.setupUi(self) 15 | self.setAttribute(Qt.WA_DeleteOnClose) 16 | self.setWindowFlags(Qt.Window) 17 | 18 | self.pause_threshold = pause_threshold 19 | self.message_length_divisor = message_length_divisor 20 | 21 | self.ui.spinBoxPauseThreshold.setValue(pause_threshold) 22 | self.ui.spinBoxMessageLengthDivisor.setValue(message_length_divisor) 23 | 24 | self.create_connects() 25 | 26 | def create_connects(self): 27 | self.ui.buttonBox.accepted.connect(self.on_accept_clicked) 28 | self.ui.buttonBox.rejected.connect(self.reject) 29 | 30 | @pyqtSlot() 31 | def on_accept_clicked(self): 32 | if self.pause_threshold != self.ui.spinBoxPauseThreshold.value(): 33 | self.pause_threshold_edited.emit(self.ui.spinBoxPauseThreshold.value()) 34 | 35 | if self.message_length_divisor != self.ui.spinBoxMessageLengthDivisor.value(): 36 | self.message_length_divisor_edited.emit(self.ui.spinBoxMessageLengthDivisor.value()) 37 | 38 | self.accept() 39 | -------------------------------------------------------------------------------- /src/urh/controller/dialogs/SignalDetailsDialog.py: -------------------------------------------------------------------------------- 1 | import locale 2 | import os 3 | import time 4 | 5 | from PyQt5.QtCore import Qt, pyqtSlot 6 | from PyQt5.QtGui import QCloseEvent 7 | from PyQt5.QtWidgets import QDialog 8 | 9 | from urh import constants 10 | from urh.ui.ui_signal_details import Ui_SignalDetails 11 | from urh.util.Formatter import Formatter 12 | 13 | 14 | class SignalDetailsDialog(QDialog): 15 | def __init__(self, signal, parent=None): 16 | super().__init__(parent) 17 | self.signal = signal 18 | self.ui = Ui_SignalDetails() 19 | self.ui.setupUi(self) 20 | self.setAttribute(Qt.WA_DeleteOnClose) 21 | self.setWindowFlags(Qt.Window) 22 | 23 | file = self.signal.filename 24 | 25 | self.ui.lblName.setText(self.signal.name) 26 | 27 | if os.path.isfile(file): 28 | self.ui.lblFile.setText(file) 29 | self.ui.lblFileSize.setText(locale.format_string("%.2fMB", os.path.getsize(file) / (1024 ** 2))) 30 | self.ui.lFileCreated.setText(time.ctime(os.path.getctime(file))) 31 | else: 32 | self.ui.lblFile.setText(self.tr("signal file not found")) 33 | self.ui.lblFileSize.setText("-") 34 | self.ui.lFileCreated.setText("-") 35 | 36 | self.ui.lblSamplesTotal.setText("{0:n}".format(self.signal.num_samples).replace(",", " ")) 37 | self.ui.dsb_sample_rate.setValue(self.signal.sample_rate) 38 | self.set_duration() 39 | 40 | self.ui.dsb_sample_rate.valueChanged.connect(self.on_dsb_sample_rate_value_changed) 41 | 42 | try: 43 | self.restoreGeometry(constants.SETTINGS.value("{}/geometry".format(self.__class__.__name__))) 44 | except TypeError: 45 | pass 46 | 47 | def closeEvent(self, event: QCloseEvent): 48 | constants.SETTINGS.setValue("{}/geometry".format(self.__class__.__name__), self.saveGeometry()) 49 | super().closeEvent(event) 50 | 51 | @pyqtSlot(float) 52 | def on_dsb_sample_rate_value_changed(self, value: float): 53 | self.signal.sample_rate = value 54 | self.set_duration() 55 | 56 | def set_duration(self): 57 | dur = self.signal.num_samples / self.signal.sample_rate 58 | self.ui.lDuration.setText(Formatter.science_time(dur)) 59 | -------------------------------------------------------------------------------- /src/urh/controller/dialogs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/controller/dialogs/__init__.py -------------------------------------------------------------------------------- /src/urh/controller/widgets/PluginFrame.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import pyqtSlot 2 | from PyQt5.QtGui import QCloseEvent 3 | from PyQt5.QtWidgets import QFrame, QVBoxLayout 4 | 5 | from urh import constants 6 | from urh.models.PluginListModel import PluginListModel 7 | from urh.ui.ui_plugins import Ui_FramePlugins 8 | 9 | 10 | class PluginFrame(QFrame): 11 | def __init__(self, plugins, highlighted_plugins=None, parent=None): 12 | """ 13 | :type plugins: list of Plugin 14 | :type highlighted_plugins: list of Plugin 15 | """ 16 | super().__init__(parent) 17 | self.ui = Ui_FramePlugins() 18 | self.ui.setupUi(self) 19 | self.model = PluginListModel(plugins, highlighted_plugins=highlighted_plugins) 20 | self.ui.listViewPlugins.setModel(self.model) 21 | self.settings_layout = QVBoxLayout() 22 | self.ui.groupBoxSettings.setLayout(self.settings_layout) 23 | self.create_connects() 24 | 25 | try: 26 | self.restoreGeometry(constants.SETTINGS.value("{}/geometry".format(self.__class__.__name__))) 27 | except TypeError: 28 | pass 29 | 30 | 31 | def create_connects(self): 32 | self.ui.listViewPlugins.selectionModel().selectionChanged.connect(self.on_list_selection_changed) 33 | for plugin in self.model.plugins: 34 | if hasattr(plugin, "show_proto_sniff_dialog_clicked"): 35 | plugin.show_proto_sniff_dialog_clicked.connect(self.parent().parent().show_proto_sniff_dialog) 36 | 37 | def save_enabled_states(self): 38 | for plugin in self.model.plugins: 39 | constants.SETTINGS.setValue(plugin.name, plugin.enabled) 40 | 41 | @pyqtSlot() 42 | def on_list_selection_changed(self): 43 | i = self.ui.listViewPlugins.currentIndex().row() 44 | self.ui.txtEditPluginDescription.setText(self.model.plugins[i].description) 45 | 46 | if self.settings_layout.count() > 0: 47 | widget = self.settings_layout.takeAt(0).widget() 48 | self.settings_layout.removeWidget(widget) 49 | widget.setParent(None) 50 | 51 | self.settings_layout.addWidget(self.model.plugins[i].settings_frame) 52 | -------------------------------------------------------------------------------- /src/urh/controller/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/controller/widgets/__init__.py -------------------------------------------------------------------------------- /src/urh/cythonext/.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.pyd 3 | -------------------------------------------------------------------------------- /src/urh/cythonext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/cythonext/__init__.py -------------------------------------------------------------------------------- /src/urh/cythonext/analyze.py: -------------------------------------------------------------------------------- 1 | from subprocess import call, Popen 2 | 3 | MODULES = ["path_creator", "signal_functions", "util", "auto_interpretation"] 4 | 5 | for module in MODULES: 6 | call(["cython", "-a", "--cplus", "-3", module + ".pyx"]) 7 | Popen(["firefox", module + ".html"]) 8 | -------------------------------------------------------------------------------- /src/urh/cythonext/build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import tempfile 4 | from subprocess import call 5 | 6 | build_dir = os.path.join(tempfile.gettempdir(), "build") 7 | 8 | 9 | def main(): 10 | cur_dir = os.path.realpath(__file__) 11 | os.chdir(os.path.realpath(os.path.join(cur_dir, "..", "..", "..", ".."))) 12 | #call([sys.executable, "setup.py", "clean", "--all"]) 13 | call([sys.executable, "setup.py", "build_ext", "--inplace"]) 14 | 15 | 16 | if __name__ == "__main__": 17 | main() 18 | -------------------------------------------------------------------------------- /src/urh/cythonext/util.pxd: -------------------------------------------------------------------------------- 1 | ctypedef fused iq: 2 | char 3 | unsigned char 4 | short 5 | unsigned short 6 | float 7 | 8 | ctypedef iq[:, ::1] IQ 9 | 10 | from libc.stdint cimport uint64_t, uint8_t, int64_t 11 | 12 | cpdef uint64_t bit_array_to_number(uint8_t[:] bits, int64_t end, int64_t start=*) nogil -------------------------------------------------------------------------------- /src/urh/dev/PCAP.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import time 4 | 5 | from urh.util.Logger import logger 6 | 7 | from urh.signalprocessing.Message import Message 8 | 9 | 10 | class PCAP(object): 11 | def __init__(self): 12 | self.timestamp_sec = None 13 | self.timestamp_nsec = None 14 | 15 | def reset_timestamp(self): 16 | self.timestamp_sec = None 17 | self.timestamp_nsec = None 18 | 19 | def build_global_header(self) -> bytes: 20 | MAGIC_NUMBER = 0xa1b23c4d # Nanosecond resolution 21 | VERSION_MAJOR, VERSION_MINOR = 2, 4 22 | THISZONE = 0 23 | SIGFIGS = 0 24 | SNAPLEN = 65535 25 | NETWORK = 147 26 | 27 | self.reset_timestamp() 28 | 29 | return struct.pack(">IHHiIII", MAGIC_NUMBER, VERSION_MAJOR, VERSION_MINOR, THISZONE, SIGFIGS, SNAPLEN, NETWORK) 30 | 31 | def build_packet(self, ts_sec: int, ts_nsec: int, data: bytes) -> bytes: 32 | if self.timestamp_nsec is None or self.timestamp_sec is None: 33 | self.timestamp_sec, self.timestamp_nsec = self.get_seconds_nseconds(time.time()) 34 | 35 | self.timestamp_sec += int(ts_sec) 36 | self.timestamp_nsec += int(ts_nsec) 37 | if self.timestamp_nsec >= 1e9: 38 | self.timestamp_sec += int(self.timestamp_nsec / 1e9) 39 | self.timestamp_nsec = int(self.timestamp_nsec % 1e9) 40 | 41 | l = len(data) 42 | return struct.pack(">IIII", self.timestamp_sec, self.timestamp_nsec, l, l) + data 43 | 44 | def write_packets(self, packets, filename: str, sample_rate: int): 45 | """ 46 | 47 | :type packets: list of Message 48 | :param filename: 49 | :return: 50 | """ 51 | if os.path.isfile(filename): 52 | logger.warning("{0} already exists. Overwriting it".format(filename)) 53 | 54 | with open(filename, "wb") as f: 55 | f.write(self.build_global_header()) 56 | 57 | with open(filename, "ab") as f: 58 | rel_time_offset_ns = 0 59 | for pkt in packets: 60 | f.write(self.build_packet(0, rel_time_offset_ns, pkt.decoded_bits_buffer)) 61 | rel_time_offset_ns = pkt.get_duration(sample_rate) * 10 ** 9 62 | 63 | @staticmethod 64 | def get_seconds_nseconds(timestamp): 65 | seconds = int(timestamp) 66 | nseconds = int((timestamp - seconds) * 10 ** 9) 67 | return seconds, nseconds 68 | -------------------------------------------------------------------------------- /src/urh/dev/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/dev/__init__.py -------------------------------------------------------------------------------- /src/urh/dev/gr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/dev/gr/__init__.py -------------------------------------------------------------------------------- /src/urh/dev/gr/scripts/Initializer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import os 4 | import tempfile 5 | 6 | 7 | def init_path(): 8 | # Append script path at end to prevent conflicts in case of frozen interpreter 9 | sys.path.append(sys.path.pop(0)) 10 | 11 | try: 12 | with open(os.path.join(tempfile.gettempdir(), "gnuradio_path.txt"), "r") as f: 13 | gnuradio_path = f.read().strip() 14 | 15 | os.environ["PATH"] = os.path.join(gnuradio_path, "bin") + os.pathsep + os.environ["PATH"] 16 | sys.path.insert(0, os.path.join(gnuradio_path, "lib", "site-packages")) 17 | 18 | except IOError: 19 | pass 20 | -------------------------------------------------------------------------------- /src/urh/dev/gr/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/dev/gr/scripts/__init__.py -------------------------------------------------------------------------------- /src/urh/dev/native/AirSpy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import time 3 | 4 | from urh.dev.native.Device import Device 5 | from urh.dev.native.lib import airspy 6 | from urh.util.Logger import logger 7 | from multiprocessing.connection import Connection 8 | 9 | 10 | class AirSpy(Device): 11 | DEVICE_LIB = airspy 12 | ASYNCHRONOUS = True 13 | DEVICE_METHODS = Device.DEVICE_METHODS.copy() 14 | DEVICE_METHODS.update({ 15 | Device.Command.SET_FREQUENCY.name: "set_center_frequency", 16 | }) 17 | del DEVICE_METHODS[Device.Command.SET_BANDWIDTH.name] 18 | 19 | DATA_TYPE = np.float32 20 | 21 | @classmethod 22 | def setup_device(cls, ctrl_connection: Connection, device_identifier): 23 | ret = airspy.open() 24 | ctrl_connection.send("OPEN:" + str(ret)) 25 | return ret == 0 26 | 27 | @classmethod 28 | def shutdown_device(cls, ctrl_connection, is_tx=False): 29 | logger.debug("AirSpy: closing device") 30 | ret = airspy.stop_rx() 31 | ctrl_connection.send("Stop RX:" + str(ret)) 32 | 33 | ret = airspy.close() 34 | ctrl_connection.send("EXIT:" + str(ret)) 35 | 36 | return True 37 | 38 | @classmethod 39 | def enter_async_receive_mode(cls, data_connection: Connection, ctrl_connection: Connection): 40 | ret = airspy.start_rx(data_connection.send_bytes) 41 | ctrl_connection.send("Start RX MODE:" + str(ret)) 42 | return ret 43 | 44 | def __init__(self, center_freq, sample_rate, bandwidth, gain, if_gain=1, baseband_gain=1, 45 | resume_on_full_receive_buffer=False): 46 | super().__init__(center_freq=center_freq, sample_rate=sample_rate, bandwidth=bandwidth, 47 | gain=gain, if_gain=if_gain, baseband_gain=baseband_gain, 48 | resume_on_full_receive_buffer=resume_on_full_receive_buffer) 49 | self.success = 0 50 | 51 | self.bandwidth_is_adjustable = False 52 | 53 | @staticmethod 54 | def bytes_to_iq(buffer) -> np.ndarray: 55 | return np.frombuffer(buffer, dtype=np.float32).reshape((-1, 2), order="C") 56 | -------------------------------------------------------------------------------- /src/urh/dev/native/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/dev/native/__init__.py -------------------------------------------------------------------------------- /src/urh/dev/native/lib/.gitignore: -------------------------------------------------------------------------------- 1 | *.so -------------------------------------------------------------------------------- /src/urh/dev/native/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/dev/native/lib/__init__.py -------------------------------------------------------------------------------- /src/urh/dev/native/lib/cplutosdr.pxd: -------------------------------------------------------------------------------- 1 | from libcpp cimport bool 2 | 3 | cdef extern from "iio.h": 4 | struct iio_context 5 | struct iio_device 6 | struct iio_channel 7 | struct iio_buffer 8 | 9 | struct iio_context_info 10 | struct iio_scan_context 11 | 12 | 13 | iio_scan_context * iio_create_scan_context(const char *backend, unsigned int flags) 14 | void iio_scan_context_destroy(iio_scan_context *ctx) 15 | ssize_t iio_scan_context_get_info_list(iio_scan_context *ctx, iio_context_info ***info) 16 | void iio_context_info_list_free(iio_context_info **info) 17 | const char * iio_context_info_get_description(const iio_context_info *info) 18 | const char * iio_context_info_get_uri(const iio_context_info *info) 19 | iio_device * iio_context_find_device(const iio_context *ctx, const char *name) 20 | void iio_context_destroy(iio_context *ctx) 21 | 22 | iio_context* iio_create_default_context() 23 | iio_context * iio_create_context_from_uri(const char *uri) 24 | unsigned int iio_context_get_devices_count(const iio_context *ctx) 25 | iio_device* iio_context_get_device(iio_context *ctx, unsigned int index) 26 | iio_buffer * iio_device_create_buffer(const iio_device *dev, size_t samples_count, bool cyclic) 27 | void iio_buffer_destroy(iio_buffer *buf) 28 | ssize_t iio_buffer_refill(iio_buffer *buf) 29 | ssize_t iio_buffer_step(const iio_buffer *buf) 30 | void * iio_buffer_first(const iio_buffer *buf, const iio_channel *chn) 31 | void * iio_buffer_end(const iio_buffer *buf) 32 | ssize_t iio_buffer_push(iio_buffer *buf) 33 | 34 | const char * iio_device_get_name(const iio_device *dev) 35 | iio_channel * iio_device_find_channel(const iio_device *dev, const char *name, bool output) 36 | unsigned int iio_device_get_channels_count(const iio_device *dev) 37 | iio_channel* iio_device_get_channel(const iio_device *dev, unsigned int index) 38 | 39 | const char * iio_channel_get_id(const iio_channel *chn) 40 | const char * iio_channel_get_name(const iio_channel *chn) 41 | bool iio_channel_is_output(const iio_channel *chn) 42 | void iio_channel_enable(iio_channel *chn) 43 | void iio_channel_disable(iio_channel *chn) 44 | bool iio_channel_is_enabled(iio_channel *chn) 45 | int iio_channel_attr_write_longlong(const iio_channel *chn, const char *attr, long long val) 46 | int iio_channel_attr_write_double(const iio_channel *chn, const char *attr, double val) 47 | ssize_t iio_channel_attr_write(const iio_channel *chn, const char *attr, const char *src) 48 | 49 | -------------------------------------------------------------------------------- /src/urh/models/FileFilterProxyModel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QModelIndex, Qt, QSortFilterProxyModel 2 | from PyQt5.QtGui import QFont, QColor 3 | 4 | 5 | class FileFilterProxyModel(QSortFilterProxyModel): 6 | def __init__(self, parent=None): 7 | super().__init__(parent) 8 | self.open_files = set() 9 | 10 | def filterAcceptsRow(self, source_row: int, source_parent: QModelIndex): 11 | index0 = self.sourceModel().index(source_row, 0, source_parent) 12 | return self.sourceModel().fileName(index0) != "URHProject.xml" 13 | 14 | def get_file_path(self, index: QModelIndex): 15 | return self.sourceModel().filePath(self.mapToSource(index)) 16 | 17 | def data(self, index: QModelIndex, role=None): 18 | if role == Qt.FontRole or role == Qt.TextColorRole: 19 | file_name = self.get_file_path(index) 20 | if hasattr(self, "open_files") and file_name in self.open_files: 21 | if role == Qt.FontRole: 22 | font = QFont() 23 | font.setBold(True) 24 | return font 25 | elif role == Qt.TextColorRole: 26 | return QColor("orange") 27 | 28 | return super().data(index, role) 29 | -------------------------------------------------------------------------------- /src/urh/models/FileIconProvider.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from PyQt5.QtGui import QIcon 4 | from PyQt5.QtCore import QFileInfo 5 | from PyQt5.QtWidgets import QFileIconProvider 6 | 7 | from urh import constants 8 | 9 | 10 | class FileIconProvider(QFileIconProvider): 11 | def __init__(self): 12 | super().__init__() 13 | 14 | def icon(self, arg): 15 | if isinstance(arg, QFileInfo): 16 | try: 17 | if (arg.isDir() and os.path.isfile(os.path.join(arg.filePath(), constants.PROJECT_FILE))) \ 18 | or (arg.isFile() and arg.fileName() == constants.PROJECT_FILE): 19 | return QIcon(":/icons/icons/appicon.png") 20 | except: 21 | # In some environments (e.g. docker) there tend to be encoding errors 22 | pass 23 | 24 | return super().icon(arg) 25 | -------------------------------------------------------------------------------- /src/urh/models/FileSystemModel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QDir 2 | from PyQt5.QtWidgets import QFileSystemModel 3 | 4 | 5 | class FileSystemModel(QFileSystemModel): 6 | def __init__(self, parent=None): 7 | super().__init__(parent) 8 | self.setFilter(QDir.Files | QDir.Dirs | QDir.NoDotAndDotDot) 9 | self.setReadOnly(True) 10 | 11 | def columnCount(self, QModelIndex_parent=None, *args, **kwargs): 12 | return 2 13 | -------------------------------------------------------------------------------- /src/urh/models/GeneratorTreeModel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QIcon, QFont 2 | from PyQt5.QtCore import QModelIndex, Qt 3 | 4 | from urh import constants 5 | from urh.models.ProtocolTreeModel import ProtocolTreeModel 6 | 7 | 8 | class GeneratorTreeModel(ProtocolTreeModel): 9 | def __init__(self, controller, parent=None): 10 | super().__init__(controller, parent) 11 | 12 | def set_root_item(self, root_item): 13 | self.rootItem = root_item 14 | 15 | def flags(self, index: QModelIndex): 16 | if not index.isValid(): 17 | return Qt.ItemIsEnabled 18 | 19 | return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled 20 | 21 | def mimeTypes(self): 22 | return [] # Prohibit Drag Drop in Generator 23 | 24 | def data(self, index: QModelIndex, role=None): 25 | item = self.getItem(index) 26 | if role == Qt.DisplayRole:# 27 | return item.data() 28 | elif role == Qt.DecorationRole and item.is_group: 29 | return QIcon.fromTheme("folder") -------------------------------------------------------------------------------- /src/urh/models/ParticipantLegendListModel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QAbstractListModel, Qt, QModelIndex 2 | from PyQt5.QtGui import QColor 3 | 4 | from urh import constants 5 | 6 | 7 | class ParticipantLegendListModel(QAbstractListModel): 8 | 9 | def __init__(self, participants, parent=None): 10 | """ 11 | 12 | :type participants: list of Participant 13 | """ 14 | super().__init__(parent) 15 | self.participants = participants 16 | 17 | def rowCount(self, QModelIndex_parent=None, *args, **kwargs): 18 | return len(self.participants) + 1 19 | 20 | def data(self, index: QModelIndex, role=None): 21 | row = index.row() 22 | if role == Qt.DisplayRole: 23 | if row == 0: 24 | return "not assigned" 25 | else: 26 | try: 27 | return str(self.participants[row-1]) 28 | except IndexError: 29 | return None 30 | elif role == Qt.BackgroundColorRole: 31 | if row > 0: 32 | try: 33 | return constants.PARTICIPANT_COLORS[self.participants[row-1].color_index] 34 | except IndexError: 35 | return None 36 | elif role == Qt.TextColorRole: 37 | if row > 0: 38 | try: 39 | bgcolor = constants.PARTICIPANT_COLORS[self.participants[row-1].color_index] 40 | red, green, blue = bgcolor.red(), bgcolor.green(), bgcolor.blue() 41 | return QColor("black") if (red * 0.299 + green * 0.587 + blue * 0.114) > 186 else QColor("white") 42 | except IndexError: 43 | return None 44 | 45 | def flags(self, index): 46 | return Qt.ItemIsEnabled 47 | 48 | def update(self): 49 | self.beginResetModel() 50 | self.endResetModel() -------------------------------------------------------------------------------- /src/urh/models/ParticipantListModel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QAbstractListModel, Qt, QModelIndex, pyqtSignal 2 | from urh.signalprocessing.Participant import Participant 3 | 4 | 5 | class ParticipantListModel(QAbstractListModel): 6 | show_state_changed = pyqtSignal() 7 | 8 | def __init__(self, participants, parent=None): 9 | """ 10 | 11 | :type participants: list of Participant 12 | """ 13 | super().__init__(parent) 14 | self.participants = participants 15 | self.show_unassigned = True 16 | 17 | def rowCount(self, QModelIndex_parent=None, *args, **kwargs): 18 | return len(self.participants) + 1 19 | 20 | def data(self, index: QModelIndex, role=None): 21 | row = index.row() 22 | if role == Qt.DisplayRole: 23 | if row == 0: 24 | return "not assigned" 25 | else: 26 | try: 27 | return str(self.participants[row-1]) 28 | except IndexError: 29 | return None 30 | 31 | elif role == Qt.CheckStateRole: 32 | if row == 0: 33 | return Qt.Checked if self.show_unassigned else Qt.Unchecked 34 | else: 35 | try: 36 | return Qt.Checked if self.participants[row-1].show else Qt.Unchecked 37 | except IndexError: 38 | return None 39 | 40 | def setData(self, index: QModelIndex, value, role=None): 41 | if index.row() == 0 and role == Qt.CheckStateRole: 42 | if bool(value) != self.show_unassigned: 43 | self.show_unassigned = bool(value) 44 | self.show_state_changed.emit() 45 | 46 | elif role == Qt.CheckStateRole: 47 | try: 48 | if self.participants[index.row()-1].show != value: 49 | self.participants[index.row()-1].show = value 50 | self.show_state_changed.emit() 51 | except IndexError: 52 | return False 53 | 54 | return True 55 | 56 | def flags(self, index): 57 | return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable 58 | 59 | def update(self): 60 | self.beginResetModel() 61 | self.endResetModel() -------------------------------------------------------------------------------- /src/urh/models/PluginListModel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QAbstractListModel, Qt, QModelIndex 2 | from PyQt5.QtGui import QFont 3 | 4 | from urh import constants 5 | from urh.plugins import Plugin 6 | 7 | 8 | class PluginListModel(QAbstractListModel): 9 | def __init__(self, plugins, highlighted_plugins = None, parent=None): 10 | """ 11 | :type plugins: list of Plugin 12 | :type highlighted_plugins: list of Plugin 13 | """ 14 | super().__init__(parent) 15 | self.plugins = plugins 16 | self.highlighted_plugins = highlighted_plugins if highlighted_plugins is not None else [] 17 | 18 | def rowCount(self, QModelIndex_parent=None, *args, **kwargs): 19 | return len(self.plugins) 20 | 21 | def data(self, index: QModelIndex, role=None): 22 | row = index.row() 23 | if role == Qt.DisplayRole: 24 | return self.plugins[row].name 25 | elif role == Qt.CheckStateRole: 26 | return self.plugins[row].enabled 27 | elif role == Qt.TextColorRole and self.plugins[row] in self.highlighted_plugins: 28 | return constants.HIGHLIGHT_TEXT_FOREGROUND_COLOR 29 | elif role == Qt.BackgroundColorRole and self.plugins[row] in self.highlighted_plugins: 30 | return constants.HIGHLIGHT_TEXT_BACKGROUND_COLOR 31 | elif role == Qt.FontRole and self.plugins[row] in self.highlighted_plugins: 32 | font = QFont() 33 | font.setBold(True) 34 | return font 35 | 36 | def setData(self, index: QModelIndex, value, role=None): 37 | if role == Qt.CheckStateRole: 38 | self.plugins[index.row()].enabled = value 39 | return True 40 | 41 | def flags(self, index): 42 | return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable -------------------------------------------------------------------------------- /src/urh/models/SimulatorParticipantListModel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import Qt, QModelIndex, QAbstractListModel, pyqtSignal 2 | 3 | from urh.signalprocessing.Participant import Participant 4 | from urh.simulator.SimulatorConfiguration import SimulatorConfiguration 5 | 6 | 7 | class SimulatorParticipantListModel(QAbstractListModel): 8 | 9 | participant_simulate_changed = pyqtSignal(Participant) 10 | 11 | def __init__(self, config: SimulatorConfiguration, parent=None): 12 | super().__init__(parent) 13 | self.simulator_config = config 14 | 15 | def update(self): 16 | self.beginResetModel() 17 | self.endResetModel() 18 | 19 | def rowCount(self, parent: QModelIndex = None, *args, **kwargs): 20 | return len(self.simulator_config.active_participants) 21 | 22 | def data(self, index: QModelIndex, role=Qt.DisplayRole): 23 | i = index.row() 24 | participant = self.simulator_config.active_participants[i] 25 | 26 | if not index.isValid(): 27 | return None 28 | 29 | if role == Qt.DisplayRole: 30 | return participant.name + " (" + participant.shortname + ")" 31 | elif role == Qt.CheckStateRole: 32 | return Qt.Checked if participant.simulate else Qt.Unchecked 33 | 34 | def setData(self, index: QModelIndex, value, role=None): 35 | i = index.row() 36 | participants = self.simulator_config.active_participants 37 | if role == Qt.CheckStateRole: 38 | participants[i].simulate = value 39 | self.update() 40 | self.participant_simulate_changed.emit(participants[i]) 41 | 42 | return True 43 | 44 | def flags(self, index: QModelIndex): 45 | return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable 46 | -------------------------------------------------------------------------------- /src/urh/models/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'joe' 2 | -------------------------------------------------------------------------------- /src/urh/plugins/InsertSine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/plugins/InsertSine/__init__.py -------------------------------------------------------------------------------- /src/urh/plugins/InsertSine/descr.txt: -------------------------------------------------------------------------------- 1 | This plugin enables you to insert custom sine waves into your signal. 2 | You will find a new context menu entry in interpretation signal view. 3 | Transform URH into a full fledged signal editor! -------------------------------------------------------------------------------- /src/urh/plugins/InsertSine/settings.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | InsertSineWaveSettings 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Frame 15 | 16 | 17 | QFrame::StyledPanel 18 | 19 | 20 | QFrame::Raised 21 | 22 | 23 | 24 | 25 | 26 | No settings available. 27 | 28 | 29 | 30 | 31 | 32 | 33 | Qt::Vertical 34 | 35 | 36 | 37 | 20 38 | 40 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/urh/plugins/MessageBreak/MessageBreakAction.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from PyQt5.QtWidgets import QUndoCommand 4 | 5 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 6 | from urh.signalprocessing.Message import Message 7 | 8 | 9 | class MessageBreakAction(QUndoCommand): 10 | def __init__(self, proto_analyzer: ProtocolAnalyzer, msg_nr: int, pos: int): 11 | super().__init__() 12 | self.proto_analyzer = proto_analyzer 13 | self.msg_nr = msg_nr 14 | self.pos = pos 15 | self.orig_messages = copy.deepcopy(proto_analyzer.messages) 16 | 17 | self.setText("Break message behind selection") 18 | 19 | def redo(self): 20 | message = copy.deepcopy(self.proto_analyzer.messages[self.msg_nr]) 21 | message1 = Message(plain_bits=message.plain_bits[:self.pos], pause=0, 22 | rssi=0, decoder=message.decoder, message_type=message.message_type, 23 | samples_per_symbol=message.samples_per_symbol) 24 | message2 = Message(plain_bits=message.plain_bits[self.pos:], pause=message.pause, 25 | rssi=0, decoder=message.decoder, message_type=message.message_type, 26 | samples_per_symbol=message.samples_per_symbol) 27 | self.proto_analyzer.messages[self.msg_nr] = message1 28 | self.proto_analyzer.messages.insert(self.msg_nr + 1, message2) 29 | 30 | def undo(self): 31 | self.proto_analyzer.messages = self.orig_messages 32 | 33 | def __get_zero_seq_indexes(self, message: str, following_zeros: int): 34 | """ 35 | :rtype: list[tuple of int] 36 | """ 37 | 38 | result = [] 39 | if following_zeros > len(message): 40 | return result 41 | 42 | zero_counter = 0 43 | for i in range(0, len(message)): 44 | if message[i] == "0": 45 | zero_counter += 1 46 | else: 47 | if zero_counter >= following_zeros: 48 | result.append((i - zero_counter, i)) 49 | zero_counter = 0 50 | 51 | if zero_counter >= following_zeros: 52 | result.append((len(message) - 1 - following_zeros, len(message) - 1)) 53 | 54 | return result 55 | -------------------------------------------------------------------------------- /src/urh/plugins/MessageBreak/MessageBreakPlugin.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QAction, QUndoStack, QMessageBox 2 | 3 | 4 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 5 | from ..Plugin import ProtocolPlugin 6 | from ..MessageBreak.MessageBreakAction import MessageBreakAction 7 | 8 | 9 | class MessageBreakPlugin(ProtocolPlugin): 10 | def __init__(self): 11 | super().__init__(name="MessageBreak") 12 | self.undo_stack = None 13 | self.command = None 14 | """:type: QUndoAction """ 15 | 16 | def get_action(self, parent, undo_stack: QUndoStack, sel_range, protocol: ProtocolAnalyzer, view: int): 17 | """ 18 | :type parent: QTableView 19 | :type undo_stack: QUndoStack 20 | :type protocol_analyzers: list of ProtocolAnalyzer 21 | """ 22 | min_row, max_row, start, end = sel_range 23 | if min_row == -1 or max_row == -1 or start == -1 or end == -1: 24 | return None 25 | 26 | if max_row != min_row: 27 | return None 28 | 29 | end = protocol.convert_index(end, view, 0, True, message_indx=min_row)[0] 30 | # factor = 1 if view == 0 else 4 if view == 1 else 8 31 | 32 | self.command = MessageBreakAction(protocol, max_row, end) 33 | action = QAction(self.command.text(), parent) 34 | action.triggered.connect(self.action_triggered) 35 | self.undo_stack = undo_stack 36 | return action 37 | 38 | def action_triggered(self): 39 | self.undo_stack.push(self.command) 40 | -------------------------------------------------------------------------------- /src/urh/plugins/MessageBreak/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'joe' 2 | -------------------------------------------------------------------------------- /src/urh/plugins/MessageBreak/descr.txt: -------------------------------------------------------------------------------- 1 | This plugin enables you to break a protocol message on an arbitrary position. 2 | This is helpful when you have redundancy in your messages. 3 | 4 | After enabling this plugin you will see a new action in the context menu of the message table in Analysis. 5 | Note, this action is only available if you select a SINGLE message. 6 | If you select multiple messages, the action will not appear in the context menu. -------------------------------------------------------------------------------- /src/urh/plugins/MessageBreak/settings.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FrameSyncCropSettings 4 | 5 | 6 | 7 | 0 8 | 0 9 | 295 10 | 79 11 | 12 | 13 | 14 | Frame 15 | 16 | 17 | QFrame::StyledPanel 18 | 19 | 20 | QFrame::Raised 21 | 22 | 23 | 24 | 25 | 26 | No settings available. 27 | 28 | 29 | 30 | 31 | 32 | 33 | Qt::Vertical 34 | 35 | 36 | 37 | 20 38 | 33 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/urh/plugins/NetworkSDRInterface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/plugins/NetworkSDRInterface/__init__.py -------------------------------------------------------------------------------- /src/urh/plugins/NetworkSDRInterface/descr.txt: -------------------------------------------------------------------------------- 1 | With this plugin you can interface external applications using TCP. 2 | You can use your external application for performing protocol simulation on logical level or advanced modulation/decoding. 3 | If you activate this plugin, a new SDR will be selectable in protocol sniffer dialog. 4 | Furthermore, a new button below generator table will be created. -------------------------------------------------------------------------------- /src/urh/plugins/Plugin.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from PyQt5 import uic 4 | from PyQt5.QtCore import QObject, pyqtSignal, Qt, QSettings 5 | from PyQt5.QtWidgets import QApplication 6 | from PyQt5.QtWidgets import QUndoCommand, QUndoStack 7 | 8 | 9 | class Plugin(QObject): 10 | enabled_changed = pyqtSignal() 11 | 12 | def __init__(self, name: str): 13 | super().__init__() 14 | self.__enabled = Qt.Unchecked 15 | self.name = name 16 | self.plugin_path = "" 17 | self.description = "" 18 | self.__settings_frame = None 19 | self.qsettings = QSettings(QSettings.IniFormat, QSettings.UserScope, "urh", self.name + "-plugin") 20 | 21 | @property 22 | def settings_frame(self): 23 | if self.__settings_frame is None: 24 | self.__settings_frame = uic.loadUi(os.path.join(self.plugin_path, "settings.ui")) 25 | self.create_connects() 26 | return self.__settings_frame 27 | 28 | @property 29 | def enabled(self) -> bool: 30 | return self.__enabled 31 | 32 | @enabled.setter 33 | def enabled(self, value: bool): 34 | if value != self.__enabled: 35 | self.__enabled = Qt.Checked if value else Qt.Unchecked 36 | self.enabled_changed.emit() 37 | 38 | def load_description(self): 39 | descr_file = os.path.join(self.plugin_path, "descr.txt") 40 | try: 41 | with open(descr_file, "r") as f: 42 | self.description = f.read() 43 | except Exception as e: 44 | print(e) 45 | 46 | def destroy_settings_frame(self): 47 | self.__settings_frame = None 48 | 49 | def create_connects(self): 50 | pass 51 | 52 | 53 | class ProtocolPlugin(Plugin): 54 | def __init__(self, name: str): 55 | Plugin.__init__(self, name) 56 | 57 | def get_action(self, parent, undo_stack: QUndoStack, sel_range, groups, 58 | view: int) -> QUndoCommand: 59 | """ 60 | :type parent: QTableView 61 | :type undo_stack: QUndoStack 62 | :type groups: list of ProtocolGroups 63 | """ 64 | raise NotImplementedError("Abstract Method.") 65 | 66 | 67 | class SDRPlugin(Plugin): 68 | def __init__(self, name: str): 69 | Plugin.__init__(self, name) 70 | 71 | 72 | class SignalEditorPlugin(Plugin): 73 | def __init__(self, name: str): 74 | Plugin.__init__(self, name) 75 | -------------------------------------------------------------------------------- /src/urh/plugins/PluginManager.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | import os 3 | 4 | from urh import constants 5 | from urh.plugins.Plugin import Plugin, ProtocolPlugin 6 | from urh.util.Logger import logger 7 | 8 | 9 | class PluginManager(object): 10 | def __init__(self): 11 | self.plugin_path = os.path.dirname(os.path.realpath(__file__)) 12 | self.installed_plugins = self.load_installed_plugins() 13 | 14 | @property 15 | def protocol_plugins(self): 16 | return [p for p in self.installed_plugins if isinstance(p, ProtocolPlugin)] 17 | 18 | def load_installed_plugins(self): 19 | """ :rtype: list of Plugin """ 20 | result = [] 21 | plugin_dirs = [d for d in os.listdir(self.plugin_path) if os.path.isdir(os.path.join(self.plugin_path, d))] 22 | settings = constants.SETTINGS 23 | 24 | for d in plugin_dirs: 25 | if d == "__pycache__": 26 | continue 27 | try: 28 | class_module = self.load_plugin(d) 29 | plugin = class_module() 30 | plugin.plugin_path = os.path.join(self.plugin_path, plugin.name) 31 | plugin.load_description() 32 | plugin.enabled = settings.value(plugin.name, type=bool) if plugin.name in settings.allKeys() else False 33 | result.append(plugin) 34 | except ImportError as e: 35 | logger.warning("Could not load plugin {0} ({1})".format(d, e)) 36 | continue 37 | 38 | return result 39 | 40 | @staticmethod 41 | def load_plugin(plugin_name): 42 | classname = plugin_name + "Plugin" 43 | module_path = "urh.plugins." + plugin_name + "." + classname 44 | 45 | module = importlib.import_module(module_path) 46 | return getattr(module, classname) 47 | 48 | def is_plugin_enabled(self, plugin_name: str): 49 | return any(plugin_name == p.name for p in self.installed_plugins if p.enabled) 50 | 51 | def get_plugin_by_name(self, plugin_name): 52 | for plugin in self.installed_plugins: 53 | if plugin.name == plugin_name: 54 | return plugin 55 | return None 56 | -------------------------------------------------------------------------------- /src/urh/plugins/RfCat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/plugins/RfCat/__init__.py -------------------------------------------------------------------------------- /src/urh/plugins/RfCat/descr.txt: -------------------------------------------------------------------------------- 1 | With this plugin we support sending bytestreams via RfCat (e.g. using YARD Stick One). 2 | Therefore a new button below generator table will be created. 3 | -------------------------------------------------------------------------------- /src/urh/plugins/RfCat/settings.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | RfCatSettings 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Frame 15 | 16 | 17 | QFrame::StyledPanel 18 | 19 | 20 | QFrame::Raised 21 | 22 | 23 | 24 | 25 | 26 | Command to execute 'rfcat'. You can write the full path before 'rfcat' or just 'rfcat' when it is executable from every path. 27 | 28 | 29 | true 30 | 31 | 32 | 33 | 34 | 35 | 36 | rfcat 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | true 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Qt::Vertical 56 | 57 | 58 | 59 | 20 60 | 40 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/urh/plugins/ZeroHide/ZeroHideAction.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QUndoCommand 2 | 3 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 4 | 5 | 6 | class ZeroHideAction(QUndoCommand): 7 | def __init__(self, protocol: ProtocolAnalyzer, following_zeros: int, view: int, zero_hide_offsets: dict): 8 | super().__init__() 9 | self.protocol = protocol 10 | self.following_zeros = following_zeros 11 | self.viewtype = view 12 | 13 | self.setText("Hide zero sequences >= " + str(self.following_zeros)) 14 | 15 | self.zero_hide_offsets = zero_hide_offsets 16 | 17 | def redo(self): 18 | factor = 1 19 | if self.viewtype == 1: 20 | factor = 4 21 | elif self.viewtype == 2: 22 | factor = 8 23 | 24 | pa = self.protocol 25 | self.zero_hide_offsets.clear() 26 | for i in range(pa.num_messages): 27 | message = pa.messages[i] 28 | if self.viewtype == 0: 29 | data = message.decoded_bits_str 30 | elif self.viewtype == 1: 31 | data = message.decoded_hex_str 32 | else: 33 | data = message.decoded_ascii_str 34 | 35 | zero_sequences = self.__get_zero_seq_indexes(data, self.following_zeros) 36 | 37 | self.zero_hide_offsets[i] = {start: end-start for start, end in zero_sequences} 38 | for seq in reversed(zero_sequences): 39 | full_bits = pa.messages[i].decoded_bits 40 | start = seq[0] * factor 41 | end = seq[1] * factor 42 | pa.messages[i].decoded_bits = full_bits[:start] + full_bits[end:] 43 | 44 | def undo(self): 45 | self.zero_hide_offsets.clear() 46 | self.protocol.clear_decoded_bits() 47 | 48 | def __get_zero_seq_indexes(self, message: str, following_zeros: int): 49 | """ 50 | :rtype: list[tuple of int] 51 | """ 52 | 53 | result = [] 54 | if following_zeros > len(message): 55 | return result 56 | 57 | zero_counter = 0 58 | for i in range(0, len(message)): 59 | if message[i] == "0": 60 | zero_counter += 1 61 | else: 62 | if zero_counter >= following_zeros: 63 | result.append((i-zero_counter, i)) 64 | zero_counter = 0 65 | 66 | if zero_counter >= following_zeros: 67 | result.append((len(message) - zero_counter, len(message))) 68 | 69 | return result -------------------------------------------------------------------------------- /src/urh/plugins/ZeroHide/ZeroHidePlugin.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QAction, QUndoStack 2 | 3 | from ..Plugin import ProtocolPlugin 4 | from ..ZeroHide.ZeroHideAction import ZeroHideAction 5 | 6 | 7 | class ZeroHidePlugin(ProtocolPlugin): 8 | def __init__(self): 9 | super().__init__(name="ZeroHide") 10 | 11 | self.following_zeros = 5 if 'following_zeros' not in self.qsettings.allKeys() else self.qsettings.value('following_zeros', type=int) 12 | self.undo_stack = None 13 | self.command = None 14 | self.zero_hide_offsets = dict() 15 | 16 | def create_connects(self): 17 | self.settings_frame.spinBoxFollowingZeros.setValue(self.following_zeros) 18 | self.settings_frame.spinBoxFollowingZeros.valueChanged.connect(self.set_following_zeros) 19 | 20 | def set_following_zeros(self): 21 | self.following_zeros = self.settings_frame.spinBoxFollowingZeros.value() 22 | self.qsettings.setValue('following_zeros', self.following_zeros) 23 | 24 | def get_action(self, parent, undo_stack: QUndoStack, sel_range, protocol, view: int): 25 | """ 26 | :type parent: QTableView 27 | :type undo_stack: QUndoStack 28 | """ 29 | self.command = ZeroHideAction(protocol, self.following_zeros, view, self.zero_hide_offsets) 30 | action = QAction(self.command.text(), parent) 31 | action.triggered.connect(self.action_triggered) 32 | self.undo_stack = undo_stack 33 | return action 34 | 35 | def action_triggered(self): 36 | self.undo_stack.push(self.command) -------------------------------------------------------------------------------- /src/urh/plugins/ZeroHide/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'joe' 2 | -------------------------------------------------------------------------------- /src/urh/plugins/ZeroHide/descr.txt: -------------------------------------------------------------------------------- 1 | This plugin allows you to entirely crop long sequences of zeros in your protocol and focus on relevant data. 2 | You can set a threshold below. All sequences of directly following zeros, which are longer than this threshold will be removed. -------------------------------------------------------------------------------- /src/urh/plugins/ZeroHide/settings.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FrameSyncCropSettings 4 | 5 | 6 | 7 | 0 8 | 0 9 | 295 10 | 79 11 | 12 | 13 | 14 | Frame 15 | 16 | 17 | QFrame::StyledPanel 18 | 19 | 20 | QFrame::Raised 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Threshold: 29 | 30 | 31 | 32 | 33 | 34 | 35 | 1 36 | 37 | 38 | 9999999 39 | 40 | 41 | 5 42 | 43 | 44 | 45 | 46 | 47 | 48 | following zeros 49 | 50 | 51 | 52 | 53 | 54 | 55 | Qt::Horizontal 56 | 57 | 58 | 59 | 40 60 | 20 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Qt::Vertical 71 | 72 | 73 | 74 | 20 75 | 33 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/urh/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'joe' 2 | -------------------------------------------------------------------------------- /src/urh/signalprocessing/Interval.py: -------------------------------------------------------------------------------- 1 | class Interval(object): 2 | __slots__ = ["data"] 3 | 4 | def __init__(self, start: int, end: int): 5 | self.data = (start, end) 6 | 7 | @property 8 | def start(self): 9 | return self.data[0] 10 | 11 | @property 12 | def end(self): 13 | return self.data[1] 14 | 15 | def __hash__(self): 16 | return hash(self.data) 17 | 18 | def __len__(self): 19 | return len(self.data) 20 | 21 | def __eq__(self, other): 22 | if isinstance(other, Interval): 23 | return self.data == other.data 24 | else: 25 | return False 26 | 27 | def __lt__(self, other): 28 | if isinstance(other, Interval): 29 | return self.data < other.data 30 | else: 31 | return self.data < other 32 | 33 | def range(self): 34 | return range(self.start, self.end) 35 | 36 | def __repr__(self): 37 | return "{}-{}".format(self.start, self.end) 38 | 39 | def overlaps_with(self, other_interval) -> bool: 40 | return any(r in self.range() for r in other_interval.range()) 41 | 42 | def find_common_interval(self, other_interval): 43 | sorted_intervals = sorted([self, other_interval]) 44 | common_values = set(sorted_intervals[0].range()).intersection(sorted_intervals[1].range()) 45 | return Interval(min(common_values), max(common_values) + 1) if common_values else None 46 | 47 | @staticmethod 48 | def find_greatest(intervals: list): 49 | return max(intervals, key=len) -------------------------------------------------------------------------------- /src/urh/signalprocessing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/signalprocessing/__init__.py -------------------------------------------------------------------------------- /src/urh/simulator/LabelItem.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QPainter, QPen 2 | 3 | from urh.simulator.GraphicsItem import GraphicsItem 4 | from urh.simulator.SimulatorProtocolLabel import SimulatorProtocolLabel 5 | 6 | from urh import constants 7 | 8 | from PyQt5.QtWidgets import QGraphicsTextItem 9 | from PyQt5.QtCore import Qt 10 | 11 | 12 | class LabelItem(GraphicsItem): 13 | font_bold_italic = None 14 | 15 | def __init__(self, model_item: SimulatorProtocolLabel, parent=None): 16 | assert isinstance(model_item, SimulatorProtocolLabel) 17 | super().__init__(model_item=model_item, parent=parent) 18 | 19 | self.name = QGraphicsTextItem(self) 20 | self.name.setFont(self.font) 21 | 22 | def update_flags(self): 23 | if self.scene().mode == 1: 24 | self.set_flags(is_selectable=True, accept_hover_events=True) 25 | 26 | def update_numbering(self): 27 | pass 28 | 29 | def paint(self, painter: QPainter, option, widget): 30 | style = Qt.DotLine if self.model_item.has_live_input else Qt.SolidLine 31 | pen = QPen(constants.LINECOLOR, 1, style) 32 | painter.setPen(pen) 33 | painter.setBrush(constants.LABEL_COLORS[self.model_item.color_index]) 34 | painter.drawRect(self.boundingRect()) 35 | 36 | if self.scene().mode == 1: 37 | super().paint(painter, option, widget) 38 | 39 | def boundingRect(self): 40 | return self.childrenBoundingRect() 41 | 42 | def refresh(self): 43 | self.name.setPlainText(self.model_item.name) 44 | if self.model_item.is_checksum_label: 45 | value_type = "Checksum" 46 | else: 47 | value_type = SimulatorProtocolLabel.VALUE_TYPES[self.model_item.value_type_index] 48 | tooltip = "Value type:
{}".format(value_type) 49 | self.setToolTip(tooltip) 50 | -------------------------------------------------------------------------------- /src/urh/simulator/ParticipantItem.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QGraphicsLineItem, QGraphicsTextItem, QGraphicsItem 2 | from PyQt5.QtGui import QPen, QFont 3 | from PyQt5.QtCore import Qt 4 | 5 | from urh import constants 6 | from urh.signalprocessing.Participant import Participant 7 | 8 | 9 | class ParticipantItem(QGraphicsItem): 10 | def __init__(self, model_item: Participant, parent=None): 11 | super().__init__(parent) 12 | 13 | self.model_item = model_item 14 | 15 | self.text = QGraphicsTextItem(self) 16 | 17 | self.line = QGraphicsLineItem(self) 18 | self.line.setPen(QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)) 19 | 20 | self.refresh() 21 | 22 | def update_position(self, x_pos=-1, y_pos=-1): 23 | if x_pos == -1: 24 | x_pos = self.x_pos() 25 | 26 | if y_pos == -1: 27 | y_pos = self.line.line().y2() 28 | 29 | self.text.setPos(x_pos - (self.text.boundingRect().width() / 2), 0) 30 | self.line.setLine(x_pos, 30, x_pos, y_pos) 31 | 32 | def x_pos(self): 33 | return self.line.line().x1() 34 | 35 | def width(self): 36 | return self.boundingRect().width() 37 | 38 | def refresh(self): 39 | self.text.setPlainText("?" if not self.model_item else self.model_item.shortname) 40 | if hasattr(self.model_item, "simulate") and self.model_item.simulate: 41 | font = QFont() 42 | font.setBold(True) 43 | self.text.setFont(font) 44 | self.text.setDefaultTextColor(Qt.darkGreen) 45 | self.line.setPen(QPen(Qt.darkGreen, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) 46 | else: 47 | self.text.setFont(QFont()) 48 | self.text.setDefaultTextColor(constants.LINECOLOR) 49 | self.line.setPen(QPen(Qt.darkGray, 1, Qt.DashLine, Qt.RoundCap, Qt.RoundJoin)) 50 | 51 | def boundingRect(self): 52 | return self.childrenBoundingRect() 53 | 54 | def paint(self, painter, option, widget): 55 | pass 56 | -------------------------------------------------------------------------------- /src/urh/simulator/SimulatorCounterAction.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from urh.simulator.SimulatorItem import SimulatorItem 4 | from urh.simulator.SimulatorRule import SimulatorRuleCondition 5 | from urh.util.Formatter import Formatter 6 | 7 | 8 | class SimulatorCounterAction(SimulatorItem): 9 | def __init__(self): 10 | super().__init__() 11 | self.start = 1 12 | self.step = 1 13 | self.__value = self.start 14 | 15 | @property 16 | def value(self): 17 | return self.__value 18 | 19 | def reset_value(self): 20 | self.__value = self.start 21 | 22 | def progress_value(self): 23 | self.__value += self.step 24 | 25 | def validate(self): 26 | return True 27 | 28 | def set_parent(self, value): 29 | if value is not None: 30 | assert value.parent() is None or isinstance(value, SimulatorRuleCondition) 31 | 32 | super().set_parent(value) 33 | 34 | def to_xml(self): 35 | attrib = {"start": str(self.start), "step": str(self.step)} 36 | return ET.Element("simulator_counter_action", attrib=attrib) 37 | 38 | @classmethod 39 | def from_xml(cls, tag): 40 | result = SimulatorCounterAction() 41 | result.start = Formatter.str2val(tag.get("start", "1"), int, 1) 42 | result.step = Formatter.str2val(tag.get("step", "1"), int, 1) 43 | return result 44 | -------------------------------------------------------------------------------- /src/urh/simulator/SimulatorGotoAction.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from urh.simulator.SimulatorCounterAction import SimulatorCounterAction 4 | from urh.simulator.SimulatorItem import SimulatorItem 5 | from urh.simulator.SimulatorProtocolLabel import SimulatorProtocolLabel 6 | from urh.simulator.SimulatorRule import SimulatorRule, SimulatorRuleCondition, ConditionType 7 | from urh.simulator.SimulatorTriggerCommandAction import SimulatorTriggerCommandAction 8 | 9 | 10 | class SimulatorGotoAction(SimulatorItem): 11 | def __init__(self): 12 | super().__init__() 13 | self.goto_target = None # type: str 14 | 15 | def set_parent(self, value): 16 | if value is not None: 17 | assert value.parent() is None or isinstance(value, SimulatorRuleCondition) 18 | 19 | super().set_parent(value) 20 | 21 | @property 22 | def target(self): 23 | return self.simulator_config.item_dict[self.goto_target] if self.validate() else None 24 | 25 | def validate(self): 26 | target = self.simulator_config.item_dict.get(self.goto_target, None) 27 | return self.is_valid_goto_target(self.goto_target, target) 28 | 29 | def get_valid_goto_targets(self): 30 | valid_targets = [] 31 | 32 | for key, value in self.simulator_config.item_dict.items(): 33 | if value != self and SimulatorGotoAction.is_valid_goto_target(key, value): 34 | valid_targets.append(key) 35 | 36 | return valid_targets 37 | 38 | def to_xml(self) -> ET.Element: 39 | attributes = dict() 40 | if self.goto_target is not None: 41 | attributes["goto_target"] = self.goto_target 42 | 43 | return ET.Element("simulator_goto_action", attrib=attributes) 44 | 45 | @classmethod 46 | def from_xml(cls, tag: ET.Element): 47 | result = SimulatorGotoAction() 48 | result.goto_target = tag.get("goto_target", None) 49 | return result 50 | 51 | @staticmethod 52 | def is_valid_goto_target(caption: str, item: SimulatorItem): 53 | if item is None: 54 | return False 55 | if isinstance(item, SimulatorProtocolLabel) or isinstance(item, SimulatorRule): 56 | return False 57 | if isinstance(item, SimulatorRuleCondition) and item.type != ConditionType.IF: 58 | return False 59 | if isinstance(item, SimulatorCounterAction): 60 | return False 61 | if isinstance(item, SimulatorTriggerCommandAction) and caption.endswith("rc"): 62 | return False 63 | 64 | return True 65 | -------------------------------------------------------------------------------- /src/urh/simulator/SimulatorSleepAction.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from urh.simulator.SimulatorItem import SimulatorItem 4 | from urh.simulator.SimulatorRule import SimulatorRuleCondition 5 | from urh.util.Formatter import Formatter 6 | 7 | 8 | class SimulatorSleepAction(SimulatorItem): 9 | def __init__(self): 10 | super().__init__() 11 | self.sleep_time = 1.0 12 | 13 | @property 14 | def caption(self): 15 | return "Sleep for " + Formatter.science_time(self.sleep_time) 16 | 17 | def validate(self): 18 | return True 19 | 20 | def set_parent(self, value): 21 | if value is not None: 22 | assert value.parent() is None or isinstance(value, SimulatorRuleCondition) 23 | 24 | super().set_parent(value) 25 | 26 | def to_xml(self): 27 | attrib = {"sleep_time": str(self.sleep_time)} 28 | return ET.Element("simulator_sleep_action", attrib=attrib) 29 | 30 | @classmethod 31 | def from_xml(cls, tag): 32 | result = SimulatorSleepAction() 33 | result.sleep_time = Formatter.str2val(tag.get("sleep_time", "1.0"), float, 1.0) 34 | return result 35 | -------------------------------------------------------------------------------- /src/urh/simulator/SimulatorTriggerCommandAction.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | 3 | from urh.simulator.SimulatorItem import SimulatorItem 4 | from urh.simulator.SimulatorRule import SimulatorRuleCondition 5 | from urh.util import util 6 | 7 | 8 | class SimulatorTriggerCommandAction(SimulatorItem): 9 | def __init__(self): 10 | super().__init__() 11 | self.command = None 12 | self.pass_transcript = False 13 | 14 | self.return_code = 0 15 | 16 | def validate(self): 17 | return util.validate_command(self.command) 18 | 19 | def set_parent(self, value): 20 | if value is not None: 21 | assert value.parent() is None or isinstance(value, SimulatorRuleCondition) 22 | 23 | super().set_parent(value) 24 | 25 | def to_xml(self): 26 | attrib = dict() 27 | if self.command: 28 | attrib["command"] = self.command 29 | attrib["pass_transcript"] = str(int(self.pass_transcript)) 30 | return ET.Element("simulator_trigger_command_action", attrib=attrib) 31 | 32 | @classmethod 33 | def from_xml(cls, tag): 34 | result = SimulatorTriggerCommandAction() 35 | result.command = tag.get("command", None) 36 | pass_transcript = tag.get("pass_transcript", None) 37 | if pass_transcript is not None: 38 | try: 39 | result.pass_transcript = bool(int(pass_transcript)) 40 | except ValueError: 41 | pass 42 | return result 43 | -------------------------------------------------------------------------------- /src/urh/simulator/Transcript.py: -------------------------------------------------------------------------------- 1 | from urh.signalprocessing.Message import Message 2 | from urh.signalprocessing.Participant import Participant 3 | 4 | 5 | class Transcript(object): 6 | FORMAT = "{0} ({1}->{2}): {3}" 7 | 8 | def __init__(self): 9 | self.__data = [] 10 | 11 | def append(self, source: Participant, destination: Participant, msg: Message, index: int): 12 | if len(self.__data) == 0: 13 | self.__data.append([]) 14 | 15 | self.__data[-1].append((source, destination, msg, index)) 16 | 17 | def start_new_round(self): 18 | if len(self.__data) == 0 or len(self.__data[-1]) > 0: 19 | self.__data.append([]) 20 | 21 | def clear(self): 22 | self.__data.clear() 23 | 24 | def get_for_all_participants(self, all_rounds: bool, use_bit=True) -> list: 25 | result = [] 26 | if len(self.__data) == 0: 27 | return result 28 | 29 | rng = range(0, len(self.__data)) if all_rounds else range(len(self.__data)-1, len(self.__data)) 30 | 31 | for i in rng: 32 | for source, destination, msg, msg_index in self.__data[i]: 33 | data = msg.plain_bits_str if use_bit else msg.plain_hex_str 34 | result.append(self.FORMAT.format(msg_index, source.shortname, destination.shortname, data)) 35 | 36 | if i != len(self.__data) - 1: 37 | result.append("") 38 | 39 | return result 40 | 41 | def get_for_participant(self, participant: Participant) -> str: 42 | if len(self.__data) == 0: 43 | return "" 44 | 45 | result = [] 46 | for source, destination, msg, _ in self.__data[-1]: 47 | if participant == destination: 48 | result.append("->" + msg.plain_bits_str) 49 | elif participant == source: 50 | result.append("<-" + msg.plain_bits_str) 51 | 52 | return "\n".join(result) 53 | -------------------------------------------------------------------------------- /src/urh/simulator/UnlabeledRangeItem.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QGraphicsTextItem 2 | from PyQt5.QtGui import QFontDatabase 3 | 4 | class UnlabeledRangeItem(QGraphicsTextItem): 5 | def __init__(self, parent): 6 | super().__init__(parent) 7 | 8 | font = QFontDatabase.systemFont(QFontDatabase.FixedFont) 9 | font.setPointSize(8) 10 | self.setFont(font) 11 | self.setPlainText("...") -------------------------------------------------------------------------------- /src/urh/simulator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/simulator/__init__.py -------------------------------------------------------------------------------- /src/urh/ui/ElidedLabel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QSize, Qt 2 | from PyQt5.QtGui import QFontMetrics 3 | from PyQt5.QtWidgets import QLabel 4 | 5 | 6 | class ElidedLabel(QLabel): 7 | def __init__(self, parent=None): 8 | super().__init__(parent) 9 | self.full_text = "" 10 | 11 | def __set_elided_text(self): 12 | fm = QFontMetrics(self.font()) 13 | super().setText(fm.elidedText(self.full_text, Qt.ElideRight, self.width())) 14 | 15 | self.setToolTip(self.full_text) 16 | 17 | def setText(self, text: str): 18 | self.full_text = text 19 | self.__set_elided_text() 20 | 21 | def resizeEvent(self, event) -> None: 22 | super().resizeEvent(event) 23 | self.__set_elided_text() 24 | 25 | def minimumSizeHint(self) -> QSize: 26 | return QSize(0, super().minimumSizeHint().height()) 27 | -------------------------------------------------------------------------------- /src/urh/ui/GeneratorListWidget.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import pyqtSignal, pyqtSlot 2 | from PyQt5.QtGui import QContextMenuEvent, QFocusEvent 3 | from PyQt5.QtWidgets import QListWidget, QMenu, QAction 4 | 5 | 6 | class GeneratorListWidget(QListWidget): 7 | item_edit_clicked = pyqtSignal(int) 8 | edit_all_items_clicked = pyqtSignal() 9 | lost_focus = pyqtSignal() 10 | 11 | def __init__(self, parent): 12 | super().__init__(parent) 13 | 14 | def create_context_menu(self) -> QMenu: 15 | menu = QMenu() 16 | sel_indexes = [index.row() for index in self.selectedIndexes()] 17 | edit_action = QAction("Edit", self) 18 | edit_action.triggered.connect(self.on_edit_action_triggered) 19 | if len(sel_indexes) == 0: 20 | edit_action.setEnabled(False) 21 | 22 | menu.addAction(edit_action) 23 | 24 | if self.count() > 0: 25 | edit_all_action = QAction("Edit all", self) 26 | edit_all_action.triggered.connect(self.on_edit_all_action_triggered) 27 | menu.addAction(edit_all_action) 28 | 29 | return menu 30 | 31 | def contextMenuEvent(self, event: QContextMenuEvent): 32 | menu = self.create_context_menu() 33 | menu.exec_(self.mapToGlobal(event.pos())) 34 | 35 | def focusOutEvent(self, event: QFocusEvent): 36 | self.lost_focus.emit() 37 | super().focusOutEvent(event) 38 | 39 | @pyqtSlot() 40 | def on_edit_action_triggered(self): 41 | if len(self.selectedIndexes()) > 0: 42 | selected_indx = self.selectedIndexes()[0].row() 43 | self.item_edit_clicked.emit(selected_indx) 44 | 45 | @pyqtSlot() 46 | def on_edit_all_action_triggered(self): 47 | self.edit_all_items_clicked.emit() 48 | -------------------------------------------------------------------------------- /src/urh/ui/RuleExpressionValidator.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QValidator 2 | from PyQt5.QtCore import pyqtSignal 3 | 4 | class RuleExpressionValidator(QValidator): 5 | validation_status_changed = pyqtSignal(QValidator.State, str) 6 | 7 | def __init__(self, sim_expression_parser, is_formula=True, parent=None): 8 | super().__init__(parent) 9 | self.parser = sim_expression_parser 10 | self.is_formula = is_formula 11 | 12 | def validate(self, text, pos): 13 | valid, message, _ = self.parser.validate_expression(text, self.is_formula) 14 | state = QValidator.Acceptable if valid else QValidator.Intermediate 15 | 16 | self.validation_status_changed.emit(state, message) 17 | return (state, text, pos) -------------------------------------------------------------------------------- /src/urh/ui/ScrollArea.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import pyqtSignal 2 | from PyQt5.QtGui import QDropEvent, QDragEnterEvent, QWheelEvent 3 | from PyQt5.QtWidgets import QScrollArea 4 | 5 | class ScrollArea(QScrollArea): 6 | files_dropped = pyqtSignal(list) 7 | 8 | def __init__(self, parent=None): 9 | super().__init__(parent) 10 | self.setAcceptDrops(True) 11 | 12 | def dropEvent(self, event: QDropEvent): 13 | self.files_dropped.emit(event.mimeData().urls()) 14 | 15 | def dragEnterEvent(self, event: QDragEnterEvent): 16 | event.accept() 17 | 18 | def wheelEvent(self, event: QWheelEvent): 19 | event.ignore() 20 | -------------------------------------------------------------------------------- /src/urh/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/ui/__init__.py -------------------------------------------------------------------------------- /src/urh/ui/actions/ChangeSignalParameter.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from PyQt5.QtWidgets import QUndoCommand 4 | 5 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 6 | from urh.signalprocessing.Signal import Signal 7 | 8 | 9 | class ChangeSignalParameter(QUndoCommand): 10 | def __init__(self, signal: Signal, protocol: ProtocolAnalyzer, parameter_name: str, parameter_value): 11 | super().__init__() 12 | if not hasattr(signal, parameter_name): 13 | raise ValueError("signal has no attribute {}".format(parameter_name)) 14 | 15 | self.signal = signal 16 | self.parameter_name = parameter_name 17 | self.parameter_value = parameter_value 18 | self.orig_value = getattr(self.signal, self.parameter_name) 19 | 20 | fmt2 = "d" if isinstance(self.orig_value, int) else ".4n" if isinstance(self.orig_value, float) else "s" 21 | fmt3 = "d" if isinstance(parameter_value, int) else ".4n" if isinstance(parameter_value, float) else "s" 22 | signal_name = signal.name[:10] + "..." if len(signal.name) > 10 else signal.name 23 | 24 | self.setText( 25 | ("change {0} of {1} from {2:" + fmt2 + "} to {3:" + fmt3 + "}") 26 | .format(parameter_name, signal_name, self.orig_value, parameter_value) 27 | ) 28 | 29 | self.protocol = protocol 30 | self.orig_messages = copy.deepcopy(self.protocol.messages) 31 | 32 | def redo(self): 33 | msg_data = [(msg.decoder, msg.participant, msg.message_type) for msg in self.protocol.messages] 34 | setattr(self.signal, self.parameter_name, self.parameter_value) 35 | # Restore msg parameters 36 | if len(msg_data) == self.protocol.num_messages: 37 | for msg, msg_params in zip(self.protocol.messages, msg_data): 38 | msg.decoder = msg_params[0] 39 | msg.participant = msg_params[1] 40 | msg.message_type = msg_params[2] 41 | self.protocol.qt_signals.protocol_updated.emit() 42 | 43 | def undo(self): 44 | block_proto_update = self.signal.block_protocol_update 45 | self.signal.block_protocol_update = True 46 | setattr(self.signal, self.parameter_name, self.orig_value) 47 | self.signal.block_protocol_update = block_proto_update 48 | 49 | self.protocol.messages = self.orig_messages 50 | self.protocol.qt_signals.protocol_updated.emit() 51 | -------------------------------------------------------------------------------- /src/urh/ui/actions/Clear.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from PyQt5.QtWidgets import QUndoCommand 4 | 5 | from urh.signalprocessing.ProtocolAnalyzerContainer import ProtocolAnalyzerContainer 6 | 7 | 8 | class Clear(QUndoCommand): 9 | def __init__(self, proto_analyzer_container: ProtocolAnalyzerContainer): 10 | super().__init__() 11 | self.proto_analyzer_container = proto_analyzer_container 12 | self.orig_messages = copy.deepcopy(self.proto_analyzer_container.messages) 13 | 14 | self.setText("Clear Generator Table") 15 | 16 | def redo(self): 17 | self.proto_analyzer_container.clear() 18 | 19 | def undo(self): 20 | self.proto_analyzer_container.messages = self.orig_messages 21 | -------------------------------------------------------------------------------- /src/urh/ui/actions/DeleteBitsAndPauses.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from PyQt5.QtWidgets import QUndoCommand 4 | 5 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 6 | 7 | 8 | class DeleteBitsAndPauses(QUndoCommand): 9 | def __init__(self, proto_analyzer: ProtocolAnalyzer, start_message: int, end_message:int, 10 | start: int, end: int, view: int, decoded: bool, subprotos=None, update_label_ranges=True): 11 | super().__init__() 12 | 13 | self.sub_protocols = [] if subprotos is None else subprotos # type: list[ProtocolAnalyzer] 14 | self.view = view 15 | self.end = end 16 | self.start = start 17 | self.end_message = end_message 18 | self.start_message = start_message 19 | self.proto_analyzer = proto_analyzer 20 | self.decoded = decoded 21 | self.saved_messages = [] 22 | self.removed_message_indices = [] 23 | self.sub_protocol_history = {} # for CFC 24 | self.update_label_ranges = update_label_ranges 25 | for sub_protocol in self.sub_protocols: 26 | self.sub_protocol_history[sub_protocol] = sub_protocol.messages 27 | 28 | self.setText("Delete") 29 | 30 | def redo(self): 31 | self.saved_messages = copy.deepcopy(self.proto_analyzer.messages[self.start_message:self.end_message+1]) 32 | self.removed_message_indices = self.proto_analyzer.delete_messages(self.start_message, self.end_message, 33 | self.start, self.end, 34 | self.view, self.decoded, self.update_label_ranges) 35 | 36 | def undo(self): 37 | for i in reversed(range(self.start_message, self.end_message+1)): 38 | if i in self.removed_message_indices: 39 | self.proto_analyzer.messages.insert(i, self.saved_messages[i-self.start_message]) 40 | else: 41 | try: 42 | self.proto_analyzer.messages[i] = self.saved_messages[i-self.start_message] 43 | except IndexError: 44 | self.proto_analyzer.messages.append(self.saved_messages[i-self.start_message]) 45 | 46 | for sub_protocol in self.sub_protocol_history.keys(): 47 | sub_protocol.messages = self.sub_protocol_history[sub_protocol] 48 | 49 | self.saved_messages.clear() 50 | self.removed_message_indices.clear() 51 | -------------------------------------------------------------------------------- /src/urh/ui/actions/Fuzz.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | import time 4 | from PyQt5.QtWidgets import QUndoCommand 5 | 6 | from urh import constants 7 | from urh.signalprocessing.ProtocolAnalyzerContainer import ProtocolAnalyzerContainer 8 | 9 | 10 | class Fuzz(QUndoCommand): 11 | def __init__(self, proto_analyzer_container: ProtocolAnalyzerContainer, fuz_mode: str): 12 | super().__init__() 13 | self.proto_analyzer_container = proto_analyzer_container 14 | self.fuz_mode = fuz_mode 15 | 16 | self.setText("{0} Fuzzing".format(self.fuz_mode)) 17 | self.added_message_indices = [] 18 | 19 | def redo(self): 20 | if constants.SETTINGS.value('use_default_fuzzing_pause', True, bool): 21 | default_pause = constants.SETTINGS.value("default_fuzzing_pause", 10**6, int) 22 | else: 23 | default_pause = None 24 | 25 | if self.fuz_mode == "Successive": 26 | added_indices = self.proto_analyzer_container.fuzz_successive(default_pause=default_pause) 27 | elif self.fuz_mode == "Concurrent": 28 | added_indices = self.proto_analyzer_container.fuzz_concurrent(default_pause=default_pause) 29 | elif self.fuz_mode == "Exhaustive": 30 | added_indices = self.proto_analyzer_container.fuzz_exhaustive(default_pause=default_pause) 31 | else: 32 | added_indices = [] 33 | 34 | self.added_message_indices.extend(added_indices) 35 | 36 | def undo(self): 37 | for index in reversed(self.added_message_indices): 38 | del self.proto_analyzer_container.messages[index] 39 | self.added_message_indices.clear() 40 | -------------------------------------------------------------------------------- /src/urh/ui/actions/InsertBitsAndPauses.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from PyQt5.QtWidgets import QUndoCommand 4 | 5 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 6 | from urh.signalprocessing.ProtocolAnalyzerContainer import ProtocolAnalyzerContainer 7 | 8 | 9 | class InsertBitsAndPauses(QUndoCommand): 10 | def __init__(self, proto_analyzer_container: ProtocolAnalyzerContainer, index: int, pa: ProtocolAnalyzer): 11 | super().__init__() 12 | self.proto_analyzer_container = proto_analyzer_container 13 | self.proto_analyzer = pa 14 | self.index = index 15 | if self.index == -1 or self.index > len(self.proto_analyzer_container.messages): 16 | self.index = len(self.proto_analyzer_container.messages) 17 | 18 | self.setText("Insert data at index {0:d}".format(self.index)) 19 | self.num_messages = 0 20 | 21 | def redo(self): 22 | self.proto_analyzer_container.insert_protocol_analyzer(self.index, self.proto_analyzer) 23 | self.num_messages += len(self.proto_analyzer.messages) 24 | 25 | def undo(self): 26 | for i in reversed(range(self.index, self.index+self.num_messages)): 27 | del self.proto_analyzer_container.messages[i] 28 | self.num_messages = 0 29 | -------------------------------------------------------------------------------- /src/urh/ui/actions/InsertColumn.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | from PyQt5.QtWidgets import QUndoCommand 4 | 5 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 6 | 7 | 8 | class InsertColumn(QUndoCommand): 9 | def __init__(self, proto_analyzer: ProtocolAnalyzer, index: int, rows: list, view: int): 10 | super().__init__() 11 | self.proto_analyzer = proto_analyzer 12 | self.index = proto_analyzer.convert_index(index, from_view=view, to_view=0, decoded=False)[0] 13 | self.nbits = 1 if view == 0 else 4 if view == 1 else 8 14 | self.rows = rows 15 | 16 | self.saved_messages = {} 17 | 18 | self.setText("Insert column at {0:d}".format(index)) 19 | 20 | def redo(self): 21 | for i in self.rows: 22 | msg = self.proto_analyzer.messages[i] 23 | self.saved_messages[i] = copy.deepcopy(msg) 24 | for j in range(self.nbits): 25 | msg.insert(self.index + j, False) 26 | 27 | def undo(self): 28 | for i in self.rows: 29 | self.proto_analyzer.messages[i] = self.saved_messages[i] 30 | self.saved_messages.clear() 31 | -------------------------------------------------------------------------------- /src/urh/ui/actions/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'joe' 2 | -------------------------------------------------------------------------------- /src/urh/ui/delegates/CheckBoxDelegate.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QModelIndex, QAbstractItemModel, Qt, pyqtSlot 2 | from PyQt5.QtWidgets import QStyledItemDelegate, QWidget, QStyleOptionViewItem, QCheckBox 3 | 4 | 5 | class CheckBoxDelegate(QStyledItemDelegate): 6 | def __init__(self, parent=None): 7 | super().__init__(parent) 8 | self.enabled = True 9 | 10 | def createEditor(self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex): 11 | editor = QCheckBox(parent) 12 | editor.stateChanged.connect(self.stateChanged) 13 | return editor 14 | 15 | def setEditorData(self, editor: QCheckBox, index: QModelIndex): 16 | editor.blockSignals(True) 17 | editor.setChecked(index.model().data(index)) 18 | self.enabled = editor.isChecked() 19 | editor.blockSignals(False) 20 | 21 | def setModelData(self, editor: QCheckBox, model: QAbstractItemModel, index: QModelIndex): 22 | model.setData(index, editor.isChecked(), Qt.EditRole) 23 | 24 | @pyqtSlot() 25 | def stateChanged(self): 26 | self.commitData.emit(self.sender()) -------------------------------------------------------------------------------- /src/urh/ui/delegates/KillerSpinBoxDelegate.py: -------------------------------------------------------------------------------- 1 | from urh.ui.KillerDoubleSpinBox import KillerDoubleSpinBox 2 | from urh.ui.delegates.SpinBoxDelegate import SpinBoxDelegate 3 | 4 | 5 | class KillerSpinBoxDelegate(SpinBoxDelegate): 6 | def __init__(self, minimum, maximum, parent=None, suffix=""): 7 | super().__init__(minimum, maximum, parent, suffix) 8 | 9 | def _get_editor(self, parent): 10 | editor = KillerDoubleSpinBox(parent) 11 | editor.setDecimals(3) 12 | return editor 13 | -------------------------------------------------------------------------------- /src/urh/ui/delegates/MessageTypeButtonDelegate.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from PyQt5.QtCore import pyqtSlot 4 | from PyQt5.QtGui import QIcon, QPixmap, QPainter, QColor, QPen, QFontMetrics, QBrush 5 | from PyQt5.QtWidgets import QStyledItemDelegate, QPushButton 6 | 7 | from urh.ui.views.MessageTypeTableView import MessageTypeTableView 8 | from urh.util import util 9 | 10 | 11 | class MessageTypeButtonDelegate(QStyledItemDelegate): 12 | def __init__(self, parent=None): 13 | assert isinstance(parent, MessageTypeTableView) 14 | super().__init__(parent) 15 | 16 | def createEditor(self, parent, option, index): 17 | button = QPushButton(parent) 18 | button.setFlat(True) 19 | 20 | num_rules = self.parent().model().get_num_active_rules_of_message_type_at(index.row()) 21 | 22 | if num_rules == 0: 23 | icon = QIcon.fromTheme("configure") 24 | else: 25 | icon = self.draw_indicator(indicator=num_rules) 26 | 27 | button.setIcon(icon) 28 | button.clicked.connect(self.on_btn_clicked) 29 | return button 30 | 31 | @staticmethod 32 | def draw_indicator(indicator: int): 33 | pixmap = QPixmap(24, 24) 34 | 35 | painter = QPainter(pixmap) 36 | w, h = pixmap.width(), pixmap.height() 37 | 38 | painter.fillRect(0, 0, w, h, QBrush((QColor(0, 0, 200, 255)))) 39 | 40 | pen = QPen(QColor("white")) 41 | pen.setWidth(2) 42 | painter.setPen(pen) 43 | 44 | font = util.get_monospace_font() 45 | font.setBold(True) 46 | font.setPixelSize(16) 47 | painter.setFont(font) 48 | 49 | f = QFontMetrics(painter.font()) 50 | indicator_str = str(indicator) if indicator < 10 else "+" 51 | 52 | fw = f.width(indicator_str) 53 | fh = f.height() 54 | painter.drawText(math.ceil(w / 2 - fw / 2), math.ceil(h / 2 + fh / 4), indicator_str) 55 | 56 | painter.end() 57 | return QIcon(pixmap) 58 | 59 | @pyqtSlot() 60 | def on_btn_clicked(self): 61 | button = self.sender() 62 | index = self.parent().indexAt(button.pos()) 63 | if index.isValid(): 64 | self.parent().configure_message_type_rules_triggered.emit(index.row()) 65 | -------------------------------------------------------------------------------- /src/urh/ui/delegates/SpinBoxDelegate.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QModelIndex, pyqtSlot, QAbstractItemModel, Qt 2 | from PyQt5.QtWidgets import QStyledItemDelegate, QWidget, QStyleOptionViewItem, QSpinBox 3 | 4 | 5 | class SpinBoxDelegate(QStyledItemDelegate): 6 | def __init__(self, minimum, maximum, parent=None, suffix=""): 7 | super().__init__(parent) 8 | self.minimum = minimum 9 | self.maximum = maximum 10 | self.suffix = suffix 11 | 12 | def _get_editor(self, parent) -> QSpinBox: 13 | return QSpinBox(parent) 14 | 15 | def createEditor(self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex): 16 | editor = self._get_editor(parent) 17 | editor.setMinimum(self.minimum) 18 | editor.setMaximum(self.maximum) 19 | editor.setSuffix(self.suffix) 20 | editor.valueChanged.connect(self.valueChanged) 21 | return editor 22 | 23 | def setEditorData(self, editor: QWidget, index: QModelIndex): 24 | editor.blockSignals(True) 25 | try: 26 | editor.setValue(int(index.model().data(index))) 27 | except ValueError: 28 | pass # If Label was deleted and UI not updated yet 29 | editor.blockSignals(False) 30 | 31 | def setModelData(self, editor: QWidget, model: QAbstractItemModel, index: QModelIndex): 32 | model.setData(index, editor.value(), Qt.EditRole) 33 | 34 | @pyqtSlot() 35 | def valueChanged(self): 36 | self.commitData.emit(self.sender()) 37 | -------------------------------------------------------------------------------- /src/urh/ui/delegates/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'joe' 2 | -------------------------------------------------------------------------------- /src/urh/ui/painting/ContinuousSceneManager.py: -------------------------------------------------------------------------------- 1 | from urh.signalprocessing.IQArray import IQArray 2 | from urh.ui.painting.SceneManager import SceneManager 3 | from urh.util.RingBuffer import RingBuffer 4 | 5 | 6 | class ContinuousSceneManager(SceneManager): 7 | def __init__(self, ring_buffer: RingBuffer, parent): 8 | super().__init__(parent) 9 | self.ring_buffer = ring_buffer 10 | self.__start = 0 11 | self.__end = 0 12 | 13 | self.minimum, self.maximum = IQArray.min_max_for_dtype(self.ring_buffer.dtype) 14 | 15 | @property 16 | def plot_data(self): 17 | return self.ring_buffer.view_data.real 18 | 19 | @plot_data.setter 20 | def plot_data(self, value): 21 | pass 22 | 23 | @property 24 | def end(self): 25 | return self.ring_buffer.size 26 | 27 | @end.setter 28 | def end(self, value): 29 | pass 30 | -------------------------------------------------------------------------------- /src/urh/ui/painting/FFTSceneManager.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from PyQt5.QtGui import QPainterPath, QPen 3 | from PyQt5.QtWidgets import QGraphicsPathItem 4 | 5 | from urh import constants 6 | from urh.cythonext import path_creator 7 | from urh.ui.painting.GridScene import GridScene 8 | from urh.ui.painting.SceneManager import SceneManager 9 | 10 | 11 | class FFTSceneManager(SceneManager): 12 | def __init__(self, parent, graphic_view=None): 13 | self.peak = [] 14 | 15 | super().__init__(parent) 16 | self.scene = GridScene(parent=graphic_view) 17 | self.scene.setBackgroundBrush(constants.BGCOLOR) 18 | 19 | self.peak_item = self.scene.addPath(QPainterPath(), QPen(constants.PEAK_COLOR, 0)) # type: QGraphicsPathItem 20 | 21 | def show_scene_section(self, x1: float, x2: float, subpath_ranges=None, colors=None): 22 | start = int(x1) if x1 > 0 else 0 23 | end = int(x2) if x2 < self.num_samples else self.num_samples 24 | paths = path_creator.create_path(np.log10(self.plot_data), start, end) 25 | self.set_path(paths, colors=None) 26 | 27 | try: 28 | if len(self.peak) > 0: 29 | peak_path = path_creator.create_path(np.log10(self.peak), start, end)[0] 30 | self.peak_item.setPath(peak_path) 31 | except RuntimeWarning: 32 | pass 33 | 34 | def init_scene(self, draw_grid=True): 35 | self.scene.draw_grid = draw_grid 36 | minimum = -4.5 37 | maximum = 2 38 | 39 | self.peak = self.plot_data if len(self.peak) < self.num_samples else np.maximum(self.peak, self.plot_data) 40 | self.scene.setSceneRect(0, minimum, self.num_samples, maximum - minimum) 41 | 42 | def clear_path(self): 43 | for item in self.scene.items(): 44 | if isinstance(item, QGraphicsPathItem) and item != self.peak_item: 45 | self.scene.removeItem(item) 46 | item.setParentItem(None) 47 | del item 48 | 49 | def clear_peak(self): 50 | self.peak = [] 51 | if self.peak_item: 52 | self.peak_item.setPath(QPainterPath()) 53 | 54 | def eliminate(self): 55 | super().eliminate() 56 | self.peak = None 57 | self.peak_item = None 58 | -------------------------------------------------------------------------------- /src/urh/ui/painting/HorizontalSelection.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QPointF 2 | from PyQt5.QtGui import QTransform 3 | 4 | from urh.ui.painting.Selection import Selection 5 | 6 | 7 | class HorizontalSelection(Selection): 8 | def __init__(self, *args, fillcolor, opacity, parent=None): 9 | super().__init__(*args, fillcolor=fillcolor, opacity=opacity, parent=parent) 10 | 11 | @property 12 | def length(self): 13 | return self.width 14 | 15 | @property 16 | def is_empty(self) -> bool: 17 | return self.width == 0 18 | 19 | @property 20 | def start(self): 21 | if self.width < 0: 22 | return self.x + self.width 23 | else: 24 | return self.x 25 | 26 | @start.setter 27 | def start(self, value): 28 | self.setX(value) 29 | 30 | @property 31 | def end(self): 32 | if self.width < 0: 33 | return self.x 34 | else: 35 | return self.x + self.width 36 | 37 | @end.setter 38 | def end(self, value): 39 | self.width = value - self.start 40 | 41 | def clear(self): 42 | self.width = 0 43 | super().clear() 44 | 45 | def get_selected_edge(self, pos: QPointF, transform: QTransform): 46 | return super()._get_selected_edge(pos, transform, horizontal_selection=True) 47 | -------------------------------------------------------------------------------- /src/urh/ui/painting/LiveSceneManager.py: -------------------------------------------------------------------------------- 1 | from urh.signalprocessing.IQArray import IQArray 2 | from urh.ui.painting.SceneManager import SceneManager 3 | 4 | 5 | class LiveSceneManager(SceneManager): 6 | def __init__(self, data_array, parent): 7 | super().__init__(parent) 8 | self.plot_data = data_array 9 | self.end = 0 10 | 11 | self.minimum, self.maximum = IQArray.min_max_for_dtype(data_array.dtype) 12 | 13 | @property 14 | def num_samples(self): 15 | return self.end 16 | -------------------------------------------------------------------------------- /src/urh/ui/painting/SignalSceneManager.py: -------------------------------------------------------------------------------- 1 | from urh.signalprocessing.Signal import Signal 2 | from urh.ui.painting.SceneManager import SceneManager 3 | 4 | 5 | class SignalSceneManager(SceneManager): 6 | def __init__(self, signal: Signal, parent): 7 | super().__init__(parent) 8 | self.signal = signal 9 | self.scene_type = 0 # 0 = Analog Signal, 1 = QuadDemodView 10 | 11 | def show_scene_section(self, x1: float, x2: float, subpath_ranges=None, colors=None): 12 | self.plot_data = self.signal.real_plot_data if self.scene_type == 0 else self.signal.qad 13 | super().show_scene_section(x1, x2, subpath_ranges=subpath_ranges, colors=colors) 14 | 15 | def init_scene(self): 16 | if self.scene_type == 0: 17 | # Ensure real plot has same y Axis 18 | self.plot_data = self.signal.real_plot_data 19 | else: 20 | self.plot_data = self.signal.qad 21 | 22 | super().init_scene(apply_padding=self.scene_type == 0) 23 | 24 | self.line_item.setLine(0, 0, 0, 0) # Hide Axis 25 | 26 | if self.scene_type == 0: 27 | self.scene.draw_noise_area(self.signal.noise_min_plot, self.signal.noise_max_plot - self.signal.noise_min_plot) 28 | else: 29 | self.scene.draw_sep_area(-self.signal.center_thresholds) 30 | 31 | def eliminate(self): 32 | super().eliminate() 33 | # do not eliminate the signal here, as it would cause data loss in tree models! 34 | # if hasattr(self.signal, "eliminate"): 35 | # self.signal.eliminate() 36 | self.signal = None 37 | -------------------------------------------------------------------------------- /src/urh/ui/painting/SniffSceneManager.py: -------------------------------------------------------------------------------- 1 | from urh.signalprocessing.IQArray import IQArray 2 | from urh.ui.painting.SceneManager import SceneManager 3 | 4 | 5 | class SniffSceneManager(SceneManager): 6 | def __init__(self, data_array, parent, window_length=5 * 10**6): 7 | super().__init__(parent) 8 | self.data_array = data_array 9 | self.__start = 0 10 | self.__end = 0 11 | self.window_length = window_length 12 | 13 | self.minimum, self.maximum = IQArray.min_max_for_dtype(data_array.dtype) 14 | 15 | @property 16 | def plot_data(self): 17 | return self.data_array[self.__start:self.end] 18 | 19 | @plot_data.setter 20 | def plot_data(self, value): 21 | pass 22 | 23 | @property 24 | def end(self): 25 | return self.__end 26 | 27 | @end.setter 28 | def end(self, value): 29 | if value > self.window_length: 30 | self.__start = value - self.window_length 31 | else: 32 | self.__start = 0 33 | self.__end = value 34 | -------------------------------------------------------------------------------- /src/urh/ui/painting/SpectrogramScene.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QRectF 2 | from PyQt5.QtGui import QImage, QPixmap 3 | 4 | from urh import constants 5 | from urh.ui.painting.VerticalSelection import VerticalSelection 6 | from urh.ui.painting.ZoomableScene import ZoomableScene 7 | 8 | 9 | class SpectrogramScene(ZoomableScene): 10 | def __init__(self, parent=None): 11 | super().__init__(parent) 12 | self.removeItem(self.selection_area) 13 | 14 | self.selection_area = VerticalSelection(0, 0, 0, 0, fillcolor=constants.SELECTION_COLOR, opacity=0.6) 15 | self.selection_area.setZValue(1) 16 | self.addItem(self.selection_area) 17 | 18 | def width_spectrogram(self): 19 | return self.spectrogram_image.pixmap().width() 20 | -------------------------------------------------------------------------------- /src/urh/ui/painting/VerticalSelection.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QPointF 2 | from PyQt5.QtGui import QTransform 3 | 4 | from urh.ui.painting.Selection import Selection 5 | 6 | 7 | class VerticalSelection(Selection): 8 | def __init__(self, *args, fillcolor, opacity, parent=None): 9 | super().__init__(*args, fillcolor=fillcolor, opacity=opacity, parent=parent) 10 | 11 | @property 12 | def length(self): 13 | return self.height 14 | 15 | @property 16 | def is_empty(self) -> bool: 17 | return self.height == 0 18 | 19 | @property 20 | def start(self): 21 | if self.height < 0: 22 | return self.y + self.height 23 | else: 24 | return self.y 25 | 26 | @start.setter 27 | def start(self, value): 28 | self.setY(value) 29 | 30 | @property 31 | def end(self): 32 | if self.height < 0: 33 | return self.y 34 | else: 35 | return self.y + self.height 36 | 37 | @end.setter 38 | def end(self, value): 39 | self.height = value - self.start 40 | 41 | def clear(self): 42 | self.height = 0 43 | super().clear() 44 | 45 | def get_selected_edge(self, pos: QPointF, transform: QTransform): 46 | return super()._get_selected_edge(pos, transform, horizontal_selection=False) 47 | -------------------------------------------------------------------------------- /src/urh/ui/painting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/src/urh/ui/painting/__init__.py -------------------------------------------------------------------------------- /src/urh/ui/ui_modulation_parameters_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # 4 | # 5 | # WARNING! All changes made in this file will be lost! 6 | 7 | from PyQt5 import QtCore, QtGui, QtWidgets 8 | 9 | 10 | class Ui_DialogModulationParameters(object): 11 | def setupUi(self, DialogModulationParameters): 12 | DialogModulationParameters.setObjectName("DialogModulationParameters") 13 | DialogModulationParameters.resize(303, 286) 14 | DialogModulationParameters.setModal(True) 15 | self.verticalLayout = QtWidgets.QVBoxLayout(DialogModulationParameters) 16 | self.verticalLayout.setObjectName("verticalLayout") 17 | self.tblSymbolParameters = QtWidgets.QTableWidget(DialogModulationParameters) 18 | self.tblSymbolParameters.setShowGrid(False) 19 | self.tblSymbolParameters.setRowCount(2) 20 | self.tblSymbolParameters.setObjectName("tblSymbolParameters") 21 | self.tblSymbolParameters.setColumnCount(2) 22 | item = QtWidgets.QTableWidgetItem() 23 | self.tblSymbolParameters.setHorizontalHeaderItem(0, item) 24 | item = QtWidgets.QTableWidgetItem() 25 | self.tblSymbolParameters.setHorizontalHeaderItem(1, item) 26 | self.tblSymbolParameters.horizontalHeader().setVisible(True) 27 | self.tblSymbolParameters.verticalHeader().setVisible(False) 28 | self.verticalLayout.addWidget(self.tblSymbolParameters) 29 | self.buttonBox = QtWidgets.QDialogButtonBox(DialogModulationParameters) 30 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) 31 | self.buttonBox.setObjectName("buttonBox") 32 | self.verticalLayout.addWidget(self.buttonBox) 33 | 34 | self.retranslateUi(DialogModulationParameters) 35 | 36 | def retranslateUi(self, DialogModulationParameters): 37 | _translate = QtCore.QCoreApplication.translate 38 | DialogModulationParameters.setWindowTitle(_translate("DialogModulationParameters", "Modulation Parameters")) 39 | item = self.tblSymbolParameters.horizontalHeaderItem(0) 40 | item.setText(_translate("DialogModulationParameters", "Symbol")) 41 | item = self.tblSymbolParameters.horizontalHeaderItem(1) 42 | item.setText(_translate("DialogModulationParameters", "Amplitude")) 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/urh/ui/views/FuzzingTableView.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import Qt, pyqtSignal 2 | from PyQt5.QtWidgets import QTableView, QApplication 3 | from PyQt5.QtGui import QKeyEvent 4 | import numpy 5 | 6 | 7 | class FuzzingTableView(QTableView): 8 | deletion_wanted = pyqtSignal(int, int) 9 | 10 | def __init__(self, parent=None): 11 | super().__init__(parent) 12 | 13 | def resize_me(self): 14 | QApplication.instance().setOverrideCursor(Qt.WaitCursor) 15 | w = self.font().pointSize() + 2 16 | for i in range(10): 17 | self.setColumnWidth(i, 2 * w) 18 | for i in range(10, self.model().col_count): 19 | self.setColumnWidth(i, w * len(str(i + 1))) 20 | QApplication.instance().restoreOverrideCursor() 21 | 22 | def selection_range(self): 23 | """ 24 | :rtype: int, int, int, int 25 | """ 26 | selected = self.selectionModel().selection() 27 | """:type: QItemSelection """ 28 | 29 | if selected.isEmpty(): 30 | return -1, -1, -1, -1 31 | 32 | min_row = numpy.min([rng.top() for rng in selected]) 33 | max_row = numpy.max([rng.bottom() for rng in selected]) 34 | start = numpy.min([rng.left() for rng in selected]) 35 | end = numpy.max([rng.right() for rng in selected]) + 1 36 | 37 | return min_row, max_row, start, end 38 | 39 | def keyPressEvent(self, event: QKeyEvent): 40 | if event.key() == Qt.Key_Delete: 41 | selected = self.selectionModel().selection() 42 | """:type: QtGui.QItemSelection """ 43 | if selected.isEmpty(): 44 | return 45 | 46 | min_row = numpy.min([rng.top() for rng in selected]) 47 | max_row = numpy.max([rng.bottom() for rng in selected]) 48 | self.deletion_wanted.emit(min_row, max_row) 49 | 50 | else: 51 | super().keyPressEvent(event) 52 | -------------------------------------------------------------------------------- /src/urh/ui/views/GeneratorTreeView.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTreeView, QAbstractItemView 2 | from PyQt5.QtCore import QItemSelectionModel 3 | 4 | from urh.models.GeneratorTreeModel import GeneratorTreeModel 5 | 6 | 7 | class GeneratorTreeView(QTreeView): 8 | def __init__(self, parent=None): 9 | super().__init__(parent) 10 | self.setSelectionMode(QAbstractItemView.ExtendedSelection) 11 | self.setDragEnabled(True) 12 | self.setAcceptDrops(True) 13 | self.setDropIndicatorShown(True) 14 | 15 | def model(self) -> GeneratorTreeModel: 16 | return super().model() 17 | 18 | def selectionModel(self) -> QItemSelectionModel: 19 | return super().selectionModel() -------------------------------------------------------------------------------- /src/urh/ui/views/LegendGraphicView.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import pyqtSignal 2 | from PyQt5.QtGui import QWheelEvent 3 | from PyQt5.QtWidgets import QGraphicsView 4 | 5 | 6 | class LegendGraphicView(QGraphicsView): 7 | resized = pyqtSignal() 8 | 9 | def __init__(self, parent=None): 10 | self.y_sep = 0 11 | self.y_scene = -1 12 | self.scene_height = 2 13 | super().__init__(parent) 14 | 15 | def eliminate(self): 16 | if self.scene() is not None: 17 | self.scene().clear() 18 | self.scene().setParent(None) 19 | 20 | def resizeEvent(self, event): 21 | self.resized.emit() 22 | 23 | def refresh(self): 24 | if self.scene() is not None: 25 | self.resetTransform() 26 | self.scene().setSceneRect(0, self.y_scene, self.width(), self.scene_height) 27 | self.scene().draw_one_zero_arrows(self.y_sep) 28 | self.fitInView(self.sceneRect()) 29 | self.show() 30 | 31 | def wheelEvent(self, event: QWheelEvent): 32 | return 33 | -------------------------------------------------------------------------------- /src/urh/ui/views/LiveGraphicView.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import pyqtSignal, QEvent 2 | from PyQt5.QtGui import QWheelEvent, QMouseEvent 3 | 4 | from urh.ui.painting.GridScene import GridScene 5 | from urh.ui.views.ZoomableGraphicView import ZoomableGraphicView 6 | 7 | 8 | class LiveGraphicView(ZoomableGraphicView): 9 | freq_clicked = pyqtSignal(float) 10 | wheel_event_triggered = pyqtSignal(QWheelEvent) 11 | 12 | def __init__(self, parent=None): 13 | super().__init__(parent) 14 | self.capturing_data = True 15 | self.setMouseTracking(True) 16 | 17 | def wheelEvent(self, event: QWheelEvent): 18 | self.wheel_event_triggered.emit(event) 19 | if self.capturing_data: 20 | return 21 | 22 | super().wheelEvent(event) 23 | 24 | def leaveEvent(self, event: QEvent): 25 | super().leaveEvent(event) 26 | if isinstance(self.scene(), GridScene): 27 | self.scene().clear_frequency_marker() 28 | 29 | def mouseMoveEvent(self, event: QMouseEvent): 30 | super().mouseMoveEvent(event) 31 | if isinstance(self.scene(), GridScene): 32 | x = int(self.mapToScene(event.pos()).x()) 33 | freq = self.scene().get_freq_for_pos(x) 34 | self.scene().draw_frequency_marker(x, freq) 35 | 36 | def mousePressEvent(self, event: QMouseEvent): 37 | if isinstance(self.scene(), GridScene): 38 | freq = self.scene().get_freq_for_pos(int(self.mapToScene(event.pos()).x())) 39 | if freq is not None: 40 | self.freq_clicked.emit(freq) 41 | 42 | def update(self, *__args): 43 | try: 44 | super().update(*__args) 45 | super().show_full_scene() 46 | except RuntimeError: 47 | pass 48 | -------------------------------------------------------------------------------- /src/urh/ui/views/ModulatorTreeView.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QTreeView 2 | from PyQt5.QtCore import pyqtSignal, QItemSelectionModel 3 | 4 | from urh.models import GeneratorTreeModel 5 | 6 | 7 | class ModulatorTreeView(QTreeView): 8 | selection_changed = pyqtSignal() 9 | 10 | def __init__(self, parent=None): 11 | super().__init__(parent) 12 | 13 | def model(self) -> GeneratorTreeModel: 14 | return super().model() 15 | 16 | def selectionModel(self) -> QItemSelectionModel: 17 | return super().selectionModel() 18 | 19 | def selectionChanged(self, QItemSelection, QItemSelection_1): 20 | self.selection_changed.emit() 21 | super().selectionChanged(QItemSelection, QItemSelection_1) -------------------------------------------------------------------------------- /src/urh/ui/views/SimulatorLabelTableView.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QMouseEvent 2 | from PyQt5.QtCore import Qt, pyqtSlot, QModelIndex, pyqtSignal 3 | 4 | from urh.models.SimulatorMessageFieldModel import SimulatorMessageFieldModel 5 | from urh.ui.views.ProtocolLabelTableView import ProtocolLabelTableView 6 | 7 | 8 | class SimulatorLabelTableView(ProtocolLabelTableView): 9 | item_link_clicked = pyqtSignal(int, int) 10 | 11 | def __init__(self, parent=None): 12 | super().__init__(parent) 13 | self.setMouseTracking(True) 14 | self.clicked.connect(self.on_clicked) 15 | 16 | 17 | def model(self) -> SimulatorMessageFieldModel: 18 | return super().model() 19 | 20 | def mouseMoveEvent(self, e: QMouseEvent): 21 | index = self.indexAt(e.pos()) 22 | if self.model().link_index(index): 23 | self.setCursor(Qt.PointingHandCursor) 24 | else: 25 | self.unsetCursor() 26 | 27 | @pyqtSlot(QModelIndex) 28 | def on_clicked(self, index: QModelIndex): 29 | if self.model().link_index(index): 30 | self.item_link_clicked.emit(index.row(), index.column()) 31 | -------------------------------------------------------------------------------- /src/urh/ui/views/ZoomAndDropableGraphicView.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import pyqtSignal 2 | from PyQt5.QtGui import QDragEnterEvent, QDropEvent 3 | 4 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 5 | from urh.signalprocessing.Signal import Signal 6 | from urh.ui.painting.SignalSceneManager import SignalSceneManager 7 | from urh.ui.views.ZoomableGraphicView import ZoomableGraphicView 8 | 9 | 10 | class ZoomAndDropableGraphicView(ZoomableGraphicView): 11 | signal_loaded = pyqtSignal(ProtocolAnalyzer) 12 | 13 | def __init__(self, parent=None): 14 | self.signal_tree_root = None # type: ProtocolTreeItem 15 | self.scene_manager = None 16 | 17 | self.signal = None # type: Signal 18 | self.proto_analyzer = None # type: ProtocolAnalyzer 19 | 20 | super().__init__(parent) 21 | 22 | def dragEnterEvent(self, event: QDragEnterEvent): 23 | event.acceptProposedAction() 24 | 25 | def dropEvent(self, event: QDropEvent): 26 | mime_data = event.mimeData() 27 | data_str = str(mime_data.text()) 28 | indexes = list(data_str.split("/")[:-1]) 29 | 30 | signal = None 31 | proto_analyzer = None 32 | for index in indexes: 33 | row, column, parent = map(int, index.split(",")) 34 | if parent == -1: 35 | parent = self.signal_tree_root 36 | else: 37 | parent = self.signal_tree_root.child(parent) 38 | node = parent.child(row) 39 | if node.protocol is not None and node.protocol.signal is not None: 40 | signal = node.protocol.signal 41 | proto_analyzer = node.protocol 42 | break 43 | 44 | if signal is None: 45 | return 46 | 47 | self.draw_signal(signal, proto_analyzer) 48 | 49 | def draw_signal(self, signal, proto_analyzer): 50 | if signal is None: 51 | return 52 | 53 | self.signal = signal # type: Signal 54 | self.proto_analyzer = proto_analyzer # type: ProtocolAnalyzer 55 | 56 | self.scene_manager = SignalSceneManager(signal, self) 57 | self.plot_data(self.signal.real_plot_data) 58 | self.show_full_scene() 59 | self.auto_fit_view() 60 | 61 | self.signal_loaded.emit(self.proto_analyzer) 62 | 63 | def eliminate(self): 64 | # Do _not_ call eliminate() for self.signal and self.proto_analyzer 65 | # as these are references to the original data! 66 | self.signal = None 67 | self.proto_analyzer = None 68 | self.signal_tree_root = None 69 | super().eliminate() 70 | -------------------------------------------------------------------------------- /src/urh/ui/views/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'joe' 2 | -------------------------------------------------------------------------------- /src/urh/util/Formatter.py: -------------------------------------------------------------------------------- 1 | import locale 2 | from urh.util.Logger import logger 3 | 4 | 5 | class Formatter: 6 | @staticmethod 7 | def local_decimal_seperator(): 8 | return locale.localeconv()["decimal_point"] 9 | 10 | @staticmethod 11 | def science_time(time_in_seconds: float, decimals=2, append_seconds=True, remove_spaces=False) -> str: 12 | if time_in_seconds < 1e-6: 13 | suffix = "n" 14 | value = time_in_seconds * 1e9 15 | elif time_in_seconds < 1e-3: 16 | suffix = "µ" 17 | value = time_in_seconds * 1e6 18 | elif time_in_seconds < 1: 19 | suffix = "m" 20 | value = time_in_seconds * 1e3 21 | else: 22 | suffix = "" 23 | value = time_in_seconds 24 | 25 | result = locale.format_string("%.{0}f ".format(decimals) + suffix, value) 26 | if append_seconds: 27 | result += "s" 28 | if remove_spaces: 29 | result = result.replace(" ", "") 30 | 31 | return result 32 | 33 | @staticmethod 34 | def big_value_with_suffix(value: float, decimals=3, strip_zeros=True) -> str: 35 | fmt_str = "%.{0:d}f".format(decimals) 36 | suffix = "" 37 | if abs(value) >= 1e9: 38 | suffix = "G" 39 | result = locale.format_string(fmt_str, value / 1e9) 40 | elif abs(value) >= 1e6: 41 | suffix = "M" 42 | result = locale.format_string(fmt_str, value / 1e6) 43 | elif abs(value) >= 1e3: 44 | suffix = "K" 45 | result = locale.format_string(fmt_str, value / 1e3) 46 | else: 47 | result = locale.format_string(fmt_str, value) 48 | 49 | if strip_zeros: 50 | result = result.rstrip("0").rstrip(Formatter.local_decimal_seperator()) 51 | 52 | return result + suffix 53 | 54 | 55 | @staticmethod 56 | def str2val(str_val, dtype, default=0): 57 | try: 58 | return dtype(str_val) 59 | except (ValueError, TypeError): 60 | logger.warning("The {0} is not a valid {1}, assuming {2}".format(str_val, str(dtype), str(default))) 61 | return default 62 | -------------------------------------------------------------------------------- /src/urh/util/HTMLFormatter.py: -------------------------------------------------------------------------------- 1 | INDENT_WIDTH_PX = 20 2 | 3 | 4 | def monospace(string): 5 | return "" + string + "" 6 | 7 | 8 | def indent_string(string, depth=1): 9 | width = depth * INDENT_WIDTH_PX 10 | return '
{1}
'.format(width, string) 11 | 12 | 13 | def mark_differences(value: str, compare_against: str): 14 | result = [] 15 | for i, char in enumerate(value): 16 | try: 17 | if char != compare_against[i]: 18 | result.append('{}'.format(char)) 19 | else: 20 | result.append(char) 21 | except IndexError: 22 | result.append(char) 23 | 24 | return "".join(result) 25 | 26 | 27 | def align_expected_and_got_value(expected: str, got: str, align_depth=1): 28 | width = align_depth * INDENT_WIDTH_PX 29 | got_marked = mark_differences(got, expected) 30 | return '' \ 31 | '' \ 32 | '
Expected: {1}
Got: {2}
'.format(width, monospace(expected), monospace(got_marked)) 33 | -------------------------------------------------------------------------------- /src/urh/util/Logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import sys 4 | import tempfile 5 | 6 | from urh.constants import color 7 | 8 | TMP = "/tmp" if sys.platform == "darwin" else tempfile.gettempdir() 9 | 10 | LOG_LEVEL_PATH = os.path.join(TMP, "urh_log_level") 11 | 12 | 13 | def read_log_level(default): 14 | try: 15 | with open(LOG_LEVEL_PATH, "r") as f: 16 | return int(f.readlines()[0].strip()) 17 | except: 18 | return default 19 | 20 | 21 | def save_log_level(): 22 | try: 23 | with open(LOG_LEVEL_PATH, "w") as f: 24 | f.write(str(logger.level)) 25 | except: 26 | pass 27 | 28 | 29 | logger_conf = { 30 | "level": read_log_level(default=logging.DEBUG), 31 | "format": '[%(levelname)s::%(filename)s::%(funcName)s] %(message)s' 32 | } 33 | 34 | log_file_handler = None 35 | if hasattr(sys, "frozen"): 36 | try: 37 | sys.stdin.isatty() 38 | except: 39 | # STDIN is not useable, so we are running in GUI mode 40 | logfile_name = os.path.join(TMP, "urh.log") 41 | # Add the log message handler to the logger 42 | import logging.handlers 43 | 44 | log_file_handler = logging.handlers.RotatingFileHandler(logfile_name, maxBytes=2e6, backupCount=5) 45 | 46 | logging.basicConfig(**logger_conf) 47 | 48 | logging_colors_per_level = { 49 | logging.WARNING: color.YELLOW, 50 | logging.ERROR: color.RED, 51 | logging.CRITICAL: color.RED 52 | } 53 | 54 | for level, level_color in logging_colors_per_level.items(): 55 | if sys.platform != "win32": 56 | logging.addLevelName(level, "{0}{1}{2}".format(level_color, logging.getLevelName(level), color.END)) 57 | 58 | logger = logging.getLogger("urh") 59 | 60 | if log_file_handler is not None: 61 | logger.addHandler(log_file_handler) 62 | -------------------------------------------------------------------------------- /src/urh/util/SettingsProxy.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import psutil 4 | 5 | from urh import constants 6 | from urh.util.Formatter import Formatter 7 | from urh.util.Logger import logger 8 | 9 | 10 | class SettingsProxy(object): 11 | OVERWRITE_RECEIVE_BUFFER_SIZE = None 12 | 13 | """ 14 | Centralize common settings operations 15 | """ 16 | 17 | @staticmethod 18 | def get_receive_buffer_size(resume_on_full_receive_buffer: bool, spectrum_mode: bool) -> int: 19 | if SettingsProxy.OVERWRITE_RECEIVE_BUFFER_SIZE: 20 | return SettingsProxy.OVERWRITE_RECEIVE_BUFFER_SIZE 21 | 22 | if resume_on_full_receive_buffer: 23 | if spectrum_mode: 24 | num_samples = constants.SPECTRUM_BUFFER_SIZE 25 | else: 26 | num_samples = constants.SNIFF_BUFFER_SIZE 27 | else: 28 | # Take 60% of avail memory 29 | threshold = constants.SETTINGS.value('ram_threshold', 0.6, float) 30 | num_samples = threshold * (psutil.virtual_memory().available / 8) 31 | 32 | # Do not let it allocate too much memory on 32 bit 33 | if 8 * 2 * num_samples > sys.maxsize: 34 | num_samples = sys.maxsize // (8 * 2 * 1.5) 35 | logger.info("Correcting buffer size to {}".format(num_samples)) 36 | 37 | logger.info("Allocate receive buffer with {0}B".format(Formatter.big_value_with_suffix(num_samples * 8))) 38 | return int(num_samples) 39 | -------------------------------------------------------------------------------- /src/urh/util/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'joe' 2 | -------------------------------------------------------------------------------- /src/urh/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "2.8.0" 2 | 3 | if __name__ == '__main__': 4 | # To read out version easy on command line for InnoSetup 5 | print(VERSION) 6 | -------------------------------------------------------------------------------- /tests/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | src/urh/tests/* 4 | */__init__.py 5 | src/urh/cythonext/* 6 | src/urh/dev/* 7 | src/urh/ui/xtra_icons_rc.py 8 | src/urh/main.py 9 | src/urh/util/Errors.py 10 | src/urh/plugins/RfCat/RfCatPlugin.py 11 | 12 | concurrency = multiprocessing 13 | source = src/urh 14 | 15 | [report] 16 | sort = Miss 17 | exclude_lines = 18 | pragma: no cover 19 | def paint 20 | def draw_indicator 21 | def mousePressEvent 22 | def mouseMoveEvent 23 | def mouseReleaseEvent 24 | def contextMenuEvent 25 | def on_import_samples_from_csv_action_triggered 26 | def show_open_dialog 27 | def on_btn_choose_python2_interpreter_clicked 28 | def on_btn_choose_gnuradio_directory_clicked 29 | def on_new_project_action_triggered 30 | def on_label_non_project_mode_link_activated 31 | raise NotImplementedError 32 | class SectionItemDelegate 33 | class SectionComboBox 34 | def createEditor 35 | def updateEditorGeometry 36 | def on_export_fta_wanted 37 | def export_to_fta 38 | def export_demodulated 39 | def main 40 | class DeviceOptionsTableModel 41 | if __name__ == "__main__" 42 | -------------------------------------------------------------------------------- /tests/PlotTests.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import unittest 3 | 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | 7 | from urh.signalprocessing.Modulator import Modulator 8 | from urh.cythonext import signal_functions 9 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 10 | from urh.signalprocessing.Signal import Signal 11 | from tests.utils_testing import get_path_for_data_file 12 | 13 | 14 | class PlotTests(unittest.TestCase): 15 | def test_plot(self): 16 | modulator = Modulator("gfsk") 17 | modulator.modulation_type = "GFSK" 18 | modulator.samples_per_symbol = 100 19 | modulator.sample_rate = 1e6 20 | modulator.parameters[1] = 20e3 21 | modulator.parameters[0] = 10e3 22 | modulator.carrier_freq_hz = 15e3 23 | modulator.carrier_phase_deg = 90 24 | 25 | modulated_samples = modulator.modulate([True, False, True, False, False], 77) 26 | data = copy.deepcopy(modulated_samples) 27 | modulated_samples = modulator.modulate([False, True, True, True, True, False, True], 100, start=len(data)) 28 | data = np.concatenate((data, modulated_samples)) 29 | 30 | plt.subplot(2, 1, 1) 31 | axes = plt.gca() 32 | axes.set_ylim([-2,2]) 33 | plt.plot(data.real) 34 | plt.title("Modulated Wave") 35 | 36 | plt.subplot(2, 1, 2) 37 | qad = signal_functions.afp_demod(np.ascontiguousarray(data), 0, "FSK") 38 | plt.plot(qad) 39 | plt.title("Quad Demod") 40 | 41 | plt.show() 42 | 43 | def test_carrier_auto_detect(self): 44 | signal = Signal(get_path_for_data_file("wsp.complex"), "test") 45 | signal.modulation_type = "ASK" 46 | signal.noise_threshold = 0.035 47 | signal.center = 0.0245 48 | signal.samples_per_symbol = 25 49 | pa = ProtocolAnalyzer(signal) 50 | pa.get_protocol_from_signal() 51 | start, num_samples = pa.get_samplepos_of_bitseq(0, 0, 0, 999999, include_pause=False) 52 | 53 | print("-----------") 54 | print(signal.estimate_frequency(start, end=start+num_samples, sample_rate=2e6)) 55 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | f = os.readlink(__file__) if os.path.islink(__file__) else __file__ 4 | path = os.path.realpath(os.path.join(f, "..", "..", "src")) 5 | 6 | if path not in sys.path: 7 | sys.path.insert(0, path) 8 | -------------------------------------------------------------------------------- /tests/auto_interpretation/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | f = os.readlink(__file__) if os.path.islink(__file__) else __file__ 4 | path = os.path.realpath(os.path.join(f, "..", "..", "..", "src")) 5 | 6 | if path not in sys.path: 7 | sys.path.insert(0, path) -------------------------------------------------------------------------------- /tests/auto_interpretation/test_modulation_detection.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tests.test_util import get_path_for_data_file 3 | from urh.ainterpretation import AutoInterpretation 4 | import numpy as np 5 | 6 | from urh.signalprocessing.Modulator import Modulator 7 | 8 | 9 | class TestModulationDetection(unittest.TestCase): 10 | def test_fsk_detection(self): 11 | fsk_signal = np.fromfile(get_path_for_data_file("fsk.complex"), dtype=np.complex64)[5:15000] 12 | 13 | mod = AutoInterpretation.detect_modulation(fsk_signal, wavelet_scale=4, median_filter_order=7) 14 | self.assertEqual(mod, "FSK") 15 | 16 | def test_ook_detection(self): 17 | data = np.fromfile(get_path_for_data_file("ask.complex"), dtype=np.complex64) 18 | mod = AutoInterpretation.detect_modulation(data) 19 | self.assertEqual(mod, "OOK") 20 | 21 | data = np.fromfile(get_path_for_data_file("ASK_mod.complex"), dtype=np.complex64) 22 | mod = AutoInterpretation.detect_modulation(data) 23 | self.assertEqual(mod, "OOK") 24 | 25 | def test_ask50_detection(self): 26 | message_indices = [(0, 8000), (18000, 26000), (36000, 44000), (54000, 62000), (72000, 80000)] 27 | data = np.fromfile(get_path_for_data_file("ask50.complex"), dtype=np.complex64) 28 | 29 | for start, end in message_indices: 30 | mod = AutoInterpretation.detect_modulation(data[start:end]) 31 | self.assertEqual(mod, "ASK", msg="{}/{}".format(start, end)) 32 | 33 | def test_psk_detection(self): 34 | modulator = Modulator("") 35 | modulator.modulation_type = "PSK" 36 | modulator.parameters[0] = 0 37 | modulator.parameters[1] = 180 38 | 39 | data = modulator.modulate("10101010111000") 40 | mod = AutoInterpretation.detect_modulation(data) 41 | self.assertEqual(mod, "PSK") 42 | -------------------------------------------------------------------------------- /tests/awre/AWRETestCase.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import unittest 4 | 5 | import numpy 6 | from urh.awre.FormatFinder import FormatFinder 7 | 8 | from tests.utils_testing import get_path_for_data_file 9 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 10 | 11 | from urh.signalprocessing.MessageType import MessageType 12 | 13 | from urh.awre.MessageTypeBuilder import MessageTypeBuilder 14 | from urh.awre.ProtocolGenerator import ProtocolGenerator 15 | from urh.signalprocessing.FieldType import FieldType 16 | 17 | 18 | class AWRETestCase(unittest.TestCase): 19 | def setUp(self): 20 | numpy.set_printoptions(linewidth=80) 21 | self.field_types = self.__init_field_types() 22 | 23 | def get_format_finder_from_protocol_file(self, filename: str, clear_participant_addresses=True, return_messages=False): 24 | proto_file = get_path_for_data_file(filename) 25 | protocol = ProtocolAnalyzer(signal=None, filename=proto_file) 26 | protocol.from_xml_file(filename=proto_file, read_bits=True) 27 | 28 | self.clear_message_types(protocol.messages) 29 | 30 | ff = FormatFinder(protocol.messages) 31 | if clear_participant_addresses: 32 | ff.known_participant_addresses.clear() 33 | 34 | if return_messages: 35 | return ff, protocol.messages 36 | else: 37 | return ff 38 | 39 | @staticmethod 40 | def __init_field_types(): 41 | result = [] 42 | for field_type_function in FieldType.Function: 43 | result.append(FieldType(field_type_function.value, field_type_function)) 44 | return result 45 | 46 | @staticmethod 47 | def clear_message_types(messages: list): 48 | mt = MessageType("empty") 49 | for msg in messages: 50 | msg.message_type = mt 51 | 52 | @staticmethod 53 | def save_protocol(name, protocol_generator, silent=False): 54 | filename = os.path.join(tempfile.gettempdir(), name + ".proto") 55 | if isinstance(protocol_generator, ProtocolGenerator): 56 | protocol_generator.to_file(filename) 57 | elif isinstance(protocol_generator, ProtocolAnalyzer): 58 | participants = list(set(msg.participant for msg in protocol_generator.messages)) 59 | protocol_generator.to_xml_file(filename, [], participants=participants, write_bits=True) 60 | info = "Protocol written to " + filename 61 | if not silent: 62 | print() 63 | print("-" * len(info)) 64 | print(info) 65 | print("-" * len(info)) 66 | -------------------------------------------------------------------------------- /tests/awre/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/awre/__init__.py -------------------------------------------------------------------------------- /tests/awre/test_common_range.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from urh.awre.CommonRange import CommonRange 4 | 5 | 6 | class TestCommonRange(unittest.TestCase): 7 | def test_ensure_not_overlaps(self): 8 | test_range = CommonRange(start=4, length=8, value="12345678") 9 | self.assertEqual(test_range.end, 11) 10 | 11 | # no overlapping 12 | self.assertEqual(test_range, test_range.ensure_not_overlaps(0, 3)[0]) 13 | self.assertEqual(test_range, test_range.ensure_not_overlaps(20, 24)[0]) 14 | 15 | # overlapping on left 16 | result = test_range.ensure_not_overlaps(2, 6)[0] 17 | self.assertEqual(result.start, 6) 18 | self.assertEqual(result.end, 11) 19 | 20 | # overlapping on right 21 | result = test_range.ensure_not_overlaps(6, 14)[0] 22 | self.assertEqual(result.start, 4) 23 | self.assertEqual(result.end, 5) 24 | 25 | # full overlapping 26 | self.assertEqual(len(test_range.ensure_not_overlaps(3, 14)), 0) 27 | 28 | # overlapping in the middle 29 | result = test_range.ensure_not_overlaps(6, 9) 30 | self.assertEqual(len(result), 2) 31 | left, right = result[0], result[1] 32 | self.assertEqual(left.start, 4) 33 | self.assertEqual(left.end, 5) 34 | self.assertEqual(right.start, 10) 35 | self.assertEqual(right.end, 11) 36 | -------------------------------------------------------------------------------- /tests/cli/switch_socket.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ../../src/urh/cli/urh_cli.py -d HackRF -f 868.3e6 -s 2e6 -g 0 -if 30 -cf 3.906e3 -mo ASK -bl 16 -p0 0 -p1 1 -m aad3d5ddddcc5d45ddbba000000/500ms aad3c5ddddcc5d45ddbaa00000000 --pause 10s -tx --hex -e "'WSP', 'Wireless Short Packet (WSP)'" -------------------------------------------------------------------------------- /tests/cli/test_cli_logic.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from urh.cli import urh_cli 4 | from urh.signalprocessing.Message import Message 5 | from urh.signalprocessing.Modulator import Modulator 6 | from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer 7 | from urh.signalprocessing.Signal import Signal 8 | 9 | 10 | class TestCLILogic(unittest.TestCase): 11 | def test_cli_modulate_messages(self): 12 | modulator = Modulator("test") 13 | modulator.sample_rate = 2e3 14 | modulator.samples_per_symbol = 100 15 | modulator.modulation_type = "ASK" 16 | modulator.parameters[0] = 0 17 | modulator.parameters[1] = 100 18 | 19 | bits = "1010111100001" 20 | 21 | self.assertIsNone(urh_cli.modulate_messages([], modulator)) 22 | 23 | message = Message.from_plain_bits_str(bits, pause=1000) 24 | 25 | modulated = urh_cli.modulate_messages([message], modulator) 26 | 27 | # Demodulate for testing 28 | s = Signal("", "", modulation="ASK", sample_rate=2e6) 29 | s.samples_per_symbol = 100 30 | s.noise_threshold = 0 31 | s.iq_array = modulated 32 | 33 | pa = ProtocolAnalyzer(s) 34 | pa.get_protocol_from_signal() 35 | self.assertEqual(len(pa.messages), 1) 36 | self.assertEqual(pa.messages[0].plain_bits_str, bits) 37 | -------------------------------------------------------------------------------- /tests/data/ASK_mod.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/ASK_mod.complex -------------------------------------------------------------------------------- /tests/data/FSK10.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/FSK10.complex -------------------------------------------------------------------------------- /tests/data/FSK15.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/FSK15.complex -------------------------------------------------------------------------------- /tests/data/TestProjectForCLI.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1000000.0 8 | 433920000.0 9 | 20 10 | USRP 11 | 1000000.0 12 | 13 | 14 | 15 | 16 | 'Non Return To Zero (NRZ)', 17 | 'Non Return To Zero Inverted (NRZ-I)', 'Invert', 18 | 'Manchester I', 'Edge Trigger', 19 | 'Manchester II', 'Edge Trigger', 'Invert', 20 | 'Differential Manchester', 'Edge Trigger', 'Differential Encoding', 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/data/ask.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/ask.complex -------------------------------------------------------------------------------- /tests/data/ask50.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/ask50.complex -------------------------------------------------------------------------------- /tests/data/ask_short.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/ask_short.complex -------------------------------------------------------------------------------- /tests/data/cc1101.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/cc1101.complex -------------------------------------------------------------------------------- /tests/data/code.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | from subprocess import call 6 | 7 | cur_dir = os.path.dirname(os.path.realpath(__file__)) 8 | 9 | if sys.argv[1] == "e": 10 | call(sys.executable + ' "' + os.path.join(cur_dir, "encode.py") + '"' + " " + sys.argv[2], shell=True) 11 | elif sys.argv[1] == "d": 12 | call(sys.executable + ' "' + os.path.join(cur_dir, "decode.py") + '"' + " " + sys.argv[2], shell=True) 13 | else: 14 | print("Unknown") 15 | -------------------------------------------------------------------------------- /tests/data/constant_bits.txt: -------------------------------------------------------------------------------- 1 | 1010101010101010101010101010101010011011001111101100110100111110100101101100000000111000101111010101011110000111000000000000000000011000110110110000000110011000000000000000100000000010000000011000000010100111101110100100010011100010110010100010100000000000000000000111111001001101101100000000000000001000000000000000000000000000000100000000100000100000101110000000000000010000000010000001010001100001111010111011100000000110110111011101101111011100110000000000000000000 2 | 1010101010101010101010101010101001100111101101000011001110110100000000110000110110111000101111010101011100000001100110111100011100010100010010000000000000000 3 | 10101010101010101010101010101010100110110011111011001101001111101001100100000000001110001011110101010111100000001101101100000001100110111100011100010100010011100000000000001000001000000001000000110011100100100100110010111111110101010000011011101011011110000111101010111101010001001010010000111111101010111100101011010101001110111011011111110010101111100111000100100110100001111101111001011111001011000100010101011011001110001111010110101111100100110011001010011111011111011010101100111011010000000000000000000 4 | 1010101010101010101010101010101010011011001111101100110100111110100000011011110001111000101111010101011100010100010010000000000000000 -------------------------------------------------------------------------------- /tests/data/csvtest.csv: -------------------------------------------------------------------------------- 1 | ; CSV, generated by libsigrok4DSL 0.2.0 on Wed Nov 1 21:19:30 2017 2 | ; Channels (2/2) 3 | ; Sample rate: 100 MHz 4 | ; Sample count: 1 M Samples 5 | 0 (Unit: mV), 1 (Unit: V) 6 | 0.23,-10.07 7 | 0.23,-10.07 8 | -0.56,-10.07 9 | -2.13,-10.26 10 | -2.13,-10.26 11 | -2.13,-10.07 12 | -5.26,-10.07 13 | -4.48,-10.07 14 | -3.69,-10.07 15 | -5.26,-10.07 16 | -3.69,-10.07 17 | -2.91,-10.07 18 | -2.91,-10.07 19 | -2.13,-10.07 20 | -2.13,-10.26 21 | -2.13,-10.07 22 | -0.56,-10.07 23 | -0.56,-10.07 24 | 0.23,-10.07 25 | 0.23,-10.26 26 | 1.01,-10.26 27 | 2.58,-10.26 28 | 2.58,-10.26 29 | 4.15,-10.07 30 | 4.93,-10.07 31 | 4.93,-10.07 32 | 4.15,-10.07 33 | 5.72,-10.07 34 | 4.15,-10.07 35 | 5.72,-10.07 36 | 4.15,-10.07 37 | 4.15,-10.07 38 | 4.15,-10.07 39 | 3.36,-10.26 40 | 2.58,-10.26 41 | 3.36,-10.07 42 | 2.58,-10.07 43 | 1.80,-10.07 44 | 1.01,-10.26 45 | 1.01,-10.26 46 | 0.23,-10.07 47 | -0.56,-10.07 48 | -2.13,-10.07 49 | -2.13,-10.26 50 | -2.91,-10.07 51 | -2.13,-10.26 52 | -2.13,-10.26 53 | -2.91,-10.07 54 | -2.91,-10.26 55 | -2.13,-10.26 56 | -2.91,-10.07 57 | -2.13,-10.26 58 | -2.13,-10.26 59 | -2.91,-10.07 60 | -2.91,-10.07 61 | -2.13,-10.07 62 | -2.91,-10.26 63 | -2.13,-10.07 64 | -0.56,-10.26 65 | -0.56,-10.07 66 | 1.01,-10.07 67 | 3.36,-10.07 68 | 3.36,-10.07 69 | 3.36,-10.07 70 | 3.36,-10.26 71 | 4.15,-10.26 72 | 3.36,-10.07 73 | 3.36,-10.07 74 | 4.15,-10.07 75 | 2.58,-10.26 76 | 3.36,-10.26 77 | 3.36,-10.07 78 | 3.36,-10.07 79 | 3.36,-10.07 80 | 3.36,-10.07 81 | 2.58,-10.26 82 | 2.58,-10.07 83 | 1.80,-10.07 84 | 1.01,-10.07 85 | 1.80,-10.07 86 | 2.58,-10.07 87 | 0.23,-10.07 88 | 0.23,-10.26 89 | 0.23,-10.07 90 | -1.34,-10.26 91 | -2.13,-10.07 92 | -0.56,-10.07 93 | -2.13,-10.07 94 | -2.13,-10.26 95 | -2.13,-10.07 96 | -1.34,-10.07 97 | -0.56,-10.07 98 | 0.23,-10.07 99 | 0.23,-10.26 100 | 0.23,-10.26 101 | 1.01,-10.07 102 | 1.01,-10.26 103 | 0.23,-10.26 104 | 1.01,-10.07 105 | 1.01,-10.07 106 | -------------------------------------------------------------------------------- /tests/data/decode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple example external decoding 4 | Simply removes every second bit 5 | """ 6 | 7 | import sys 8 | bits = sys.argv[1] 9 | print("".join(b for b in bits[::2])) 10 | -------------------------------------------------------------------------------- /tests/data/decoded_with_trash.txt: -------------------------------------------------------------------------------- 1 | 101010101010101010101010101010101001101001111101100110100111110100101101100000000111000011100000000000000000001100001001010100110110110100000000000000010000000001000000001100000001010011110111010010001001110001011001111110111100000000000000000000010111001011100100000000000000000100000000000000000000000000001000000001000000010000011000000000000000000100000010000000100111111101010010010111000011000100110001101001000101000111110101010011001111101110 2 | 1010101010101010101010101010101001100111011010000110011101101000000001100000100101010011011011010111100011100010100010010000000000000000 3 | 1010101010101010101010101010101001100111011010000110011101101000000101000010000001110000101111010111001110011011011110001110001010001001000000000000110101010101101110100101001100110010010000010001110111110100000011000111001010010010000011100001111010 4 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000 5 | 101010101010101010101010101010100110011101101000011001110110100000000011101111010111001110011011101010111000110111 6 | 101010101010101010101010101010101001101001111101100110100111110100110010000000000111000000001001010100110110110101111000111000101000100111000000000000010000010000000010100011010110111110001000101101100100101001011000110101111111011101000111111011100010110100011010111101101110001010001010110111101010101011100011001000010011100010010111100100000001110001111110111101011000101101101111001110000110010111111011101001101111000001010111011010001000100110001110011001110110100010110100000010111 7 | 10101010101010101010101010101010100110100111110110011010011111010000001101111000111000101000100100001010111000000 -------------------------------------------------------------------------------- /tests/data/elektromaten.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/elektromaten.coco -------------------------------------------------------------------------------- /tests/data/encode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple example external encoding 4 | Simply doubles each bit of the input 5 | """ 6 | 7 | import sys 8 | bits = sys.argv[1] 9 | print("".join(b+b for b in bits)) 10 | -------------------------------------------------------------------------------- /tests/data/enocean.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/enocean.complex -------------------------------------------------------------------------------- /tests/data/enocean_bits.txt: -------------------------------------------------------------------------------- 1 | 1010101010010110000101010000000000101100000111000000001010011011 2 | 1010101010010110000101010000000000101100000111000000001010011011 3 | 1010101010010110000101010000000000101100000111000000001010011011 4 | 1010101010010101000000000000000000101100000111000000001000101011 5 | 1010101010010101000000000000000000101100000111000000001000101011 6 | 1010101010010101000000000000000000101100000111000000001000101011 7 | 1010101010010110000100000000000000101100000111000000001001001011 8 | 1010101010010110000100000000000000101100000111000000001001001011 9 | 1010101010010110000100000000000000101100000111000000001001001011 10 | 1010101010010101000000000000000000101100000111000000001000101011 11 | 1010101010010101000000000000000000101100000111000000001000101011 12 | 1010101010010101000000000000000000101100000111000000001000101011 -------------------------------------------------------------------------------- /tests/data/esaver.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/esaver.coco -------------------------------------------------------------------------------- /tests/data/external_program_simulator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | messages = sys.stdin.readlines() 6 | 7 | # we get something like 8 | # ->1010100000111111 9 | # <-1000001111000000 10 | 11 | message = messages[0] 12 | direction = message[0:2] 13 | 14 | if direction == "->": 15 | result = "10" * int(sys.argv[1]) 16 | else: 17 | result = "01" * int(sys.argv[1]) 18 | 19 | print(result, end="") 20 | -------------------------------------------------------------------------------- /tests/data/fsk.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/fsk.complex -------------------------------------------------------------------------------- /tests/data/fsk_live.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/fsk_live.coco -------------------------------------------------------------------------------- /tests/data/homematic.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/homematic.coco -------------------------------------------------------------------------------- /tests/data/multi_messages_different_rssi.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/multi_messages_different_rssi.coco -------------------------------------------------------------------------------- /tests/data/noised_homematic.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/noised_homematic.complex -------------------------------------------------------------------------------- /tests/data/ook_overshoot.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/ook_overshoot.coco -------------------------------------------------------------------------------- /tests/data/psk_gen_noisy.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/psk_gen_noisy.complex -------------------------------------------------------------------------------- /tests/data/psk_generated.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/psk_generated.complex -------------------------------------------------------------------------------- /tests/data/pwm.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/pwm.coco -------------------------------------------------------------------------------- /tests/data/steckdose_anlernen.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/steckdose_anlernen.complex -------------------------------------------------------------------------------- /tests/data/three_channels.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/three_channels.complex -------------------------------------------------------------------------------- /tests/data/two_participants.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/two_participants.coco -------------------------------------------------------------------------------- /tests/data/unaveraged.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/unaveraged.coco -------------------------------------------------------------------------------- /tests/data/wsp.complex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/wsp.complex -------------------------------------------------------------------------------- /tests/data/xavax.coco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/data/xavax.coco -------------------------------------------------------------------------------- /tests/debug_tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import subprocess 4 | import sys 5 | import time 6 | 7 | RUNS = 2000 8 | os.system("mkdir -p /tmp/tests") 9 | 10 | streak = 0 11 | longest_streak = 0 12 | 13 | for i in range(RUNS): 14 | try: 15 | filename = "/tmp/tests/" + str(datetime.datetime.now()).replace(" ", "-") 16 | t = time.time() 17 | completed = subprocess.run("pytest -s -v ../tests", shell=True, stdout=subprocess.PIPE, 18 | stderr=subprocess.STDOUT) 19 | duration = time.time() - t 20 | if completed.returncode == 0: 21 | streak += 1 22 | longest_streak = max(streak, longest_streak) 23 | print("#{} was successful [{:.2f}s] (Streak: {}/{})".format(i + 1, duration, streak, longest_streak)) 24 | else: 25 | streak = 0 26 | print("#{} failed [{:.2f}s]".format(i + 1, duration)) 27 | with open(filename, "wb") as f: 28 | f.write(completed.stdout) 29 | print("Written output to file {}".format(filename)) 30 | except KeyboardInterrupt: 31 | sys.exit(1) 32 | -------------------------------------------------------------------------------- /tests/device/TestAirSpy.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import time 4 | 5 | from multiprocessing import Queue, Pipe 6 | 7 | import numpy as np 8 | 9 | from urh.dev.native.lib import airspy 10 | 11 | 12 | class TestAirSpy(unittest.TestCase): 13 | def test_cython_wrapper(self): 14 | result = airspy.open() 15 | print("Open:", airspy.error_name(result), result) 16 | 17 | sample_rates = airspy.get_sample_rates() 18 | print("Samples rates:", sample_rates) 19 | 20 | result = airspy.set_sample_rate(10**6) 21 | print("Set sample rate", airspy.error_name(result), result) 22 | 23 | result = airspy.set_center_frequency(int(433.92e6)) 24 | print("Set center frequency", airspy.error_name(result), result) 25 | 26 | result = airspy.set_if_rx_gain(5) 27 | print("Set lna gain", airspy.error_name(result), result) 28 | 29 | result = airspy.set_rf_gain(8) 30 | print("Set mixer gain", airspy.error_name(result), result) 31 | 32 | result = airspy.set_baseband_gain(10) 33 | print("Set vga gain", airspy.error_name(result), result) 34 | 35 | parent_conn, child_conn = Pipe() 36 | 37 | result = airspy.start_rx(child_conn.send_bytes) 38 | print("Set start rx", airspy.error_name(result), result) 39 | 40 | time.sleep(0.01) 41 | print(np.fromstring(parent_conn.recv_bytes(8*65536), dtype=np.complex64)) 42 | 43 | print("Closing") 44 | 45 | parent_conn.close() 46 | child_conn.close() 47 | 48 | result = airspy.stop_rx() 49 | print("Set stop rx", airspy.error_name(result), result) 50 | 51 | result = airspy.close() 52 | print("Close:", airspy.error_name(result), result) 53 | 54 | 55 | if __name__ == '__main__': 56 | unittest.main() 57 | -------------------------------------------------------------------------------- /tests/device/TestBladeRF.py: -------------------------------------------------------------------------------- 1 | import time 2 | from multiprocessing.connection import Pipe 3 | 4 | import numpy as np 5 | import unittest 6 | 7 | from urh.dev.native.BladeRF import BladeRF 8 | from urh.util import util 9 | 10 | util.set_shared_library_path() 11 | 12 | from urh.dev.native.lib import bladerf 13 | 14 | class TestBladeRF(unittest.TestCase): 15 | def test_version(self): 16 | bladerf.get_api_version() 17 | 18 | def test_cython_wrapper(self): 19 | serials = bladerf.get_device_list() 20 | print("Connected serials", serials) 21 | 22 | bladerf.open() 23 | 24 | bladerf.set_tx(True) 25 | bladerf.set_channel(0) 26 | print("set gain", bladerf.set_gain(20)) 27 | print("set gain", bladerf.set_gain(21)) 28 | 29 | bladerf.set_tx(False) 30 | bladerf.set_channel(1) 31 | print("Sample Rate", bladerf.get_sample_rate()) 32 | print("Set sample rate to 2e6", bladerf.set_sample_rate(int(2e6))) 33 | print("sample rate", bladerf.get_sample_rate()) 34 | print("Set sample rate to 40e6", bladerf.set_sample_rate(int(40e6))) 35 | print("sample rate", bladerf.get_sample_rate()) 36 | print("Set sample rate to 200e6", bladerf.set_sample_rate(int(200e6))) 37 | print("sample rate", bladerf.get_sample_rate()) 38 | 39 | bladerf.set_tx(True) 40 | bladerf.set_channel(1) 41 | print("Bandwidth", bladerf.get_bandwidth()) 42 | print("Set Bandwidth to 2e6", bladerf.set_bandwidth(int(2e6))) 43 | print("Bandwidth", bladerf.get_bandwidth()) 44 | 45 | bladerf.set_tx(False) 46 | bladerf.set_channel(0) 47 | print("Frequency", bladerf.get_center_freq()) 48 | print("Set Frequency to 433.92e6", bladerf.set_center_freq(int(433.92e6))) 49 | print("Frequency", bladerf.get_center_freq()) 50 | 51 | bladerf.prepare_sync() 52 | 53 | parent_conn, child_conn = Pipe() 54 | 55 | for i in range(3): 56 | bladerf.receive_sync(child_conn, 4096) 57 | data = parent_conn.recv_bytes() 58 | print(data) 59 | 60 | bladerf.close() 61 | 62 | bladerf.open() 63 | bladerf.set_tx(True) 64 | bladerf.set_channel(0) 65 | bladerf.prepare_sync() 66 | 67 | for i in range(10): 68 | print("Send", bladerf.send_sync(np.fromstring(data, dtype=np.int16))) 69 | bladerf.close() 70 | -------------------------------------------------------------------------------- /tests/device/TestPlutoSDR.py: -------------------------------------------------------------------------------- 1 | from multiprocessing import Pipe 2 | 3 | from urh.util import util 4 | import numpy as np 5 | util.set_shared_library_path() 6 | 7 | from urh.dev.native.lib import plutosdr 8 | 9 | 10 | def test_cython_wrapper(): 11 | descs, uris = plutosdr.scan_devices() 12 | plutosdr.set_tx(False) 13 | print("Devices", descs) 14 | print("Open", plutosdr.open(uris[0])) 15 | print("Set Freq to 433.92e6", plutosdr.set_center_freq(int(433.92e6))) 16 | print("Set Sample Rate to 2M", plutosdr.set_sample_rate(int(2.5e6))) 17 | print("Set bandwidth to 4M", plutosdr.set_bandwidth(int(4e6))) 18 | print("Set gain to 10", plutosdr.set_rf_gain(10)) 19 | 20 | print("prepare rx", plutosdr.setup_rx()) 21 | 22 | parent_conn, child_conn = Pipe() 23 | 24 | for i in range(10): 25 | plutosdr.receive_sync(child_conn) 26 | data = parent_conn.recv_bytes() 27 | print(np.frombuffer(data, dtype=np.int16)) 28 | 29 | print(plutosdr.get_tx()) 30 | print("Close", plutosdr.close()) 31 | 32 | plutosdr.set_tx(True) 33 | 34 | print("Open", plutosdr.open(uris[0])) 35 | print("Setup tx", plutosdr.setup_tx()) 36 | print("Set Freq to 433.92e6", plutosdr.set_center_freq(int(433.92e6))) 37 | print("Set Sample Rate to 2M", plutosdr.set_sample_rate(int(2.5e6))) 38 | print("Set bandwidth to 4M", plutosdr.set_bandwidth(int(4e6))) 39 | print("Set gain to 10", plutosdr.set_rf_gain(-89)) 40 | 41 | print("Send", plutosdr.send_sync(np.zeros(4096, dtype=np.int16))) 42 | 43 | print("Close", plutosdr.close()) 44 | 45 | 46 | if __name__ == '__main__': 47 | test_cython_wrapper() 48 | -------------------------------------------------------------------------------- /tests/device/TestRTLSDRTCP.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from urh.dev.native.RTLSDRTCP import RTLSDRTCP 4 | 5 | class TestRTLSDRTCP(unittest.TestCase): 6 | 7 | def test_device_communication(self): 8 | error = 0 9 | sdr = RTLSDRTCP(0, 0, 0, device_number=0) 10 | sdr.open(sdr.child_ctrl_conn) 11 | if sdr.socket_is_open == False: 12 | error += 1 13 | if sdr.set_parameter("centerFreq", 927000000, sdr.child_ctrl_conn): 14 | error += 1 15 | if sdr.set_parameter("sampleRate", 2000000, sdr.child_ctrl_conn): 16 | error += 1 17 | if sdr.set_parameter("bandwidth", 2000000, sdr.child_ctrl_conn): 18 | error += 1 19 | if sdr.set_parameter("tunerGain", 200, sdr.child_ctrl_conn): 20 | error += 1 21 | data = sdr.read_sync() 22 | if len(data) < 1: 23 | error += 1 24 | sdr.close() 25 | self.assertEqual(error, 0) 26 | 27 | 28 | if __name__ == "__main__": 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tests/device/TestSDRPlay.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import time 4 | from multiprocessing.connection import Connection, Pipe 5 | 6 | import numpy as np 7 | from multiprocessing import Process 8 | 9 | from urh.dev.native.SDRPlay import SDRPlay 10 | from urh.util import util 11 | import ctypes 12 | 13 | util.set_shared_library_path() 14 | 15 | from urh.dev.native.lib import sdrplay 16 | 17 | def recv(conn: Connection): 18 | while True: 19 | t = time.time() 20 | result = SDRPlay.bytes_to_iq(conn.recv_bytes()) 21 | print("UNPACK", time.time()-t) 22 | 23 | class TestSDRPlay(unittest.TestCase): 24 | def test_c_wrapper(self): 25 | def pycallback(data): 26 | arr = np.asarray(data) 27 | #result = np.empty(len(arr) // 2, dtype=np.complex64) 28 | #result.real = (arr[::2] + 0.5) / 32767.5 29 | #result.imag = (arr[1::2] + 0.5) / 32767.5 30 | 31 | print(sdrplay.get_api_version()) 32 | print(sdrplay.get_devices()) 33 | print(sdrplay.set_device_index(0)) 34 | 35 | parent_conn, child_conn = Pipe() 36 | p = Process(target=recv, args=(parent_conn,)) 37 | p.daemon = True 38 | p.start() 39 | 40 | null_ptr = ctypes.POINTER(ctypes.c_voidp)() 41 | print("Init stream", sdrplay.init_stream(50, 2e6, 433.92e6, 2e6, 500, child_conn)) 42 | 43 | time.sleep(2) 44 | print("settings sample rate") 45 | print("Set sample rate", sdrplay.set_sample_rate(2e6)) 46 | 47 | time.sleep(1) 48 | p.terminate() 49 | p.join() 50 | -------------------------------------------------------------------------------- /tests/device/TestUSRP.py: -------------------------------------------------------------------------------- 1 | from multiprocessing.connection import Pipe 2 | 3 | import sys 4 | 5 | from urh.util import util 6 | 7 | util.set_shared_library_path() 8 | 9 | 10 | from urh.dev.native.USRP import USRP 11 | from urh.dev.native.lib import usrp 12 | import unittest 13 | 14 | class TestUSRP(unittest.TestCase): 15 | def test_cython_wrapper(self): 16 | print(usrp.find_devices("")) 17 | 18 | usrp.set_tx(False) 19 | 20 | return_code = usrp.open("addr=192.168.10.2") 21 | print("open", return_code) 22 | 23 | usrp.setup_stream() 24 | print("Made rx_streame handler") 25 | 26 | print(usrp.get_device_representation()) 27 | 28 | print("Set sample rate", usrp.set_sample_rate(2e6)) 29 | print("Set freq", usrp.set_center_freq(433.92e6)) 30 | print("Set bandwidth", usrp.set_bandwidth(1e6)) 31 | print("Set gain", usrp.set_rf_gain(0.5)) 32 | 33 | 34 | 35 | buffer = bytearray() 36 | 37 | num_samples = 32768 // 2 38 | 39 | usrp.start_stream(num_samples) 40 | parent_conn, child_conn = Pipe() 41 | 42 | for i in range(500): 43 | usrp.recv_stream(child_conn, num_samples) 44 | received_bytes = parent_conn.recv_bytes() 45 | #print(received_bytes) 46 | print(i) 47 | buffer.extend(received_bytes) 48 | #print(USRP.bytes_to_iq(received_bytes, len(received_bytes) // 8)) 49 | 50 | f = open("/tmp/test.complex", "wb") 51 | f.write(buffer) 52 | f.close() 53 | 54 | usrp.destroy_stream() 55 | print("Freed rx streamer handler") 56 | 57 | return_code = usrp.close() 58 | print("close", return_code) 59 | 60 | 61 | #self.assertTrue(True) 62 | 63 | if __name__ == '__main__': 64 | unittest.main() 65 | -------------------------------------------------------------------------------- /tests/device/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/device/__init__.py -------------------------------------------------------------------------------- /tests/docker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blackhatethicalhacking/urh/c611a8148ffd6869105a84442e6355a66e02b194/tests/docker/__init__.py -------------------------------------------------------------------------------- /tests/docker/archlinux/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM base/archlinux 2 | 3 | RUN mkdir -p /root/.gnupg\ 4 | && pacman -Sy --noconfirm archlinux-keyring\ 5 | && pacman -Syyuu --noconfirm\ 6 | && pacman-db-upgrade\ 7 | && pacman -S --noconfirm ca-certificates ca-certificates-mozilla\ 8 | && pacman -S --noconfirm curl base-devel nano vim\ 9 | && useradd -m -G wheel -s /bin/bash dockeruser\ 10 | && echo "dockeruser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers 11 | 12 | 13 | WORKDIR /tmp 14 | RUN curl -O https://aur.archlinux.org/cgit/aur.git/snapshot/package-query.tar.gz\ 15 | && sudo -u dockeruser tar -xvzf package-query.tar.gz 16 | 17 | WORKDIR /tmp/package-query 18 | RUN sudo -u dockeruser makepkg -si --noconfirm 19 | 20 | WORKDIR /tmp 21 | RUN curl -O https://aur.archlinux.org/cgit/aur.git/snapshot/yaourt.tar.gz\ 22 | && sudo -u dockeruser tar -xvzf yaourt.tar.gz 23 | 24 | WORKDIR /tmp/yaourt 25 | RUN sudo -u dockeruser makepkg -si --noconfirm 26 | 27 | ADD install_from_aur.sh /bin/install_from_aur.sh 28 | RUN chmod +x /bin/install_from_aur.sh 29 | CMD /bin/install_from_aur.sh -------------------------------------------------------------------------------- /tests/docker/archlinux/install_from_aur.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo -u dockeruser yaourt -S --noconfirm urh 4 | urh autoclose -------------------------------------------------------------------------------- /tests/docker/centos7/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN yum -y update 4 | RUN yum -y install git epel-release wget make gcc-c++ 5 | RUN yum -y install python34 python34-numpy python34-devel qt5-qtbase-devel qt5-qttools-devel *x11* 6 | 7 | # Get SIP working 8 | WORKDIR /tmp 9 | RUN wget http://sourceforge.net/projects/pyqt/files/sip/sip-4.18/sip-4.18.tar.gz 10 | RUN tar xf sip-4.18.tar.gz 11 | WORKDIR /tmp/sip-4.18 12 | RUN python3 configure.py 13 | RUN make -j4 14 | RUN make install 15 | 16 | # Compile PyQt5 from source, because it is not available in repos 17 | WORKDIR /tmp 18 | RUN wget http://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/PyQt-gpl-5.5.1.tar.gz 19 | RUN tar xf PyQt-gpl-5.5.1.tar.gz 20 | WORKDIR /tmp/PyQt-gpl-5.5.1 21 | RUN python3 configure.py --qmake /usr/bin/qmake-qt5 --confirm-license 22 | RUN make -j4 23 | RUN make install 24 | 25 | ADD run.sh /bin/run.sh 26 | RUN chmod +x /bin/run.sh 27 | 28 | RUN dbus-uuidgen > /etc/machine-id 29 | 30 | CMD /bin/run.sh -------------------------------------------------------------------------------- /tests/docker/debian8/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:8 2 | 3 | RUN apt-get update\ 4 | && apt-get -y dist-upgrade\ 5 | && apt-get -y install nano vim\ 6 | && apt-get -y install python3-numpy python3-psutil python3-pyqt5 git g++ libpython3-dev python3-setuptools 7 | 8 | ADD install_from_source.sh /bin/install_from_source.sh 9 | RUN chmod +x /bin/install_from_source.sh 10 | CMD /bin/install_from_source.sh -------------------------------------------------------------------------------- /tests/docker/debian8/install_from_source.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git clone https://github.com/jopohl/urh 4 | cd urh 5 | python3 setup.py install 6 | urh autoclose -------------------------------------------------------------------------------- /tests/docker/docker_util.py: -------------------------------------------------------------------------------- 1 | from urh.util.Logger import logger 2 | from subprocess import check_output, call 3 | import os, sys 4 | 5 | USE_SUDO = True 6 | SUPPORTED_IMAGES = ("archlinux", "centos7", "debian8", "ubuntu1404", "ubuntu1604", "kali", "gentoo") 7 | 8 | def is_image_there(imagename: str) -> bool: 9 | cmd = ["sudo"] if USE_SUDO else [] 10 | cmd.extend(["docker", "images", "-q", "urh/" + imagename]) 11 | return len(check_output(cmd)) > 0 12 | 13 | def build_image(imagename: str): 14 | if imagename not in SUPPORTED_IMAGES: 15 | logger.error("{} is not a supported docker image".format(imagename)) 16 | sys.exit(1) 17 | 18 | cmd = ["sudo"] if USE_SUDO else [] 19 | cmd.extend(["docker", "build", "--force-rm", "--no-cache", "--tag", "urh/"+imagename, "."]) 20 | 21 | print(" ".join(cmd)) 22 | 23 | script = __file__ if not os.path.islink(__file__) else os.readlink(__file__) 24 | os.chdir(os.path.realpath(os.path.join(script, "..", imagename))) 25 | 26 | call(cmd) 27 | 28 | def run_image(imagename: str, rebuild=False): 29 | if not is_image_there(imagename) or rebuild: 30 | build_image(imagename) 31 | 32 | cmd = ["sudo"] if USE_SUDO else [] 33 | call(cmd + ["xhost", "+"]) # Allow docker to connect to hosts X Server 34 | 35 | cmd.extend(["docker", "run", "-e", "DISPLAY=$DISPLAY", "-v", "/tmp/.X11-unix:/tmp/.X11-unix", "urh/"+imagename]) 36 | logger.info("call {}".format(" ".join(cmd))) 37 | rc = call(" ".join(cmd), shell=True) 38 | return rc == 0 39 | 40 | def remove_containers(): 41 | logger.info("removing containers") 42 | cmd = ["sudo"] if USE_SUDO else [] 43 | cmd.extend(["docker", "rm", "-f", "$({}docker ps -aq)".format("sudo " if USE_SUDO else "")]) 44 | call(" ".join(cmd), shell=True) 45 | 46 | if __name__ == "__main__": 47 | run_image("archlinux") 48 | -------------------------------------------------------------------------------- /tests/docker/fedora25/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:25 2 | RUN dnf -y update 3 | RUN dnf -y install urh 4 | -------------------------------------------------------------------------------- /tests/docker/gentoo/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gentoo/stage3-amd64 2 | 3 | RUN emerge --sync\ 4 | && emerge dev-vcs/git app-portage/cfg-update\ 5 | && PYTHON_TARGETS="python3_4" emerge psutil numpy 6 | 7 | # PYTHON_TARGETS="python3_4" 8 | RUN USE="gui widgets" emerge --autounmask-write PyQt5 || /bin/true 9 | RUN etc-update --automode -5 10 | RUN USE="gui widgets" emerge PyQt5 # PYTHON_TARGETS="python3_4" 11 | 12 | ADD run_from_source.sh /bin/run_from_source.sh 13 | 14 | RUN chmod +x /bin/run_from_source.sh 15 | CMD /bin/run_from_source.sh -------------------------------------------------------------------------------- /tests/docker/gentoo/run_from_source.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | git clone https://github.com/jopohl/urh 4 | cd urh/src/urh 5 | ./main.py autoclose -------------------------------------------------------------------------------- /tests/docker/info.txt: -------------------------------------------------------------------------------- 1 | # Build: 2 | sudo docker build --tag ubuntu1404 -f ubuntu1404 . 3 | 4 | # Run 5 | sudo docker run -it -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix ubuntu1404 6 | 7 | # On Ubuntu Host: 8 | xhost local:root # For enabling GUI 9 | 10 | # With USB Support (for HackRF) 11 | docker run --rm -it -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix --privileged -v /dev/bus/usb:/dev/bus/usb kali bash -------------------------------------------------------------------------------- /tests/docker/kali/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM kalilinux/kali-linux-docker 2 | 3 | RUN apt-get -y update\ 4 | && apt-get -y dist-upgrade\ 5 | && apt-get clean\ 6 | && apt-get -y install git g++ libpython3-dev libhackrf-dev python3-pip python3-pyqt5 python3-numpy 7 | 8 | ADD install_with_pip.sh /bin/install_with_pip.sh 9 | RUN chmod +x /bin/install_with_pip.sh 10 | CMD /bin/install_with_pip.sh -------------------------------------------------------------------------------- /tests/docker/kali/install_with_pip.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pip3 install urh 4 | urh autoclose -------------------------------------------------------------------------------- /tests/docker/ubuntu1404/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | RUN apt-get update\ 4 | && apt-get -y dist-upgrade\ 5 | && apt-get -y install nano vim\ 6 | && apt-get -y install git g++ libpython3-dev python3-pip python3-pyqt5 python3-numpy python3-setuptools\ 7 | && apt-get -y install build-essential cmake libusb-1.0-0-dev liblog4cpp5-dev libboost-dev libboost-system-dev libboost-thread-dev libboost-program-options-dev swig pkg-config # Install HackRF 8 | 9 | WORKDIR /tmp 10 | 11 | RUN git clone https://github.com/mossmann/hackrf.git 12 | WORKDIR /tmp/hackrf/host 13 | 14 | RUN mkdir build 15 | WORKDIR /tmp/hackrf/host/build 16 | RUN cmake ../ -DINSTALL_UDEV_RULES=ON\ 17 | && make -j4\ 18 | && make install\ 19 | && ldconfig 20 | 21 | # Install Gnuradio - Doesnt work fails with package uhd-host 22 | # RUN apt-get -y install gnuradio gnuradio-dev gr-iqbal 23 | 24 | ADD install_with_pip.sh /bin/install_with_pip.sh 25 | RUN chmod +x /bin/install_with_pip.sh 26 | CMD /bin/install_with_pip.sh 27 | -------------------------------------------------------------------------------- /tests/docker/ubuntu1404/install_with_pip.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pip3 install urh 4 | urh autoclose -------------------------------------------------------------------------------- /tests/docker/ubuntu1604/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update\ 4 | && apt-get -y dist-upgrade\ 5 | && apt-get -y install nano vim\ 6 | && apt-get -y install git g++ libpython3-dev python3-pip python3-pyqt5 python3-numpy cmake libsqlite3-dev libi2c-dev libusb-1.0-0-dev\ 7 | && apt-get -y install libairspy-dev libhackrf-dev librtlsdr-dev libuhd-dev software-properties-common\ 8 | && add-apt-repository -y ppa:myriadrf/drivers\ 9 | && apt-get update && apt-get install -y limesuite\ 10 | && ln -s /usr/lib/x86_64-linux-gnu/libLimeSuite.so.17.02.2 /usr/lib/x86_64-linux-gnu/libLimeSuite.so 11 | 12 | ADD test_native_backends_installed.sh /bin/test_native_backends_installed.sh 13 | RUN chmod +x /bin/test_native_backends_installed.sh 14 | CMD /bin/test_native_backends_installed.sh 15 | -------------------------------------------------------------------------------- /tests/docker/ubuntu1604/test_native_backends_installed.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | git clone https://github.com/jopohl/urh 3 | cd urh/src 4 | python3 urh/cythonext/build.py 5 | 6 | python3 -c "import urh.dev.native.lib.airspy; print('AirSpy found!')" && \ 7 | python3 -c "import urh.dev.native.lib.hackrf; print('HackRF found!')" && \ 8 | python3 -c "import urh.dev.native.lib.rtlsdr; print('RTL-SDR found!')" && \ 9 | python3 -c "import urh.dev.native.lib.limesdr; print('LimeSDR found!')" && \ 10 | python3 -c "import urh.dev.native.lib.usrp; print('USRP found!')" 11 | -------------------------------------------------------------------------------- /tests/performance/fft.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import time 3 | 4 | 5 | def init_array(size: int): 6 | result = np.empty((size, size), dtype=np.complex64) 7 | result.real = np.random.rand(size) 8 | result.imag = np.random.rand(size) 9 | return result 10 | 11 | def numpy_fft(array, window_size:int): 12 | np.fft.fft(array, window_size) 13 | 14 | def scipy_fft(array, window_size: int): 15 | import scipy.fftpack 16 | scipy.fftpack.fft(array, window_size) 17 | 18 | def pyfftw_fft(array): 19 | import pyfftw 20 | pyfftw.interfaces.cache.enable() 21 | fft_a = pyfftw.interfaces.numpy_fft.fft(array, threads=2, overwrite_input=True) 22 | 23 | if __name__ == '__main__': 24 | print("Size\tIterations\tNumpy\tScipy\tPyFFTW") 25 | iterations = 70 26 | arr = init_array(1024) 27 | numpy_time = time.time() 28 | for _ in range(iterations): 29 | numpy_fft(arr, 1024) 30 | numpy_time = time.time()-numpy_time 31 | 32 | scipy_time = time.time() 33 | for _ in range(iterations): 34 | scipy_fft(arr, 1024) 35 | scipy_time = time.time()-scipy_time 36 | 37 | pyfftw_time = time.time() 38 | for _ in range(iterations): 39 | pyfftw_fft(arr) 40 | pyfftw_time = time.time()-pyfftw_time 41 | 42 | print("{0}\t{1}\t\t\t{2:.4f}\t{3:.4f}\t{4:.4f}".format(1024, iterations, numpy_time, scipy_time, pyfftw_time)) 43 | -------------------------------------------------------------------------------- /tests/performance/modulator_performance.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from urh.signalprocessing.Modulator import Modulator 4 | 5 | 6 | def test_fsk_performance(): 7 | bit_data = "10" * 100 + "0000011111" + "001101011" * 100 + "111111100000" * 100 8 | modulator = Modulator("Perf") 9 | modulator.modulation_type = "FSK" 10 | t = time.time() 11 | result = modulator.modulate(bit_data, pause=10000000) 12 | elapsed = time.time() - t 13 | 14 | result.tofile("/tmp/fsk.complex") 15 | print("FSK {}ms".format(elapsed * 1000)) 16 | 17 | 18 | def test_ask_performance(): 19 | bit_data = "10" * 100 + "0000011111" + "001101011" * 100 + "111111100000" * 1000 20 | modulator = Modulator("Perf") 21 | modulator.modulation_type = "ASK" 22 | t = time.time() 23 | result = modulator.modulate(bit_data, pause=10000000) 24 | elapsed = time.time() - t 25 | 26 | result.tofile("/tmp/ask.complex") 27 | print("ASK {}ms".format(elapsed * 1000)) 28 | 29 | 30 | def test_psk_performance(): 31 | bit_data = "10" * 100 + "0000011111" + "001101011" * 100 + "111111100000" * 1000 32 | modulator = Modulator("Perf") 33 | modulator.modulation_type = "PSK" 34 | t = time.time() 35 | result = modulator.modulate(bit_data, pause=10000000) 36 | elapsed = time.time() - t 37 | 38 | result.tofile("/tmp/psk.complex") 39 | print("PSK {}ms".format(elapsed * 1000)) 40 | 41 | def test_gfsk_performance(): 42 | bit_data = "10" * 100 + "0000011111" + "001101011" * 100 + "111111100000" * 100 43 | modulator = Modulator("Perf") 44 | modulator.modulation_type = "GFSK" 45 | t = time.time() 46 | result = modulator.modulate(bit_data, pause=10000000) 47 | elapsed = time.time() - t 48 | 49 | result.tofile("/tmp/gfsk.complex") 50 | print("GFSK {}ms".format(elapsed * 1000)) 51 | 52 | if __name__ == '__main__': 53 | test_fsk_performance() 54 | -------------------------------------------------------------------------------- /tests/performance/spectrum_analyzer.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | import numpy as np 4 | from PyQt5.QtWidgets import QApplication 5 | from multiprocessing import Process 6 | 7 | from urh import constants 8 | from urh.controller.MainController import MainController 9 | from urh.controller.dialogs.SpectrumDialogController import SpectrumDialogController 10 | from urh.util.ProjectManager import ProjectManager 11 | 12 | 13 | def get_free_port(): 14 | s = socket.socket() 15 | s.bind(("", 0)) 16 | port = s.getsockname()[1] 17 | s.close() 18 | return port 19 | 20 | 21 | def send_data(port): 22 | num_samples = constants.SPECTRUM_BUFFER_SIZE 23 | frequency = 0.1 24 | divisor = 200 25 | pos = 0 26 | while True: 27 | sock = open_socket(port) 28 | result = np.zeros(num_samples, dtype=np.complex64) 29 | result.real = np.cos(2 * np.pi * frequency * np.arange(pos, pos + num_samples)) 30 | result.imag = np.sin(2 * np.pi * frequency * np.arange(pos, pos + num_samples)) 31 | pos += num_samples 32 | if pos / num_samples >= divisor: 33 | frequency *= 2 34 | if frequency >= 1: 35 | frequency = 0.1 36 | pos = 0 37 | sock.sendall(result.tostring()) 38 | close_socket(sock) 39 | 40 | 41 | def open_socket(port): 42 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 43 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 44 | sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 45 | 46 | sock.connect(("127.0.0.1", port)) 47 | return sock 48 | 49 | 50 | def close_socket(sock): 51 | sock.shutdown(socket.SHUT_RDWR) 52 | sock.close() 53 | 54 | 55 | if __name__ == '__main__': 56 | app = QApplication(["test"]) 57 | main = MainController() 58 | port = get_free_port() 59 | dialog = SpectrumDialogController(ProjectManager(main)) 60 | dialog.showMaximized() 61 | dialog.ui.cbDevice.setCurrentText("Network SDR") 62 | dialog.device.set_server_port(port) 63 | dialog.ui.btnStart.click() 64 | 65 | p = Process(target=send_data, args=(port,)) 66 | p.daemon = True 67 | p.start() 68 | 69 | num_samples = 32768 70 | frequency = 0.1 71 | divisor = 200 72 | pos = 0 73 | app.exec_() 74 | p.terminate() 75 | p.join() 76 | 77 | -------------------------------------------------------------------------------- /tests/test_continuous_modulator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import time 4 | 5 | from urh.signalprocessing.ContinuousModulator import ContinuousModulator 6 | from urh.signalprocessing.Message import Message 7 | from urh.signalprocessing.MessageType import MessageType 8 | from urh.signalprocessing.Modulator import Modulator 9 | 10 | 11 | class TestContinuousModulator(unittest.TestCase): 12 | NUM_MESSAGES = 20 13 | BITS_PER_MESSAGE = 100 14 | 15 | def test_modulate_continuously(self): 16 | modulator = Modulator("Test") 17 | continuous_modulator = ContinuousModulator(self.__create_messages(), [modulator]) 18 | 19 | self.assertEqual(continuous_modulator.current_message_index.value, 0) 20 | self.assertTrue(continuous_modulator.ring_buffer.is_empty) 21 | continuous_modulator.start() 22 | self.assertTrue(continuous_modulator.process.is_alive()) 23 | time.sleep(1) 24 | self.assertFalse(continuous_modulator.ring_buffer.is_empty) 25 | continuous_modulator.stop() 26 | self.assertFalse(continuous_modulator.process.is_alive()) 27 | 28 | def __create_messages(self): 29 | mt = MessageType("test") 30 | return [Message([True] * self.BITS_PER_MESSAGE, 1000, mt) for _ in range(self.NUM_MESSAGES)] 31 | 32 | 33 | if __name__ == '__main__': 34 | unittest.main() 35 | -------------------------------------------------------------------------------- /tests/test_filter_bandwidth_dialog.py: -------------------------------------------------------------------------------- 1 | from tests.QtTestCase import QtTestCase 2 | from urh.controller.dialogs.FilterBandwidthDialog import FilterBandwidthDialog 3 | from urh.signalprocessing.Filter import Filter 4 | 5 | 6 | class TestFilterBandwidthDialog(QtTestCase): 7 | def setUp(self): 8 | super().setUp() 9 | self.dialog = FilterBandwidthDialog() 10 | 11 | def test_change_custom_bw(self): 12 | bw = 0.3 13 | N = Filter.get_filter_length_from_bandwidth(bw) 14 | self.dialog.ui.doubleSpinBoxCustomBandwidth.setValue(bw) 15 | self.assertEqual(N, self.dialog.ui.spinBoxCustomKernelLength.value()) 16 | 17 | N = 401 18 | bw = Filter.get_bandwidth_from_filter_length(N) 19 | self.dialog.ui.spinBoxCustomKernelLength.setValue(N) 20 | self.assertAlmostEqual(bw, self.dialog.ui.doubleSpinBoxCustomBandwidth.value(), 21 | places=self.dialog.ui.doubleSpinBoxCustomBandwidth.decimals()) 22 | -------------------------------------------------------------------------------- /tests/test_fuzzing_profile.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | 4 | from tests.QtTestCase import QtTestCase 5 | from urh import constants 6 | from urh.controller.MainController import MainController 7 | from urh.signalprocessing.Encoding import Encoding 8 | from urh.signalprocessing.Message import Message 9 | from urh.signalprocessing.Modulator import Modulator 10 | from urh.signalprocessing.ProtocolAnalyzerContainer import ProtocolAnalyzerContainer 11 | 12 | 13 | class TestFuzzingProfile(QtTestCase): 14 | def test_load_profile(self): 15 | filename = os.path.join(tempfile.gettempdir(), "test.fuzz.xml") 16 | mod = Modulator("mod 2") 17 | mod.parameters[1] = 42 18 | 19 | decoders = [Encoding(["NRZ"]), Encoding(["NRZ-I", constants.DECODING_INVERT])] 20 | 21 | pac = ProtocolAnalyzerContainer() 22 | pac.messages.append( 23 | Message([True, False, False, True], 100, decoder=decoders[0], message_type=pac.default_message_type)) 24 | pac.messages.append( 25 | Message([False, False, False, False], 200, decoder=decoders[1], message_type=pac.default_message_type)) 26 | pac.create_fuzzing_label(1, 10, 0) 27 | assert isinstance(self.form, MainController) 28 | pac.to_xml_file(filename, decoders=decoders, 29 | participants=self.form.project_manager.participants) 30 | 31 | self.wait_before_new_file() 32 | self.form.add_files([os.path.join(tempfile.gettempdir(), "test.fuzz.xml")]) 33 | 34 | self.assertEqual(self.form.ui.tabWidget.currentWidget(), self.form.ui.tab_generator) 35 | 36 | pac = self.form.generator_tab_controller.table_model.protocol 37 | 38 | self.assertEqual(len(pac.messages), 2) 39 | self.assertEqual(pac.messages[1][0], False) 40 | self.assertEqual(len(pac.protocol_labels), 1) 41 | -------------------------------------------------------------------------------- /tests/test_interval.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from urh.signalprocessing.Interval import Interval 4 | 5 | 6 | class TestInterval(unittest.TestCase): 7 | def test_is_overlapping(self): 8 | i1 = Interval(40, 64) 9 | i2 = Interval(64, 104) 10 | self.assertFalse(i1.overlaps_with(i2)) 11 | self.assertFalse(i2.overlaps_with(i1)) 12 | self.assertTrue(i1.overlaps_with(i1)) 13 | self.assertTrue(i2.overlaps_with(i2)) 14 | 15 | def test_find_common_interval(self): 16 | i1 = Interval(0, 32) 17 | self.assertEqual(i1, i1.find_common_interval(i1)) 18 | 19 | i2 = Interval(0, 80) 20 | self.assertEqual(i2, i2.find_common_interval(i2)) 21 | 22 | expected_result = Interval(0, 32) 23 | self.assertTrue(i1.overlaps_with(i2)) 24 | self.assertTrue(i2.overlaps_with(i1)) 25 | self.assertEqual(i1.find_common_interval(i2), expected_result) 26 | self.assertEqual(i2.find_common_interval(i1), expected_result) 27 | 28 | i1 = Interval(41, 56) 29 | i2 = Interval(43, 56) 30 | expected_result = Interval(43, 56) 31 | self.assertTrue(i1.overlaps_with(i2)) 32 | self.assertTrue(i2.overlaps_with(i1)) 33 | self.assertEqual(i1.find_common_interval(i2), expected_result) 34 | self.assertEqual(i2.find_common_interval(i1), expected_result) 35 | 36 | def test_sort(self): 37 | i1 = Interval(0, 10) 38 | i2 = Interval(0, 20) 39 | i3 = Interval(0, 30) 40 | 41 | s = {i1, i2, i3} 42 | self.assertEqual(max(s), i3) 43 | self.assertEqual(sorted(s)[-1], i3) 44 | -------------------------------------------------------------------------------- /tests/test_messagetype.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from urh.signalprocessing.MessageType import MessageType 4 | from urh.signalprocessing.ProtocoLabel import ProtocolLabel 5 | 6 | 7 | class TestMessageType(unittest.TestCase): 8 | def test_find_unlabeled_range(self): 9 | lbl11 = ProtocolLabel(name="Label 1.1", start=2, end=10, color_index=0) 10 | lbl12 = ProtocolLabel(name="Label 1.2", start=15, end=20, color_index=0) 11 | lbl13 = ProtocolLabel(name="Label 1.3", start=40, end=60, color_index=0) 12 | 13 | mt1 = MessageType(name="MT1", iterable=[lbl11, lbl12, lbl13]) 14 | 15 | self.assertEqual([(0, 2), (11, 15), (21, 40), (61, None)], mt1.unlabeled_ranges) 16 | self.assertEqual([(0, 2), (11, 15), (21, 40), (61, None)], mt1.unlabeled_ranges_with_other_mt(mt1)) 17 | 18 | lbl21 = ProtocolLabel(name="Label 2.1", start=1, end=11, color_index=0) 19 | lbl22 = ProtocolLabel(name="Label 2.2", start=14, end=18, color_index=0) 20 | lbl23 = ProtocolLabel(name="Label 2.3", start=50, end=70, color_index=0) 21 | 22 | mt2 = MessageType(name="MT2", iterable=[lbl21, lbl22, lbl23]) 23 | 24 | self.assertEqual(mt1.unlabeled_ranges_with_other_mt(mt2), mt2.unlabeled_ranges_with_other_mt(mt1)) 25 | self.assertEqual(mt1.unlabeled_ranges_with_other_mt(mt2), [(0, 1), (11, 14), (21, 40), (71, None)]) 26 | -------------------------------------------------------------------------------- /tests/test_signal_details_gui.py: -------------------------------------------------------------------------------- 1 | from tests.QtTestCase import QtTestCase 2 | from tests.utils_testing import get_path_for_data_file 3 | from urh.controller.dialogs.SignalDetailsDialog import SignalDetailsDialog 4 | from urh.signalprocessing.Signal import Signal 5 | from urh.util.Formatter import Formatter 6 | 7 | 8 | class TestSignalDetailsGUI(QtTestCase): 9 | def setUp(self): 10 | self.signal = Signal(get_path_for_data_file("esaver.coco"), "test") 11 | self.signal.sample_rate = 2e6 12 | self.dialog = SignalDetailsDialog(self.signal) 13 | 14 | if self.SHOW: 15 | self.dialog.show() 16 | 17 | def test_set_sample_rate(self): 18 | self.assertEqual(Formatter.science_time(self.signal.num_samples / self.signal.sample_rate), 19 | self.dialog.ui.lDuration.text()) 20 | 21 | self.dialog.ui.dsb_sample_rate.setValue(5e6) 22 | self.assertEqual(self.signal.sample_rate, 5e6) 23 | self.assertEqual(Formatter.science_time(self.signal.num_samples / self.signal.sample_rate), 24 | self.dialog.ui.lDuration.text()) 25 | -------------------------------------------------------------------------------- /tests/utils_testing.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from PyQt5.QtTest import QSignalSpy 5 | 6 | from urh import constants 7 | from urh.signalprocessing.ProtocolSniffer import ProtocolSniffer 8 | 9 | 10 | def trace_calls(frame, event, arg): 11 | if event != 'call': 12 | return 13 | co = frame.f_code 14 | func_name = co.co_name 15 | if func_name == 'write': 16 | # Ignore write() calls from print statements 17 | return 18 | func_line_no = frame.f_lineno 19 | func_filename = co.co_filename 20 | caller = frame.f_back 21 | caller_line_no = caller.f_lineno 22 | caller_filename = caller.f_code.co_filename 23 | if "urh" in caller_filename or "urh" in func_filename: 24 | if "logging" in caller_filename or "logging" in func_filename: 25 | return 26 | 27 | if "_test" in caller_filename or "_test" in func_filename: 28 | start = '\033[91m' 29 | else: 30 | start = "\033[0;32m" 31 | end = "\033[0;0m" 32 | else: 33 | start, end = "", "" 34 | 35 | print('%s Call to %s on line %s of %s from line %s of %s %s' % \ 36 | (start, func_name, func_line_no, func_filename, 37 | caller_line_no, caller_filename, end)) 38 | return 39 | 40 | 41 | global settings_written 42 | 43 | 44 | def write_settings(): 45 | global settings_written 46 | try: 47 | settings_written 48 | except NameError: 49 | settings_written = True 50 | settings = constants.SETTINGS 51 | settings.setValue("not_show_close_dialog", True) # prevent interactive close questions 52 | settings.setValue("not_show_save_dialog", True) 53 | settings.setValue("NetworkSDRInterface", True) 54 | settings.setValue("align_labels", True) 55 | 56 | 57 | # sys.settrace(trace_calls) 58 | 59 | f = os.readlink(__file__) if os.path.islink(__file__) else __file__ 60 | path = os.path.realpath(os.path.join(f, "..")) 61 | 62 | 63 | def get_path_for_data_file(filename): 64 | return os.path.join(path, "data", filename) 65 | --------------------------------------------------------------------------------