├── .DS_Store ├── .github ├── FUNDING.yml └── workflows │ └── cmake.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── Jenkinsfile ├── LICENSE ├── README.md ├── configure_flags.sh ├── debian ├── conffiles ├── postinst ├── postrm ├── prerm └── sdr-server.service ├── docker ├── bionic.Dockerfile ├── bookworm.Dockerfile ├── build_all.sh ├── bullseye.Dockerfile ├── buster.Dockerfile ├── focal.Dockerfile ├── jammy.Dockerfile ├── r2cloud.gpg └── stretch.Dockerfile ├── docs ├── dsp.drawio ├── dsp.jpg ├── queue.drawio ├── queue.png ├── threads.drawio └── threads.png ├── sonar-project.properties ├── src ├── api.h ├── client │ ├── tcp_client.c │ ├── tcp_client.h │ └── tcp_client_main.c ├── config.c ├── config.h ├── dsp_worker.c ├── dsp_worker.h ├── lpf.c ├── lpf.h ├── main.c ├── queue.c ├── queue.h ├── resources │ └── config.conf ├── sdr │ ├── airspy_device.c │ ├── airspy_device.h │ ├── airspy_lib.c │ ├── airspy_lib.h │ ├── hackrf_device.c │ ├── hackrf_device.h │ ├── hackrf_lib.c │ ├── hackrf_lib.h │ ├── rtlsdr_device.c │ ├── rtlsdr_device.h │ ├── rtlsdr_lib.c │ └── rtlsdr_lib.h ├── sdr_device.c ├── sdr_device.h ├── tcp_server.c ├── tcp_server.h ├── xlating.c └── xlating.h └── test ├── airspy_lib_mock.c ├── airspy_lib_mock.h ├── hackrf_lib_mock.c ├── hackrf_lib_mock.h ├── perf_xlating.c ├── resources ├── configuration.config ├── core.config ├── invalid.basepath.config ├── invalid.config ├── invalid.format.config ├── invalid.queue_size.config ├── invalid2.config ├── minimal.config ├── run_tests.sh └── tcp_server.config ├── rtlsdr_lib_mock.c ├── rtlsdr_lib_mock.h ├── test_config.c ├── test_lpf.c ├── test_queue.c ├── test_tcp_server.c ├── test_xlating.c ├── unity-2.5.2 ├── LICENSE.txt ├── README.md └── src │ ├── unity.c │ ├── unity.h │ └── unity_internals.h ├── utils.c └── utils.h /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-server/f9b0aebc3fd396dbe06c4448d8e21c96b9cf4c5c/.DS_Store -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dernasherbrezon -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | jobs: 7 | build: 8 | name: Build and analyze 9 | runs-on: ubuntu-latest 10 | env: 11 | BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 16 | - name: Cache SonarQube packages 17 | uses: actions/cache@v4 18 | with: 19 | path: ~/.sonar/cache 20 | key: ${{ runner.os }}-sonar 21 | restore-keys: ${{ runner.os }}-sonar 22 | - name: Install Build Wrapper 23 | uses: SonarSource/sonarqube-scan-action/install-build-wrapper@v4 24 | env: 25 | SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} # SonarQube URL is stored in a GitHub secret 26 | - name: install dependencies 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install -y libconfig-dev valgrind cmake pkg-config libairspy-dev libhackrf-dev librtlsdr-dev 30 | - name: Run build-wrapper 31 | run: | 32 | mkdir build 33 | cmake -DCMAKE_BUILD_TYPE=Debug -S . -B build 34 | build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/ --config Debug 35 | - name: run tests & coverage 36 | run: | 37 | cd build 38 | bash ./run_tests.sh 39 | make coverage 40 | cd .. 41 | - name: SonarQube Scan 42 | uses: SonarSource/sonarqube-scan-action@v4 43 | env: 44 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Put the name of your token here 45 | SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} # SonarQube URL is stored in a GitHub secret 46 | with: 47 | # if you are using using sonarqube 10.5 or earlier, replace sonar.cfamily.compile-commands with sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" 48 | # as build-wrapper does not generate a compile_commands.json file before sonarqube 10.6 49 | args: > 50 | --define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | /.idea/ 52 | Mkfile.old 53 | dkms.conf 54 | Debug 55 | .settings 56 | .project 57 | .cproject 58 | /build/ 59 | /dbg/ 60 | /cmake-build-debug/ 61 | /cmake-build-debug-system/ 62 | /cmake-build-release/ 63 | .cache 64 | /release/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [] 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "airspy_device.h": "c", 4 | "__threading_support": "c", 5 | "complex": "c", 6 | "compare": "c", 7 | "cstdint": "c", 8 | "utils.h": "c", 9 | "stddef.h": "c", 10 | "rtlsdr_lib_mock.h": "c", 11 | "future": "c", 12 | "system_error": "c", 13 | "airspy_lib.h": "c", 14 | "__node_handle": "c", 15 | "queue": "c", 16 | "xlating.h": "c", 17 | "airspy_lib_mock.h": "c", 18 | "shared_mutex": "c", 19 | "__tree": "c", 20 | "memory_resource": "c", 21 | "airspy.h": "c", 22 | "cstdlib": "c", 23 | "stdlib.h": "c", 24 | "__locale": "c", 25 | "chrono": "c", 26 | "functional": "c", 27 | "typeindex": "c", 28 | "typeinfo": "c", 29 | "text_encoding": "c", 30 | "ccomplex": "cpp", 31 | "xlating_cs16.h": "c", 32 | "complex.h": "c", 33 | "api.h": "c" 34 | } 35 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "type": "cppbuild", 5 | "label": "C/C++: clang build active file", 6 | "command": "/usr/bin/clang", 7 | "args": [ 8 | "-fcolor-diagnostics", 9 | "-fansi-escape-codes", 10 | "-g", 11 | "${file}", 12 | "-o", 13 | "${fileDirname}/${fileBasenameNoExtension}" 14 | ], 15 | "options": { 16 | "cwd": "${fileDirname}" 17 | }, 18 | "problemMatcher": [ 19 | "$gcc" 20 | ], 21 | "group": { 22 | "kind": "build", 23 | "isDefault": true 24 | }, 25 | "detail": "Task generated by Debugger." 26 | } 27 | ], 28 | "version": "2.0.0" 29 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(sdr-server) 3 | set(CMAKE_C_STANDARD 11) 4 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") 5 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffast-math") 6 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} --coverage -fprofile-arcs -ftest-coverage") 7 | 8 | if(NO_MANUAL_SIMD) 9 | add_definitions(-DNO_MANUAL_SIMD) 10 | else() 11 | remove_definitions(-DNO_MANUAL_SIMD) 12 | endif() 13 | 14 | if(CMAKE_BUILD_TYPE MATCHES Debug) 15 | set(BUILD_COMPILATION_FLAGS "${CMAKE_C_FLAGS_DEBUG}") 16 | elseif(CMAKE_BUILD_TYPE MATCHES Release) 17 | set(BUILD_COMPILATION_FLAGS "${CMAKE_C_FLAGS_RELEASE}") 18 | endif() 19 | add_definitions(-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} ${BUILD_COMPILATION_FLAGS}") 20 | 21 | add_library(sdr_serverLib 22 | ${CMAKE_CURRENT_SOURCE_DIR}/src/config.c 23 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr_device.c 24 | ${CMAKE_CURRENT_SOURCE_DIR}/src/dsp_worker.c 25 | ${CMAKE_CURRENT_SOURCE_DIR}/src/lpf.c 26 | ${CMAKE_CURRENT_SOURCE_DIR}/src/queue.c 27 | ${CMAKE_CURRENT_SOURCE_DIR}/src/tcp_server.c 28 | ${CMAKE_CURRENT_SOURCE_DIR}/src/xlating.c 29 | ${CMAKE_CURRENT_SOURCE_DIR}/src/client/tcp_client.c 30 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/airspy_device.c 31 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/rtlsdr_device.c 32 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/hackrf_device.c 33 | ) 34 | 35 | # at some point homebrew changed default location 36 | # so all libraries need an explicit path 37 | if (APPLE) 38 | include_directories("/opt/homebrew/include") 39 | include_directories("/usr/local/include") 40 | link_directories("/opt/homebrew/lib") 41 | link_directories("/usr/local/lib") 42 | endif () 43 | 44 | 45 | find_package(PkgConfig REQUIRED) 46 | 47 | pkg_check_modules(PC_RTLSDR REQUIRED librtlsdr>=0.5.4) 48 | include_directories(${PC_RTLSDR_INCLUDE_DIRS}) 49 | 50 | pkg_check_modules(PC_LIBAIRSPY REQUIRED libairspy) 51 | include_directories(${PC_LIBAIRSPY_INCLUDE_DIRS}) 52 | # libairspy pkg config was invalid at some point 53 | include_directories("/usr/include/libairspy/") 54 | 55 | pkg_check_modules(PC_LIBHACKRF REQUIRED libhackrf) 56 | include_directories(${PC_LIBHACKRF_INCLUDE_DIRS}) 57 | # libhackrf pkg config was invalid at some point 58 | include_directories("/usr/include/libhackrf/") 59 | 60 | pkg_check_modules(PC_ZLIB REQUIRED zlib) 61 | include_directories(${PC_ZLIB_INCLUDE_DIRS}) 62 | link_directories(${PC_ZLIB_LIBRARY_DIRS}) 63 | target_link_libraries(sdr_serverLib ${PC_ZLIB_LIBRARIES}) 64 | 65 | pkg_check_modules(PC_LIBCONFIG REQUIRED libconfig) 66 | include_directories(${PC_LIBCONFIG_INCLUDE_DIRS}) 67 | link_directories(${PC_LIBCONFIG_LIBRARY_DIRS}) 68 | target_link_libraries(sdr_serverLib ${PC_LIBCONFIG_LIBRARIES}) 69 | 70 | find_package(Threads REQUIRED) 71 | target_link_libraries(sdr_serverLib m ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}) 72 | 73 | # link real libraries into final executable so that tests can use mocks 74 | add_executable(sdr_server 75 | ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c 76 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/rtlsdr_lib.c 77 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/airspy_lib.c 78 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sdr/hackrf_lib.c 79 | ) 80 | target_link_libraries(sdr_server sdr_serverLib) 81 | 82 | add_executable(sdr_server_client 83 | ${CMAKE_CURRENT_SOURCE_DIR}/src/client/tcp_client.c 84 | ${CMAKE_CURRENT_SOURCE_DIR}/src/client/tcp_client_main.c 85 | ) 86 | 87 | install(TARGETS sdr_server DESTINATION /usr/bin/) 88 | install(TARGETS sdr_server_client DESTINATION /usr/bin/) 89 | install(FILES src/resources/config.conf DESTINATION /etc/sdr-server/) 90 | install(FILES debian/sdr-server.service DESTINATION /lib/systemd/system/) 91 | 92 | enable_testing() 93 | 94 | file(GLOB TEST_RESOURCES test/resources/*) 95 | file(COPY ${TEST_RESOURCES} DESTINATION "${CMAKE_BINARY_DIR}") 96 | file(GLOB PERF_SOURCES test/perf_*.c) 97 | 98 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/test/unity-2.5.2/src/) 99 | 100 | # speed up test compilation. 101 | add_library(sdr_serverTestLib 102 | ${CMAKE_CURRENT_SOURCE_DIR}/test/rtlsdr_lib_mock.c 103 | ${CMAKE_CURRENT_SOURCE_DIR}/test/airspy_lib_mock.c 104 | ${CMAKE_CURRENT_SOURCE_DIR}/test/hackrf_lib_mock.c 105 | ${CMAKE_CURRENT_SOURCE_DIR}/src/client/tcp_client.c 106 | ${CMAKE_CURRENT_SOURCE_DIR}/test/utils.c 107 | ${CMAKE_CURRENT_SOURCE_DIR}/test/unity-2.5.2/src/unity.c 108 | ) 109 | file(GLOB TEST_SOURCES test/test_*.c) 110 | foreach(curTest ${TEST_SOURCES}) 111 | get_filename_component(curTestName ${curTest} NAME_WE) 112 | add_test(NAME ${curTestName} COMMAND ${curTestName} ${curTest}) 113 | add_executable(${curTestName} ${curTest}) 114 | target_link_libraries(${curTestName} sdr_serverLib sdr_serverTestLib) 115 | endforeach() 116 | 117 | add_executable(perf_xlating 118 | ${CMAKE_CURRENT_SOURCE_DIR}/test/perf_xlating.c 119 | ) 120 | target_link_libraries(perf_xlating sdr_serverLib) 121 | 122 | if(CMAKE_BUILD_TYPE MATCHES Debug) 123 | add_custom_target("coverage") 124 | add_custom_command(TARGET "coverage" COMMAND gcov ${CMAKE_BINARY_DIR}/CMakeFiles/sdr_serverLib.dir/src/*.c.o ${CMAKE_BINARY_DIR}/CMakeFiles/sdr_serverLib.dir/src/sdr/*.c.o) 125 | endif() 126 | 127 | set(CPACK_GENERATOR "DEB") 128 | set(CPACK_DEBIAN_PACKAGE_NAME "sdr-server") 129 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "librtlsdr2 (>= 2.0.1), libconfig9, zlib1g") 130 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Andrey Rodionov ") 131 | set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "High performant TCP server for rtl-sdr") 132 | set(CPACK_DEBIAN_PACKAGE_SECTION "comm") 133 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/dernasherbrezon/sdr-server") 134 | set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/debian/prerm;${CMAKE_CURRENT_SOURCE_DIR}/debian/postrm;${CMAKE_CURRENT_SOURCE_DIR}/debian/conffiles") 135 | set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON) 136 | set(CPACK_DEBIAN_FILE_NAME "sdr-server_${CPACK_DEBIAN_PACKAGE_VERSION}_${CUSTOM_ARCHITECTURE}.deb") 137 | include(CPack) 138 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | parameters { 4 | string(name: 'BASE_VERSION', defaultValue: '1.1', description: 'From https://github.com/dernasherbrezon/sdr-server/actions') 5 | string(name: 'BUILD_NUMBER', defaultValue: '77', description: 'From https://github.com/dernasherbrezon/sdr-server/actions') 6 | choice(name: 'DOCKER_IMAGE', choices: ['debian:stretch-slim', 'debian:buster-slim', 'debian:bullseye-slim', 'debian:bookworm-slim', 'ubuntu:jammy', 'ubuntu:bionic', 'ubuntu:focal'], description: 'From https://github.com/dernasherbrezon/sdr-server/actions') 7 | choice(name: 'OS_ARCHITECTURE', choices: ['armhf', 'arm64', 'amd64'], description: 'From https://github.com/dernasherbrezon/sdr-server/actions') 8 | } 9 | stages { 10 | stage('Checkout') { 11 | steps { 12 | script { 13 | env.OS_CODENAME = "${DOCKER_IMAGE}".split(':')[1].split('-')[0] 14 | } 15 | git(url: 'git@github.com:dernasherbrezon/sdr-server.git', branch: "${OS_CODENAME}", credentialsId: 'github', changelog: false) 16 | sh ''' 17 | git config user.email "gpg@r2cloud.ru" 18 | git config user.name "r2cloud" 19 | git merge origin/main --no-edit 20 | ''' 21 | withCredentials([sshUserPrivateKey(credentialsId: 'github', keyFileVariable: 'SSH_KEY')]) { 22 | sh ''' 23 | GIT_SSH_COMMAND="ssh -i ${SSH_KEY}" git push --set-upstream origin ${OS_CODENAME} 24 | ''' 25 | } 26 | } 27 | } 28 | stage('build') { 29 | agent { 30 | docker { 31 | image "sdrserver-${OS_CODENAME}-${OS_ARCHITECTURE}" 32 | reuseNode true 33 | args "--platform=linux/${OS_ARCHITECTURE}" 34 | } 35 | } 36 | steps { 37 | sh '''#!/bin/bash 38 | set -e 39 | . ./configure_flags.sh nocpuspecific 40 | rm -rf build 41 | mkdir build 42 | cd build 43 | cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCPACK_DEBIAN_PACKAGE_VERSION=${BASE_VERSION}.${BUILD_NUMBER}~${OS_CODENAME} -DCUSTOM_ARCHITECTURE=${OS_ARCHITECTURE} .. 44 | VERBOSE=1 make -j $(nproc) 45 | cpack 46 | ''' 47 | } 48 | } 49 | stage('deploy') { 50 | steps { 51 | withCredentials([ 52 | usernamePassword(credentialsId: 'gpg_pass', usernameVariable: 'gpg_pass_keyid', passwordVariable: 'gpg_pass'), 53 | file(credentialsId: 'gpg', variable: 'gpg_file'), 54 | file(credentialsId: 'gpg_passphrase', variable: 'gpg_passphrase'), 55 | aws(credentialsId: 'aws')]) { 56 | sh '''#!/bin/bash 57 | set -e 58 | . ./configure_flags.sh nocpuspecific 59 | APT_CLI_VERSION="apt-cli-1.11" 60 | if [ ! -f ${HOME}/${APT_CLI_VERSION}.jar ]; then 61 | wget -O ${APT_CLI_VERSION}.jar.temp https://github.com/dernasherbrezon/apt-cli/releases/download/${APT_CLI_VERSION}/apt-cli.jar 62 | mv ${APT_CLI_VERSION}.jar.temp ${HOME}/${APT_CLI_VERSION}.jar 63 | fi 64 | 65 | java -jar ${HOME}/${APT_CLI_VERSION}.jar --url s3://${BUCKET} --component main --codename ${OS_CODENAME} --gpg-keyfile ${gpg_file} --gpg-keyname ${gpg_pass_keyid} --gpg-passphrase-file ${gpg_passphrase} save --patterns ./build/*.deb,./build/*.ddeb 66 | 67 | ''' 68 | } 69 | } 70 | } 71 | stage('test') { 72 | agent { 73 | docker { 74 | image "${DOCKER_IMAGE}" 75 | reuseNode true 76 | args "--platform=linux/${OS_ARCHITECTURE} --pull always --user root" 77 | } 78 | } 79 | steps { 80 | sh '''#!/bin/bash 81 | export DEBIAN_FRONTEND=noninteractive 82 | cp ./docker/r2cloud.gpg /etc/apt/trusted.gpg.d/r2cloud.gpg 83 | chmod 644 /etc/apt/trusted.gpg.d/r2cloud.gpg 84 | if [ ${OS_CODENAME} == stretch ]; then 85 | echo 'deb http://archive.debian.org/debian/ stretch main non-free contrib' > /etc/apt/sources.list 86 | echo 'deb http://archive.debian.org/debian-security/ stretch/updates main non-free contrib' >> /etc/apt/sources.list 87 | fi 88 | echo "deb http://r2cloud.s3.amazonaws.com ${OS_CODENAME} main" >> /etc/apt/sources.list 89 | echo "deb http://r2cloud.s3.amazonaws.com/cpu-generic ${OS_CODENAME} main" >> /etc/apt/sources.list 90 | apt update && apt-get install --no-install-recommends -y sdr-server 91 | ''' 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About [![CMake](https://github.com/dernasherbrezon/sdr-server/actions/workflows/cmake.yml/badge.svg)](https://github.com/dernasherbrezon/sdr-server/actions/workflows/cmake.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=dernasherbrezon_sdr-server&metric=alert_status)](https://sonarcloud.io/dashboard?id=dernasherbrezon_sdr-server) 2 | 3 | ![design](/docs/dsp.jpg?raw=true) 4 | 5 | ## Key features 6 | 7 | * Share available RF bandwidth between several independent clients: 8 | * Total bandwidth can be 2016000 samples/sec at 436,600,000 hz 9 | * One client might request 48000 samples/sec at 436,700,000 hz 10 | * Another client might request 96000 samples/sec at 435,000,000 hz 11 | * Several clients can access the same band simultaneously 12 | * Output saved onto disk or streamed back via TCP socket 13 | * Output can be gzipped (by default = true) 14 | * Output will be decimated to the requested bandwidth 15 | * Clients can request overlapping RF spectrum 16 | * Rtl-sdr starts only after first client connects (i.e. saves solar power &etc). Stops only when the last client disconnects 17 | * MacOS and Linux (Debian Raspberrypi) 18 | 19 | ## Design 20 | 21 | ![design](/docs/threads.png?raw=true) 22 | 23 | * Each client has its own dsp thread 24 | * Each dsp thread executes [Frequency Xlating FIR Filter](http://blog.sdr.hu/grblocks/xlating-fir.html) 25 | * Only RTL-SDRs are supported 26 | 27 | ## API 28 | 29 | * Defined in the [api.h](https://github.com/dernasherbrezon/sdr-server/blob/main/src/api.h) 30 | * Clients can connect and send request to initiate listening: 31 | * center_freq - this is required center frequency. For example, 436,700,000 hz 32 | * sampling_rate - required sampling rate. For example, 48000 33 | * band\_freq - first connected client can select the center of the band. All other clients should request center\_freq within the currently selected band 34 | * destination - "0" - save into file on local disk, "1" - stream back via TCP socket 35 | * To stop listening, clients can send SHUTDOWN request or disconnect 36 | 37 | ## Queue 38 | 39 | ![design](/docs/queue.png?raw=true) 40 | 41 | The data between rtl-sdr worker and the dsp workers is passed via queue. This is bounded queue with pre-allocated memory blocks. It has the following features: 42 | 43 | * Thread-safe 44 | * If no free blocks (consumer is slow), then the last block will be overriden by the next one 45 | * there is a special detached block. It is used to minimize synchronization section. All potentially long operations on it are happening outside of synchronization section. 46 | * Consumer will block and wait until new data produced 47 | 48 | ## Configuration 49 | 50 | Sample configuration can be found in tests: 51 | 52 | [https://github.com/dernasherbrezon/sdr-server/blob/main/test/resources/configuration.config](https://github.com/dernasherbrezon/sdr-server/blob/main/test/resources/configuration.config) 53 | 54 | ## Performance 55 | 56 | Is good. Some numbers in ```test/perf_xlating.c``` 57 | 58 | ## Dependencies 59 | 60 | sdr-server depends on several libraries: 61 | 62 | * [librtlsdr](https://github.com/dernasherbrezon/librtlsdr). Version >=0.5.4 is required. 63 | * [libairspy](https://github.com/airspy/airspyone_host/) 64 | * [libhackrf](https://github.com/greatscottgadgets/hackrf) 65 | * [libconfig](https://hyperrealm.github.io/libconfig/libconfig_manual.html) 66 | * libz. Should be installed in every operational system 67 | * libm. Same 68 | 69 | All dependencies can be easily installed from [leosatdata APT repository](https://leosatdata.com/apt): 70 | 71 | ``` 72 | sudo apt-get install curl lsb-release 73 | curl -fsSL https://leosatdata.com/r2cloud.gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/r2cloud.gpg 74 | sudo bash -c "echo 'deb [signed-by=/usr/share/keyrings/r2cloud.gpg] http://apt.leosatdata.com $(lsb_release --codename --short) main' > /etc/apt/sources.list.d/r2cloud.list" 75 | sudo apt-get update 76 | sudo apt-get install librtlsdr-dev libconfig-dev 77 | ``` 78 | 79 | ## Build 80 | 81 | To build the project execute the following commands: 82 | 83 | ``` 84 | mkdir build 85 | cd build 86 | cmake .. 87 | make 88 | ``` 89 | 90 | ## Install 91 | 92 | sdr-server can be installed from [leosatdata APT repository](https://leosatdata.com/apt): 93 | 94 | ``` 95 | sudo apt-get install sdr-server 96 | ``` 97 | -------------------------------------------------------------------------------- /configure_flags.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CPU=$1 4 | 5 | if [ "${CPU}" = "arm1176jzf-s" ]; then 6 | export CXXFLAGS="-fdebug-prefix-map=$(pwd)=. -mcpu=${CPU} -mfpu=vfp -mfloat-abi=hard" 7 | elif [ "${CPU}" = "cortex-a53" ]; then 8 | export CXXFLAGS="-fdebug-prefix-map=$(pwd)=. -mcpu=${CPU} -mfpu=neon-fp-armv8 -mfloat-abi=hard" 9 | elif [ "${CPU}" = "cortex-a7" ]; then 10 | export CXXFLAGS="-fdebug-prefix-map=$(pwd)=. -mcpu=${CPU} -mfpu=neon-vfpv4 -mfloat-abi=hard" 11 | elif [ "${CPU}" = "cortex-a72" ]; then 12 | export CXXFLAGS="-fdebug-prefix-map=$(pwd)=. -mcpu=${CPU} -mfpu=neon-fp-armv8 -mfloat-abi=hard" 13 | elif [ "${CPU}" = "generic" ]; then 14 | export CXXFLAGS="-fdebug-prefix-map=$(pwd)=." 15 | elif [ "${CPU}" = "nocpuspecific" ]; then 16 | export CXXFLAGS="-fdebug-prefix-map=$(pwd)=." 17 | else 18 | echo "unknown core: ${CPU}" 19 | exit 1 20 | fi 21 | 22 | if [[ "${CPU}" = "nocpuspecific" ]]; then 23 | export BUCKET=r2cloud 24 | else 25 | export BUCKET="r2cloud/cpu-${CPU}" 26 | fi 27 | 28 | export ASMFLAGS="${CXXFLAGS} -mthumb" 29 | export CFLAGS=${CXXFLAGS} 30 | 31 | -------------------------------------------------------------------------------- /debian/conffiles: -------------------------------------------------------------------------------- 1 | /etc/sdr-server/config.conf -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ -d /run/systemd/system ]; then 6 | systemctl enable sdr-server.service 7 | systemctl start sdr-server.service 8 | fi -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ -d /run/systemd/system ]; then 6 | systemctl --system daemon-reload >/dev/null || true 7 | fi 8 | -------------------------------------------------------------------------------- /debian/prerm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ -d /run/systemd/system ]; then 6 | if systemctl is-active sdr-server; then 7 | systemctl stop sdr-server.service 8 | fi 9 | if systemctl is-enabled sdr-server; then 10 | systemctl disable sdr-server.service 11 | fi 12 | fi -------------------------------------------------------------------------------- /debian/sdr-server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=sdr-server Service 3 | 4 | [Service] 5 | WorkingDirectory=/etc/sdr-server 6 | ExecStart=/usr/bin/sdr_server /etc/sdr-server/config.conf 7 | Restart=always 8 | 9 | [Install] 10 | Alias=sdr-server.service 11 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /docker/bionic.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | ENV DEBIAN_FRONTEND noninteractive 3 | COPY --chmod=644 r2cloud.gpg /etc/apt/trusted.gpg.d/r2cloud.gpg 4 | RUN echo "deb http://r2cloud.s3.amazonaws.com bionic main" >> /etc/apt/sources.list 5 | RUN echo "deb http://r2cloud.s3.amazonaws.com/cpu-generic bionic main" >> /etc/apt/sources.list 6 | RUN apt-get update && apt-get install --no-install-recommends -y build-essential file valgrind cmake libconfig-dev libhackrf-dev pkg-config libairspy-dev librtlsdr-dev zlib1g-dev && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /docker/bookworm.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | ENV DEBIAN_FRONTEND noninteractive 3 | COPY --chmod=644 r2cloud.gpg /etc/apt/trusted.gpg.d/r2cloud.gpg 4 | RUN echo "deb http://r2cloud.s3.amazonaws.com bookworm main" >> /etc/apt/sources.list 5 | RUN echo "deb http://r2cloud.s3.amazonaws.com/cpu-generic bookworm main" >> /etc/apt/sources.list 6 | RUN apt-get update && apt-get install --no-install-recommends -y build-essential file valgrind cmake libconfig-dev pkg-config libairspy-dev libhackrf-dev librtlsdr-dev zlib1g-dev && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /docker/build_all.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker buildx build --load --platform=armhf -t sdrserver-buster-armhf -f buster.Dockerfile . 4 | docker buildx build --load --platform=armhf -t sdrserver-stretch-armhf -f stretch.Dockerfile . 5 | docker buildx build --load --platform=armhf -t sdrserver-bullseye-armhf -f bullseye.Dockerfile . 6 | docker buildx build --load --platform=armhf -t sdrserver-bookworm-armhf -f bookworm.Dockerfile . 7 | docker buildx build --load --platform=arm64 -t sdrserver-bookworm-arm64 -f bookworm.Dockerfile . 8 | docker buildx build --load --platform=amd64 -t sdrserver-bionic-amd64 -f bionic.Dockerfile . 9 | docker buildx build --load --platform=amd64 -t sdrserver-focal-amd64 -f focal.Dockerfile . 10 | docker buildx build --load --platform=amd64 -t sdrserver-jammy-amd64 -f jammy.Dockerfile . 11 | -------------------------------------------------------------------------------- /docker/bullseye.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim 2 | ENV DEBIAN_FRONTEND noninteractive 3 | COPY --chmod=644 r2cloud.gpg /etc/apt/trusted.gpg.d/r2cloud.gpg 4 | RUN echo "deb http://r2cloud.s3.amazonaws.com bullseye main" >> /etc/apt/sources.list 5 | RUN echo "deb http://r2cloud.s3.amazonaws.com/cpu-generic bullseye main" >> /etc/apt/sources.list 6 | RUN apt-get update && apt-get install --no-install-recommends -y build-essential file valgrind cmake libconfig-dev pkg-config libairspy-dev libhackrf-dev librtlsdr-dev zlib1g-dev && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /docker/buster.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim 2 | ENV DEBIAN_FRONTEND noninteractive 3 | COPY --chmod=644 r2cloud.gpg /etc/apt/trusted.gpg.d/r2cloud.gpg 4 | RUN echo "deb http://r2cloud.s3.amazonaws.com buster main" >> /etc/apt/sources.list 5 | RUN echo "deb http://r2cloud.s3.amazonaws.com/cpu-generic buster main" >> /etc/apt/sources.list 6 | RUN apt-get update && apt-get install --no-install-recommends -y build-essential file valgrind cmake libconfig-dev pkg-config libairspy-dev libhackrf-dev librtlsdr-dev zlib1g-dev && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /docker/focal.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | ENV DEBIAN_FRONTEND noninteractive 3 | COPY --chmod=644 r2cloud.gpg /etc/apt/trusted.gpg.d/r2cloud.gpg 4 | RUN echo "deb http://r2cloud.s3.amazonaws.com focal main" >> /etc/apt/sources.list 5 | RUN echo "deb http://r2cloud.s3.amazonaws.com/cpu-generic focal main" >> /etc/apt/sources.list 6 | RUN apt-get update && apt-get install --no-install-recommends -y build-essential file valgrind cmake libconfig-dev pkg-config libairspy-dev libhackrf-dev librtlsdr-dev zlib1g-dev && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /docker/jammy.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:jammy 2 | ENV DEBIAN_FRONTEND noninteractive 3 | COPY --chmod=644 r2cloud.gpg /etc/apt/trusted.gpg.d/r2cloud.gpg 4 | RUN echo "deb http://r2cloud.s3.amazonaws.com jammy main" >> /etc/apt/sources.list 5 | RUN echo "deb http://r2cloud.s3.amazonaws.com/cpu-generic jammy main" >> /etc/apt/sources.list 6 | RUN apt-get update && apt-get install --no-install-recommends -y build-essential file valgrind cmake libconfig-dev pkg-config libairspy-dev libhackrf-dev librtlsdr-dev zlib1g-dev && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /docker/r2cloud.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-server/f9b0aebc3fd396dbe06c4448d8e21c96b9cf4c5c/docker/r2cloud.gpg -------------------------------------------------------------------------------- /docker/stretch.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stretch-slim 2 | ENV DEBIAN_FRONTEND noninteractive 3 | COPY --chmod=644 r2cloud.gpg /etc/apt/trusted.gpg.d/r2cloud.gpg 4 | RUN echo 'deb http://archive.debian.org/debian/ stretch main non-free contrib' > /etc/apt/sources.list 5 | RUN echo 'deb http://archive.debian.org/debian-security/ stretch/updates main non-free contrib' >> /etc/apt/sources.list 6 | RUN echo "deb http://r2cloud.s3.amazonaws.com stretch main" >> /etc/apt/sources.list 7 | RUN echo "deb http://r2cloud.s3.amazonaws.com/cpu-generic stretch main" >> /etc/apt/sources.list 8 | RUN apt-get update && apt-get install --no-install-recommends -y build-essential file valgrind cmake libconfig-dev pkg-config libairspy-dev libhackrf-dev librtlsdr-dev zlib1g-dev && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /docs/dsp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-server/f9b0aebc3fd396dbe06c4448d8e21c96b9cf4c5c/docs/dsp.jpg -------------------------------------------------------------------------------- /docs/queue.drawio: -------------------------------------------------------------------------------- 1 | 5ZpRl5soFMc/TR7bo6LGPE5mptuH9mzOmfZs+0gUlRmULGKT9NMvKEZFp3GmOmbTPCRwRSL/+wMu4ALcJoe/GNzFn2mAyMIygsMC3C0sa2W74lsajqXBWVmlIWI4KE1mbXjAP5EyGsqa4wBlrYKcUsLxrm30aZoin7dskDG6bxcLKWn/6w5GqGN48CHpWv/BAY9Lq2cta/tHhKO4+mfTXZVXElgVVi3JYhjQfcME7hfgllHKy1RyuEVEalfpUt734ZmrpwdjKOVDbgj9dL0GRgrvHreP3/7Ov3758vGdqbzxA5JctVg9LT9WEjCapwGStRgLsN7HmKOHHfTl1b3wubDFPCEiZ4qkqg4xjg7PPqh5ar7ABtEEcXYURaobXKWYQsZT2X2tv2MrW9zUvioIlc+jU9W1LCKhlHmJSiOLFGJCbimhrLgXBBB5oS/sGWf0CTWuuL6HtuE4slpGW1bT6uraJ6s9laqgIyIKRN9TWcp4TCOaQnJfW9dtmesynyjdKXEfEedHNZDAnNO29EIsdvzWzHyXlb13quzdQVVe5o4qVz6rfMBfyy/aQ3Pmo1+0W8HDIYsQP0dd150MEcjxj/ZzjO6csceFeZD3Lgx55w9F3h6IvDUn8vY1IA/cC0O+CuRmYt6YjXl3IPPLOZl3J2beQV5g9zHvWVvguuMwb2uRzSnSmY15s6Pin8H8ciDz3pzML6+Cee/CmPeuQVXHvTBVV9OqGno+8ntjkq3n2I4xjqqurcUk9tzjM+jI+m+OcvTe76grWs3bEralSmmKNF2VCRIcpSLrC5WQsK+lhtiH5EZdSHAQFCN/n8/aXh3DCeDsrsqyxwfWZD7ohtsEp0+iyZZBcMbFDw3FV8gQEj9bQv2n7Hrdo+/OrLruORV5G/84A/0jpC2M1+4hPco0l7O7qBu93yEO/bj2x8JyYSLlSLfZrtDBJdIdWyZSkUyFQnIhA6M+yjKcRtfrQFd3oNl1oNnnQDCZA7uh6IbRIPeFWrobshjuZDJPyI3PaVPOT3CLyIZmmGMqZd1SzmnSozenWjhAcy46tXBldXgyktRLTWnQVRr0CO1OJvSA6BSlwY08MJLAEij6goyLApjFBYNmWzhp30AusE4Li2WAU8eozoksudI6YH5akol0Y0UmcvWCTGaq9dhz+3VA+5xZ1hW5DWJYSCh7X2HMxNKMV81U3bSwfcBSUXVjoHL1gc4oq8Pq/O7sxvczWyLNA6Aeeirb4FWk+ocNxSmv2TU1eMFSo7Jsp7qrBrNT0WngryrSJ4JSiE5FBeGnZv8G9AMWD9NA/8qNiHnhHLp34c4Jp6VF8UA/8xwKp7Z9AFZvy2bVFRpsikkoy5P//cyn77/PPfVVTZht6jNfMPW9fASox5p6ePnevDb9pqc3cOBYzTlw6FwCHbihA4drnpkepx45rKvmeUQuVwO5bLyNNAOYns7Tq8HUCX9rMLubim8L5hhrjIsO0YYCbV9ShGa+dvmgv7uiR3qvxllk67cOy+L1q5vg/j8= -------------------------------------------------------------------------------- /docs/queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-server/f9b0aebc3fd396dbe06c4448d8e21c96b9cf4c5c/docs/queue.png -------------------------------------------------------------------------------- /docs/threads.drawio: -------------------------------------------------------------------------------- 1 | 7VvRcqs2EP0aPyYDEmD8mDi5t9OmbTppp81TR4ACNMJyhYjt+/UVRoBBJOZOADlOn4xWgKWz5+yuJHsGl8n2K0Pr6GcaYDIDRrCdwZsZAKZpQvGRW3aFxVk4hSFkcSBvqg0P8TcsjYa0ZnGA08aNnFLC43XT6NPVCvu8YUOM0U3ztidKmt+6RiFWDA8+Iqr1zzjgUWF1wby2/4DjMCq/2XQWRU+CypvlTNIIBXRzYIK3M7hklPLiKtkuMcnBK3EpnvvySm81MIZXvM8DT7/N8Y+A/vRC04f7q+3j/JcsvDDlaF8QyeSM5Wj5roSA0WwV4Pwtxgxeb6KY44c18vPejXC6sEU8IaJlikv5Osw43r46ULOavuANpgnmbCdukQ8ARyK2KzGV7U3tANuQtugAfGBJI5JOD6t317iICwnNd8AEFVBwIGgim5TxiIZ0hchtbb1uwlbfc0fpWoL1D+Z8JzmPMk6bUAq42O4v+fy+8Zg3Lu2yebM97LzZyVYx1nyAbztAzIdmzMdv0UMqDrEQ8zfuA90OZZggHr80xzG4c0yFwiT2stR7g8jmJEQ2W0QGhkpkt4PHo9F4rpfGNXMfD/teofG+dY9ZLOaO2fDcBj25bWvltvW/y77fZabWeASUeMQ4uUgDliufsmeBzLsi01NMyJISyvbPwsDGbmAJe8oZfcYHPS7woOMMlJTdE4tlpnNKyjA0K8Pqqwyt0cxSlBGk64+sChu2SlVHsyrcT1qo2j35b+mkv63QX/GWWCSu80t/R2LhFAaPE98r3HfnVQbkP4d7p/6acfEaLO1p4T9373Iu5ktXorkwhpGCZbWkYKlSsDqk4I4lBXVl+2+GxWUbcjFl3kS0GTJWNEewEV+kCZE4zCH0BUR5UL/OAYx9RK5kRxIHwV5iXS5sym4ID7SD0bxr3ax6AIyWojVHo9NK0eUuxvEcPdcZpMphnm+SBh2RadrS1VRQ/BxZ2uy9gDO6PTqRBNQV3Bkn6ir+60rU5RHB583UVVrWlqnV0lSgKDzgkBxxT4R9J8yvjEuGNpfhNx16GCgdOD12MpwO8O3RwJ9/YvAXmsEHarnjU4Yv/fONPm0fdJ2vddVDo0WfcgqndAjpGsdBAhMfQqqoTFk2Vo2ibAQL3YcBvYvJRbenexeT8tF7GosxVgyZO2aTIS5svqIYmHyq5ftqGO9QjVqlcv9DL9Tc9haS7t1UqHeh1lKcA44t1UaXHJxKcu/LJ+qC4syUoX0LA6hHOcj38ZrTs4B3YWuGV12PCQb/nWIm5nnOtekCnFptquU3M0PGbKdnzC7qCW0x21EY/0eax+qyxu5Y7mYJufLziFOz9g55mNzTNJabZh7lnCYdtOa0FXhosepdVr8jHYjRpmEfr2rApFWNiqjGqmbC7Wew6CsFqFUK6smllIJajn5sKXSVMdNKQa0UP2D5AltJ0+r526zRkiZUq8M7HOJVMELhQvAT1162tD0AO47ezS5mj+eCrh+bOCjJp7/y0vzjQnT/HmEUpOfqFss4Loyh3CKa9X85ij2e+h8x8PY/ -------------------------------------------------------------------------------- /docs/threads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dernasherbrezon/sdr-server/f9b0aebc3fd396dbe06c4448d8e21c96b9cf4c5c/docs/threads.png -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=dernasherbrezon_sdr-server 2 | sonar.organization=dernasherbrezon-github 3 | 4 | # This is the name and version displayed in the SonarCloud UI. 5 | #sonar.projectName=sdr-server 6 | #sonar.projectVersion=1.0 7 | 8 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 9 | sonar.sources=./src/ 10 | 11 | # Encoding of the source code. Default is default system encoding 12 | sonar.sourceEncoding=UTF-8 13 | 14 | sonar.cfamily.gcov.reportsPath=build 15 | 16 | sonar.cpp.file.suffixes=- 17 | sonar.objc.file.suffixes=- 18 | -------------------------------------------------------------------------------- /src/api.h: -------------------------------------------------------------------------------- 1 | #ifndef API_H_ 2 | #define API_H_ 3 | 4 | #define PROTOCOL_VERSION 0 5 | 6 | // client to server 7 | #define TYPE_REQUEST 0 8 | #define TYPE_SHUTDOWN 1 9 | #define TYPE_PING 3 10 | //server to client 11 | #define TYPE_RESPONSE 2 12 | 13 | struct message_header { 14 | uint8_t protocol_version; 15 | uint8_t type; 16 | } __attribute__((packed)); 17 | 18 | #define REQUEST_DESTINATION_FILE 0 19 | #define REQUEST_DESTINATION_SOCKET 1 20 | 21 | struct request { 22 | uint32_t center_freq; 23 | uint32_t sampling_rate; 24 | uint32_t band_freq; 25 | uint8_t destination; 26 | } __attribute__((packed)); 27 | 28 | #define RESPONSE_STATUS_SUCCESS 0 29 | #define RESPONSE_STATUS_FAILURE 1 30 | 31 | #define RESPONSE_DETAILS_INVALID_REQUEST 1 32 | #define RESPONSE_DETAILS_OUT_OF_BAND_FREQ 2 33 | #define RESPONSE_DETAILS_INTERNAL_ERROR 3 34 | 35 | struct response { 36 | uint8_t status; 37 | uint32_t details; // on success contains file index, on error contains error code 38 | } __attribute__((packed)); 39 | 40 | 41 | #endif /* API_H_ */ 42 | -------------------------------------------------------------------------------- /src/client/tcp_client.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "tcp_client.h" 11 | 12 | int create_client(const char *addr, int port, struct tcp_client **tcp_client) { 13 | struct tcp_client *result = malloc(sizeof(struct tcp_client)); 14 | if (result == NULL) { 15 | return -ENOMEM; 16 | } 17 | int client_socket = socket(AF_INET, SOCK_STREAM, 0); 18 | if (client_socket == -1) { 19 | free(result); 20 | fprintf(stderr, "socket creation failed: %d\n", client_socket); 21 | return -1; 22 | } 23 | result->client_socket = client_socket; 24 | 25 | struct sockaddr_in address; 26 | address.sin_family = AF_INET; 27 | address.sin_addr.s_addr = inet_addr(addr); 28 | address.sin_port = htons(port); 29 | int code = connect(client_socket, (struct sockaddr*) &address, sizeof(address)); 30 | if (code != 0) { 31 | free(result); 32 | fprintf(stderr, "unable to connect to: %s:%d - %d\n", addr, port, code); 33 | return -1; 34 | } 35 | fprintf(stderr, "connected to the server..\n"); 36 | 37 | *tcp_client = result; 38 | return 0; 39 | } 40 | 41 | int write_data(void *buffer, size_t total_len, struct tcp_client *tcp_client) { 42 | size_t left = total_len; 43 | while (left > 0) { 44 | int written = write(tcp_client->client_socket, buffer + (total_len - left), left); 45 | if (written < 0) { 46 | perror("unable to write the message"); 47 | return -1; 48 | } 49 | left -= written; 50 | } 51 | return 0; 52 | } 53 | 54 | int write_request(struct message_header header, struct request req, struct tcp_client *tcp_client) { 55 | req.band_freq = htonl(req.band_freq); 56 | req.center_freq = htonl(req.center_freq); 57 | req.sampling_rate = htonl(req.sampling_rate); 58 | // it is possible to directly populate *buffer with the fields, 59 | // however populating structs and then serializing them into byte array 60 | // is more readable 61 | size_t total_len = sizeof(struct message_header) + sizeof(struct request); 62 | uint8_t *buffer = malloc(total_len); 63 | if (buffer == NULL) { 64 | return -ENOMEM; 65 | } 66 | memcpy(buffer, &header, sizeof(struct message_header)); 67 | memcpy(buffer + sizeof(struct message_header), &req, sizeof(struct request)); 68 | int code = write_data(buffer, total_len, tcp_client); 69 | free(buffer); 70 | return code; 71 | } 72 | 73 | int send_message(struct tcp_client *client, uint8_t protocol, uint8_t type, uint32_t center_freq, uint32_t sampling_rate, uint32_t band_freq, uint8_t destination) { 74 | struct message_header header; 75 | header.protocol_version = protocol; 76 | header.type = type; 77 | struct request req; 78 | req.band_freq = band_freq; 79 | req.center_freq = center_freq; 80 | req.sampling_rate = sampling_rate; 81 | req.destination = destination; 82 | return write_request(header, req, client); 83 | } 84 | 85 | int read_data(void *result, size_t len, struct tcp_client *tcp_client) { 86 | size_t left = len; 87 | while (left > 0) { 88 | int received = recv(tcp_client->client_socket, (char*) result + (len - left), left, 0); 89 | if (received < 0) { 90 | if (errno == EWOULDBLOCK || errno == EAGAIN) { 91 | return -errno; 92 | } 93 | if (errno == EINTR) { 94 | continue; 95 | } 96 | return -1; 97 | } 98 | // client has closed the socket 99 | if (received == 0) { 100 | return -1; 101 | } 102 | left -= received; 103 | } 104 | return 0; 105 | } 106 | 107 | int read_response(struct message_header **response_header, struct response **resp, struct tcp_client *tcp_client) { 108 | struct message_header *header = malloc(sizeof(struct message_header)); 109 | if (header == NULL) { 110 | return -ENOMEM; 111 | } 112 | int code = read_data(header, sizeof(struct message_header), tcp_client); 113 | if (code != 0) { 114 | free(header); 115 | return code; 116 | } 117 | struct response *result = malloc(sizeof(struct response)); 118 | if (result == NULL) { 119 | free(header); 120 | return -ENOMEM; 121 | } 122 | code = read_data(result, sizeof(struct response), tcp_client); 123 | if (code != 0) { 124 | free(header); 125 | free(result); 126 | return code; 127 | } 128 | result->details = ntohl(result->details); 129 | *response_header = header; 130 | *resp = result; 131 | return 0; 132 | } 133 | 134 | void destroy_client(struct tcp_client *tcp_client) { 135 | if (tcp_client == NULL) { 136 | return; 137 | } 138 | close(tcp_client->client_socket); 139 | free(tcp_client); 140 | } 141 | 142 | void gracefully_destroy_client(struct tcp_client *tcp_client) { 143 | while (true) { 144 | struct message_header header; 145 | int code = read_data(&header, sizeof(struct message_header), tcp_client); 146 | if (code < -1) { 147 | // read timeout happened. it's ok. 148 | // client already sent all information we need 149 | continue; 150 | } 151 | if (code == -1) { 152 | break; 153 | } 154 | } 155 | fprintf(stderr, "disconnected from the server..\n"); 156 | destroy_client(tcp_client); 157 | } 158 | 159 | -------------------------------------------------------------------------------- /src/client/tcp_client.h: -------------------------------------------------------------------------------- 1 | #ifndef TCP_CLIENT_H_ 2 | #define TCP_CLIENT_H_ 3 | 4 | #include 5 | #include "../api.h" 6 | 7 | struct tcp_client { 8 | int client_socket; 9 | }; 10 | 11 | int create_client(const char *addr, int port, struct tcp_client **tcp_client); 12 | 13 | int write_data(void *buffer, size_t len, struct tcp_client *tcp_client); 14 | int read_data(void *buffer, size_t len, struct tcp_client *tcp_client); 15 | 16 | int write_request(struct message_header header, struct request req, struct tcp_client *tcp_client); 17 | int send_message(struct tcp_client *client, uint8_t protocol, uint8_t type, uint32_t center_freq, uint32_t sampling_rate, uint32_t band_freq, uint8_t destination); 18 | int read_response(struct message_header **header, struct response **resp, struct tcp_client *tcp_client); 19 | 20 | void destroy_client(struct tcp_client *tcp_client); 21 | // this will wait until server release all resources, stop all threads and closes connection 22 | void gracefully_destroy_client(struct tcp_client *tcp_client); 23 | 24 | #endif /* TCP_CLIENT_H_ */ 25 | -------------------------------------------------------------------------------- /src/client/tcp_client_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "tcp_client.h" 9 | 10 | #define ERROR_CHECK(x) \ 11 | do { \ 12 | int __err_rc = (x); \ 13 | if (__err_rc != 0) { \ 14 | return __err_rc; \ 15 | } \ 16 | } while (0) 17 | 18 | volatile sig_atomic_t do_exit = 0; 19 | 20 | static void sighandler(int signum) { 21 | fprintf(stderr, "Signal caught, exiting!\n"); 22 | do_exit = 1; 23 | } 24 | 25 | void usage() { 26 | printf("Usage:\n"); 27 | printf(" -h print help\n"); 28 | printf(" -k sdr_server hostname (default: localhost)\n"); 29 | printf(" -p sdr_server port (default: 8090)\n"); 30 | printf(" -s sampling rate (default: 48000)\n"); 31 | printf(" -f Center frequency. Frequency where signal of interest is\n"); 32 | printf(" -b Band frequency. Multiple clients have to specify same frequency band\n"); 33 | printf(" Filename to output (a '-' dumps samples to stdout)\n"); 34 | } 35 | 36 | int main(int argc, char **argv) { 37 | int port = 8090; 38 | uint32_t center_freq = 0; 39 | uint32_t band_freq = 0; 40 | uint32_t sampling_rate = 48000; 41 | char *filename = NULL; 42 | char *hostname = "127.0.0.1"; 43 | 44 | int dopt; 45 | while ((dopt = getopt(argc, argv, "hr:k:m:b:s:p:n:f:")) != EOF) { 46 | switch (dopt) { 47 | case 'h': 48 | usage(); 49 | return EXIT_SUCCESS; 50 | case 'k': 51 | hostname = optarg; 52 | break; 53 | case 'b': 54 | band_freq = (uint32_t) atof(optarg); 55 | break; 56 | case 's': 57 | sampling_rate = (uint32_t) atof(optarg); 58 | break; 59 | case 'p': 60 | port = atoi(optarg); 61 | break; 62 | case 'f': 63 | center_freq = (uint32_t) atof(optarg); 64 | break; 65 | default: 66 | exit(EXIT_FAILURE); 67 | } 68 | } 69 | 70 | if (argc <= optind || center_freq == 0) { 71 | usage(); 72 | return EXIT_SUCCESS; 73 | } else { 74 | filename = argv[optind]; 75 | } 76 | 77 | if (band_freq == 0) { 78 | band_freq = center_freq; 79 | } 80 | 81 | struct tcp_client *client = NULL; 82 | ERROR_CHECK(create_client(hostname, port, &client)); 83 | ERROR_CHECK(send_message(client, PROTOCOL_VERSION, TYPE_REQUEST, center_freq, sampling_rate, band_freq, REQUEST_DESTINATION_SOCKET)); 84 | struct message_header *response_header = NULL; 85 | struct response *resp = NULL; 86 | ERROR_CHECK(read_response(&response_header, &resp, client)); 87 | if (response_header->type != TYPE_RESPONSE) { 88 | fprintf(stderr, "invalid response type received: %d\n", response_header->type); 89 | destroy_client(client); 90 | return EXIT_FAILURE; 91 | } 92 | if (resp->status != RESPONSE_STATUS_SUCCESS) { 93 | fprintf(stderr, "invalid request: %d\n", resp->details); 94 | destroy_client(client); 95 | return EXIT_FAILURE; 96 | } 97 | FILE *output; 98 | if (strcmp(filename, "-") == 0) { 99 | output = stdout; 100 | } else { 101 | output = fopen(filename, "wb"); 102 | if (!output) { 103 | fprintf(stderr, "failed to open %s\n", filename); 104 | destroy_client(client); 105 | return EXIT_FAILURE; 106 | } 107 | } 108 | size_t buffer_length = 262144; 109 | uint8_t *buffer = malloc(buffer_length); 110 | if (buffer == NULL) { 111 | destroy_client(client); 112 | return -ENOMEM; 113 | } 114 | while (!do_exit) { 115 | int code = read_data(buffer, buffer_length, client); 116 | if (code != 0) { 117 | fprintf(stderr, "unable to read data. shutdown\n"); 118 | destroy_client(client); 119 | return EXIT_FAILURE; 120 | } 121 | 122 | size_t left = buffer_length; 123 | while (left > 0) { 124 | size_t written = fwrite(buffer + (buffer_length - left), sizeof(uint8_t), left, output); 125 | if (written == 0) { 126 | perror("unable to write the message"); 127 | destroy_client(client); 128 | return EXIT_FAILURE; 129 | } 130 | left -= written; 131 | } 132 | } 133 | 134 | return EXIT_SUCCESS; 135 | } -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define AIRSPY_BUFFER_SIZE 262144 10 | 11 | char *read_and_copy_str(const config_setting_t *setting, const char *default_value) { 12 | const char *value; 13 | if (setting == NULL) { 14 | if (default_value == NULL) { 15 | return NULL; 16 | } 17 | value = default_value; 18 | } else { 19 | value = config_setting_get_string(setting); 20 | } 21 | char *bind_address; 22 | size_t length = strlen(value); 23 | char *str_bind_address = malloc(sizeof(char) * length + 1); 24 | if (str_bind_address == NULL) { 25 | return NULL; 26 | } 27 | strncpy(str_bind_address, value, length); 28 | str_bind_address[length] = '\0'; 29 | bind_address = str_bind_address; 30 | return bind_address; 31 | } 32 | 33 | int config_read_int(config_t *libconfig, const char *config_name, int default_value) { 34 | const config_setting_t *setting = config_lookup(libconfig, config_name); 35 | int result; 36 | if (setting == NULL) { 37 | result = default_value; 38 | } else { 39 | result = config_setting_get_int(setting); 40 | } 41 | fprintf(stdout, "%s: %d\n", config_name, result); 42 | return result; 43 | } 44 | 45 | float config_read_float(config_t *libconfig, const char *config_name, float default_value) { 46 | const config_setting_t *setting = config_lookup(libconfig, config_name); 47 | float result; 48 | if (setting == NULL) { 49 | result = default_value; 50 | } else { 51 | result = (float)config_setting_get_float(setting); 52 | } 53 | fprintf(stdout, "%s: %f\n", config_name, result); 54 | return result; 55 | } 56 | 57 | bool config_read_bool(config_t *libconfig, const char *config_name, bool default_value) { 58 | const config_setting_t *setting = config_lookup(libconfig, config_name); 59 | bool result; 60 | if (setting == NULL) { 61 | result = default_value; 62 | } else { 63 | result = config_setting_get_bool(setting); 64 | } 65 | fprintf(stdout, "%s: %d\n", config_name, result); 66 | return result; 67 | } 68 | 69 | uint32_t config_read_uint32_t(config_t *libconfig, const char *config_name, uint32_t default_value) { 70 | const config_setting_t *setting = config_lookup(libconfig, config_name); 71 | uint32_t result; 72 | if (setting == NULL) { 73 | result = default_value; 74 | } else { 75 | result = (uint32_t)config_setting_get_int(setting); 76 | } 77 | fprintf(stdout, "%s: %d\n", config_name, result); 78 | return result; 79 | } 80 | 81 | int create_server_config(struct server_config **config, const char *path) { 82 | fprintf(stdout, "loading configuration from: %s\n", path); 83 | struct server_config *result = malloc(sizeof(struct server_config)); 84 | if (result == NULL) { 85 | return -ENOMEM; 86 | } 87 | *result = (struct server_config){0}; 88 | 89 | config_t libconfig; 90 | config_init(&libconfig); 91 | 92 | int code = config_read_file(&libconfig, path); 93 | if (code == CONFIG_FALSE) { 94 | fprintf(stderr, "<3>unable to read configuration: %s\n", config_error_text(&libconfig)); 95 | config_destroy(&libconfig); 96 | free(result); 97 | return -1; 98 | } 99 | 100 | result->sdr_type = config_read_int(&libconfig, "sdr_type", 0); 101 | result->bias_t = config_read_int(&libconfig, "bias_t", 0); 102 | result->gain_mode = config_read_int(&libconfig, "gain_mode", 0); 103 | result->gain = (int)(config_read_float(&libconfig, "gain", 0) * 10); 104 | result->ppm = config_read_int(&libconfig, "ppm", 0); 105 | 106 | result->airspy_gain_mode = config_read_int(&libconfig, "airspy_gain_mode", AIRSPY_GAIN_MANUAL); 107 | result->airspy_vga_gain = config_read_int(&libconfig, "airspy_vga_gain", 5); 108 | if (result->airspy_vga_gain > 15 || result->airspy_vga_gain < 0) { 109 | fprintf(stderr, "<3>invalid airspy_vga_gain configuration\n"); 110 | config_destroy(&libconfig); 111 | free(result); 112 | return -1; 113 | } 114 | result->airspy_mixer_gain = config_read_int(&libconfig, "airspy_mixer_gain", 0); 115 | if (result->airspy_mixer_gain > 15 || result->airspy_mixer_gain < 0) { 116 | fprintf(stderr, "<3>invalid airspy_mixer_gain configuration\n"); 117 | config_destroy(&libconfig); 118 | free(result); 119 | return -1; 120 | } 121 | result->airspy_lna_gain = config_read_int(&libconfig, "airspy_lna_gain", 1); 122 | if (result->airspy_lna_gain > 14 || result->airspy_lna_gain < 0) { 123 | fprintf(stderr, "<3>invalid airspy_lna_gain configuration\n"); 124 | config_destroy(&libconfig); 125 | free(result); 126 | return -1; 127 | } 128 | result->airspy_linearity_gain = config_read_int(&libconfig, "airspy_linearity_gain", 0); 129 | if (result->airspy_linearity_gain > 21 || result->airspy_linearity_gain < 0) { 130 | fprintf(stderr, "<3>invalid airspy_linearity_gain configuration\n"); 131 | config_destroy(&libconfig); 132 | free(result); 133 | return -1; 134 | } 135 | result->airspy_sensitivity_gain = config_read_int(&libconfig, "airspy_sensitivity_gain", 0); 136 | if (result->airspy_sensitivity_gain > 21 || result->airspy_sensitivity_gain < 0) { 137 | fprintf(stderr, "<3>invalid airspy_sensitivity_gain configuration\n"); 138 | config_destroy(&libconfig); 139 | free(result); 140 | return -1; 141 | } 142 | 143 | result->hackrf_bias_t = (uint8_t)config_read_int(&libconfig, "hackrf_bias_t", 0); 144 | result->hackrf_amp = config_read_int(&libconfig, "hackrf_amp", 0); 145 | if (result->hackrf_amp > 1) { 146 | fprintf(stderr, "<3>hackrf_amp is either turned on (1) or off (0)\n"); 147 | config_destroy(&libconfig); 148 | free(result); 149 | return -1; 150 | } 151 | result->hackrf_lna_gain = config_read_int(&libconfig, "hackrf_lna_gain", 16); 152 | if (result->hackrf_lna_gain < 0 || result->hackrf_lna_gain > 40) { 153 | fprintf(stderr, "<3>invalid hackrf_lna_gain configuration\n"); 154 | config_destroy(&libconfig); 155 | free(result); 156 | return -1; 157 | } 158 | result->hackrf_vga_gain = config_read_int(&libconfig, "hackrf_vga_gain", 16); 159 | if (result->hackrf_vga_gain < 0 || result->hackrf_vga_gain > 62) { 160 | fprintf(stderr, "<3>invalid hackrf_vga_gain configuration\n"); 161 | config_destroy(&libconfig); 162 | free(result); 163 | return -1; 164 | } 165 | 166 | result->queue_size = config_read_int(&libconfig, "queue_size", 64); 167 | if (result->queue_size <= 0) { 168 | fprintf(stderr, "<3>queue size should be positive: %d\n", result->queue_size); 169 | config_destroy(&libconfig); 170 | free(result); 171 | return -1; 172 | } 173 | 174 | const config_setting_t *setting = config_lookup(&libconfig, "band_sampling_rate"); 175 | if (setting == NULL) { 176 | fprintf(stderr, "<3>missing required configuration: band_sampling_rate\n"); 177 | config_destroy(&libconfig); 178 | free(result); 179 | return -1; 180 | } 181 | uint32_t band_sampling_rate = (uint32_t)config_setting_get_int(setting); 182 | fprintf(stdout, "band sampling rate: %d\n", band_sampling_rate); 183 | result->band_sampling_rate = band_sampling_rate; 184 | 185 | result->device_index = config_read_int(&libconfig, "device_index", 0); 186 | result->device_serial = read_and_copy_str(config_lookup(&libconfig, "device_serial"), NULL); 187 | if (result->device_serial != NULL) { 188 | fprintf(stdout, "device_serial: %s\n", result->device_serial); 189 | } 190 | 191 | result->buffer_size = config_read_uint32_t(&libconfig, "buffer_size", 262144); 192 | if (result->sdr_type == SDR_TYPE_AIRSPY && result->buffer_size != AIRSPY_BUFFER_SIZE) { 193 | result->buffer_size = AIRSPY_BUFFER_SIZE; 194 | fprintf(stdout, "force airspy buffer_size to: %d\n", result->buffer_size); 195 | } 196 | result->lpf_cutoff_rate = config_read_int(&libconfig, "lpf_cutoff_rate", 5); 197 | 198 | setting = config_lookup(&libconfig, "bind_address"); 199 | char *bind_address = read_and_copy_str(setting, "127.0.0.1"); 200 | if (bind_address == NULL) { 201 | config_destroy(&libconfig); 202 | free(result); 203 | return -ENOMEM; 204 | } 205 | result->bind_address = bind_address; 206 | result->port = config_read_int(&libconfig, "port", 8090); 207 | fprintf(stdout, "start listening on %s:%d\n", result->bind_address, result->port); 208 | 209 | int read_timeout_seconds = config_read_int(&libconfig, "read_timeout_seconds", 5); 210 | if (read_timeout_seconds <= 0) { 211 | config_destroy(&libconfig); 212 | destroy_server_config(result); 213 | fprintf(stderr, "<3>read timeout should be positive: %d\n", read_timeout_seconds); 214 | return -1; 215 | } 216 | result->read_timeout_seconds = read_timeout_seconds; 217 | 218 | const char *default_folder = getenv("TMPDIR"); 219 | if (default_folder == NULL) { 220 | default_folder = "/tmp"; 221 | } 222 | 223 | setting = config_lookup(&libconfig, "base_path"); 224 | char *base_path = read_and_copy_str(setting, default_folder); 225 | if (base_path == NULL) { 226 | config_destroy(&libconfig); 227 | free(result); 228 | return -ENOMEM; 229 | } 230 | result->base_path = base_path; 231 | fprintf(stdout, "base path for storing results: %s\n", result->base_path); 232 | 233 | result->use_gzip = config_read_bool(&libconfig, "use_gzip", true); 234 | 235 | config_destroy(&libconfig); 236 | 237 | *config = result; 238 | return 0; 239 | } 240 | 241 | void destroy_server_config(struct server_config *config) { 242 | if (config == NULL) { 243 | return; 244 | } 245 | if (config->bind_address != NULL) { 246 | free(config->bind_address); 247 | } 248 | if (config->base_path != NULL) { 249 | free(config->base_path); 250 | } 251 | if (config->device_serial != NULL) { 252 | free(config->device_serial); 253 | } 254 | free(config); 255 | } 256 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_ 2 | #define CONFIG_H_ 3 | 4 | #include 5 | #include 6 | 7 | typedef enum { 8 | SDR_TYPE_RTL = 0, 9 | SDR_TYPE_AIRSPY = 1, 10 | SDR_TYPE_HACKRF = 2 11 | } sdr_type_t; 12 | 13 | typedef enum { 14 | AIRSPY_GAIN_AUTO = 0, 15 | AIRSPY_GAIN_SENSITIVITY = 1, 16 | AIRSPY_GAIN_LINEARITY = 2, 17 | AIRSPY_GAIN_MANUAL = 3 18 | } airspy_gain_mode_t; 19 | 20 | struct server_config { 21 | // socket settings 22 | char *bind_address; 23 | int port; 24 | int read_timeout_seconds; 25 | char *device_serial; 26 | 27 | sdr_type_t sdr_type; 28 | 29 | // rtl-sdr settings 30 | int device_index; 31 | int gain_mode; 32 | int gain; 33 | int ppm; 34 | int bias_t; 35 | uint32_t buffer_size; 36 | // 4GHz max 37 | uint32_t band_sampling_rate; 38 | int queue_size; 39 | int lpf_cutoff_rate; 40 | 41 | // airspy settings 42 | airspy_gain_mode_t airspy_gain_mode; 43 | int airspy_vga_gain; 44 | int airspy_mixer_gain; 45 | int airspy_lna_gain; 46 | int airspy_linearity_gain; 47 | int airspy_sensitivity_gain; 48 | 49 | // hackrf settings 50 | uint8_t hackrf_bias_t; 51 | int hackrf_amp; 52 | int hackrf_lna_gain; 53 | int hackrf_vga_gain; 54 | 55 | // output settings 56 | char *base_path; 57 | bool use_gzip; 58 | }; 59 | 60 | int create_server_config(struct server_config **config, const char *path); 61 | 62 | void destroy_server_config(struct server_config *config); 63 | 64 | #endif /* CONFIG_H_ */ 65 | -------------------------------------------------------------------------------- /src/dsp_worker.c: -------------------------------------------------------------------------------- 1 | #include "dsp_worker.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "api.h" 8 | #include "lpf.h" 9 | 10 | static int write_to_file(dsp_worker *config, const float complex *filter_output, size_t filter_output_len) { 11 | size_t n_written; 12 | if (config->file != NULL) { 13 | n_written = fwrite(filter_output, sizeof(float complex), filter_output_len, config->file); 14 | } else if (config->gz != NULL) { 15 | n_written = gzwrite(config->gz, filter_output, sizeof(float complex) * filter_output_len); 16 | } else { 17 | fprintf(stderr, "<3>unknown file output\n"); 18 | return -1; 19 | } 20 | // if disk is full, then terminate the client 21 | if (n_written < filter_output_len) { 22 | return -1; 23 | } else { 24 | return 0; 25 | } 26 | } 27 | 28 | static int write_to_socket(int client_socket, const float complex *filter_output, size_t filter_output_len) { 29 | size_t total_len = filter_output_len * sizeof(float complex); 30 | size_t left = total_len; 31 | while (left > 0) { 32 | ssize_t written = write(client_socket, (char *)filter_output + (total_len - left), left); 33 | if (written < 0) { 34 | return -1; 35 | } 36 | left -= written; 37 | } 38 | return 0; 39 | } 40 | 41 | static void *callback(void *arg) { 42 | dsp_worker *worker = (dsp_worker *)arg; 43 | client_config *config = worker->config; 44 | fprintf(stdout, "[%d] dsp_worker started\n", config->id); 45 | uint8_t *input = NULL; 46 | size_t input_len = 0; 47 | float complex *filter_output = NULL; 48 | size_t filter_output_len = 0; 49 | while (true) { 50 | take_buffer_for_processing(&input, &input_len, worker->queue); 51 | // poison pill received 52 | if (input == NULL) { 53 | break; 54 | } 55 | switch (config->sdr_type) { 56 | case SDR_TYPE_HACKRF: { 57 | process_optimized_cs8_cf32((const int8_t *)input, input_len, &filter_output, &filter_output_len, worker->filter); 58 | break; 59 | } 60 | case SDR_TYPE_RTL: { 61 | process_optimized_cu8_cf32(input, input_len, &filter_output, &filter_output_len, worker->filter); 62 | break; 63 | } 64 | case SDR_TYPE_AIRSPY: { 65 | process_optimized_cs16_cf32((const int16_t *)input, input_len / sizeof(int16_t), &filter_output, &filter_output_len, worker->filter); 66 | break; 67 | } 68 | default: { 69 | fprintf(stderr, "<3>unsupported sdr type: %d\n", config->sdr_type); 70 | break; 71 | } 72 | } 73 | int code; 74 | if (config->destination == REQUEST_DESTINATION_FILE) { 75 | code = write_to_file(worker, filter_output, filter_output_len); 76 | } else if (config->destination == REQUEST_DESTINATION_SOCKET) { 77 | code = write_to_socket(config->client_socket, filter_output, filter_output_len); 78 | } else { 79 | fprintf(stderr, "<3>unknown destination: %d\n", config->destination); 80 | code = -1; 81 | } 82 | complete_buffer_processing(worker->queue); 83 | if (code != 0) { 84 | close(config->client_socket); 85 | } 86 | } 87 | return (void *)0; 88 | } 89 | 90 | int dsp_worker_start(client_config *config, struct server_config *server_config, dsp_worker **worker) { 91 | dsp_worker *result = malloc(sizeof(dsp_worker)); 92 | *result = (dsp_worker){0}; 93 | result->config = config; 94 | 95 | // setup taps 96 | float *taps = NULL; 97 | size_t len; 98 | int code = create_low_pass_filter(1.0F, server_config->band_sampling_rate, config->sampling_rate / 2, config->sampling_rate / server_config->lpf_cutoff_rate, &taps, &len); 99 | if (code != 0) { 100 | dsp_worker_destroy(result); 101 | return code; 102 | } 103 | // setup xlating frequency filter 104 | code = create_frequency_xlating_filter(server_config->band_sampling_rate / config->sampling_rate, taps, len, (int64_t)config->center_freq - (int64_t)config->band_freq, server_config->band_sampling_rate, server_config->buffer_size, &result->filter); 105 | if (code != 0) { 106 | dsp_worker_destroy(result); 107 | return code; 108 | } 109 | 110 | if (server_config->use_gzip) { 111 | char file_path[4096]; 112 | snprintf(file_path, sizeof(file_path), "%s/%d.cf32.gz", server_config->base_path, config->id); 113 | result->gz = gzopen(file_path, "wb"); 114 | if (result->gz == NULL) { 115 | fprintf(stderr, "<3>unable to open gz file for output: %s\n", file_path); 116 | dsp_worker_destroy(result); 117 | return -1; 118 | } 119 | } else { 120 | char file_path[4096]; 121 | snprintf(file_path, sizeof(file_path), "%s/%d.cf32", server_config->base_path, config->id); 122 | result->file = fopen(file_path, "wb"); 123 | if (result->file == NULL) { 124 | fprintf(stderr, "<3>unable to open file for output: %s\n", file_path); 125 | dsp_worker_destroy(result); 126 | return -1; 127 | } 128 | } 129 | 130 | // setup queue 131 | code = create_queue(server_config->buffer_size, server_config->queue_size, &result->queue); 132 | if (code != 0) { 133 | dsp_worker_destroy(result); 134 | return -1; 135 | } 136 | 137 | // start processing 138 | pthread_t *dsp_thread = malloc(sizeof(pthread_t)); 139 | if (dsp_thread == NULL) { 140 | dsp_worker_destroy(result); 141 | return -ENOMEM; 142 | } 143 | code = pthread_create(dsp_thread, NULL, &callback, result); 144 | if (code != 0) { 145 | // destroy_node does pthread_join which might fail on undefined pthread_t 146 | // release memory explicitly 147 | free(dsp_thread); 148 | dsp_worker_destroy(result); 149 | return -1; 150 | } 151 | result->dsp_thread = dsp_thread; 152 | *worker = result; 153 | return 0; 154 | } 155 | 156 | void dsp_worker_destroy(dsp_worker *node) { 157 | if (node == NULL) { 158 | return; 159 | } 160 | fprintf(stdout, "[%d] dsp_worker is stopping\n", node->config->id); 161 | if (node->queue != NULL) { 162 | interrupt_waiting_the_data(node->queue); 163 | } 164 | // wait until thread terminates and only then destroy the node 165 | if (node->dsp_thread != NULL) { 166 | pthread_join(*node->dsp_thread, NULL); 167 | free(node->dsp_thread); 168 | } 169 | // cleanup everything only when thread terminates 170 | if (node->queue != NULL) { 171 | destroy_queue(node->queue); 172 | } 173 | if (node->file != NULL) { 174 | fclose(node->file); 175 | } 176 | if (node->gz != NULL) { 177 | gzclose(node->gz); 178 | } 179 | if (node->filter != NULL) { 180 | destroy_xlating(node->filter); 181 | } 182 | printf("[%d] dsp_worker stopped\n", node->config->id); 183 | free(node); 184 | } 185 | 186 | void dsp_worker_process(uint8_t *buf, uint32_t buf_len, dsp_worker *config) { 187 | queue_put(buf, buf_len, config->queue); 188 | } -------------------------------------------------------------------------------- /src/dsp_worker.h: -------------------------------------------------------------------------------- 1 | #ifndef DSP_WORKER_H_ 2 | #define DSP_WORKER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "config.h" 11 | #include "queue.h" 12 | #include "xlating.h" 13 | 14 | typedef struct { 15 | uint32_t center_freq; 16 | uint32_t sampling_rate; 17 | uint32_t band_freq; 18 | uint8_t destination; 19 | int client_socket; 20 | uint32_t id; 21 | sdr_type_t sdr_type; 22 | bool is_running; 23 | } client_config; 24 | 25 | typedef struct { 26 | client_config *config; 27 | 28 | queue *queue; 29 | xlating *filter; 30 | pthread_t *dsp_thread; 31 | FILE *file; 32 | gzFile gz; 33 | } dsp_worker; 34 | 35 | int dsp_worker_start(client_config *config, struct server_config *server_config, dsp_worker **worker); 36 | 37 | void dsp_worker_process(uint8_t *buf, uint32_t buf_len, dsp_worker *worker); 38 | 39 | void dsp_worker_destroy(dsp_worker *worker); 40 | 41 | #endif -------------------------------------------------------------------------------- /src/lpf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #ifndef M_PI 7 | #define M_PI 3.14159265358979323846 8 | #endif 9 | 10 | #include "lpf.h" 11 | 12 | int sanity_check_1f(uint32_t sampling_freq, uint32_t cutoff_freq, uint32_t transition_width) { 13 | if (sampling_freq <= 0) { 14 | fprintf(stderr, "<3>sampling frequency should be positive\n"); 15 | return -1; 16 | } 17 | 18 | if (cutoff_freq <= 0 || cutoff_freq > (float) sampling_freq / 2) { 19 | fprintf(stderr, "<3>cutoff frequency should be positive and less than sampling freq / 2. got: %u\n", cutoff_freq); 20 | return -1; 21 | } 22 | 23 | if (transition_width <= 0) { 24 | fprintf(stderr, "<3>transition width should be positive\n"); 25 | return -1; 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | int computeNtaps(uint32_t sampling_freq, uint32_t transition_width) { 32 | double a = 53; 33 | int ntaps = (int) (a * sampling_freq / (22.0F * transition_width)); 34 | if ((ntaps & 1) == 0) { // if even... 35 | ntaps++; // ...make odd 36 | } 37 | return ntaps; 38 | } 39 | 40 | int create_hamming_window(int ntaps, float **output) { 41 | float *result = malloc(sizeof(float) * ntaps); 42 | if (result == NULL) { 43 | return -ENOMEM; 44 | } 45 | int m = ntaps - 1; 46 | for (int n = 0; n < ntaps; n++) { 47 | result[n] = (float) (0.54 - 0.46 * cos((2 * M_PI * n) / m)); 48 | } 49 | *output = result; 50 | return 0; 51 | } 52 | 53 | int create_low_pass_filter(float gain, uint32_t sampling_freq, uint32_t cutoff_freq, uint32_t transition_width, float **output_taps, size_t *len) { 54 | int code = sanity_check_1f(sampling_freq, cutoff_freq, transition_width); 55 | if (code != 0) { 56 | return code; 57 | } 58 | 59 | int ntaps = computeNtaps(sampling_freq, transition_width); 60 | float *taps = malloc(sizeof(float) * ntaps); 61 | if (taps == NULL) { 62 | return -ENOMEM; 63 | } 64 | float *w = NULL; 65 | code = create_hamming_window(ntaps, &w); 66 | if (code != 0) { 67 | free(taps); 68 | return code; 69 | } 70 | 71 | int M = (ntaps - 1) / 2; 72 | float fwT0 = 2 * M_PI * cutoff_freq / sampling_freq; 73 | 74 | for (int n = -M; n <= M; n++) { 75 | if (n == 0) { 76 | taps[n + M] = fwT0 / M_PI * w[n + M]; 77 | } else { 78 | // a little algebra gets this into the more familiar sin(x)/x form 79 | taps[n + M] = (float) (sin((double) n * fwT0) / (n * M_PI) * w[n + M]); 80 | } 81 | } 82 | 83 | free(w); 84 | 85 | float fmax = taps[0 + M]; 86 | for (int n = 1; n <= M; n++) { 87 | fmax += 2 * taps[n + M]; 88 | } 89 | 90 | gain /= fmax; // normalize 91 | 92 | for (int i = 0; i < ntaps; i++) { 93 | taps[i] *= gain; 94 | } 95 | 96 | *output_taps = taps; 97 | *len = ntaps; 98 | return 0; 99 | } 100 | 101 | -------------------------------------------------------------------------------- /src/lpf.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_LPF_H_ 2 | #define SRC_LPF_H_ 3 | 4 | #include 5 | 6 | int create_low_pass_filter(float gain, uint32_t sampling_freq, uint32_t cutoff_freq, uint32_t transition_width, float **taps, size_t *len); 7 | 8 | #endif /* SRC_LPF_H_ */ 9 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "config.h" 6 | #include "tcp_server.h" 7 | 8 | static tcp_server *server = NULL; 9 | 10 | extern const char *SIMD_STATUS; 11 | 12 | void sdrserver_stop_async(int signum) { 13 | stop_tcp_server(server); 14 | server = NULL; 15 | } 16 | 17 | int main(int argc, char **argv) { 18 | if (argc < 2) { 19 | fprintf(stderr, "<3>parameter missing: configuration file\n"); 20 | exit(EXIT_FAILURE); 21 | } 22 | setvbuf(stdout, NULL, _IOLBF, 0); 23 | printf("SIMD optimization: %s\n", SIMD_STATUS); 24 | printf("compilation flags: %s\n", CMAKE_C_FLAGS); 25 | struct server_config *server_config = NULL; 26 | int code = create_server_config(&server_config, argv[1]); 27 | if (code != 0) { 28 | exit(EXIT_FAILURE); 29 | } 30 | 31 | signal(SIGINT, sdrserver_stop_async); 32 | signal(SIGHUP, sdrserver_stop_async); 33 | signal(SIGTERM, sdrserver_stop_async); 34 | 35 | code = start_tcp_server(server_config, &server); 36 | if (code != 0) { 37 | destroy_server_config(server_config); 38 | exit(EXIT_FAILURE); 39 | } 40 | 41 | // wait here until server terminates 42 | join_tcp_server_thread(server); 43 | 44 | // server will be freed on its own thread 45 | destroy_server_config(server_config); 46 | } 47 | -------------------------------------------------------------------------------- /src/queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "queue.h" 8 | 9 | struct queue_node { 10 | uint8_t *buffer; 11 | size_t len; 12 | struct queue_node *next; 13 | }; 14 | 15 | struct queue_t { 16 | struct queue_node *first_free_node; 17 | struct queue_node *last_free_node; 18 | 19 | struct queue_node *first_filled_node; 20 | struct queue_node *last_filled_node; 21 | 22 | struct queue_node *detached_node; 23 | 24 | pthread_mutex_t mutex; 25 | pthread_cond_t condition; 26 | 27 | int poison_pill; 28 | }; 29 | 30 | void destroy_nodes(struct queue_node *nodes) { 31 | struct queue_node *cur_node = nodes; 32 | while (cur_node != NULL) { 33 | struct queue_node *next = cur_node->next; 34 | if (cur_node->buffer != NULL) { 35 | free(cur_node->buffer); 36 | } 37 | free(cur_node); 38 | cur_node = next; 39 | } 40 | } 41 | 42 | int create_queue(uint32_t buffer_size, int queue_size, queue **queue) { 43 | struct queue_t *result = malloc(sizeof(struct queue_t)); 44 | if (result == NULL) { 45 | return -ENOMEM; 46 | } 47 | 48 | struct queue_node *first_node = NULL; 49 | struct queue_node *last_node = NULL; 50 | for (int i = 0; i < queue_size; i++) { 51 | struct queue_node *cur = malloc(sizeof(struct queue_node)); 52 | if (cur == NULL) { 53 | destroy_nodes(first_node); 54 | free(result); 55 | return -ENOMEM; 56 | } 57 | cur->buffer = malloc(sizeof(uint8_t) * buffer_size); 58 | cur->next = NULL; 59 | cur->len = 0; 60 | if (cur->buffer == NULL) { 61 | destroy_nodes(first_node); 62 | free(cur); 63 | free(result); 64 | return -ENOMEM; 65 | } 66 | if (last_node == NULL) { 67 | first_node = cur; 68 | } else { 69 | last_node->next = cur; 70 | } 71 | last_node = cur; 72 | } 73 | 74 | result->first_free_node = first_node; 75 | result->last_free_node = last_node; 76 | result->first_filled_node = NULL; 77 | result->last_filled_node = NULL; 78 | result->detached_node = NULL; 79 | result->condition = (pthread_cond_t) PTHREAD_COND_INITIALIZER; 80 | result->mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; 81 | result->poison_pill = 0; 82 | 83 | *queue = result; 84 | return 0; 85 | } 86 | 87 | void queue_put(const uint8_t *buffer, const size_t len, queue *queue) { 88 | pthread_mutex_lock(&queue->mutex); 89 | struct queue_node *to_fill; 90 | if (queue->first_free_node == NULL) { 91 | // queue is full 92 | // overwrite last node 93 | to_fill = queue->last_filled_node; 94 | fprintf(stderr, "<3>queue is full\n"); 95 | } else { 96 | // remove from free nodes pool 97 | to_fill = queue->first_free_node; 98 | queue->first_free_node = queue->first_free_node->next; 99 | to_fill->next = NULL; 100 | if (queue->first_free_node == NULL) { 101 | queue->last_free_node = NULL; 102 | } 103 | 104 | 105 | // add to filled nodes pool 106 | if (queue->last_filled_node == NULL) { 107 | queue->first_filled_node = to_fill; 108 | } else { 109 | queue->last_filled_node->next = to_fill; 110 | } 111 | queue->last_filled_node = to_fill; 112 | } 113 | 114 | memcpy(to_fill->buffer, buffer, sizeof(uint8_t) * len); 115 | to_fill->len = len; 116 | pthread_cond_broadcast(&queue->condition); 117 | 118 | pthread_mutex_unlock(&queue->mutex); 119 | } 120 | 121 | void destroy_queue(queue *queue) { 122 | pthread_mutex_lock(&queue->mutex); 123 | destroy_nodes(queue->first_free_node); 124 | destroy_nodes(queue->first_filled_node); 125 | if (queue->detached_node != NULL) { 126 | free(queue->detached_node->buffer); 127 | free(queue->detached_node); 128 | } 129 | pthread_mutex_unlock(&queue->mutex); 130 | free(queue); 131 | } 132 | 133 | void take_buffer_for_processing(uint8_t **buffer, size_t *len, queue *queue) { 134 | pthread_mutex_lock(&queue->mutex); 135 | if (queue->poison_pill == 1 && queue->first_filled_node == NULL) { 136 | pthread_mutex_unlock(&queue->mutex); 137 | *buffer = NULL; 138 | return; 139 | } 140 | // "while" loop is for spurious wakeups 141 | while (queue->first_filled_node == NULL) { 142 | pthread_cond_wait(&queue->condition, &queue->mutex); 143 | // destroy all queue data 144 | // and return NULL buffer 145 | if (queue->poison_pill == 1 && queue->first_filled_node == NULL) { 146 | pthread_mutex_unlock(&queue->mutex); 147 | *buffer = NULL; 148 | return; 149 | } 150 | } 151 | // the idea is to keep a node that being processed in the detached mode 152 | // i.e. input thread cannot read or write into it, while buffer is being sent to client/file (which can be slow) 153 | // it would allow non-mutex access to such buffer 154 | // and as a result small synchornization section 155 | // and as a result faster concurrency 156 | queue->detached_node = queue->first_filled_node; 157 | queue->first_filled_node = queue->first_filled_node->next; 158 | queue->detached_node->next = NULL; 159 | if (queue->first_filled_node == NULL) { 160 | queue->last_filled_node = NULL; 161 | } 162 | *buffer = queue->detached_node->buffer; 163 | *len = queue->detached_node->len; 164 | pthread_mutex_unlock(&queue->mutex); 165 | } 166 | 167 | void complete_buffer_processing(queue *queue) { 168 | pthread_mutex_lock(&queue->mutex); 169 | if (queue->last_free_node == NULL) { 170 | queue->first_free_node = queue->detached_node; 171 | } else { 172 | queue->last_free_node->next = queue->detached_node; 173 | } 174 | queue->last_free_node = queue->detached_node; 175 | queue->detached_node = NULL; 176 | pthread_mutex_unlock(&queue->mutex); 177 | } 178 | 179 | void interrupt_waiting_the_data(queue *queue) { 180 | if (queue == NULL) { 181 | return; 182 | } 183 | pthread_mutex_lock(&queue->mutex); 184 | queue->poison_pill = 1; 185 | pthread_cond_broadcast(&queue->condition); 186 | pthread_mutex_unlock(&queue->mutex); 187 | } 188 | 189 | -------------------------------------------------------------------------------- /src/queue.h: -------------------------------------------------------------------------------- 1 | #ifndef QUEUE_H_ 2 | #define QUEUE_H_ 3 | 4 | #include 5 | 6 | typedef struct queue_t queue; 7 | 8 | int create_queue(uint32_t buffer_size, int queue_size, queue **queue); 9 | 10 | void queue_put(const uint8_t *buffer, size_t buffer_len, queue *queue); 11 | void take_buffer_for_processing(uint8_t **buffer, size_t *buffer_len, queue *queue); 12 | void complete_buffer_processing(queue *queue); 13 | 14 | void interrupt_waiting_the_data(queue *queue); 15 | void destroy_queue(queue *queue); 16 | 17 | #endif /* QUEUE_H_ */ 18 | -------------------------------------------------------------------------------- /src/resources/config.conf: -------------------------------------------------------------------------------- 1 | ##### Server settings ##### 2 | # bind address for a server 3 | bind_address="127.0.0.1" 4 | 5 | # port for a server 6 | port=8090 7 | 8 | # buffer size (in bytes) for passing the data from USB 9 | # same buffer setting for passing data between threads 10 | # the bigger buffer the less context switching, but 11 | # bigger latency for RF messages 12 | # AIRSPY and HackRF have fixed buffer = 262144 13 | buffer_size=262144 14 | 15 | # number of elements in the DSP queue 16 | # the more queue, the better performance spikes handled 17 | # the less queue, the better latency and memory consumption 18 | # total memory = queue_size * buffer_size * number_of_clients 19 | queue_size=64 20 | 21 | # if client requests to save output locally, then 22 | # the base path controls the directory where it is saved 23 | # tmp directory is recommended. By default will be saved into $TMPDIR 24 | # base_path="/tmp/" 25 | 26 | # timeout for reading client's requests 27 | # in seconds 28 | # should be positive 29 | read_timeout_seconds=5 30 | 31 | # use gzip while saving data into file 32 | use_gzip=false 33 | 34 | # the bigger rate, the smaller low pass filter (LPF) cutoff frequency 35 | # - the smaller cutoff frequency the less aliasing on the sides of the stream 36 | # - the bigger cutoff frequency the faster sdr-server works 37 | # default is 5. Should be positive >= 1 38 | lpf_cutoff_rate=5 39 | 40 | # Type of SDR device. Supported types: 41 | # 0 - RTL-SDR. librtlsdr is required. Can be installed separately using the command: sudo apt install librtlsdr 42 | # 1 - AIRSPY. libarispy is required. Can be installed separately using the command: sudo apt install libairspy 43 | # 2 - HackRF. libhackrf is required. Can be installed separately using the command: sudo apt install libhackrf 44 | # Each type has it's own settings. See the following sections for fune tuning 45 | sdr_type=0 46 | 47 | # Use device serial number (SN) instead of device index 48 | # Serial number can be changed and used to uniquely identify dongles 49 | # Can be used by rtl-sdr and hackrf 50 | #device_serial="00000100" 51 | 52 | ##### Generic SDR settings ##### 53 | # clients can select the band freq, 54 | # but server controls the sample rate of the band 55 | # each client's sample rate should be a fraction of 56 | # band_sampling_rate 57 | # Note that airspy has fixed number of sample rates. Use the command "sudo airspy_info" to get them. 58 | band_sampling_rate=2016000 59 | 60 | # controls bias tee: 61 | # 0 - disabled 62 | # 1 - enabled 63 | bias_t=0 64 | 65 | ##### RTL-SDR settings ##### 66 | # controls the gain mode: 67 | # 0 - gain auto 68 | # 1 - gain manual, the gain setting should be specified 69 | gain_mode=1 70 | 71 | # gain setting for manual gain mode 72 | gain=49.0 73 | 74 | # manual ppm correction 75 | ppm=0 76 | 77 | # device index 78 | device_index=0 79 | 80 | ##### Airspy settings ##### 81 | # controls the gain mode: 82 | # 0 - auto 83 | # 1 - sensitivity 84 | # 2 - linearity 85 | # 3 - manual. Configure gain manually using airspy_vga_gain, airspy_mixer_gain and airspy_lna_gain parameters. 86 | airspy_gain_mode=3 87 | 88 | # IFVGA - variable gain amplifier for intermediate frequency. 89 | # From 0 to 15 90 | airspy_vga_gain=5 91 | 92 | # From 0 to 15 93 | airspy_mixer_gain=13 94 | 95 | # Gain of the low noise amplifier. Similar to "gain" in RTL-SDR. 96 | # From 0 to 14 97 | airspy_lna_gain=14 98 | 99 | # Predefined combination of VGA, MIXER and LNA gains to archive better linearity. 100 | # From 0 to 21 101 | airspy_linearity_gain=0 102 | 103 | # Predefined combination of VGA, MIXER and LNA gains to archive better sensitivity. 104 | # From 0 to 21 105 | airspy_sensitivity_gain=0 106 | 107 | ##### HackRF settings ##### 108 | 109 | # Enable / disable the ~11dB RF RX/TX amplifiers U13/U25 via controlling switches U9 and U14. 110 | # enable (1) or disable (0) amplifier 111 | hackrf_amp=0 112 | 113 | # Set the RF RX gain of the MAX2837 transceiver IC ("IF" gain setting) in decibels. Must be in range 0-40dB, with 8dB steps. 114 | hackrf_lna_gain=16 115 | 116 | # Set baseband RX gain of the MAX2837 transceier IC ("BB" or "VGA" gain setting) in decibels. Must be in range 0-62dB with 2dB steps. 117 | hackrf_vga_gain=16 118 | 119 | # Enable or disable the **3.3V (max 50mA)** bias-tee (antenna port power). Defaults to disabled. 120 | hackrf_bias_t=0 121 | -------------------------------------------------------------------------------- /src/sdr/airspy_device.c: -------------------------------------------------------------------------------- 1 | #include "airspy_device.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define ERROR_CHECK(x, y) \ 8 | do { \ 9 | int __err_rc = (x); \ 10 | if (__err_rc != 0) { \ 11 | fprintf(stderr, "%s: %d\n", y, __err_rc); \ 12 | if (device->dev != NULL) { \ 13 | device->lib->airspy_close(device->dev); \ 14 | device->dev = NULL; \ 15 | } \ 16 | return __err_rc; \ 17 | } \ 18 | } while (0) 19 | 20 | struct airspy_device_t { 21 | uint32_t id; 22 | struct airspy_device *dev; 23 | void (*sdr_callback)(uint8_t *buf, uint32_t len, void *ctx); 24 | void *ctx; 25 | 26 | struct server_config *server_config; 27 | airspy_lib *lib; 28 | }; 29 | 30 | int airspy_device_create(struct server_config *server_config, airspy_lib *lib, void (*sdr_callback)(uint8_t *buf, uint32_t len, void *ctx), void *ctx, void **plugin) { 31 | struct airspy_device_t *device = malloc(sizeof(struct airspy_device_t)); 32 | if (device == NULL) { 33 | return -ENOMEM; 34 | } 35 | *device = (struct airspy_device_t){0}; 36 | device->lib = lib; 37 | device->sdr_callback = sdr_callback; 38 | device->ctx = ctx; 39 | device->server_config = server_config; 40 | fprintf(stdout, "airspy device created\n"); 41 | *plugin = device; 42 | return 0; 43 | } 44 | 45 | void airspy_device_destroy(void *plugin) { 46 | if (plugin == NULL) { 47 | return; 48 | } 49 | struct airspy_device_t *device = (struct airspy_device_t *)plugin; 50 | free(device); 51 | fprintf(stdout, "airspy device destroyed\n"); 52 | } 53 | 54 | int airspy_device_callback(airspy_transfer *transfer) { 55 | struct airspy_device_t *device = (struct airspy_device_t *)transfer->ctx; 56 | device->sdr_callback(transfer->samples, transfer->sample_count * 2 * sizeof(int16_t), device->ctx); 57 | return 0; 58 | } 59 | 60 | int airspy_device_start_rx(uint32_t band_freq, void *plugin) { 61 | struct airspy_device_t *device = (struct airspy_device_t *)plugin; 62 | struct server_config *server_config = device->server_config; 63 | ERROR_CHECK(device->lib->airspy_open(&device->dev), "<3>unable to init airspy device"); 64 | ERROR_CHECK(device->lib->airspy_set_sample_type(device->dev, AIRSPY_SAMPLE_INT16_IQ), "<3>unable to set sample type int16 iq"); 65 | ERROR_CHECK(device->lib->airspy_set_samplerate(device->dev, server_config->band_sampling_rate), "<3>unable to set sample rate"); 66 | ERROR_CHECK(device->lib->airspy_set_packing(device->dev, 1), "<3>unable to set packing"); 67 | ERROR_CHECK(device->lib->airspy_set_rf_bias(device->dev, server_config->bias_t), "<3>unable to set bias_t"); 68 | switch (server_config->airspy_gain_mode) { 69 | case AIRSPY_GAIN_SENSITIVITY: { 70 | ERROR_CHECK(device->lib->airspy_set_sensitivity_gain(device->dev, server_config->airspy_sensitivity_gain), "<3>unable to set sensitivity gain"); 71 | fprintf(stdout, "sensitivity gain is configured: %d\n", server_config->airspy_sensitivity_gain); 72 | break; 73 | } 74 | case AIRSPY_GAIN_LINEARITY: { 75 | ERROR_CHECK(device->lib->airspy_set_linearity_gain(device->dev, server_config->airspy_linearity_gain), "<3>unable to set linearity gain"); 76 | fprintf(stdout, "linearity gain is configured: %d\n", server_config->airspy_linearity_gain); 77 | break; 78 | } 79 | case AIRSPY_GAIN_AUTO: { 80 | ERROR_CHECK(device->lib->airspy_set_lna_agc(device->dev, 1), "<3>unable to set lna agc"); 81 | ERROR_CHECK(device->lib->airspy_set_mixer_agc(device->dev, 1), "<3>unable to set mixer agc"); 82 | fprintf(stdout, "auto gain is configured\n"); 83 | break; 84 | } 85 | case AIRSPY_GAIN_MANUAL: { 86 | ERROR_CHECK(device->lib->airspy_set_vga_gain(device->dev, server_config->airspy_vga_gain), "<3>unable to set vga gain"); 87 | ERROR_CHECK(device->lib->airspy_set_mixer_gain(device->dev, server_config->airspy_mixer_gain), "<3>unable to set mixer gain"); 88 | ERROR_CHECK(device->lib->airspy_set_lna_gain(device->dev, server_config->airspy_lna_gain), "<3>unable to set lna gain"); 89 | fprintf(stdout, "manual gain is configured: %d %d %d\n", server_config->airspy_vga_gain, server_config->airspy_mixer_gain, server_config->airspy_lna_gain); 90 | break; 91 | } 92 | default: { 93 | fprintf(stderr, "unknown airspy gain mode: %d\n", server_config->airspy_gain_mode); 94 | airspy_device_destroy(device); 95 | return 1; 96 | } 97 | } 98 | ERROR_CHECK(device->lib->airspy_set_freq(device->dev, band_freq), "<3>unable to set freq"); 99 | return device->lib->airspy_start_rx(device->dev, airspy_device_callback, device); 100 | } 101 | 102 | void airspy_device_stop_rx(void *plugin) { 103 | struct airspy_device_t *device = (struct airspy_device_t *)plugin; 104 | if (device->dev != NULL) { 105 | device->lib->airspy_stop_rx(device->dev); 106 | device->lib->airspy_close(device->dev); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/sdr/airspy_device.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_AIRSPY_DEVICE_H 2 | #define SDR_SERVER_AIRSPY_DEVICE_H 3 | 4 | #include "../config.h" 5 | #include "airspy_lib.h" 6 | 7 | int airspy_device_create(struct server_config *server_config, airspy_lib *lib, void (*sdr_callback)(uint8_t *buf, uint32_t len, void *ctx), void *ctx, void **plugin); 8 | 9 | void airspy_device_destroy(void *plugin); 10 | 11 | int airspy_device_start_rx(uint32_t band_freq, void *plugin); 12 | 13 | void airspy_device_stop_rx(void *plugin); 14 | 15 | #endif //SDR_SERVER_AIRSPY_DEVICE_H 16 | -------------------------------------------------------------------------------- /src/sdr/airspy_lib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "airspy_lib.h" 7 | 8 | #define SETUP_FUNCTION(result, name) \ 9 | do { \ 10 | result->name = airspy_lib_dlsym(result->handle, #name); \ 11 | if (result->name == NULL) { \ 12 | airspy_lib_destroy(result); \ 13 | return -1; \ 14 | } \ 15 | } while (0) 16 | 17 | void *airspy_lib_dlsym(void *handle, const char *symbol) { 18 | void *result = dlsym(handle, symbol); 19 | if (result == NULL) { 20 | fprintf(stderr, "unable to load function %s: %s\n", symbol, dlerror()); 21 | } 22 | return result; 23 | } 24 | 25 | int airspy_lib_create(airspy_lib **lib) { 26 | struct airspy_lib_t *result = malloc(sizeof(struct airspy_lib_t)); 27 | if (result == NULL) { 28 | return -ENOMEM; 29 | } 30 | *result = (struct airspy_lib_t) {0}; 31 | 32 | #if defined(__APPLE__) 33 | result->handle = dlopen("libairspy.dylib", RTLD_LAZY); 34 | #else 35 | result->handle = dlopen("libairspy.so", RTLD_LAZY); 36 | #endif 37 | if (!result->handle) { 38 | fprintf(stderr, "<3>unable to load arispy lib: %s\n", dlerror()); 39 | airspy_lib_destroy(result); 40 | return -1; 41 | } 42 | SETUP_FUNCTION(result, airspy_lib_version); 43 | SETUP_FUNCTION(result, airspy_open); 44 | SETUP_FUNCTION(result, airspy_set_sample_type); 45 | SETUP_FUNCTION(result, airspy_close); 46 | SETUP_FUNCTION(result, airspy_set_lna_agc); 47 | SETUP_FUNCTION(result, airspy_set_samplerate); 48 | SETUP_FUNCTION(result, airspy_set_packing); 49 | SETUP_FUNCTION(result, airspy_set_rf_bias); 50 | SETUP_FUNCTION(result, airspy_set_lna_gain); 51 | SETUP_FUNCTION(result, airspy_set_mixer_agc); 52 | SETUP_FUNCTION(result, airspy_set_vga_gain); 53 | SETUP_FUNCTION(result, airspy_set_mixer_gain); 54 | SETUP_FUNCTION(result, airspy_set_linearity_gain); 55 | SETUP_FUNCTION(result, airspy_set_sensitivity_gain); 56 | SETUP_FUNCTION(result, airspy_start_rx); 57 | SETUP_FUNCTION(result, airspy_set_freq); 58 | SETUP_FUNCTION(result, airspy_stop_rx); 59 | 60 | airspy_lib_version_t version; 61 | result->airspy_lib_version(&version); 62 | fprintf(stdout, "airspy library initialized: %"PRIu32".%"PRIu32".%"PRIu32"\n", version.major_version, version.minor_version, version.revision); 63 | 64 | *lib = result; 65 | return 0; 66 | } 67 | 68 | void airspy_lib_destroy(airspy_lib *lib) { 69 | if (lib->handle != NULL) { 70 | dlclose(lib->handle); 71 | } 72 | free(lib); 73 | } 74 | -------------------------------------------------------------------------------- /src/sdr/airspy_lib.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_AIRSPY_LIB_H 2 | #define SDR_SERVER_AIRSPY_LIB_H 3 | 4 | #include 5 | 6 | typedef struct airspy_lib_t airspy_lib; 7 | 8 | struct airspy_lib_t { 9 | void *handle; 10 | 11 | int (*airspy_open)(struct airspy_device **device); 12 | 13 | int (*airspy_set_sample_type)(struct airspy_device *device, enum airspy_sample_type sample_type); 14 | 15 | int (*airspy_close)(struct airspy_device *device); 16 | 17 | int (*airspy_set_lna_agc)(struct airspy_device *device, uint8_t value); 18 | 19 | int (*airspy_set_mixer_agc)(struct airspy_device *device, uint8_t value); 20 | 21 | int (*airspy_set_samplerate)(struct airspy_device *device, uint32_t samplerate); 22 | 23 | int (*airspy_set_packing)(struct airspy_device *device, uint8_t value); 24 | 25 | int (*airspy_set_rf_bias)(struct airspy_device *dev, uint8_t value); 26 | 27 | int (*airspy_set_lna_gain)(struct airspy_device *device, uint8_t value); 28 | 29 | int (*airspy_set_vga_gain)(struct airspy_device *device, uint8_t value); 30 | 31 | int (*airspy_set_mixer_gain)(struct airspy_device *device, uint8_t value); 32 | 33 | int (*airspy_set_linearity_gain)(struct airspy_device *device, uint8_t value); 34 | 35 | int (*airspy_set_sensitivity_gain)(struct airspy_device *device, uint8_t value); 36 | 37 | int (*airspy_start_rx)(struct airspy_device *device, airspy_sample_block_cb_fn callback, void *rx_ctx); 38 | 39 | int (*airspy_set_freq)(struct airspy_device *device, const uint32_t freq_hz); 40 | 41 | int (*airspy_stop_rx)(struct airspy_device *device); 42 | 43 | void (*airspy_lib_version)(airspy_lib_version_t *lib_version); 44 | }; 45 | 46 | int airspy_lib_create(airspy_lib **lib); 47 | 48 | void airspy_lib_destroy(airspy_lib *lib); 49 | 50 | #endif //SDR_SERVER_AIRSPY_LIB_H 51 | -------------------------------------------------------------------------------- /src/sdr/hackrf_device.c: -------------------------------------------------------------------------------- 1 | #include "hackrf_device.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define ERROR_CHECK(x, y) \ 8 | do { \ 9 | int __err_rc = (x); \ 10 | if (__err_rc != HACKRF_SUCCESS) { \ 11 | fprintf(stderr, "%s: %s (%d)\n", y, lib->hackrf_error_name(__err_rc), __err_rc); \ 12 | hackrf_device_stop_rx(wrapper); \ 13 | return __err_rc; \ 14 | } \ 15 | } while (0) 16 | 17 | typedef struct { 18 | uint32_t id; 19 | hackrf_device *dev; 20 | void (*sdr_callback)(uint8_t *buf, uint32_t len, void *ctx); 21 | void *ctx; 22 | 23 | struct server_config *server_config; 24 | hackrf_lib *lib; 25 | } hackrf_wrapper; 26 | 27 | int hackrf_device_create(struct server_config *server_config, hackrf_lib *lib, void (*sdr_callback)(uint8_t *buf, uint32_t len, void *ctx), void *ctx, void **plugin) { 28 | int code = lib->hackrf_init(); 29 | if (code != 0) { 30 | fprintf(stderr, "<3>unable to initialize hackrf library: %s (%d)\n", lib->hackrf_error_name(code), code); 31 | return code; 32 | } 33 | hackrf_wrapper *wrapper = malloc(sizeof(hackrf_wrapper)); 34 | if (wrapper == NULL) { 35 | return -ENOMEM; 36 | } 37 | *wrapper = (hackrf_wrapper){0}; 38 | wrapper->lib = lib; 39 | wrapper->sdr_callback = sdr_callback; 40 | wrapper->ctx = ctx; 41 | wrapper->server_config = server_config; 42 | fprintf(stdout, "hackrf device created\n"); 43 | *plugin = wrapper; 44 | return 0; 45 | } 46 | 47 | void hackrf_device_destroy(void *plugin) { 48 | if (plugin == NULL) { 49 | return; 50 | } 51 | hackrf_wrapper *device = (hackrf_wrapper *)plugin; 52 | if (device->lib != NULL) { 53 | device->lib->hackrf_exit(); 54 | } 55 | free(device); 56 | fprintf(stdout, "hackrf device destroyed\n"); 57 | } 58 | 59 | static int hackrf_callback(hackrf_transfer *transfer) { 60 | hackrf_wrapper *device = (hackrf_wrapper *)transfer->rx_ctx; 61 | device->sdr_callback(transfer->buffer, transfer->buffer_length, device->ctx); 62 | return 0; 63 | } 64 | 65 | int hackrf_device_start_rx(uint32_t band_freq, void *plugin) { 66 | hackrf_wrapper *wrapper = (hackrf_wrapper *)plugin; 67 | hackrf_lib *lib = wrapper->lib; 68 | if (wrapper->server_config->device_serial != NULL) { 69 | ERROR_CHECK(lib->hackrf_open_by_serial(wrapper->server_config->device_serial, &wrapper->dev), "<3>unable to open device by serial number"); 70 | } else { 71 | ERROR_CHECK(lib->hackrf_open(&wrapper->dev), "<3>unable to open device"); 72 | } 73 | char version[255 + 1]; 74 | ERROR_CHECK(lib->hackrf_version_string_read(wrapper->dev, &version[0], 255), "<3>unable to get version string"); 75 | uint16_t usb_version; 76 | ERROR_CHECK(lib->hackrf_usb_api_version_read(wrapper->dev, &usb_version), "<3>unable to read usb api version"); 77 | // similar to hackrf_info 78 | printf("Firmware Version: %s (API:%x.%02x)\n", version, (usb_version >> 8) & 0xFF, usb_version & 0xFF); 79 | ERROR_CHECK(lib->hackrf_set_freq(wrapper->dev, band_freq), "<3>unable to setup frequency"); 80 | ERROR_CHECK(lib->hackrf_set_sample_rate(wrapper->dev, wrapper->server_config->band_sampling_rate), "<3>unable to setup sample rate"); 81 | // should be the same as sampling rate otherwise clients might get nothing when tuned to >75% range 82 | ERROR_CHECK(lib->hackrf_set_baseband_filter_bandwidth(wrapper->dev, wrapper->server_config->band_sampling_rate), "<3>unable to setup filter bandwidth"); 83 | ERROR_CHECK(lib->hackrf_set_amp_enable(wrapper->dev, wrapper->server_config->hackrf_amp), "<3>unable to enable amplifier"); 84 | ERROR_CHECK(lib->hackrf_set_lna_gain(wrapper->dev, wrapper->server_config->hackrf_lna_gain), "<3>unable to setup lna gain"); 85 | ERROR_CHECK(lib->hackrf_set_vga_gain(wrapper->dev, wrapper->server_config->hackrf_vga_gain), "<3>unable to setup vga gain"); 86 | ERROR_CHECK(lib->hackrf_set_antenna_enable(wrapper->dev, wrapper->server_config->hackrf_bias_t), "<3>unable to setup bias-t"); 87 | ERROR_CHECK(lib->hackrf_start_rx(wrapper->dev, hackrf_callback, wrapper), "<3>unable to start rx"); 88 | return 0; 89 | } 90 | 91 | void hackrf_device_stop_rx(void *plugin) { 92 | hackrf_wrapper *wrapper = (hackrf_wrapper *)plugin; 93 | if (wrapper->dev != NULL) { 94 | wrapper->lib->hackrf_stop_rx(wrapper->dev); 95 | wrapper->lib->hackrf_close(wrapper->dev); 96 | wrapper->dev = NULL; 97 | } 98 | } -------------------------------------------------------------------------------- /src/sdr/hackrf_device.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_HACKRF_DEVICE_H 2 | #define SDR_SERVER_HACKRF_DEVICE_H 3 | 4 | #include "../config.h" 5 | #include "hackrf_lib.h" 6 | 7 | int hackrf_device_create(struct server_config *server_config, hackrf_lib *lib, void (*sdr_callback)(uint8_t *buf, uint32_t len, void *ctx), void *ctx, void **plugin); 8 | 9 | void hackrf_device_destroy(void *plugin); 10 | 11 | int hackrf_device_start_rx(uint32_t band_freq, void *plugin); 12 | 13 | void hackrf_device_stop_rx(void *plugin); 14 | 15 | #endif -------------------------------------------------------------------------------- /src/sdr/hackrf_lib.c: -------------------------------------------------------------------------------- 1 | #include "hackrf_lib.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define SETUP_FUNCTION(result, name) \ 9 | do { \ 10 | result->name = hackrf_lib_dlsym(result->handle, #name); \ 11 | if (result->name == NULL) { \ 12 | hackrf_lib_destroy(result); \ 13 | return -1; \ 14 | } \ 15 | } while (0) 16 | 17 | void *hackrf_lib_dlsym(void *handle, const char *symbol) { 18 | void *result = dlsym(handle, symbol); 19 | if (result == NULL) { 20 | fprintf(stderr, "unable to load function %s: %s\n", symbol, dlerror()); 21 | } 22 | return result; 23 | } 24 | 25 | int hackrf_lib_create(hackrf_lib **lib) { 26 | struct hackrf_lib_t *result = malloc(sizeof(struct hackrf_lib_t)); 27 | if (result == NULL) { 28 | return -ENOMEM; 29 | } 30 | *result = (struct hackrf_lib_t){0}; 31 | #if defined(__APPLE__) 32 | result->handle = dlopen("libhackrf.dylib", RTLD_LAZY); 33 | #else 34 | result->handle = dlopen("libhackrf.so", RTLD_LAZY); 35 | #endif 36 | if (!result->handle) { 37 | fprintf(stderr, "<3>unable to load hackrf lib: %s\n", dlerror()); 38 | hackrf_lib_destroy(result); 39 | return -1; 40 | } 41 | 42 | SETUP_FUNCTION(result, hackrf_init); 43 | SETUP_FUNCTION(result, hackrf_error_name); 44 | SETUP_FUNCTION(result, hackrf_version_string_read); 45 | SETUP_FUNCTION(result, hackrf_usb_api_version_read); 46 | SETUP_FUNCTION(result, hackrf_set_vga_gain); 47 | SETUP_FUNCTION(result, hackrf_set_lna_gain); 48 | SETUP_FUNCTION(result, hackrf_set_amp_enable); 49 | SETUP_FUNCTION(result, hackrf_set_freq); 50 | SETUP_FUNCTION(result, hackrf_set_baseband_filter_bandwidth); 51 | SETUP_FUNCTION(result, hackrf_set_sample_rate); 52 | SETUP_FUNCTION(result, hackrf_start_rx); 53 | SETUP_FUNCTION(result, hackrf_stop_rx); 54 | SETUP_FUNCTION(result, hackrf_close); 55 | SETUP_FUNCTION(result, hackrf_exit); 56 | SETUP_FUNCTION(result, hackrf_open); 57 | SETUP_FUNCTION(result, hackrf_open_by_serial); 58 | SETUP_FUNCTION(result, hackrf_set_antenna_enable); 59 | 60 | *lib = result; 61 | return 0; 62 | } 63 | 64 | void hackrf_lib_destroy(hackrf_lib *lib) { 65 | if (lib == NULL) { 66 | return; 67 | } 68 | if (lib->handle != NULL) { 69 | dlclose(lib->handle); 70 | } 71 | free(lib); 72 | } -------------------------------------------------------------------------------- /src/sdr/hackrf_lib.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_HACKRF_LIB_H 2 | #define SDR_SERVER_HACKRF_LIB_H 3 | 4 | #include 5 | 6 | typedef struct hackrf_lib_t hackrf_lib; 7 | 8 | struct hackrf_lib_t { 9 | void* handle; 10 | 11 | int (*hackrf_init)(); 12 | const char* (*hackrf_error_name)(enum hackrf_error errcode); 13 | int (*hackrf_version_string_read)(hackrf_device* device, char* version, uint8_t length); 14 | int (*hackrf_usb_api_version_read)(hackrf_device* device, uint16_t* version); 15 | int (*hackrf_set_vga_gain)(hackrf_device* device, uint32_t value); 16 | int (*hackrf_set_lna_gain)(hackrf_device* device, uint32_t value); 17 | int (*hackrf_set_amp_enable)(hackrf_device* device, const uint8_t value); 18 | int (*hackrf_set_freq)(hackrf_device* device, const uint64_t freq_hz); 19 | int (*hackrf_set_baseband_filter_bandwidth)(hackrf_device* device, const uint32_t bandwidth_hz); 20 | int (*hackrf_set_sample_rate)(hackrf_device* device, const double freq_hz); 21 | int (*hackrf_start_rx)(hackrf_device* device, hackrf_sample_block_cb_fn callback, void* rx_ctx); 22 | int (*hackrf_stop_rx)(hackrf_device* device); 23 | int (*hackrf_close)(hackrf_device* device); 24 | int (*hackrf_exit)(); 25 | int (*hackrf_open)(hackrf_device** device); 26 | int (*hackrf_open_by_serial)(const char* const desired_serial_number, hackrf_device** device); 27 | int (*hackrf_set_antenna_enable)(hackrf_device* device, const uint8_t value); 28 | }; 29 | 30 | int hackrf_lib_create(hackrf_lib** lib); 31 | 32 | void hackrf_lib_destroy(hackrf_lib* lib); 33 | 34 | #endif -------------------------------------------------------------------------------- /src/sdr/rtlsdr_device.c: -------------------------------------------------------------------------------- 1 | #include "rtlsdr_device.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct rtlsdr_device_t { 11 | rtlsdr_dev_t *dev; 12 | pthread_t rtlsdr_device_thread; 13 | atomic_bool running; 14 | struct server_config *server_config; 15 | 16 | uint8_t *output; 17 | size_t output_len; 18 | 19 | void (*rtlsdr_callback)(uint8_t *buf, uint32_t len, void *ctx); 20 | void *ctx; 21 | 22 | rtlsdr_lib *lib; 23 | }; 24 | 25 | #define ERROR_CHECK(x, y) \ 26 | do { \ 27 | int __err_rc = (x); \ 28 | if (__err_rc != 0) { \ 29 | fprintf(stderr, "%s: %d\n", y, __err_rc); \ 30 | device->lib->rtlsdr_close(device->dev); \ 31 | device->dev = NULL; \ 32 | return __err_rc; \ 33 | } \ 34 | } while (0) 35 | 36 | int find_nearest_gain(struct rtlsdr_device_t *dev, int target_gain, int *nearest) { 37 | int count = dev->lib->rtlsdr_get_tuner_gains(dev->dev, NULL); 38 | if (count <= 0) { 39 | return -1; 40 | } 41 | int *gains = malloc(sizeof(int) * count); 42 | if (gains == NULL) { 43 | return -ENOMEM; 44 | } 45 | int code = dev->lib->rtlsdr_get_tuner_gains(dev->dev, gains); 46 | if (code <= 0) { 47 | free(gains); 48 | return -1; 49 | } 50 | *nearest = gains[0]; 51 | for (int i = 0; i < count; i++) { 52 | int err1 = abs(target_gain - *nearest); 53 | int err2 = abs(target_gain - gains[i]); 54 | if (err2 < err1) { 55 | *nearest = gains[i]; 56 | } 57 | } 58 | free(gains); 59 | return 0; 60 | } 61 | 62 | int rtlsdr_device_create(struct server_config *server_config, rtlsdr_lib *lib, void (*rtlsdr_callback)(uint8_t *buf, uint32_t len, void *ctx), void *ctx, void **plugin) { 63 | struct rtlsdr_device_t *device = malloc(sizeof(struct rtlsdr_device_t)); 64 | if (device == NULL) { 65 | return -ENOMEM; 66 | } 67 | *device = (struct rtlsdr_device_t){0}; 68 | device->lib = lib; 69 | device->rtlsdr_callback = rtlsdr_callback; 70 | device->ctx = ctx; 71 | device->output_len = server_config->buffer_size; 72 | device->output = malloc(server_config->buffer_size * sizeof(uint8_t)); 73 | if (device->output == NULL) { 74 | rtlsdr_device_destroy(device); 75 | return -ENOMEM; 76 | } 77 | memset(device->output, 0, server_config->buffer_size); 78 | device->server_config = server_config; 79 | fprintf(stdout, "rtl-sdr device created\n"); 80 | *plugin = device; 81 | return 0; 82 | } 83 | 84 | static void *rtlsdr_callback(void *arg) { 85 | struct rtlsdr_device_t *device = (struct rtlsdr_device_t *)arg; 86 | device->running = true; 87 | int n_read = 0; 88 | while (device->running) { 89 | int code = device->lib->rtlsdr_read_sync(device->dev, device->output, device->output_len, &n_read); 90 | if (code != 0) { 91 | break; 92 | } 93 | device->rtlsdr_callback(device->output, n_read, device->ctx); 94 | } 95 | return (void *)0; 96 | } 97 | 98 | int rtlsdr_device_start_rx(uint32_t band_freq, void *plugin) { 99 | struct rtlsdr_device_t *device = (struct rtlsdr_device_t *)plugin; 100 | rtlsdr_lib *lib = device->lib; 101 | struct server_config *server_config = device->server_config; 102 | int device_index = -1; 103 | if (server_config->device_serial != NULL) { 104 | device_index = lib->rtlsdr_get_index_by_serial(server_config->device_serial); 105 | if (device_index < 0) { 106 | fprintf(stdout, "can't find device by serial: %s. fallback to device index\n", server_config->device_serial); 107 | } 108 | } 109 | if (device_index < 0) { 110 | device_index = server_config->device_index; 111 | } 112 | ERROR_CHECK(lib->rtlsdr_open(&device->dev, device_index), "<3>unable to open device"); 113 | ERROR_CHECK(lib->rtlsdr_set_sample_rate(device->dev, server_config->band_sampling_rate), "<3>unable to set sample rate"); 114 | ERROR_CHECK(lib->rtlsdr_set_tuner_gain_mode(device->dev, server_config->gain_mode), "<3>unable to set gain mode"); 115 | if (server_config->ppm != 0) { 116 | ERROR_CHECK(lib->rtlsdr_set_freq_correction(device->dev, server_config->ppm), "<3>unable to set freq correction"); 117 | } 118 | if (server_config->gain_mode == 1) { 119 | int nearest_gain = 0; 120 | ERROR_CHECK(find_nearest_gain(device, server_config->gain, &nearest_gain), "<3>unable to find nearest gain"); 121 | if (nearest_gain != server_config->gain) { 122 | fprintf(stdout, "the actual nearest supported gain is: %f\n", (float)nearest_gain / 10); 123 | } 124 | ERROR_CHECK(lib->rtlsdr_set_tuner_gain(device->dev, nearest_gain), "<3>unable to set gain"); 125 | } 126 | ERROR_CHECK(lib->rtlsdr_set_bias_tee(device->dev, server_config->bias_t), "<3>unable to set bias tee"); 127 | ERROR_CHECK(lib->rtlsdr_reset_buffer(device->dev), "<3>unable to reset buffers"); 128 | ERROR_CHECK(lib->rtlsdr_set_center_freq(device->dev, band_freq), "<3>unable to set freq"); 129 | int code = pthread_create(&device->rtlsdr_device_thread, NULL, &rtlsdr_callback, device); 130 | if (code != 0) { 131 | return 0x04; 132 | } 133 | return 0; 134 | } 135 | 136 | void rtlsdr_device_stop_rx(void *plugin) { 137 | if (plugin == NULL) { 138 | return; 139 | } 140 | struct rtlsdr_device_t *device = (struct rtlsdr_device_t *)plugin; 141 | device->running = false; 142 | device->lib->rtlsdr_close(device->dev); 143 | device->dev = NULL; 144 | pthread_join(device->rtlsdr_device_thread, NULL); 145 | } 146 | 147 | void rtlsdr_device_destroy(void *plugin) { 148 | if (plugin == NULL) { 149 | return; 150 | } 151 | struct rtlsdr_device_t *device = (struct rtlsdr_device_t *)plugin; 152 | if (device->output != NULL) { 153 | free(device->output); 154 | } 155 | free(device); 156 | fprintf(stdout, "rtl-sdr device destroyed\n"); 157 | } 158 | -------------------------------------------------------------------------------- /src/sdr/rtlsdr_device.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_RTLSDR_DEVICE_H 2 | #define SDR_SERVER_RTLSDR_DEVICE_H 3 | 4 | #include "rtlsdr_lib.h" 5 | #include "../config.h" 6 | 7 | int rtlsdr_device_create(struct server_config *server_config, rtlsdr_lib *lib, void (*sdr_callback)(uint8_t *buf, uint32_t len, void *ctx), void *ctx, void **plugin); 8 | 9 | void rtlsdr_device_destroy(void *plugin); 10 | 11 | int rtlsdr_device_start_rx(uint32_t band_freq, void *plugin); 12 | 13 | void rtlsdr_device_stop_rx(void *plugin); 14 | 15 | #endif //SDR_SERVER_RTLSDR_DEVICE_H 16 | -------------------------------------------------------------------------------- /src/sdr/rtlsdr_lib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "rtlsdr_lib.h" 7 | 8 | #define SETUP_FUNCTION(result, name) \ 9 | do { \ 10 | result->name = rtlsdr_lib_dlsym(result->handle, #name); \ 11 | if (result->name == NULL) { \ 12 | rtlsdr_lib_destroy(result); \ 13 | return -1; \ 14 | } \ 15 | } while (0) 16 | 17 | void *rtlsdr_lib_dlsym(void *handle, const char *symbol) { 18 | void *result = dlsym(handle, symbol); 19 | if (result == NULL) { 20 | fprintf(stderr, "unable to load function %s: %s\n", symbol, dlerror()); 21 | } 22 | return result; 23 | } 24 | 25 | int rtlsdr_lib_create(rtlsdr_lib **lib) { 26 | struct rtlsdr_lib_t *result = malloc(sizeof(struct rtlsdr_lib_t)); 27 | if (result == NULL) { 28 | return -ENOMEM; 29 | } 30 | *result = (struct rtlsdr_lib_t) {0}; 31 | 32 | #if defined(__APPLE__) 33 | result->handle = dlopen("librtlsdr.dylib", RTLD_LAZY); 34 | #else 35 | result->handle = dlopen("librtlsdr.so", RTLD_LAZY); 36 | #endif 37 | if (!result->handle) { 38 | fprintf(stderr, "unable to load librtlsdr: %s\n", dlerror()); 39 | rtlsdr_lib_destroy(result); 40 | return -1; 41 | } 42 | SETUP_FUNCTION(result, rtlsdr_open); 43 | SETUP_FUNCTION(result, rtlsdr_close); 44 | SETUP_FUNCTION(result, rtlsdr_set_sample_rate); 45 | SETUP_FUNCTION(result, rtlsdr_set_center_freq); 46 | SETUP_FUNCTION(result, rtlsdr_set_tuner_gain_mode); 47 | SETUP_FUNCTION(result, rtlsdr_get_tuner_gains); 48 | SETUP_FUNCTION(result, rtlsdr_set_tuner_gain); 49 | SETUP_FUNCTION(result, rtlsdr_set_bias_tee); 50 | SETUP_FUNCTION(result, rtlsdr_reset_buffer); 51 | SETUP_FUNCTION(result, rtlsdr_read_sync); 52 | SETUP_FUNCTION(result, rtlsdr_set_freq_correction); 53 | SETUP_FUNCTION(result, rtlsdr_get_index_by_serial); 54 | *lib = result; 55 | return 0; 56 | } 57 | 58 | void rtlsdr_lib_destroy(rtlsdr_lib *lib) { 59 | if (lib->handle != NULL) { 60 | dlclose(lib->handle); 61 | } 62 | free(lib); 63 | } 64 | -------------------------------------------------------------------------------- /src/sdr/rtlsdr_lib.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_RTLSDR_LIB_H 2 | #define SDR_SERVER_RTLSDR_LIB_H 3 | 4 | #include 5 | 6 | typedef struct rtlsdr_lib_t rtlsdr_lib; 7 | 8 | struct rtlsdr_lib_t { 9 | void *handle; 10 | 11 | int (*rtlsdr_open)(rtlsdr_dev_t **dev, uint32_t index); 12 | 13 | int (*rtlsdr_close)(rtlsdr_dev_t *dev); 14 | 15 | int (*rtlsdr_set_sample_rate)(rtlsdr_dev_t *dev, uint32_t rate); 16 | 17 | int (*rtlsdr_set_center_freq)(rtlsdr_dev_t *dev, uint32_t freq); 18 | 19 | int (*rtlsdr_set_tuner_gain_mode)(rtlsdr_dev_t *dev, int manual); 20 | 21 | int (*rtlsdr_get_tuner_gains)(rtlsdr_dev_t *dev, int *gains); 22 | 23 | int (*rtlsdr_set_tuner_gain)(rtlsdr_dev_t *dev, int gain); 24 | 25 | int (*rtlsdr_set_bias_tee)(rtlsdr_dev_t *dev, int on); 26 | 27 | int (*rtlsdr_reset_buffer)(rtlsdr_dev_t *dev); 28 | 29 | int (*rtlsdr_read_sync)(rtlsdr_dev_t *dev, void *buf, int len, int *n_read); 30 | 31 | int (*rtlsdr_set_freq_correction)(rtlsdr_dev_t *dev, int ppm); 32 | 33 | int (*rtlsdr_get_index_by_serial)(const char *serial); 34 | }; 35 | 36 | int rtlsdr_lib_create(rtlsdr_lib **lib); 37 | 38 | void rtlsdr_lib_destroy(rtlsdr_lib *lib); 39 | 40 | #endif //SDR_SERVER_RTLSDR_LIB_H 41 | -------------------------------------------------------------------------------- /src/sdr_device.c: -------------------------------------------------------------------------------- 1 | #include "sdr_device.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "sdr/airspy_device.h" 13 | #include "sdr/hackrf_device.h" 14 | #include "sdr/rtlsdr_device.h" 15 | 16 | struct sdr_device_t { 17 | struct server_config *server_config; 18 | 19 | rtlsdr_lib *rtllib; 20 | airspy_lib *airspy; 21 | hackrf_lib *hackrf; 22 | void (*sdr_callback)(uint8_t *buf, uint32_t buf_len, void *ctx); 23 | void *ctx; 24 | 25 | void *plugin; 26 | void (*destroy)(void *plugin); 27 | int (*start_rx)(uint32_t band_freq, void *plugin); 28 | void (*stop_rx)(void *plugin); 29 | }; 30 | 31 | int sdr_device_create(void (*sdr_callback)(uint8_t *buf, uint32_t buf_len, void *ctx), void *ctx, struct server_config *server_config, sdr_device **device) { 32 | struct sdr_device_t *result = malloc(sizeof(struct sdr_device_t)); 33 | if (result == NULL) { 34 | return -ENOMEM; 35 | } 36 | // init all fields with 0 so that destroy_* method would work 37 | *result = (struct sdr_device_t){0}; 38 | result->server_config = server_config; 39 | result->sdr_callback = sdr_callback; 40 | result->ctx = ctx; 41 | int code = -1; 42 | switch (server_config->sdr_type) { 43 | case SDR_TYPE_RTL: { 44 | code = rtlsdr_lib_create(&result->rtllib); 45 | result->destroy = rtlsdr_device_destroy; 46 | result->start_rx = rtlsdr_device_start_rx; 47 | result->stop_rx = rtlsdr_device_stop_rx; 48 | break; 49 | } 50 | case SDR_TYPE_AIRSPY: { 51 | code = airspy_lib_create(&result->airspy); 52 | result->destroy = airspy_device_destroy; 53 | result->start_rx = airspy_device_start_rx; 54 | result->stop_rx = airspy_device_stop_rx; 55 | break; 56 | } 57 | case SDR_TYPE_HACKRF: { 58 | code = hackrf_lib_create(&result->hackrf); 59 | result->destroy = hackrf_device_destroy; 60 | result->start_rx = hackrf_device_start_rx; 61 | result->stop_rx = hackrf_device_stop_rx; 62 | break; 63 | } 64 | default: { 65 | fprintf(stderr, "<3>unsupported sdr type: %d\n", server_config->sdr_type); 66 | code = -1; 67 | break; 68 | } 69 | } 70 | if (code != 0) { 71 | sdr_device_destroy(result); 72 | return code; 73 | } 74 | *device = result; 75 | return 0; 76 | } 77 | 78 | int sdr_device_start(client_config *config, sdr_device *device) { 79 | if (device->plugin == NULL) { 80 | int code = -1; 81 | switch (config->sdr_type) { 82 | case SDR_TYPE_RTL: { 83 | code = rtlsdr_device_create(device->server_config, device->rtllib, device->sdr_callback, device->ctx, &device->plugin); 84 | break; 85 | } 86 | case SDR_TYPE_AIRSPY: { 87 | code = airspy_device_create(device->server_config, device->airspy, device->sdr_callback, device->ctx, &device->plugin); 88 | break; 89 | } 90 | case SDR_TYPE_HACKRF: { 91 | code = hackrf_device_create(device->server_config, device->hackrf, device->sdr_callback, device->ctx, &device->plugin); 92 | break; 93 | } 94 | default: { 95 | fprintf(stderr, "<3>unsupported sdr type: %d\n", config->sdr_type); 96 | code = -1; 97 | break; 98 | } 99 | } 100 | if (code != 0) { 101 | fprintf(stderr, "<3>unable to create device\n"); 102 | return 0x04; 103 | } 104 | } 105 | int code = device->start_rx(config->band_freq, device->plugin); 106 | if (code != 0) { 107 | fprintf(stderr, "<3>unable to start rx\n"); 108 | return 0x04; 109 | } 110 | // reset internal state 111 | fprintf(stdout, "sdr created\n"); 112 | return 0; 113 | } 114 | 115 | void sdr_device_stop(sdr_device *device) { 116 | // synchronous wait until all threads shutdown 117 | device->stop_rx(device->plugin); 118 | } 119 | 120 | void sdr_device_destroy(sdr_device *device) { 121 | if (device == NULL) { 122 | return; 123 | } 124 | if (device->plugin != NULL) { 125 | device->destroy(device->plugin); 126 | } 127 | if (device->rtllib != NULL) { 128 | rtlsdr_lib_destroy(device->rtllib); 129 | } 130 | if (device->airspy != NULL) { 131 | airspy_lib_destroy(device->airspy); 132 | } 133 | if (device->hackrf != NULL) { 134 | hackrf_lib_destroy(device->hackrf); 135 | } 136 | fprintf(stdout, "sdr destroyed\n"); 137 | free(device); 138 | } 139 | -------------------------------------------------------------------------------- /src/sdr_device.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_DEVICE_H_ 2 | #define SDR_DEVICE_H_ 3 | 4 | #include "config.h" 5 | #include "dsp_worker.h" 6 | 7 | typedef struct sdr_device_t sdr_device; 8 | 9 | int sdr_device_create(void (*sdr_callback)(uint8_t *buf, uint32_t buf_len, void *ctx), void *ctx, struct server_config *server_config, sdr_device **result); 10 | 11 | int sdr_device_start(client_config *config, sdr_device *sdr_device); 12 | 13 | void sdr_device_stop(sdr_device *sdr_device); 14 | 15 | void sdr_device_destroy(sdr_device *sdr_device); 16 | 17 | #endif /* SDR_DEVICE_H_ */ 18 | -------------------------------------------------------------------------------- /src/tcp_server.h: -------------------------------------------------------------------------------- 1 | #ifndef TCP_SERVER_H_ 2 | #define TCP_SERVER_H_ 3 | 4 | #include "config.h" 5 | 6 | typedef struct tcp_server_t tcp_server; 7 | 8 | int start_tcp_server(struct server_config *config, tcp_server **server); 9 | 10 | void join_tcp_server_thread(tcp_server *server); 11 | 12 | void stop_tcp_server(tcp_server *server); 13 | 14 | 15 | #endif /* TCP_SERVER_H_ */ 16 | -------------------------------------------------------------------------------- /src/xlating.h: -------------------------------------------------------------------------------- 1 | #ifndef SRC_XLATING_H_ 2 | #define SRC_XLATING_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct xlating_t xlating; 9 | 10 | int create_frequency_xlating_filter(uint32_t decimation, float *taps, size_t taps_len, int32_t center_freq, uint32_t sampling_freq, uint32_t max_input_buffer_length, xlating **filter); 11 | 12 | void process_native_cu8_cf32(const uint8_t *input, size_t input_len, float complex **output, size_t *output_len, xlating *filter); 13 | 14 | void process_native_cs8_cf32(const int8_t *input, size_t input_len, float complex **output, size_t *output_len, xlating *filter); 15 | 16 | void process_native_cs16_cf32(const int16_t *input, size_t input_len, float complex **output, size_t *output_len, xlating *filter); 17 | 18 | void process_optimized_cu8_cf32(const uint8_t *input, size_t input_len, float complex **output, size_t *output_len, xlating *filter); 19 | 20 | void process_optimized_cs8_cf32(const int8_t *input, size_t input_len, float complex **output, size_t *output_len, xlating *filter); 21 | 22 | void process_optimized_cs16_cf32(const int16_t *input, size_t input_len, float complex **output, size_t *output_len, xlating *filter); 23 | 24 | // cs16 math and output 25 | 26 | void process_native_cu8_cs16(const uint8_t *input, size_t input_len, int16_t **output, size_t *output_len, xlating *filter); 27 | 28 | void process_native_cs8_cs16(const int8_t *input, size_t input_len, int16_t **output, size_t *output_len, xlating *filter); 29 | 30 | void process_native_cs16_cs16(const int16_t *input, size_t input_len, int16_t **output, size_t *output_len, xlating *filter); 31 | 32 | void process_optimized_cu8_cs16(const uint8_t *input, size_t input_len, int16_t **output, size_t *output_len, xlating *filter); 33 | 34 | void process_optimized_cs8_cs16(const int8_t *input, size_t input_len, int16_t **output, size_t *output_len, xlating *filter); 35 | 36 | void process_optimized_cs16_cs16(const int16_t *input, size_t input_len, int16_t **output, size_t *output_len, xlating *filter); 37 | 38 | void destroy_xlating(xlating *filter); 39 | 40 | #endif /* SRC_XLATING_H_ */ 41 | -------------------------------------------------------------------------------- /test/airspy_lib_mock.c: -------------------------------------------------------------------------------- 1 | #include "airspy_lib_mock.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../src/sdr/airspy_lib.h" 11 | 12 | struct mock_status { 13 | int16_t *buffer; 14 | int len; 15 | 16 | pthread_t worker_thread; 17 | airspy_sample_block_cb_fn callback; 18 | pthread_mutex_t mutex; 19 | pthread_cond_t condition; 20 | bool stopped; 21 | int data_was_read; 22 | }; 23 | 24 | struct rtlsdr_dev { 25 | int dummy; 26 | }; 27 | 28 | struct mock_status airspy_mock = {0}; 29 | 30 | int airspy_open(struct airspy_device **device) { 31 | return 0; 32 | } 33 | 34 | int airspy_set_sample_type(struct airspy_device *device, enum airspy_sample_type sample_type) { 35 | return 0; 36 | } 37 | 38 | int airspy_close(struct airspy_device *device) { 39 | return 0; 40 | } 41 | 42 | int airspy_set_lna_agc(struct airspy_device *device, uint8_t value) { 43 | return 0; 44 | } 45 | 46 | int airspy_set_mixer_agc(struct airspy_device *device, uint8_t value) { 47 | return 0; 48 | } 49 | 50 | int airspy_set_samplerate(struct airspy_device *device, uint32_t samplerate) { 51 | return 0; 52 | } 53 | 54 | int airspy_set_packing(struct airspy_device *device, uint8_t value) { 55 | return 0; 56 | } 57 | 58 | int airspy_set_rf_bias(struct airspy_device *dev, uint8_t value) { 59 | return 0; 60 | } 61 | 62 | int airspy_set_lna_gain(struct airspy_device *device, uint8_t value) { 63 | return 0; 64 | } 65 | 66 | int airspy_set_vga_gain(struct airspy_device *device, uint8_t value) { 67 | return 0; 68 | } 69 | 70 | int airspy_set_mixer_gain(struct airspy_device *device, uint8_t value) { 71 | return 0; 72 | } 73 | 74 | int airspy_set_linearity_gain(struct airspy_device *device, uint8_t value) { 75 | return 0; 76 | } 77 | 78 | int airspy_set_sensitivity_gain(struct airspy_device *device, uint8_t value) { 79 | return 0; 80 | } 81 | 82 | static void *airspy_worker(void *ctx) { 83 | pthread_mutex_lock(&airspy_mock.mutex); 84 | while (!airspy_mock.stopped) { 85 | if (airspy_mock.data_was_read) { 86 | pthread_cond_broadcast(&airspy_mock.condition); 87 | } 88 | if (airspy_mock.buffer == NULL) { 89 | pthread_cond_wait(&airspy_mock.condition, &airspy_mock.mutex); 90 | } 91 | if (airspy_mock.buffer != NULL) { 92 | airspy_transfer transfer = { 93 | .ctx = ctx, 94 | .device = NULL, 95 | .dropped_samples = 0, 96 | .sample_count = airspy_mock.len / 2, 97 | .sample_type = AIRSPY_SAMPLE_INT16_IQ, 98 | .samples = airspy_mock.buffer}; 99 | airspy_mock.callback(&transfer); 100 | airspy_mock.buffer = NULL; 101 | airspy_mock.data_was_read = true; 102 | pthread_cond_broadcast(&airspy_mock.condition); 103 | } 104 | } 105 | pthread_mutex_unlock(&airspy_mock.mutex); 106 | return (void *)0; 107 | } 108 | 109 | int airspy_start_rx(struct airspy_device *device, airspy_sample_block_cb_fn callback, void *rx_ctx) { 110 | airspy_mock.callback = callback; 111 | return pthread_create(&airspy_mock.worker_thread, NULL, &airspy_worker, rx_ctx); 112 | } 113 | 114 | int airspy_set_freq(struct airspy_device *device, const uint32_t freq_hz) { 115 | return 0; 116 | } 117 | 118 | int airspy_stop_rx(struct airspy_device *device) { 119 | airspy_stop_mock(); 120 | return 0; 121 | } 122 | 123 | void airspy_lib_version(airspy_lib_version_t *lib_version) { 124 | } 125 | 126 | int airspy_lib_create(airspy_lib **lib) { 127 | struct airspy_lib_t *result = malloc(sizeof(struct airspy_lib_t)); 128 | if (result == NULL) { 129 | return -ENOMEM; 130 | } 131 | *result = (struct airspy_lib_t){0}; 132 | result->airspy_close = airspy_close; 133 | result->airspy_lib_version = airspy_lib_version; 134 | result->airspy_open = airspy_open; 135 | result->airspy_set_freq = airspy_set_freq; 136 | result->airspy_set_linearity_gain = airspy_set_linearity_gain; 137 | result->airspy_set_lna_agc = airspy_set_lna_agc; 138 | result->airspy_set_lna_gain = airspy_set_lna_gain; 139 | result->airspy_set_mixer_agc = airspy_set_mixer_agc; 140 | result->airspy_set_mixer_gain = airspy_set_mixer_gain; 141 | result->airspy_set_packing = airspy_set_packing; 142 | result->airspy_set_rf_bias = airspy_set_rf_bias; 143 | result->airspy_set_sample_type = airspy_set_sample_type; 144 | result->airspy_set_samplerate = airspy_set_samplerate; 145 | result->airspy_set_sensitivity_gain = airspy_set_sensitivity_gain; 146 | result->airspy_set_vga_gain = airspy_set_vga_gain; 147 | result->airspy_start_rx = airspy_start_rx; 148 | result->airspy_stop_rx = airspy_stop_rx; 149 | 150 | airspy_mock.mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; 151 | airspy_mock.condition = (pthread_cond_t)PTHREAD_COND_INITIALIZER; 152 | airspy_mock.stopped = false; 153 | airspy_mock.data_was_read = false; 154 | *lib = result; 155 | return 0; 156 | } 157 | 158 | void airspy_lib_destroy(airspy_lib *lib) { 159 | if (lib == NULL) { 160 | return; 161 | } 162 | free(lib); 163 | } 164 | 165 | void airspy_wait_for_data_read() { 166 | pthread_mutex_lock(&airspy_mock.mutex); 167 | while (!airspy_mock.data_was_read) { 168 | pthread_cond_wait(&airspy_mock.condition, &airspy_mock.mutex); 169 | } 170 | pthread_cond_broadcast(&airspy_mock.condition); 171 | pthread_mutex_unlock(&airspy_mock.mutex); 172 | } 173 | 174 | void airspy_setup_mock_data(int16_t *buffer, int len) { 175 | pthread_mutex_lock(&airspy_mock.mutex); 176 | airspy_mock.buffer = buffer; 177 | airspy_mock.len = len; 178 | pthread_cond_broadcast(&airspy_mock.condition); 179 | pthread_mutex_unlock(&airspy_mock.mutex); 180 | } 181 | 182 | void airspy_stop_mock() { 183 | pthread_mutex_lock(&airspy_mock.mutex); 184 | airspy_mock.stopped = true; 185 | airspy_mock.buffer = NULL; 186 | pthread_cond_broadcast(&airspy_mock.condition); 187 | pthread_mutex_unlock(&airspy_mock.mutex); 188 | pthread_join(airspy_mock.worker_thread, NULL); 189 | } 190 | -------------------------------------------------------------------------------- /test/airspy_lib_mock.h: -------------------------------------------------------------------------------- 1 | #ifndef AIRSPY_LIB_MOCK_H 2 | #define AIRSPY_LIB_MOCK_H 3 | 4 | #include 5 | 6 | void airspy_wait_for_data_read(); 7 | 8 | void airspy_setup_mock_data(int16_t *buffer, int len); 9 | 10 | void airspy_stop_mock(); 11 | 12 | #endif //AIRSPY_LIB_MOCK_H 13 | -------------------------------------------------------------------------------- /test/hackrf_lib_mock.c: -------------------------------------------------------------------------------- 1 | #include "hackrf_lib_mock.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../src/sdr/hackrf_lib.h" 11 | 12 | struct mock_status { 13 | int8_t* buffer; 14 | int len; 15 | 16 | pthread_t worker_thread; 17 | hackrf_sample_block_cb_fn callback; 18 | pthread_mutex_t mutex; 19 | pthread_cond_t condition; 20 | bool stopped; 21 | int data_was_read; 22 | }; 23 | 24 | struct hackrf_device { 25 | int dummy; 26 | }; 27 | 28 | struct mock_status hackrf_mock = {0}; 29 | 30 | static void* hackrf_worker(void* ctx) { 31 | pthread_mutex_lock(&hackrf_mock.mutex); 32 | while (!hackrf_mock.stopped) { 33 | if (hackrf_mock.data_was_read) { 34 | pthread_cond_broadcast(&hackrf_mock.condition); 35 | } 36 | if (hackrf_mock.buffer == NULL) { 37 | pthread_cond_wait(&hackrf_mock.condition, &hackrf_mock.mutex); 38 | } 39 | if (hackrf_mock.buffer != NULL) { 40 | hackrf_transfer transfer = { 41 | .rx_ctx = ctx, 42 | .device = NULL, 43 | .buffer = (uint8_t*)hackrf_mock.buffer, 44 | .buffer_length = hackrf_mock.len}; 45 | hackrf_mock.callback(&transfer); 46 | hackrf_mock.buffer = NULL; 47 | hackrf_mock.data_was_read = true; 48 | pthread_cond_broadcast(&hackrf_mock.condition); 49 | } 50 | } 51 | pthread_mutex_unlock(&hackrf_mock.mutex); 52 | return (void*)0; 53 | } 54 | 55 | int hackrf_start_rx(hackrf_device* device, hackrf_sample_block_cb_fn callback, void* rx_ctx) { 56 | hackrf_mock.callback = callback; 57 | return pthread_create(&hackrf_mock.worker_thread, NULL, &hackrf_worker, rx_ctx); 58 | } 59 | 60 | int hackrf_init() { 61 | return 0; 62 | } 63 | const char* hackrf_error_name(enum hackrf_error errcode) { 64 | return "unknown"; 65 | } 66 | int hackrf_version_string_read(hackrf_device* device, char* version, uint8_t length) { 67 | snprintf(version, length, "test"); 68 | return 0; 69 | } 70 | int hackrf_usb_api_version_read(hackrf_device* device, uint16_t* version) { 71 | *version = 0x102; 72 | return 0; 73 | } 74 | int hackrf_set_vga_gain(hackrf_device* device, uint32_t value) { 75 | return 0; 76 | } 77 | int hackrf_set_lna_gain(hackrf_device* device, uint32_t value) { 78 | return 0; 79 | } 80 | int hackrf_set_amp_enable(hackrf_device* device, const uint8_t value) { 81 | return 0; 82 | } 83 | int hackrf_set_freq(hackrf_device* device, const uint64_t freq_hz) { 84 | return 0; 85 | } 86 | int hackrf_set_baseband_filter_bandwidth(hackrf_device* device, const uint32_t bandwidth_hz) { 87 | return 0; 88 | } 89 | int hackrf_set_sample_rate(hackrf_device* device, const double freq_hz) { 90 | return 0; 91 | } 92 | int hackrf_stop_rx(hackrf_device* device) { 93 | hackrf_stop_mock(); 94 | return 0; 95 | } 96 | int hackrf_close(hackrf_device* device) { 97 | free(device); 98 | return 0; 99 | } 100 | int hackrf_exit() { 101 | return 0; 102 | } 103 | int hackrf_open(hackrf_device** device) { 104 | *device = malloc(sizeof(hackrf_device*)); 105 | return 0; 106 | } 107 | int hackrf_open_by_serial(const char* const desired_serial_number, hackrf_device** device) { 108 | return 0; 109 | } 110 | int hackrf_set_antenna_enable(hackrf_device* device, const uint8_t value) { 111 | return 0; 112 | } 113 | 114 | int hackrf_lib_create(hackrf_lib** lib) { 115 | struct hackrf_lib_t* result = malloc(sizeof(struct hackrf_lib_t)); 116 | if (result == NULL) { 117 | return -ENOMEM; 118 | } 119 | *result = (struct hackrf_lib_t){0}; 120 | 121 | result->hackrf_init = hackrf_init; 122 | result->hackrf_error_name = hackrf_error_name; 123 | result->hackrf_version_string_read = hackrf_version_string_read; 124 | result->hackrf_usb_api_version_read = hackrf_usb_api_version_read; 125 | result->hackrf_set_vga_gain = hackrf_set_vga_gain; 126 | result->hackrf_set_lna_gain = hackrf_set_lna_gain; 127 | result->hackrf_set_amp_enable = hackrf_set_amp_enable; 128 | result->hackrf_set_freq = hackrf_set_freq; 129 | result->hackrf_set_baseband_filter_bandwidth = hackrf_set_baseband_filter_bandwidth; 130 | result->hackrf_set_sample_rate = hackrf_set_sample_rate; 131 | result->hackrf_start_rx = hackrf_start_rx; 132 | result->hackrf_stop_rx = hackrf_stop_rx; 133 | result->hackrf_close = hackrf_close; 134 | result->hackrf_exit = hackrf_exit; 135 | result->hackrf_open = hackrf_open; 136 | result->hackrf_open_by_serial = hackrf_open_by_serial; 137 | result->hackrf_set_antenna_enable = hackrf_set_antenna_enable; 138 | 139 | hackrf_mock.mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; 140 | hackrf_mock.condition = (pthread_cond_t)PTHREAD_COND_INITIALIZER; 141 | hackrf_mock.stopped = false; 142 | hackrf_mock.data_was_read = false; 143 | *lib = result; 144 | return 0; 145 | } 146 | 147 | void hackrf_lib_destroy(hackrf_lib* lib) { 148 | if (lib == NULL) { 149 | return; 150 | } 151 | free(lib); 152 | } 153 | 154 | void hackrf_wait_for_data_read() { 155 | pthread_mutex_lock(&hackrf_mock.mutex); 156 | while (!hackrf_mock.data_was_read) { 157 | pthread_cond_wait(&hackrf_mock.condition, &hackrf_mock.mutex); 158 | } 159 | pthread_cond_broadcast(&hackrf_mock.condition); 160 | pthread_mutex_unlock(&hackrf_mock.mutex); 161 | } 162 | 163 | void hackrf_setup_mock_data(int8_t* buffer, int len) { 164 | pthread_mutex_lock(&hackrf_mock.mutex); 165 | hackrf_mock.buffer = buffer; 166 | hackrf_mock.len = len; 167 | pthread_cond_broadcast(&hackrf_mock.condition); 168 | pthread_mutex_unlock(&hackrf_mock.mutex); 169 | } 170 | 171 | void hackrf_stop_mock() { 172 | pthread_mutex_lock(&hackrf_mock.mutex); 173 | hackrf_mock.stopped = true; 174 | hackrf_mock.buffer = NULL; 175 | pthread_cond_broadcast(&hackrf_mock.condition); 176 | pthread_mutex_unlock(&hackrf_mock.mutex); 177 | pthread_join(hackrf_mock.worker_thread, NULL); 178 | } 179 | -------------------------------------------------------------------------------- /test/hackrf_lib_mock.h: -------------------------------------------------------------------------------- 1 | #ifndef hackrf_LIB_MOCK_H 2 | #define hackrf_LIB_MOCK_H 3 | 4 | #include 5 | 6 | void hackrf_wait_for_data_read(); 7 | 8 | void hackrf_setup_mock_data(int8_t *buffer, int len); 9 | 10 | void hackrf_stop_mock(); 11 | 12 | #endif // hackrf_LIB_MOCK_H 13 | -------------------------------------------------------------------------------- /test/perf_xlating.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../src/lpf.h" 6 | #include "../src/xlating.h" 7 | 8 | #ifndef CMAKE_C_FLAGS 9 | #define CMAKE_C_FLAGS "" 10 | #endif 11 | 12 | extern const char *SIMD_STATUS; 13 | 14 | int main(void) { 15 | printf("SIMD optimization: %s\n", SIMD_STATUS); 16 | printf("compilation flags: %s\n", CMAKE_C_FLAGS); 17 | uint32_t sampling_freq = 2016000; 18 | uint32_t target_freq = 48000; 19 | float *taps = NULL; 20 | size_t len; 21 | int code = create_low_pass_filter(1.0f, sampling_freq, target_freq / 2, 2000, &taps, &len); 22 | if (code != 0) { 23 | exit(EXIT_FAILURE); 24 | } 25 | size_t max_input = 200000; 26 | xlating *filter = NULL; 27 | code = create_frequency_xlating_filter((int)(sampling_freq / target_freq), taps, len, -12000, sampling_freq, max_input, &filter); 28 | if (code != 0) { 29 | exit(EXIT_FAILURE); 30 | } 31 | uint8_t *input = NULL; 32 | input = malloc(sizeof(float) * max_input); 33 | if (input == NULL) { 34 | exit(EXIT_FAILURE); 35 | } 36 | for (size_t i = 0; i < max_input; i++) { 37 | // don't care about the loss of data 38 | input[i] = ((float)(i)) / 128.0f; 39 | } 40 | 41 | int total_executions = 1000; 42 | 43 | clock_t begin = clock(); 44 | for (int i = 0; i < total_executions; i++) { 45 | float complex *output; 46 | size_t output_len = 0; 47 | process_native_cu8_cf32(input, max_input, &output, &output_len, filter); 48 | } 49 | clock_t end = clock(); 50 | double time_spent = (double)(end - begin) / CLOCKS_PER_SEC; 51 | printf("native cu8_cf32: %f seconds\n", time_spent / total_executions); 52 | begin = clock(); 53 | for (int i = 0; i < total_executions; i++) { 54 | float complex *output; 55 | size_t output_len = 0; 56 | process_optimized_cu8_cf32(input, max_input, &output, &output_len, filter); 57 | } 58 | end = clock(); 59 | time_spent = (double)(end - begin) / CLOCKS_PER_SEC; 60 | printf("optimized cu8_cf32: %f seconds\n", time_spent / total_executions); 61 | 62 | begin = clock(); 63 | for (int i = 0; i < total_executions; i++) { 64 | int16_t *output; 65 | size_t output_len = 0; 66 | process_native_cu8_cs16(input, max_input, &output, &output_len, filter); 67 | } 68 | end = clock(); 69 | time_spent = (double)(end - begin) / CLOCKS_PER_SEC; 70 | printf("native cu8_cs16: %f seconds\n", time_spent / total_executions); 71 | 72 | begin = clock(); 73 | for (int i = 0; i < total_executions; i++) { 74 | int16_t *output; 75 | size_t output_len = 0; 76 | process_optimized_cu8_cs16(input, max_input, &output, &output_len, filter); 77 | } 78 | end = clock(); 79 | time_spent = (double)(end - begin) / CLOCKS_PER_SEC; 80 | printf("optimized cu8_cs16: %f seconds\n", time_spent / total_executions); 81 | 82 | // MacBook Air 83 | // volk_generic cu8_cf32: 0.005615 seconds 84 | // volk optimized cu8_cf32: 0.002649 seconds 85 | // optimized cu8_cf32: 0.002038 seconds 86 | // native cu8_cf32: 0.002902 seconds 87 | 88 | // MacBook Air M1 89 | // volk_generic cu8_cf32: 0.001693 seconds 90 | // volk optimized cu8_cf32: 0.001477 seconds 91 | // native cu8_cf32: 0.001440 seconds 92 | // optimized cu8_cf32: 0.003617 seconds 93 | // native cu8_cs16: 0.001161 seconds 94 | // optimized cu8_cs16: 0.001161 seconds 95 | 96 | // Raspberrypi 3 97 | // volk_generic cu8_cf32: 0.073828 seconds 98 | // volk optimized cu8_cf32: 0.024855 seconds 99 | 100 | // Raspberrypi 4 101 | // volk_generic cu8_cf32: 0.041116 seconds 102 | // volk optimized cu8_cf32: 0.013621 seconds 103 | // native cu8_cf32: 0.039529 seconds 104 | // optimized cu8_cf32: 0.011978 seconds 105 | 106 | // Raspberrypi 1 107 | // volk_generic cu8_cf32: 0.291598 seconds 108 | // volk optimized cu8_cf32: 0.332934 seconds 109 | 110 | // Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz 111 | // volk_generic cu8_cf32: 0.003249 seconds 112 | // volk optimized cu8_cf32: 0.001609 seconds 113 | // native cu8_cf32: 0.001603 seconds 114 | // optimized cu8_cf32: 0.001609 seconds 115 | // native cu8_cs16: 0.003384 seconds 116 | // optimized cu8_cs16: 0.003382 seconds 117 | return 0; 118 | } 119 | -------------------------------------------------------------------------------- /test/resources/configuration.config: -------------------------------------------------------------------------------- 1 | # bind address for a server 2 | bind_address="127.0.0.1" 3 | 4 | # port for a server 5 | port=8089 6 | 7 | # clients can select the band freq, 8 | # but server controls the sample rate of the band 9 | # each client's sample rate should be a fraction of 10 | # band_sampling_rate 11 | band_sampling_rate=2400000 12 | 13 | # controls the gain setting: 14 | # 0 - gain auto 15 | # 1 - gain manual, the gain setting should be specified 16 | gain_mode=1 17 | 18 | # gain setting for manual gain mode 19 | gain=4.2 20 | 21 | # controls bias tee: 22 | # 0 - disabled 23 | # 1 - enabled 24 | bias_t=0 25 | 26 | # manual ppm correction 27 | ppm=10 28 | 29 | # device index 30 | device_index=1 31 | 32 | # device serial number (SN) 33 | device_serial="00000400" 34 | 35 | # buffer size for passing the data from USB 36 | # same buffer setting for passing data between threads 37 | # the bigger buffer the less context switching, but 38 | # bigger latency for RF messages 39 | buffer_size=131072 40 | 41 | # number of elements in the DSP queue 42 | # the more queue, the better performance spikes handled 43 | # the less queue, the better latency and memory consumption 44 | # total memory = queue_size * buffer_size * number_of_clients 45 | queue_size=64 46 | 47 | # if client requests to save output locally, then 48 | # the base path controls the directory where it is saved 49 | # tmp directory is recommended 50 | base_path="/tmp/" 51 | 52 | # timeout for reading client's requests 53 | # in seconds 54 | # should be positive 55 | read_timeout_seconds=10 56 | 57 | # use gzip while saving data into file 58 | use_gzip=false 59 | 60 | # the bigger rate, the smaller low pass filter (LPF) cutoff frequency 61 | # - the smaller cutoff frequency the less aliasing on the sides of the stream 62 | # - the bigger cutoff frequency the faster sdr-server works 63 | # default is 5. Should be positive >= 1 64 | lpf_cutoff_rate=5 -------------------------------------------------------------------------------- /test/resources/core.config: -------------------------------------------------------------------------------- 1 | bind_address="127.0.0.1" 2 | port=8089 3 | band_sampling_rate=48000 4 | gain_mode=1 5 | gain=4.2 6 | bias_t=0 7 | ppm=10 8 | buffer_size=131072 9 | use_gzip=false 10 | lpf_cutoff_rate=24 -------------------------------------------------------------------------------- /test/resources/invalid.basepath.config: -------------------------------------------------------------------------------- 1 | bind_address="127.0.0.1" 2 | port=8089 3 | band_sampling_rate=48000 4 | gain_mode=1 5 | gain=4.2 6 | bias_t=0 7 | ppm=10 8 | buffer_size=131072 9 | use_gzip=false 10 | base_path="/non-existing-directory-in-root" -------------------------------------------------------------------------------- /test/resources/invalid.config: -------------------------------------------------------------------------------- 1 | #band_sampling_rate=2400000 2 | gain_mode=1 3 | gain=4.2 4 | bias_t=0 5 | ppm=10 -------------------------------------------------------------------------------- /test/resources/invalid.format.config: -------------------------------------------------------------------------------- 1 | bind_address=127.0.0.2 2 | port=8089 3 | band_sampling_rate=2400000 4 | gain_mode=1 5 | gain=4.2 6 | bias_t=0 7 | ppm=10 -------------------------------------------------------------------------------- /test/resources/invalid.queue_size.config: -------------------------------------------------------------------------------- 1 | bind_address="127.0.0.1" 2 | band_sampling_rate=2400000 3 | queue_size=0 -------------------------------------------------------------------------------- /test/resources/invalid2.config: -------------------------------------------------------------------------------- 1 | band_sampling_rate=2400000 2 | gain_mode=1 3 | gain=4.2 4 | bias_t=0 5 | ppm=10 6 | read_timeout_seconds=-10 -------------------------------------------------------------------------------- /test/resources/minimal.config: -------------------------------------------------------------------------------- 1 | bind_address="127.0.0.2" 2 | band_sampling_rate=2400000 3 | -------------------------------------------------------------------------------- /test/resources/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set +e 4 | 5 | EXIT_CODE=0 6 | for file in test_*; do 7 | [[ ${file} == *.dSYM ]] && continue 8 | valgrind -v --error-exitcode=1 -q --tool=memcheck --leak-check=yes --show-reachable=yes ./${file} 9 | CURRENT_EXIT_CODE=$? 10 | if [ ${CURRENT_EXIT_CODE} != 0 ]; then 11 | EXIT_CODE=${CURRENT_EXIT_CODE} 12 | fi 13 | done 14 | 15 | exit ${EXIT_CODE} 16 | -------------------------------------------------------------------------------- /test/resources/tcp_server.config: -------------------------------------------------------------------------------- 1 | bind_address="127.0.0.1" 2 | port=8089 3 | band_sampling_rate=2400000 4 | gain_mode=1 5 | gain=4.2 6 | bias_t=0 7 | ppm=10 8 | buffer_size=131072 9 | use_gzip=false -------------------------------------------------------------------------------- /test/rtlsdr_lib_mock.c: -------------------------------------------------------------------------------- 1 | #include "rtlsdr_lib_mock.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../src/sdr/rtlsdr_lib.h" 10 | 11 | struct mock_status { 12 | uint8_t *buffer; 13 | int len; 14 | 15 | pthread_mutex_t mutex; 16 | pthread_cond_t condition; 17 | bool stopped; 18 | int data_was_read; 19 | }; 20 | 21 | struct rtlsdr_dev { 22 | int dummy; 23 | }; 24 | 25 | struct mock_status mock; 26 | 27 | // make sure data was read 28 | // the core will be terminated gracefully 29 | // so the data will be processed 30 | void rtlsdr_wait_for_data_read() { 31 | pthread_mutex_lock(&mock.mutex); 32 | while (!mock.data_was_read) { 33 | pthread_cond_wait(&mock.condition, &mock.mutex); 34 | } 35 | pthread_cond_broadcast(&mock.condition); 36 | pthread_mutex_unlock(&mock.mutex); 37 | } 38 | 39 | void rtlsdr_setup_mock_data(uint8_t *buffer, int len) { 40 | pthread_mutex_lock(&mock.mutex); 41 | mock.buffer = buffer; 42 | mock.len = len; 43 | pthread_cond_broadcast(&mock.condition); 44 | pthread_mutex_unlock(&mock.mutex); 45 | } 46 | 47 | void rtlsdr_stop_mock() { 48 | pthread_mutex_lock(&mock.mutex); 49 | mock.stopped = true; 50 | mock.buffer = NULL; 51 | pthread_cond_broadcast(&mock.condition); 52 | pthread_mutex_unlock(&mock.mutex); 53 | } 54 | 55 | int rtlsdr_read_sync_mocked(rtlsdr_dev_t *dev, void *buf, int len, int *n_read) { 56 | pthread_mutex_lock(&mock.mutex); 57 | while (!mock.stopped) { 58 | if (mock.data_was_read) { 59 | pthread_cond_broadcast(&mock.condition); 60 | } 61 | if (mock.buffer == NULL) { 62 | pthread_cond_wait(&mock.condition, &mock.mutex); 63 | } 64 | if (mock.buffer != NULL) { 65 | memcpy(buf, mock.buffer, mock.len); 66 | *n_read = mock.len; 67 | mock.buffer = NULL; 68 | mock.data_was_read = true; 69 | pthread_mutex_unlock(&mock.mutex); 70 | return 0; 71 | } 72 | } 73 | pthread_mutex_unlock(&mock.mutex); 74 | *n_read = 0; 75 | return -1; 76 | } 77 | 78 | int rtlsdr_close_mocked(rtlsdr_dev_t *dev) { 79 | rtlsdr_stop_mock(); 80 | if (dev != NULL) { 81 | free(dev); 82 | } 83 | return 0; 84 | } 85 | 86 | int rtlsdr_open_mocked(rtlsdr_dev_t **dev, uint32_t index) { 87 | mock.stopped = false; 88 | mock.data_was_read = false; 89 | rtlsdr_dev_t *result = malloc(sizeof(rtlsdr_dev_t)); 90 | *dev = result; 91 | return 0; 92 | } 93 | 94 | int rtlsdr_set_sample_rate_mocked(rtlsdr_dev_t *dev, uint32_t rate) { 95 | return 0; 96 | } 97 | 98 | int rtlsdr_set_center_freq_mocked(rtlsdr_dev_t *dev, uint32_t freq) { 99 | return 0; 100 | } 101 | 102 | int rtlsdr_set_tuner_gain_mode_mocked(rtlsdr_dev_t *dev, int manual) { 103 | return 0; 104 | } 105 | 106 | int rtlsdr_set_tuner_gain_mocked(rtlsdr_dev_t *dev, int gain) { 107 | return 0; 108 | } 109 | 110 | int rtlsdr_set_bias_tee_mocked(rtlsdr_dev_t *dev, int on) { 111 | return 0; 112 | } 113 | 114 | int rtlsdr_reset_buffer_mocked(rtlsdr_dev_t *dev) { 115 | return 0; 116 | } 117 | 118 | int rtlsdr_set_freq_correction_mocked(rtlsdr_dev_t *dev, int ppm) { 119 | return 0; 120 | } 121 | 122 | int rtlsdr_get_tuner_gains_mocked(rtlsdr_dev_t *dev, int *gains) { 123 | if (gains == NULL) { 124 | return 1; 125 | } 126 | gains[0] = 43; 127 | return 1; 128 | } 129 | 130 | int rtlsdr_get_index_by_serial(const char *serial) { 131 | return 0; 132 | } 133 | 134 | int rtlsdr_lib_create(rtlsdr_lib **lib) { 135 | struct rtlsdr_lib_t *result = malloc(sizeof(struct rtlsdr_lib_t)); 136 | if (result == NULL) { 137 | return -ENOMEM; 138 | } 139 | *result = (struct rtlsdr_lib_t){0}; 140 | result->rtlsdr_open = rtlsdr_open_mocked; 141 | result->rtlsdr_close = rtlsdr_close_mocked; 142 | result->rtlsdr_get_tuner_gains = rtlsdr_get_tuner_gains_mocked; 143 | result->rtlsdr_reset_buffer = rtlsdr_reset_buffer_mocked; 144 | result->rtlsdr_set_bias_tee = rtlsdr_set_bias_tee_mocked; 145 | result->rtlsdr_set_center_freq = rtlsdr_set_center_freq_mocked; 146 | result->rtlsdr_set_sample_rate = rtlsdr_set_sample_rate_mocked; 147 | result->rtlsdr_set_tuner_gain = rtlsdr_set_tuner_gain_mocked; 148 | result->rtlsdr_set_tuner_gain_mode = rtlsdr_set_tuner_gain_mode_mocked; 149 | result->rtlsdr_read_sync = rtlsdr_read_sync_mocked; 150 | result->rtlsdr_set_freq_correction = rtlsdr_set_freq_correction_mocked; 151 | result->rtlsdr_get_index_by_serial = rtlsdr_get_index_by_serial; 152 | 153 | mock.mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER; 154 | mock.condition = (pthread_cond_t)PTHREAD_COND_INITIALIZER; 155 | mock.stopped = false; 156 | mock.data_was_read = false; 157 | mock.buffer = NULL; 158 | *lib = result; 159 | return 0; 160 | } 161 | 162 | void rtlsdr_lib_destroy(rtlsdr_lib *lib) { 163 | if (lib == NULL) { 164 | return; 165 | } 166 | free(lib); 167 | } 168 | -------------------------------------------------------------------------------- /test/rtlsdr_lib_mock.h: -------------------------------------------------------------------------------- 1 | #ifndef SDR_SERVER_RTLSDR_LIB_MOCK_H 2 | #define SDR_SERVER_RTLSDR_LIB_MOCK_H 3 | 4 | #include 5 | 6 | void rtlsdr_wait_for_data_read(); 7 | 8 | void rtlsdr_setup_mock_data(uint8_t *buffer, int len); 9 | 10 | void rtlsdr_stop_mock(); 11 | 12 | #endif //SDR_SERVER_RTLSDR_LIB_MOCK_H 13 | -------------------------------------------------------------------------------- /test/test_config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../src/config.h" 3 | 4 | struct server_config *config = NULL; 5 | 6 | void test_missing_file() { 7 | int code = create_server_config(&config, "non-existing-configration-file.config"); 8 | TEST_ASSERT_EQUAL_INT(code, -1); 9 | } 10 | 11 | void test_invalid_format() { 12 | int code = create_server_config(&config, "invalid.format.config"); 13 | TEST_ASSERT_EQUAL_INT(code, -1); 14 | } 15 | 16 | void test_missing_required() { 17 | int code = create_server_config(&config, "invalid.config"); 18 | TEST_ASSERT_EQUAL_INT(code, -1); 19 | } 20 | 21 | void test_invalid_timeout() { 22 | int code = create_server_config(&config, "invalid2.config"); 23 | TEST_ASSERT_EQUAL_INT(code, -1); 24 | } 25 | 26 | void test_invalid_queue_size_config() { 27 | int code = create_server_config(&config, "invalid.queue_size.config"); 28 | TEST_ASSERT_EQUAL_INT(code, -1); 29 | } 30 | 31 | void test_minimal_config() { 32 | int code = create_server_config(&config, "minimal.config"); 33 | TEST_ASSERT_EQUAL_INT(code, 0); 34 | } 35 | 36 | void test_success() { 37 | int code = create_server_config(&config, "configuration.config"); 38 | TEST_ASSERT_EQUAL_INT(code, 0); 39 | TEST_ASSERT_EQUAL_INT(2400000, config->band_sampling_rate); 40 | TEST_ASSERT_EQUAL_INT(1, config->gain_mode); 41 | TEST_ASSERT_EQUAL_INT(42, config->gain); 42 | TEST_ASSERT_EQUAL_INT(0, config->bias_t); 43 | TEST_ASSERT_EQUAL_INT(config->ppm, 10); 44 | TEST_ASSERT_EQUAL_INT(config->device_index, 1); 45 | TEST_ASSERT_EQUAL_STRING(config->device_serial, "00000400"); 46 | TEST_ASSERT_EQUAL_STRING(config->bind_address, "127.0.0.1"); 47 | TEST_ASSERT_EQUAL_INT(config->port, 8089); 48 | TEST_ASSERT_EQUAL_INT(config->buffer_size, 131072); 49 | TEST_ASSERT_EQUAL_STRING(config->base_path, "/tmp/"); 50 | TEST_ASSERT_EQUAL_INT(config->read_timeout_seconds, 10); 51 | TEST_ASSERT_EQUAL_INT(config->use_gzip, 0); 52 | TEST_ASSERT_EQUAL_INT(config->queue_size, 64); 53 | TEST_ASSERT_EQUAL_INT(config->lpf_cutoff_rate, 5); 54 | } 55 | 56 | void tearDown() { 57 | destroy_server_config(config); 58 | config = NULL; 59 | } 60 | 61 | void setUp() { 62 | //do nothing 63 | } 64 | 65 | int main(void) { 66 | UNITY_BEGIN(); 67 | RUN_TEST(test_success); 68 | RUN_TEST(test_missing_required); 69 | RUN_TEST(test_missing_file); 70 | RUN_TEST(test_invalid_format); 71 | RUN_TEST(test_minimal_config); 72 | RUN_TEST(test_invalid_timeout); 73 | RUN_TEST(test_invalid_queue_size_config); 74 | return UNITY_END(); 75 | } 76 | -------------------------------------------------------------------------------- /test/test_lpf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../src/lpf.h" 4 | 5 | float *taps = NULL; 6 | 7 | void test_bounds1() { 8 | size_t len; 9 | int code = create_low_pass_filter(1.0f, 0, 1750, 500, &taps, &len); 10 | TEST_ASSERT_EQUAL_INT(code, -1); 11 | } 12 | 13 | void test_bounds2() { 14 | size_t len; 15 | int code = create_low_pass_filter(1.0f, 8000, 5000, 500, &taps, &len); 16 | TEST_ASSERT_EQUAL_INT(code, -1); 17 | } 18 | 19 | void test_bounds3() { 20 | size_t len; 21 | int code = create_low_pass_filter(1.0f, 8000, 1750, 0, &taps, &len); 22 | TEST_ASSERT_EQUAL_INT(code, -1); 23 | } 24 | 25 | void test_lowpassTaps() { 26 | size_t len; 27 | int code = create_low_pass_filter(1.0f, 8000, 1750, 500, &taps, &len); 28 | TEST_ASSERT_EQUAL_INT(code, 0); 29 | 30 | const float expected_taps[] = {0.00111410965f, -0.000583702058f, -0.00192639488f, 2.30933896e-18f, 0.00368289859f, 0.00198723329f, -0.0058701504f, -0.00666110823f, 0.0068643163f, 0.0147596458f, -0.00398709066f, -0.0259727165f, -0.0064281947f, 0.0387893915f, 0.0301109217f, -0.0507995859f, 31 | -0.0833103433f, 0.0593735874f, 0.310160041f, 0.437394291f, 0.310160041f, 0.0593735874f, -0.0833103433f, -0.0507995859f, 0.0301109217f, 0.0387893915f, -0.0064281947f, -0.0259727165f, -0.00398709066f, 0.0147596458f, 0.0068643163f, -0.00666110823f, -0.0058701504f, 32 | 0.00198723329f, 33 | 0.00368289859f, 2.30933896e-18f, -0.00192639488f, -0.000583702058f, 0.00111410965f}; 34 | 35 | TEST_ASSERT_EQUAL_INT(len, 39); 36 | for (int i = 0; i < len; i++) { 37 | TEST_ASSERT_EQUAL_INT((int32_t) (expected_taps[i] * 10000), (int32_t) (taps[i] * 10000)); 38 | } 39 | } 40 | 41 | void tearDown() { 42 | if (taps != NULL) { 43 | free(taps); 44 | taps = NULL; 45 | } 46 | } 47 | 48 | void setUp() { 49 | //do nothing 50 | } 51 | 52 | int main(void) { 53 | UNITY_BEGIN(); 54 | RUN_TEST(test_lowpassTaps); 55 | RUN_TEST(test_bounds1); 56 | RUN_TEST(test_bounds2); 57 | RUN_TEST(test_bounds3); 58 | return UNITY_END(); 59 | } 60 | 61 | -------------------------------------------------------------------------------- /test/test_queue.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../src/queue.h" 3 | 4 | queue *queue_obj = NULL; 5 | 6 | void assert_buffers(const uint8_t *expected, size_t expected_len, uint8_t *actual, size_t actual_len) { 7 | TEST_ASSERT_EQUAL_INT(expected_len, actual_len); 8 | for (int i = 0; i < expected_len; i++) { 9 | TEST_ASSERT_EQUAL_INT(expected[i], actual[i]); 10 | } 11 | } 12 | 13 | void assert_buffer(const uint8_t *expected, int expected_len) { 14 | uint8_t *result = NULL; 15 | size_t len = 0; 16 | take_buffer_for_processing(&result, &len, queue_obj); 17 | // TODO doesn't work. segmentation fault if result is null 18 | TEST_ASSERT(result != NULL); 19 | assert_buffers(expected, expected_len, result, len); 20 | complete_buffer_processing(queue_obj); 21 | } 22 | 23 | void test_terminated_only_after_fully_processed() { 24 | int code = create_queue(262144, 10, &queue_obj); 25 | TEST_ASSERT_EQUAL_INT(code, 0); 26 | 27 | const uint8_t buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 28 | queue_put(buffer, sizeof(buffer), queue_obj); 29 | 30 | interrupt_waiting_the_data(queue_obj); 31 | 32 | assert_buffer(buffer, 10); 33 | } 34 | 35 | void test_put_take() { 36 | int code = create_queue(262144, 10, &queue_obj); 37 | TEST_ASSERT_EQUAL_INT(code, 0); 38 | 39 | const uint8_t buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 40 | queue_put(buffer, sizeof(buffer), queue_obj); 41 | 42 | const uint8_t buffer2[2] = {1, 2}; 43 | queue_put(buffer2, sizeof(buffer2), queue_obj); 44 | 45 | assert_buffer(buffer, 10); 46 | assert_buffer(buffer2, 2); 47 | } 48 | 49 | void test_overflow() { 50 | int code = create_queue(262144, 1, &queue_obj); 51 | TEST_ASSERT_EQUAL_INT(code, 0); 52 | 53 | const uint8_t buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 54 | queue_put(buffer, sizeof(buffer), queue_obj); 55 | const uint8_t buffer2[9] = {11, 12, 13, 14, 15, 16, 17, 18, 19}; 56 | queue_put(buffer2, sizeof(buffer2), queue_obj); 57 | 58 | assert_buffer(buffer2, 9); 59 | } 60 | 61 | void tearDown() { 62 | destroy_queue(queue_obj); 63 | } 64 | 65 | void setUp() { 66 | //do nothing 67 | } 68 | 69 | int main(void) { 70 | UNITY_BEGIN(); 71 | RUN_TEST(test_put_take); 72 | RUN_TEST(test_overflow); 73 | RUN_TEST(test_terminated_only_after_fully_processed); 74 | return UNITY_END(); 75 | } 76 | 77 | -------------------------------------------------------------------------------- /test/test_tcp_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../src/client/tcp_client.h" 5 | #include "../src/tcp_server.h" 6 | #include "airspy_lib_mock.h" 7 | #include "hackrf_lib_mock.h" 8 | #include "rtlsdr_lib_mock.h" 9 | #include "utils.h" 10 | 11 | tcp_server *server = NULL; 12 | struct server_config *config = NULL; 13 | struct tcp_client *client0 = NULL; 14 | struct tcp_client *client1 = NULL; 15 | struct tcp_client *client2 = NULL; 16 | uint8_t *input = NULL; 17 | int16_t *input_cs16 = NULL; 18 | int8_t *input_cs8 = NULL; 19 | 20 | static void reconnect_client() { 21 | destroy_client(client0); 22 | client0 = NULL; 23 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client0)); 24 | } 25 | 26 | static void create_and_init_tcpserver() { 27 | TEST_ASSERT_EQUAL_INT(0, create_server_config(&config, "tcp_server.config")); 28 | TEST_ASSERT_EQUAL_INT(0, start_tcp_server(config, &server)); 29 | reconnect_client(); 30 | } 31 | 32 | static void assert_response(struct tcp_client *client, uint8_t type, uint8_t status, uint8_t details) { 33 | struct message_header *response_header = NULL; 34 | struct response *resp = NULL; 35 | TEST_ASSERT_EQUAL_INT(0, read_response(&response_header, &resp, client)); 36 | TEST_ASSERT_EQUAL_INT(type, response_header->type); 37 | TEST_ASSERT_EQUAL_INT(status, resp->status); 38 | TEST_ASSERT_EQUAL_INT(details, resp->details); 39 | free(resp); 40 | free(response_header); 41 | } 42 | 43 | void test_out_of_band_frequency_clients() { 44 | create_and_init_tcpserver(); 45 | 46 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 460600000, REQUEST_DESTINATION_FILE); 47 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 0); 48 | 49 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client1)); 50 | send_message(client1, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 461600000, REQUEST_DESTINATION_FILE); 51 | assert_response(client1, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_OUT_OF_BAND_FREQ); 52 | destroy_client(client1); 53 | 54 | // then the first client disconnects 55 | send_message(client0, PROTOCOL_VERSION, TYPE_SHUTDOWN, 0, 0, 0, 0); 56 | gracefully_destroy_client(client0); 57 | client0 = NULL; 58 | 59 | // now band freq is available 60 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client1)); 61 | send_message(client1, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 461600000, REQUEST_DESTINATION_FILE); 62 | assert_response(client1, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 2); 63 | } 64 | 65 | // this test will ensure tcp server stops 66 | // if client connected and didn't send anything 67 | void test_connect_and_keep_quiet() { 68 | create_and_init_tcpserver(); 69 | } 70 | 71 | void test_partial_request() { 72 | create_and_init_tcpserver(); 73 | uint8_t buffer[] = {PROTOCOL_VERSION}; 74 | TEST_ASSERT_EQUAL_INT(0, write_data(buffer, sizeof(buffer), client0)); 75 | } 76 | 77 | void test_invalid_request() { 78 | create_and_init_tcpserver(); 79 | 80 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 0, REQUEST_DESTINATION_FILE); 81 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 82 | 83 | reconnect_client(); 84 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 0, 460600000, REQUEST_DESTINATION_FILE); 85 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 86 | 87 | reconnect_client(); 88 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 0, 48000, 460600000, REQUEST_DESTINATION_FILE); 89 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 90 | 91 | reconnect_client(); 92 | send_message(client0, 0x99, TYPE_REQUEST, 460700000, 48000, 460600000, REQUEST_DESTINATION_FILE); 93 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 94 | 95 | reconnect_client(); 96 | send_message(client0, PROTOCOL_VERSION, 0x99, 460700000, 48000, 460600000, REQUEST_DESTINATION_FILE); 97 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 98 | 99 | reconnect_client(); 100 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 47000, 460600000, REQUEST_DESTINATION_FILE); 101 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 102 | 103 | reconnect_client(); 104 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 462400000, 48000, 460600000, REQUEST_DESTINATION_FILE); 105 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 106 | 107 | reconnect_client(); 108 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 458800000, 48000, 460600000, REQUEST_DESTINATION_FILE); 109 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 110 | 111 | reconnect_client(); 112 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 460600000, 0x99); 113 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_FAILURE, RESPONSE_DETAILS_INVALID_REQUEST); 114 | } 115 | 116 | void test_connect_disconnect() { 117 | create_and_init_tcpserver(); 118 | 119 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 460600000, REQUEST_DESTINATION_FILE); 120 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 0); 121 | 122 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client1)); 123 | send_message(client1, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 460600000, REQUEST_DESTINATION_FILE); 124 | assert_response(client1, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 1); 125 | 126 | send_message(client0, PROTOCOL_VERSION, TYPE_SHUTDOWN, 0, 0, 0, 0); 127 | // no response here is expected 128 | 129 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client2)); 130 | send_message(client2, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 460600000, REQUEST_DESTINATION_FILE); 131 | assert_response(client2, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 2); 132 | 133 | send_message(client1, PROTOCOL_VERSION, TYPE_SHUTDOWN, 0, 0, 0, 0); 134 | // no response here is expected 135 | } 136 | 137 | void test_connect_disconnect_single_client() { 138 | create_and_init_tcpserver(); 139 | 140 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 460600000, REQUEST_DESTINATION_FILE); 141 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 0); 142 | } 143 | 144 | void test_disconnect_client() { 145 | create_and_init_tcpserver(); 146 | 147 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, 460700000, 48000, 460600000, REQUEST_DESTINATION_FILE); 148 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 0); 149 | 150 | destroy_client(client0); 151 | client0 = NULL; 152 | } 153 | 154 | void test_rtlsdr() { 155 | TEST_ASSERT_EQUAL_INT(0, create_server_config(&config, "tcp_server.config")); 156 | config->sdr_type = SDR_TYPE_RTL; 157 | config->band_sampling_rate = 48000; 158 | TEST_ASSERT_EQUAL_INT(0, start_tcp_server(config, &server)); 159 | 160 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client0)); 161 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, -12000 + 460100200, 9600, 460100200, REQUEST_DESTINATION_SOCKET); 162 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 0); 163 | 164 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client1)); 165 | send_message(client1, PROTOCOL_VERSION, TYPE_REQUEST, -12000 + 460100200, 9600, 460100200, REQUEST_DESTINATION_FILE); 166 | assert_response(client1, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 1); 167 | 168 | int length = 200; 169 | setup_input_cu8(&input, 0, length); 170 | rtlsdr_setup_mock_data(input, length); 171 | rtlsdr_wait_for_data_read(); 172 | 173 | const float expected[] = {-0.0000000f, -0.0000000f, -0.0005348f, -0.0006878f, 0.0023769f, 0.0014759f, -0.0050460f, -0.0028901f, 0.0100345f, 0.0064948f, -0.0245430f, -0.0142574f, 0.0051531f, -0.2082227f, 0.0151599f, 0.0243944f, -0.0072941f, -0.0100809f, 0.0034306f, 0.0046447f, -0.0012999f, 174 | -0.0020388f, 0.0004290f, 0.0008074f, -0.0002939f, -0.0002891f, 0.0002456f, -0.0002504f, 0.0002068f, 0.0002021f, -0.0001587f, 0.0001633f, -0.0001197f, -0.0001152f, 0.0000717f, -0.0000762f, 0.0000326f, 0.0000283f, 0.0000152f, -0.0000109f}; 175 | float *actual = malloc(sizeof(expected)); 176 | TEST_ASSERT(actual != NULL); 177 | TEST_ASSERT_EQUAL_INT(0, read_data(actual, sizeof(expected), client0)); 178 | assert_float_array(expected, sizeof(expected) / sizeof(float), actual, sizeof(expected) / sizeof(float)); 179 | free(actual); 180 | 181 | rtlsdr_stop_mock(); 182 | stop_tcp_server(server); 183 | join_tcp_server_thread(server); 184 | server = NULL; 185 | assert_file(config, 1, expected, sizeof(expected) / sizeof(float) / 2); 186 | } 187 | 188 | void test_airspy() { 189 | TEST_ASSERT_EQUAL_INT(0, create_server_config(&config, "tcp_server.config")); 190 | config->sdr_type = SDR_TYPE_AIRSPY; 191 | config->band_sampling_rate = 48000; 192 | config->use_gzip = true; 193 | TEST_ASSERT_EQUAL_INT(0, start_tcp_server(config, &server)); 194 | 195 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client0)); 196 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, -12000 + 460100200, 9600, 460100200, REQUEST_DESTINATION_SOCKET); 197 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 0); 198 | 199 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client1)); 200 | send_message(client1, PROTOCOL_VERSION, TYPE_REQUEST, -12000 + 460100200, 9600, 460100200, REQUEST_DESTINATION_FILE); 201 | assert_response(client1, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 1); 202 | 203 | int length = 200; 204 | setup_input_cs16(&input_cs16, 0, length); 205 | airspy_setup_mock_data(input_cs16, length); 206 | airspy_wait_for_data_read(); 207 | 208 | const float expected[] = {-0.000000f, -0.000000f, -0.000002f, -0.000002f, 0.000007f, 0.000004f, -0.000015f, -0.000009f, 0.000031f, 0.000020f, -0.000075f, -0.000043f, 0.000013f, -0.000639f, 0.000047f, 0.000074f, -0.000022f, -0.000031f, 0.000010f, 0.000014f, -0.000004f, -0.000006f, 0.000002f, 0.000002f, -0.000001f, -0.000001f, 0.000000f, -0.000001f, 0.000000f, 0.000000f, -0.000000f, 0.000000f, 0.000000f, 0.000000f, -0.000000f, 0.000000f, -0.000000f, -0.000000f, 0.000001f, -0.000001f}; 209 | float *actual = malloc(sizeof(expected)); 210 | TEST_ASSERT(actual != NULL); 211 | TEST_ASSERT_EQUAL_INT(0, read_data(actual, sizeof(expected), client0)); 212 | assert_float_array(expected, sizeof(expected) / sizeof(float), actual, sizeof(expected) / sizeof(float)); 213 | free(actual); 214 | 215 | airspy_stop_mock(); 216 | stop_tcp_server(server); 217 | join_tcp_server_thread(server); 218 | server = NULL; 219 | assert_gzfile(config, 1, expected, sizeof(expected) / sizeof(float) / 2); 220 | } 221 | 222 | void test_hackrf() { 223 | TEST_ASSERT_EQUAL_INT(0, create_server_config(&config, "tcp_server.config")); 224 | config->sdr_type = SDR_TYPE_HACKRF; 225 | config->band_sampling_rate = 48000; 226 | TEST_ASSERT_EQUAL_INT(0, start_tcp_server(config, &server)); 227 | 228 | TEST_ASSERT_EQUAL_INT(0, create_client(config->bind_address, config->port, &client0)); 229 | send_message(client0, PROTOCOL_VERSION, TYPE_REQUEST, -12000 + 460100200, 9600, 460100200, REQUEST_DESTINATION_SOCKET); 230 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 0); 231 | 232 | int length = 200; 233 | setup_input_cs8(&input_cs8, 0, length); 234 | hackrf_setup_mock_data(input_cs8, length); 235 | hackrf_wait_for_data_read(); 236 | 237 | const float expected[] = {0.000000f, 0.000000f, -0.000030f, 0.000040f, -0.000028f, -0.000109f, 0.000032f, 0.000072f, 0.000170f, -0.000192f, -0.000235f, 0.000763f, -0.002799f, -0.001175f, 0.000693f, -0.000469f, -0.000052f, 0.000338f, -0.000086f, 0.000122f, -0.000271f, -0.000188f, 0.000256f, -0.000252f, 0.000261f, 0.000265f, 0.000758f, 0.001372f, -0.001510f, -0.003988f, 0.004179f, 0.011403f, -0.009369f, -0.025508f, 0.019858f, 0.054053f, -0.060386f, -0.139357f, 0.042227f, -0.358664f}; 238 | float *actual = malloc(sizeof(expected)); 239 | TEST_ASSERT(actual != NULL); 240 | TEST_ASSERT_EQUAL_INT(0, read_data(actual, sizeof(expected), client0)); 241 | assert_float_array(expected, sizeof(expected) / sizeof(float), actual, sizeof(expected) / sizeof(float)); 242 | free(actual); 243 | 244 | hackrf_stop_mock(); 245 | stop_tcp_server(server); 246 | join_tcp_server_thread(server); 247 | server = NULL; 248 | } 249 | 250 | void test_ping() { 251 | create_and_init_tcpserver(); 252 | 253 | send_message(client0, PROTOCOL_VERSION, TYPE_PING, 0, 0, 0, 0); 254 | assert_response(client0, TYPE_RESPONSE, RESPONSE_STATUS_SUCCESS, 0); 255 | } 256 | 257 | void tearDown() { 258 | rtlsdr_stop_mock(); 259 | stop_tcp_server(server); 260 | join_tcp_server_thread(server); 261 | server = NULL; 262 | destroy_server_config(config); 263 | config = NULL; 264 | destroy_client(client0); 265 | client0 = NULL; 266 | destroy_client(client1); 267 | client1 = NULL; 268 | destroy_client(client2); 269 | client2 = NULL; 270 | if (input != NULL) { 271 | free(input); 272 | input = NULL; 273 | } 274 | if (input_cs16 != NULL) { 275 | free(input_cs16); 276 | input_cs16 = NULL; 277 | } 278 | if (input_cs8 != NULL) { 279 | free(input_cs8); 280 | input_cs8 = NULL; 281 | } 282 | } 283 | 284 | void setUp() { 285 | // do nothing 286 | } 287 | 288 | int main(void) { 289 | UNITY_BEGIN(); 290 | RUN_TEST(test_connect_disconnect); 291 | RUN_TEST(test_invalid_request); 292 | RUN_TEST(test_partial_request); 293 | RUN_TEST(test_connect_and_keep_quiet); 294 | RUN_TEST(test_connect_disconnect_single_client); 295 | RUN_TEST(test_disconnect_client); 296 | RUN_TEST(test_rtlsdr); 297 | RUN_TEST(test_airspy); 298 | RUN_TEST(test_hackrf); 299 | RUN_TEST(test_out_of_band_frequency_clients); 300 | RUN_TEST(test_ping); 301 | return UNITY_END(); 302 | } 303 | -------------------------------------------------------------------------------- /test/test_xlating.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../src/lpf.h" 5 | #include "../src/xlating.h" 6 | #include "utils.h" 7 | 8 | xlating *filter = NULL; 9 | uint8_t *input_cu8 = NULL; 10 | int16_t *input_cs16 = NULL; 11 | float complex *output_cf32 = NULL; 12 | size_t output_len = 0; 13 | int16_t *output_cs16 = NULL; 14 | 15 | static void setup_filter(size_t max_input) { 16 | uint32_t sampling_freq = 48000; 17 | uint32_t target_freq = 9600; 18 | float *taps = NULL; 19 | size_t len; 20 | TEST_ASSERT_EQUAL_INT(0, create_low_pass_filter(1.0f, sampling_freq, target_freq / 2, 2000, &taps, &len)); 21 | TEST_ASSERT_EQUAL_INT(0, create_frequency_xlating_filter((int)(sampling_freq / target_freq), taps, len, -12000, sampling_freq, max_input, &filter)); 22 | } 23 | 24 | void test_max_input_buffer_size() { 25 | size_t input_len = 2000; 26 | setup_filter(input_len); 27 | setup_input_cu8(&input_cu8, 0, input_len); 28 | process_native_cu8_cf32(input_cu8, input_len, &output_cf32, &output_len, filter); 29 | const float expected_cf32[] = {0.0008628f, 0.0008561f, -0.0003212f, -0.0018911f, 0.0005033f, 0.0077399f, -0.0025557f, -0.0186932f, 0.0062834f, 0.0407742f, -0.0307208f, -0.1272911f, 0.0272516f, -0.1294040f, -0.0046343f, 0.0409085f, 0.0021693f, -0.0183142f, -0.0008918f, 0.0078921f, 0.0002495f, -0.0026127f, -0.0000701f, 0.0009386f, -0.0001126f, 0.0007490f, -0.0006985f, -0.0001631f, 0.0002136f, -0.0006479f, 0.0005974f, 0.0002640f, -0.0003145f, 0.0005468f, -0.0004963f, -0.0003650f, 0.0004154f, -0.0004458f, 0.0003952f, 0.0004659f, -0.0005164f, 0.0003447f, -0.0002941f, -0.0005669f, 0.0006173f, -0.0002436f, 0.0001930f, 0.0006678f, -0.0007183f, 0.0001425f, -0.0000920f, -0.0007687f, -0.0014555f, 0.0014070f, 0.0016889f, -0.0042796f, -0.0050061f, 0.0084395f, 0.0136318f, -0.0190434f, -0.0296605f, 0.0494813f, -0.0160297f, -0.4166399f, 0.0494911f, -0.0296489f, -0.0190547f, 0.0136408f, 0.0084302f, -0.0050168f, -0.0042689f, 0.0016794f, 0.0014166f, -0.0014449f, -0.0007793f, -0.0000824f, 0.0001328f, -0.0007288f, 0.0006782f, 0.0001833f, -0.0002338f, 0.0006277f, -0.0005772f, -0.0002842f, 0.0003347f, -0.0005266f, 0.0004761f, 0.0003852f, -0.0004356f, 0.0004255f, -0.0003750f, -0.0004861f, 0.0005366f, -0.0003244f, 0.0002739f, 0.0005870f, -0.0006375f, 0.0002234f, -0.0001728f, -0.0006880f, 0.0007385f, -0.0001223f, 0.0000717f, 0.0007889f, -0.0005251f, -0.0033877f, 0.0041549f, 0.0100833f, -0.0094282f, -0.0228742f, 0.0190230f, 0.0517398f, -0.0597780f, -0.1389234f, 0.0429869f, -0.3576464f, 0.0067186f, 0.0858603f, -0.0045379f, -0.0371938f, 0.0017999f, 0.0152471f, 0.0004572f, -0.0055316f, -0.0000771f, 0.0027844f, 0.0007591f, 0.0001026f, -0.0001530f, 0.0007086f, -0.0006580f, -0.0002035f, 0.0002540f, -0.0006075f, 0.0005569f, 0.0003044f, -0.0003549f, 0.0005064f, -0.0004559f, -0.0004054f, 0.0004558f, -0.0004053f, 0.0003548f, 0.0005063f, -0.0005568f, 0.0003042f, -0.0002537f, -0.0006072f, 0.0006577f, -0.0002031f, 0.0001526f, 0.0007082f, -0.0007586f, 0.0001021f, 0.0000775f, 0.0027850f, -0.0004576f, -0.0055310f, -0.0018012f, 0.0152463f, 0.0045405f, -0.0371944f, -0.0067214f, 0.0858608f, -0.0429713f, -0.3576475f, 0.0597849f, -0.1389197f, -0.0190261f, 0.0517399f, 0.0094284f, -0.0228746f, -0.0041547f, 0.0100824f, 0.0005258f, -0.0033871f, -0.0000723f, 0.0007894f, -0.0007389f, -0.0001228f, 0.0001732f, -0.0006884f, 0.0006378f, 0.0002237f, -0.0002741f, 0.0005873f, -0.0005367f, -0.0003246f, 0.0003751f, -0.0004862f, 0.0004356f, 0.0004256f, -0.0004760f, 0.0003851f, -0.0003346f, -0.0005265f, 0.0005769f, -0.0002840f, 0.0002335f, 0.0006274f, -0.0006779f, 0.0001829f, -0.0001324f, -0.0007284f, 0.0007788f, -0.0000819f, -0.0014171f, -0.0014455f, 0.0042695f, 0.0016789f, -0.0084292f, -0.0050164f, 0.0190531f, 0.0136423f, -0.0494906f, -0.0296517f, 0.0160477f, -0.4166386f, 0.0296577f, 0.0494818f, -0.0136302f, -0.0190450f, 0.0050065f, 0.0084404f, -0.0016894f, -0.0042791f, 0.0014549f, 0.0014065f, 0.0000925f, -0.0007692f, 0.0007187f, 0.0001429f, -0.0001934f, 0.0006681f, -0.0006176f, -0.0002439f, 0.0002943f, -0.0005670f, 0.0005165f, 0.0003448f, -0.0003953f, 0.0004660f, -0.0004154f, -0.0004457f, 0.0004962f, -0.0003649f, 0.0003143f, 0.0005467f, -0.0005971f, 0.0002638f, -0.0002132f, -0.0006476f, 0.0006981f, -0.0001627f, 0.0001122f, 0.0007486f, 0.0009334f, 0.0017942f, -0.0005699f, -0.0045031f, 0.0013936f, 0.0156327f, -0.0047241f, -0.0370084f, 0.0109150f, 0.0816823f, -0.0579601f, -0.2566969f, 0.0579837f, -0.2566923f, -0.0109213f, 0.0816821f, 0.0047266f, -0.0370071f, -0.0013957f, 0.0156319f, 0.0005708f, -0.0045037f, -0.0009330f, 0.0017946f, -0.0001127f, 0.0007490f, -0.0006984f, -0.0001631f, 0.0002136f, -0.0006479f, 0.0005974f, 0.0002641f, -0.0003145f, 0.0005468f, -0.0004963f, -0.0003650f, 0.0004155f, -0.0004457f, 0.0003952f, 0.0004659f, -0.0005164f, 0.0003446f, -0.0002941f, -0.0005669f, 0.0006173f, -0.0002436f, 0.0001930f, 0.0006678f, -0.0007183f, 0.0001425f, -0.0000919f, -0.0007687f, -0.0014556f, 0.0014069f, 0.0016891f, -0.0042796f, -0.0050064f, 0.0084392f, 0.0136326f, -0.0190428f, -0.0296626f, 0.0494800f, -0.0160117f, -0.4166406f, 0.0494924f, -0.0296468f, -0.0190553f, 0.0136400f, 0.0084304f, -0.0050164f, -0.0042690f, 0.0016792f, 0.0014166f, -0.0014448f, -0.0007793f, -0.0000824f, 0.0001329f, -0.0007288f, 0.0006782f, 0.0001833f, -0.0002338f, 0.0006277f, -0.0005771f, -0.0002843f, 0.0003347f, -0.0005266f, 0.0004761f, 0.0003852f, -0.0004357f, 0.0004255f, -0.0003750f, -0.0004861f, 0.0005366f, -0.0003244f, 0.0002739f, 0.0005871f, -0.0006375f, 0.0002233f, -0.0001728f, -0.0006880f, 0.0007385f, -0.0001223f, 0.0000717f, 0.0007889f, -0.0005249f, -0.0033877f, 0.0041545f, 0.0100835f, -0.0094272f, -0.0228746f, 0.0190208f, 0.0517406f, -0.0597720f, -0.1389259f, 0.0430024f, -0.3576446f, 0.0067149f, 0.0858606f, -0.0045363f, -0.0371940f, 0.0017992f, 0.0152472f, 0.0004575f, -0.0055315f, -0.0000772f, 0.0027844f, 0.0007591f, 0.0001026f, -0.0001531f, 0.0007086f, -0.0006580f, -0.0002035f, 0.0002540f, -0.0006075f, 0.0005569f, 0.0003044f, -0.0003549f, 0.0005064f, -0.0004558f, -0.0004054f, 0.0004558f, -0.0004053f, 0.0003548f, 0.0005063f}; 30 | assert_cf32(expected_cf32, sizeof(expected_cf32) / (2 * sizeof(float)), output_cf32, output_len); 31 | 32 | process_native_cu8_cs16(input_cu8, input_len, &output_cs16, &output_len, filter); 33 | // expected_cs16[] is very similar to expected[] with 0.001 tolerance 34 | // fixed point math is precise, so it's better to compare to int16_t 35 | const int16_t expected_cs16[] = {27, 26, -11, -63, 17, 252, -85, -613, 204, 1339, -1010, -4188, 896, -4256, -152, 1344, 70, -602, -29, 256, 8, -85, -3, 29, -4, 22, -22, -5, 6, -20, 18, 8, -10, 16, -15, -12, 12, -14, 11, 14, -17, 9, -9, -18, 19, -7, 4, 21, -23, 3, -2, -25, -47, 45, 56, -138, -166, 276, 445, -625, -971, 1621, -526, -13645, 1621, -971, -625, 445, 277, -165, -138, 56, 44, -48, -24, -3, 3, -23, 20, 5, -8, 18, -18, -9, 10, -16, 14, 11, -14, 12, -11, -16, 16, -10, 7, 18, -21, 5, -5, -22, 23, -3, 0, 25, -17, -110, 133, 326, -306, -749, 622, 1694, -1959, -4549, 1406, -11709, 218, 2809, -148, -1216, 55, 498, 14, -182, -2, 90, 23, 2, -5, 21, -20, -7, 7, -19, 16, 9, -11, 15, -14, -13, 14, -12, 10, 15, -18, 8, -7, -20, 20, -6, 3, 22, -25, 1, 2, 89, -14, -181, -56, 499, 146, -1217, -218, 2807, -1407, -11704, 1956, -4547, -624, 1692, 305, -748, -133, 327, 15, -109, -2, 24, -23, -4, 5, -21, 19, 6, -9, 17, -17, -10, 11, -15, 12, 13, -15, 11, -10, -17, 18, -8, 6, 19, -22, 4, -3, -24, 24, -2, -46, -47, 136, 55, -277, -166, 624, 446, -1620, -970, 524, -13630, 970, 1618, -446, -624, 164, 277, -58, -139, 47, 44, 2, -24, 21, 4, -6, 20, -19, -8, 8, -18, 15, 10, -13, 13, -13, -14, 15, -11, 8, 17, -19, 7, -6, -21, 22, -4, 2, 23, 29, 56, -19, -148, 46, 506, -156, -1207, 355, 2669, -1896, -8395, 1896, -8394, -356, 2669, 154, -1208, -46, 505, 19, -147, -31, 57, -4, 22, -22, -5, 6, -20, 18, 8, -10, 16, -15, -12, 12, -14, 11, 14, -17, 9, -9, -18, 19, -7, 4, 21, -23, 3, -2, -25, -47, 45, 56, -138, -166, 276, 444, -624, -969, 1618, -525, -13618, 1617, -969, -624, 444, 277, -165, -138, 56, 44, -48, -24, -3, 3, -23, 20, 5, -8, 18, -18, -9, 10, -16, 14, 11, -14, 12, -11, -16, 16, -10, 7, 18, -21, 5, -5, -22, 23, -3, 0, 25, -17, -110, 133, 326, -306, -747, 621, 1691, -1955, -4541, 1403, -11686, 218, 2803, -148, -1214, 55, 497, 14, -182, -2, 90, 23, 2, -5, 21, -20, -7, 7, -19, 16, 9, -11, 15, -14, -13, 14, -12, 10, 15}; 36 | assert_cs16(expected_cs16, sizeof(expected_cs16) / (2 * sizeof(int16_t)), output_cs16, output_len); 37 | } 38 | 39 | void test_partial_input_buffer_size() { 40 | size_t input_len = 200; // taps is 57 41 | setup_filter(2000); 42 | setup_input_cu8(&input_cu8, 0, input_len); 43 | process_native_cu8_cf32(input_cu8, input_len, &output_cf32, &output_len, filter); 44 | const float expected_cf32[] = {0.0008628f, 0.0008561f, -0.0003212f, -0.0018911f, 0.0005033f, 0.0077399f, -0.0025557f, -0.0186932f, 0.0062834f, 0.0407742f, -0.0307208f, -0.1272911f, 0.0272516f, -0.1294040f, -0.0046343f, 0.0409085f, 0.0021693f, -0.0183142f, -0.0008918f, 0.0078921f, 0.0002495f, -0.0026127f, -0.0000701f, 0.0009386f, -0.0001126f, 0.0007490f, -0.0006985f, -0.0001631f, 0.0002136f, -0.0006479f, 0.0005974f, 0.0002640f, -0.0003145f, 0.0005468f, -0.0004963f, -0.0003650f, 0.0004154f, -0.0004458f, 0.0003952f, 0.0004659f}; 45 | assert_cf32(expected_cf32, 20, output_cf32, output_len); 46 | 47 | process_native_cu8_cs16(input_cu8, input_len, &output_cs16, &output_len, filter); 48 | const int16_t expected_cs16[] = {27, 26, -11, -63, 17, 252, -85, -613, 204, 1339, -1010, -4188, 896, -4256, -152, 1344, 70, -602, -29, 256, 8, -85, -3, 29, -4, 22, -22, -5, 6, -20, 18, 8, -10, 16, -15, -12, 12, -14, 11, 14}; 49 | assert_cs16(expected_cs16, 20, output_cs16, output_len); 50 | 51 | free(input_cu8); 52 | // another 200 inputs 53 | setup_input_cu8(&input_cu8, 200, 200); 54 | process_native_cu8_cf32(input_cu8, input_len, &output_cf32, &output_len, filter); 55 | const float expected_next_cf32[] = {-0.0005164f, 0.0003447f, -0.0002941f, -0.0005669f, 0.0006173f, -0.0002436f, 0.0001930f, 0.0006678f, -0.0007183f, 0.0001425f, -0.0000920f, -0.0007687f, -0.0014555f, 0.0014070f, 0.0016889f, -0.0042796f, -0.0050061f, 0.0084395f, 0.0136318f, -0.0190434f, -0.0296605f, 0.0494813f, -0.0160297f, -0.4166399f, 0.0494911f, -0.0296489f, -0.0190547f, 0.0136408f, 0.0084302f, -0.0050168f, -0.0042689f, 0.0016794f, 0.0014166f, -0.0014449f, -0.0007793f, -0.0000824f, 0.0001328f, -0.0007288f, 0.0006782f, 0.0001833f}; 56 | assert_cf32(expected_next_cf32, 20, output_cf32, output_len); 57 | 58 | process_native_cu8_cs16(input_cu8, input_len, &output_cs16, &output_len, filter); 59 | const int16_t expected_next_cs16[] = {-17, 9, -9, -18, 19, -7, 4, 21, -23, 3, -2, -25, -47, 45, 56, -138, -166, 276, 445, -625, -971, 1621, -526, -13645, 1621, -971, -625, 445, 277, -165, -138, 56, 44, -48, -24, -3, 3, -23, 20, 5}; 60 | assert_cs16(expected_next_cs16, 20, output_cs16, output_len); 61 | } 62 | 63 | void test_small_input_data() { 64 | size_t input_len = 198; // taps is 57 65 | setup_filter(2000); 66 | setup_input_cu8(&input_cu8, 0, input_len); 67 | process_native_cu8_cf32(input_cu8, input_len, &output_cf32, &output_len, filter); 68 | process_native_cu8_cs16(input_cu8, input_len, &output_cs16, &output_len, filter); 69 | 70 | free(input_cu8); 71 | // add only 1 complex sample 72 | // shouldn't be enough for 1 output 73 | setup_input_cu8(&input_cu8, 200, 2); 74 | process_native_cu8_cf32(input_cu8, 2, &output_cf32, &output_len, filter); 75 | const float expected_cf32[] = {0}; 76 | assert_cf32(expected_cf32, 0, output_cf32, output_len); 77 | 78 | const int16_t expected_cs16[] = {0}; 79 | process_native_cu8_cs16(input_cu8, 2, &output_cs16, &output_len, filter); 80 | assert_cs16(expected_cs16, 0, output_cs16, output_len); 81 | } 82 | 83 | void tearDown() { 84 | destroy_xlating(filter); 85 | filter = NULL; 86 | if (input_cu8 != NULL) { 87 | free(input_cu8); 88 | input_cu8 = NULL; 89 | } 90 | if (input_cs16 != NULL) { 91 | free(input_cs16); 92 | input_cs16 = NULL; 93 | } 94 | } 95 | 96 | void setUp() { 97 | // do nothing 98 | } 99 | 100 | int main(void) { 101 | UNITY_BEGIN(); 102 | RUN_TEST(test_max_input_buffer_size); 103 | RUN_TEST(test_partial_input_buffer_size); 104 | RUN_TEST(test_small_input_data); 105 | return UNITY_END(); 106 | } 107 | -------------------------------------------------------------------------------- /test/unity-2.5.2/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/unity-2.5.2/README.md: -------------------------------------------------------------------------------- 1 | Unity Test ![CI](https://github.com/ThrowTheSwitch/Unity/workflows/CI/badge.svg) 2 | ========== 3 | __Copyright (c) 2007 - 2021 Unity Project by Mike Karlesky, Mark VanderVoord, and Greg Williams__ 4 | 5 | Welcome to the Unity Test Project, one of the main projects of ThrowTheSwitch.org. Unity Test is a 6 | unit testing framework built for C, with a focus on working with embedded toolchains. 7 | 8 | This project is made to test code targetting microcontrollers big and small. The core project is a 9 | single C file and a pair of headers, allowing it to the added to your existing build setup without 10 | too much headache. You may use any compiler you wish, and may use most existing build systems 11 | including make, cmake, etc. If you'd like to leave the hard work to us, you might be interested 12 | in Ceedling, a build tool also by ThrowTheSwitch.org. 13 | 14 | If you're new to Unity, we encourage you to tour the [getting started guide](docs/UnityGettingStartedGuide.md) 15 | 16 | Getting Started 17 | =============== 18 | The [docs](docs/) folder contains a [getting started guide](docs/UnityGettingStartedGuide.md) 19 | and much more tips about using Unity. 20 | 21 | Unity Assertion Summary 22 | ======================= 23 | For the full list, see [UnityAssertionsReference.md](docs/UnityAssertionsReference.md). 24 | 25 | Basic Validity Tests 26 | -------------------- 27 | 28 | TEST_ASSERT_TRUE(condition) 29 | 30 | Evaluates whatever code is in condition and fails if it evaluates to false 31 | 32 | TEST_ASSERT_FALSE(condition) 33 | 34 | Evaluates whatever code is in condition and fails if it evaluates to true 35 | 36 | TEST_ASSERT(condition) 37 | 38 | Another way of calling `TEST_ASSERT_TRUE` 39 | 40 | TEST_ASSERT_UNLESS(condition) 41 | 42 | Another way of calling `TEST_ASSERT_FALSE` 43 | 44 | TEST_FAIL() 45 | TEST_FAIL_MESSAGE(message) 46 | 47 | This test is automatically marked as a failure. The message is output stating why. 48 | 49 | Numerical Assertions: Integers 50 | ------------------------------ 51 | 52 | TEST_ASSERT_EQUAL_INT(expected, actual) 53 | TEST_ASSERT_EQUAL_INT8(expected, actual) 54 | TEST_ASSERT_EQUAL_INT16(expected, actual) 55 | TEST_ASSERT_EQUAL_INT32(expected, actual) 56 | TEST_ASSERT_EQUAL_INT64(expected, actual) 57 | 58 | Compare two integers for equality and display errors as signed integers. A cast will be performed 59 | to your natural integer size so often this can just be used. When you need to specify the exact size, 60 | like when comparing arrays, you can use a specific version: 61 | 62 | TEST_ASSERT_EQUAL_UINT(expected, actual) 63 | TEST_ASSERT_EQUAL_UINT8(expected, actual) 64 | TEST_ASSERT_EQUAL_UINT16(expected, actual) 65 | TEST_ASSERT_EQUAL_UINT32(expected, actual) 66 | TEST_ASSERT_EQUAL_UINT64(expected, actual) 67 | 68 | Compare two integers for equality and display errors as unsigned integers. Like INT, there are 69 | variants for different sizes also. 70 | 71 | TEST_ASSERT_EQUAL_HEX(expected, actual) 72 | TEST_ASSERT_EQUAL_HEX8(expected, actual) 73 | TEST_ASSERT_EQUAL_HEX16(expected, actual) 74 | TEST_ASSERT_EQUAL_HEX32(expected, actual) 75 | TEST_ASSERT_EQUAL_HEX64(expected, actual) 76 | 77 | Compares two integers for equality and display errors as hexadecimal. Like the other integer comparisons, 78 | you can specify the size... here the size will also effect how many nibbles are shown (for example, `HEX16` 79 | will show 4 nibbles). 80 | 81 | TEST_ASSERT_EQUAL(expected, actual) 82 | 83 | Another way of calling TEST_ASSERT_EQUAL_INT 84 | 85 | TEST_ASSERT_INT_WITHIN(delta, expected, actual) 86 | 87 | Asserts that the actual value is within plus or minus delta of the expected value. This also comes in 88 | size specific variants. 89 | 90 | 91 | TEST_ASSERT_GREATER_THAN(threshold, actual) 92 | 93 | Asserts that the actual value is greater than the threshold. This also comes in size specific variants. 94 | 95 | 96 | TEST_ASSERT_LESS_THAN(threshold, actual) 97 | 98 | Asserts that the actual value is less than the threshold. This also comes in size specific variants. 99 | 100 | 101 | Arrays 102 | ------ 103 | 104 | _ARRAY 105 | 106 | You can append `_ARRAY` to any of these macros to make an array comparison of that type. Here you will 107 | need to care a bit more about the actual size of the value being checked. You will also specify an 108 | additional argument which is the number of elements to compare. For example: 109 | 110 | TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, elements) 111 | 112 | _EACH_EQUAL 113 | 114 | Another array comparison option is to check that EVERY element of an array is equal to a single expected 115 | value. You do this by specifying the EACH_EQUAL macro. For example: 116 | 117 | TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, elements) 118 | 119 | Numerical Assertions: Bitwise 120 | ----------------------------- 121 | 122 | TEST_ASSERT_BITS(mask, expected, actual) 123 | 124 | Use an integer mask to specify which bits should be compared between two other integers. High bits in the mask are compared, low bits ignored. 125 | 126 | TEST_ASSERT_BITS_HIGH(mask, actual) 127 | 128 | Use an integer mask to specify which bits should be inspected to determine if they are all set high. High bits in the mask are compared, low bits ignored. 129 | 130 | TEST_ASSERT_BITS_LOW(mask, actual) 131 | 132 | Use an integer mask to specify which bits should be inspected to determine if they are all set low. High bits in the mask are compared, low bits ignored. 133 | 134 | TEST_ASSERT_BIT_HIGH(bit, actual) 135 | 136 | Test a single bit and verify that it is high. The bit is specified 0-31 for a 32-bit integer. 137 | 138 | TEST_ASSERT_BIT_LOW(bit, actual) 139 | 140 | Test a single bit and verify that it is low. The bit is specified 0-31 for a 32-bit integer. 141 | 142 | Numerical Assertions: Floats 143 | ---------------------------- 144 | 145 | TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) 146 | 147 | Asserts that the actual value is within plus or minus delta of the expected value. 148 | 149 | TEST_ASSERT_EQUAL_FLOAT(expected, actual) 150 | TEST_ASSERT_EQUAL_DOUBLE(expected, actual) 151 | 152 | Asserts that two floating point values are "equal" within a small % delta of the expected value. 153 | 154 | String Assertions 155 | ----------------- 156 | 157 | TEST_ASSERT_EQUAL_STRING(expected, actual) 158 | 159 | Compare two null-terminate strings. Fail if any character is different or if the lengths are different. 160 | 161 | TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) 162 | 163 | Compare two strings. Fail if any character is different, stop comparing after len characters. 164 | 165 | TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message) 166 | 167 | Compare two null-terminate strings. Fail if any character is different or if the lengths are different. Output a custom message on failure. 168 | 169 | TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message) 170 | 171 | Compare two strings. Fail if any character is different, stop comparing after len characters. Output a custom message on failure. 172 | 173 | Pointer Assertions 174 | ------------------ 175 | 176 | Most pointer operations can be performed by simply using the integer comparisons above. However, a couple of special cases are added for clarity. 177 | 178 | TEST_ASSERT_NULL(pointer) 179 | 180 | Fails if the pointer is not equal to NULL 181 | 182 | TEST_ASSERT_NOT_NULL(pointer) 183 | 184 | Fails if the pointer is equal to NULL 185 | 186 | Memory Assertions 187 | ----------------- 188 | 189 | TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) 190 | 191 | Compare two blocks of memory. This is a good generic assertion for types that can't be coerced into acting like 192 | standard types... but since it's a memory compare, you have to be careful that your data types are packed. 193 | 194 | \_MESSAGE 195 | --------- 196 | 197 | you can append \_MESSAGE to any of the macros to make them take an additional argument. This argument 198 | is a string that will be printed at the end of the failure strings. This is useful for specifying more 199 | information about the problem. 200 | 201 | -------------------------------------------------------------------------------- /test/utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void setup_input_cu8(uint8_t **input, size_t input_offset, size_t len) { 9 | uint8_t *result = malloc(sizeof(uint8_t) * len); 10 | TEST_ASSERT(result != NULL); 11 | for (size_t i = 0; i < len; i++) { 12 | // don't care about the loss of data 13 | result[i] = (uint8_t)(input_offset + i); 14 | } 15 | *input = result; 16 | } 17 | 18 | void setup_input_cs8(int8_t **input, size_t input_offset, size_t len) { 19 | int8_t *result = malloc(sizeof(int8_t) * len); 20 | TEST_ASSERT(result != NULL); 21 | for (size_t i = 0; i < len; i++) { 22 | // don't care about the loss of data 23 | result[i] = (int8_t)(input_offset + i); 24 | } 25 | *input = result; 26 | } 27 | 28 | void setup_input_cs16(int16_t **input, size_t input_offset, size_t len) { 29 | int16_t *result = malloc(sizeof(int16_t) * len); 30 | TEST_ASSERT(result != NULL); 31 | for (size_t i = 0; i < len; i++) { 32 | // don't care about the loss of data 33 | result[i] = (int16_t)(input_offset + i) - (int16_t)(len / 2); 34 | } 35 | *input = result; 36 | } 37 | 38 | void assert_cf32(const float expected[], size_t expected_size, float complex *actual, size_t actual_size) { 39 | TEST_ASSERT_EQUAL_INT(expected_size, actual_size); 40 | for (size_t i = 0, j = 0; i < expected_size * 2; i += 2, j++) { 41 | TEST_ASSERT_EQUAL_INT((int32_t)(expected[i] * 10000), (int32_t)(crealf(actual[j]) * 10000)); 42 | TEST_ASSERT_EQUAL_INT((int32_t)(expected[i + 1] * 10000), (int32_t)(cimagf(actual[j]) * 10000)); 43 | } 44 | } 45 | 46 | void assert_cs16(const int16_t expected[], size_t expected_size, int16_t *actual, size_t actual_size) { 47 | TEST_ASSERT_EQUAL_INT(expected_size, actual_size); 48 | for (size_t i = 0; i < expected_size * 2; i++) { 49 | TEST_ASSERT_EQUAL_INT(expected[i], actual[i]); 50 | } 51 | } 52 | 53 | void assert_float_array(const float expected[], size_t expected_size, const float *actual, size_t actual_size) { 54 | TEST_ASSERT_EQUAL_INT(expected_size, actual_size); 55 | for (size_t i = 0; i < expected_size; i++) { 56 | TEST_ASSERT_EQUAL_INT((int32_t)(expected[i] * 10000), (int32_t)(actual[i] * 10000)); 57 | } 58 | } 59 | 60 | void assert_file(struct server_config *config, int id, const float expected[], size_t expected_size) { 61 | char file_path[4096]; 62 | snprintf(file_path, sizeof(file_path), "%s/%d.cf32", config->base_path, id); 63 | fprintf(stdout, "checking: %s\n", file_path); 64 | FILE *f = fopen(file_path, "rb"); 65 | TEST_ASSERT(f != NULL); 66 | fseek(f, 0, SEEK_END); 67 | long fsize = ftell(f); 68 | TEST_ASSERT(fsize > 0); 69 | fseek(f, 0, SEEK_SET); 70 | uint8_t *buffer = malloc(fsize); 71 | TEST_ASSERT(buffer != NULL); 72 | fread(buffer, 1, fsize, f); 73 | fclose(f); 74 | size_t actual_size = fsize / sizeof(float complex); 75 | assert_cf32(expected, expected_size, (float complex *)buffer, actual_size); 76 | free(buffer); 77 | } 78 | 79 | static int read_gzfile_fully(gzFile f, void *result, size_t len) { 80 | size_t left = len; 81 | while (left > 0) { 82 | int received = gzread(f, (char *)result + (len - left), left); 83 | if (received <= 0) { 84 | perror("unable read the file"); 85 | return -1; 86 | } 87 | left -= received; 88 | } 89 | return 0; 90 | } 91 | 92 | void assert_gzfile(struct server_config *config, int id, const float expected[], size_t expected_size) { 93 | char file_path[4096]; 94 | snprintf(file_path, sizeof(file_path), "%s/%d.cf32.gz", config->base_path, id); 95 | fprintf(stdout, "checking: %s\n", file_path); 96 | gzFile f = gzopen(file_path, "rb"); 97 | TEST_ASSERT(f != NULL); 98 | size_t expected_size_bytes = sizeof(float complex) * expected_size; 99 | uint8_t *buffer = malloc(expected_size_bytes); 100 | TEST_ASSERT(buffer != NULL); 101 | int code = read_gzfile_fully(f, buffer, expected_size_bytes); 102 | gzclose(f); 103 | TEST_ASSERT_EQUAL_INT(0, code); 104 | size_t actual_size = expected_size; 105 | assert_cf32(expected, expected_size, (float complex *)buffer, actual_size); 106 | free(buffer); 107 | } 108 | -------------------------------------------------------------------------------- /test/utils.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef UTILS_H_ 3 | #define UTILS_H_ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../src/config.h" 10 | 11 | void setup_input_cu8(uint8_t **input, size_t input_offset, size_t len); 12 | void setup_input_cs16(int16_t **input, size_t input_offset, size_t len); 13 | void setup_input_cs8(int8_t **input, size_t input_offset, size_t len); 14 | void assert_cf32(const float expected[], size_t expected_size, float complex *actual, size_t actual_size); 15 | void assert_cs16(const int16_t expected[], size_t expected_size, int16_t *actual, size_t actual_size); 16 | void assert_float_array(const float expected[], size_t expected_size, const float *actual, size_t actual_size); 17 | void assert_file(struct server_config *config, int id, const float expected[], size_t expected_size); 18 | void assert_gzfile(struct server_config *config, int id, const float expected[], size_t expected_size); 19 | 20 | #endif /* UTILS_H_ */ 21 | --------------------------------------------------------------------------------