--- grc/gen_osmosdr_blocks.py.orig +++ grc/gen_osmosdr_blocks.py @@ -163,6 +163,7 @@ * gnuradio .cfile input through libgnuradio-blocks * RFSPACE SDR-IQ, SDR-IP, NetSDR (incl. X2 option) * AirSpy Wideband Receiver through libairspy + * SpyServer Devices through spyserver % endif % if sourk == 'sink': * gnuradio .cfile output through libgnuradio-blocks @@ -202,6 +203,7 @@ cloudiq=127.0.0.1[:50000] sdr-iq=/dev/ttyUSB0 airspy=0[,bias=0|1][,linearity][,sensitivity] + spyserver=0,ip=192.168.0.10[,port=5555] % endif % if sourk == 'sink': file='/path/to/your file',rate=1e6[,freq=100e6][,append=true][,throttle=true] ... --- lib/CMakeLists.txt.orig +++ lib/CMakeLists.txt @@ -201,6 +201,14 @@ add_subdirectory(bladerf) endif(ENABLE_BLADERF) +######################################################################## +# Setup SPYSERVER component +######################################################################## +GR_REGISTER_COMPONENT("AIRSPY SPY Server Receiver" ENABLE_SPYSERVER) +if(ENABLE_SPYSERVER) + add_subdirectory(spyserver) +endif(ENABLE_SPYSERVER) + ######################################################################## # Setup RFSPACE component ######################################################################## --- lib/config.h.in.orig +++ lib/config.h.in @@ -13,6 +13,7 @@ #cmakedefine ENABLE_HACKRF #cmakedefine ENABLE_BLADERF #cmakedefine ENABLE_RFSPACE +#cmakedefine ENABLE_SPYSERVER #cmakedefine ENABLE_AIRSPY #cmakedefine ENABLE_AIRSPYHF #cmakedefine ENABLE_SOAPY --- lib/device.cc.orig +++ lib/device.cc @@ -61,6 +61,10 @@ #include #endif +#ifdef ENABLE_SPYSERVER +#include +#endif + #ifdef ENABLE_RFSPACE #include #endif @@ -169,6 +173,10 @@ for (std::string dev : rfspace_source_c::get_devices( fake )) devices.push_back( device_t(dev) ); #endif +#ifdef ENABLE_SPYSERVER + BOOST_FOREACH( std::string dev, spyserver_source_c::get_devices( fake ) ) + devices.push_back( device_t(dev) ); +#endif #ifdef ENABLE_AIRSPY for (std::string dev : airspy_source_c::get_devices()) devices.push_back( device_t(dev) ); --- lib/source_impl.cc.orig +++ lib/source_impl.cc @@ -68,6 +68,10 @@ #include #endif +#ifdef ENABLE_SPYSERVER +#include +#endif + #ifdef ENABLE_AIRSPY #include #endif @@ -145,6 +149,9 @@ #ifdef ENABLE_BLADERF dev_types.push_back("bladerf"); #endif +#ifdef ENABLE_SPYSERVER + dev_types.push_back("spyserver"); +#endif #ifdef ENABLE_RFSPACE dev_types.push_back("rfspace"); #endif @@ -217,6 +224,10 @@ for (std::string dev : rfspace_source_c::get_devices()) dev_list.push_back( dev ); #endif +#ifdef ENABLE_SPYSERVER + BOOST_FOREACH( std::string dev, spyserver_source_c::get_devices() ) + dev_list.push_back( dev ); +#endif #ifdef ENABLE_HACKRF for (std::string dev : hackrf_source_c::get_devices()) dev_list.push_back( dev ); @@ -323,6 +334,13 @@ } #endif +#ifdef ENABLE_AIRSPY + if ( dict.count("spyserver") ) { + spyserver_source_c_sptr src = make_spyserver_source_c( arg ); + block = src; iface = src.get(); + } +#endif + #ifdef ENABLE_RFSPACE if ( dict.count("rfspace") || dict.count("sdr-iq") || --- /dev/null +++ lib/spyserver/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright 2012 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +target_include_directories(gnuradio-osmosdr PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(gnuradio-osmosdr + ${Gnuradio-blocks_LIBRARIES} +) + +list(APPEND gr_osmosdr_srcs + ${CMAKE_CURRENT_SOURCE_DIR}/tcp_client.cc + ${CMAKE_CURRENT_SOURCE_DIR}/spyserver_source_c.cc +) +set(gr_osmosdr_srcs ${gr_osmosdr_srcs} PARENT_SCOPE) --- /dev/null +++ lib/spyserver/spyserver_protocol.h @@ -0,0 +1,179 @@ +/* + +SPY Server protocol structures and constants +Copyright (C) 2017 Youssef Touil youssef@live.com + +*/ + + +#pragma once + +#include +#include + +#define SPYSERVER_PROTOCOL_VERSION (((2) << 24) | ((0) << 16) | (1700)) + +#define SPYSERVER_MAX_COMMAND_BODY_SIZE (256) +#define SPYSERVER_MAX_MESSAGE_BODY_SIZE (1 << 20) +#define SPYSERVER_MAX_DISPLAY_PIXELS (1 << 15) +#define SPYSERVER_MIN_DISPLAY_PIXELS (100) +#define SPYSERVER_MAX_FFT_DB_RANGE (150) +#define SPYSERVER_MIN_FFT_DB_RANGE (10) +#define SPYSERVER_MAX_FFT_DB_OFFSET (100) + +enum DeviceType +{ + DEVICE_INVALID = 0, + DEVICE_AIRSPY_ONE = 1, + DEVICE_AIRSPY_HF = 2, + DEVICE_RTLSDR = 3, +}; + +enum CommandType +{ + CMD_HELLO = 0, + CMD_GET_SETTING = 1, + CMD_SET_SETTING = 2, + CMD_PING = 3, +}; + +enum SettingType +{ + SETTING_STREAMING_MODE = 0, + SETTING_STREAMING_ENABLED = 1, + SETTING_GAIN = 2, + + SETTING_IQ_FORMAT = 100, + SETTING_IQ_FREQUENCY = 101, + SETTING_IQ_DECIMATION = 102, + SETTING_IQ_DIGITAL_GAIN = 103, + + SETTING_FFT_FORMAT = 200, + SETTING_FFT_FREQUENCY = 201, + SETTING_FFT_DECIMATION = 202, + SETTING_FFT_DB_OFFSET = 203, + SETTING_FFT_DB_RANGE = 204, + SETTING_FFT_DISPLAY_PIXELS = 205, +}; + +enum StreamType +{ + STREAM_TYPE_STATUS = 0, + STREAM_TYPE_IQ = 1, + STREAM_TYPE_AF = 2, + STREAM_TYPE_FFT = 4, +}; + + +enum StreamingMode +{ + STREAM_MODE_IQ_ONLY = STREAM_TYPE_IQ, + STREAM_MODE_AF_ONLY = STREAM_TYPE_AF, + STREAM_MODE_FFT_ONLY = STREAM_TYPE_FFT, + STREAM_MODE_FFT_IQ = STREAM_TYPE_FFT | STREAM_TYPE_IQ, + STREAM_MODE_FFT_AF = STREAM_TYPE_FFT | STREAM_TYPE_AF, +}; + +enum StreamFormat +{ + STREAM_FORMAT_INVALID = 0, + STREAM_FORMAT_UINT8 = 1, + STREAM_FORMAT_INT16 = 2, + STREAM_FORMAT_INT24 = 3, + STREAM_FORMAT_FLOAT = 4, + STREAM_FORMAT_DINT4 = 5, +}; + +enum MessageType +{ + MSG_TYPE_DEVICE_INFO = 0, + MSG_TYPE_CLIENT_SYNC = 1, + MSG_TYPE_PONG = 2, + MSG_TYPE_READ_SETTING = 3, + + MSG_TYPE_UINT8_IQ = 100, + MSG_TYPE_INT16_IQ = 101, + MSG_TYPE_INT24_IQ = 102, + MSG_TYPE_FLOAT_IQ = 103, + + MSG_TYPE_UINT8_AF = 200, + MSG_TYPE_INT16_AF = 201, + MSG_TYPE_INT24_AF = 202, + MSG_TYPE_FLOAT_AF = 203, + + MSG_TYPE_DINT4_FFT = 300, + MSG_TYPE_UINT8_FFT = 301, +}; + +struct ClientHandshake +{ + uint32_t ProtocolVersion; + uint32_t ClientNameLength; +}; + +struct CommandHeader +{ + uint32_t CommandType; + uint32_t BodySize; +}; + +struct SettingTarget +{ + uint32_t StreamType; + uint32_t SettingType; +}; + +struct MessageHeader +{ + uint32_t ProtocolID; + uint32_t MessageType; + uint32_t StreamType; + uint32_t SequenceNumber; + uint32_t BodySize; +}; + +struct DeviceInfo +{ + uint32_t DeviceType; + uint32_t DeviceSerial; + uint32_t MaximumSampleRate; + uint32_t MaximumBandwidth; + uint32_t DecimationStageCount; + uint32_t GainStageCount; + uint32_t MaximumGainIndex; + uint32_t MinimumFrequency; + uint32_t MaximumFrequency; + uint32_t Resolution; + uint32_t MinimumIQDecimation; + uint32_t ForcedIQFormat; +}; + +struct ClientSync +{ + uint32_t CanControl; + uint32_t Gain; + uint32_t DeviceCenterFrequency; + uint32_t IQCenterFrequency; + uint32_t FFTCenterFrequency; + uint32_t MinimumIQCenterFrequency; + uint32_t MaximumIQCenterFrequency; + uint32_t MinimumFFTCenterFrequency; + uint32_t MaximumFFTCenterFrequency; +}; + +struct ComplexInt16 +{ + int16_t real; + int16_t imag; +}; + +struct ComplexUInt8 +{ + uint8_t real; + uint8_t imag; +}; + +enum ParserPhase { + AcquiringHeader, + ReadingData +}; --- /dev/null +++ lib/spyserver/spyserver_source_c.cc @@ -0,0 +1,879 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Lucas Teske + * Based on Youssef Touil (youssef@live.com) C# implementation. + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +/* + * config.h is generated by configure. It contains the results + * of probing for features, options etc. It should be the first + * file included in your .cc file. + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "spyserver_source_c.h" +#include "spyserver_protocol.h" + +#include "arg_helpers.h" + +using namespace boost::assign; + +spyserver_source_c_sptr make_spyserver_source_c (const std::string & args) +{ + return gnuradio::get_initial_sptr(new spyserver_source_c (args)); +} + +/* + * Specify constraints on number of input and output streams. + * This info is used to construct the input and output signatures + * (2nd & 3rd args to gr::block's constructor). The input and + * output signatures are used by the runtime system to + * check that a valid number and type of inputs and outputs + * are connected to this block. In this case, we accept + * only 0 input and 1 output. + */ +static const int MIN_IN = 0; // mininum number of input streams +static const int MAX_IN = 0; // maximum number of input streams +static const int MIN_OUT = 1; // minimum number of output streams +static const int MAX_OUT = 1; // maximum number of output streams + +/* + * The private constructor + */ +spyserver_source_c::spyserver_source_c (const std::string &args) + : gr::sync_block ("spyserver_source_c", + gr::io_signature::make(MIN_IN, MAX_IN, sizeof (gr_complex)), + gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (gr_complex))), + terminated(false), + streaming(false), + got_device_info(false), + receiver_thread(NULL), + header_data(new uint8_t[sizeof(MessageHeader)]), + body_buffer(NULL), + body_buffer_length(0), + parser_position(0), + last_sequence_number(0), + + streaming_mode(STREAM_MODE_IQ_ONLY), + _sample_rate(0), + _center_freq(0), + _gain(0), + _digitalGain(0) +{ + dict_t dict = params_to_dict(args); + + if (dict.count("ip")) + { + ip = boost::lexical_cast( dict["ip"] ); + } + else if (dict.count("host")) + { + ip = boost::lexical_cast( dict["host"] ); + } + else + { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "You should defined an IP to connect." ); + } + + if (dict.count("port")) + { + port = boost::lexical_cast( dict["port"] ); + } + else + { + port = 5555; + } + + std::cerr << "SpyServer(" << ip << ", " << port << ")" << std::endl; + client = tcp_client(ip, port); + + connect(); + + _fifo = new boost::circular_buffer(5000000); + if (!_fifo) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "Failed to allocate a sample FIFO!" ); + } + std::cerr << "SpyServer: Ready" << std::endl; +} + +// const std::string &spyserver_source_c::getName() { +// switch (device_info.DeviceType) { +// case DEVICE_INVALID: +// return spyserver_source_c::NameNoDevice; +// case DEVICE_AIRSPY_ONE: +// return spyserver_source_c::NameAirspyOne; +// case DEVICE_AIRSPY_HF: +// return spyserver_source_c::NameAirspyHF; +// case DEVICE_RTLSDR: +// return spyserver_source_c::NameRTLSDR; +// default: +// return spyserver_source_c::NameUnknown; +// } +// } + +void spyserver_source_c::connect() +{ + bool hasError = false; + if (receiver_thread != NULL) { + return; + } + + std::cerr << "SpyServer: Trying to connect" << std::endl; + client.connect_conn(); + is_connected = true; + std::cerr << "SpyServer: Connected" << std::endl; + + say_hello(); + cleanup(); + + terminated = false; + got_sync_info = false; + got_device_info = false; + + std::exception error; + + receiver_thread = new std::thread(&spyserver_source_c::thread_loop, this); + + for (int i=0; i<1000 && !hasError; i++) { + if (got_device_info) { + if (device_info.DeviceType == DEVICE_INVALID) { + error = std::runtime_error( std::string(__FUNCTION__) + " " + "Server is up but no device is available"); + hasError = true; + break; + } + + if (got_sync_info) { + std::cerr << "SpyServer: Got sync Info" << std::endl; + on_connect(); + return; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + disconnect(); + if (hasError) { + throw error; + } + + throw std::runtime_error( std::string(__FUNCTION__) + " " + "Server didn't send the device capability and synchronization info."); +} + +void spyserver_source_c::disconnect() +{ + terminated = true; + if (is_connected) { + client.close_conn(); + } + + if (receiver_thread != NULL) { + receiver_thread->join(); + receiver_thread = NULL; + } + + cleanup(); +} + + +void spyserver_source_c::on_connect() +{ + set_setting(SETTING_STREAMING_MODE, { streaming_mode }); + set_setting(SETTING_IQ_FORMAT, { STREAM_FORMAT_INT16 }); + // set_setting(SETTING_FFT_FORMAT, { STREAM_FORMAT_UINT8 }); + //set_setting(SETTING_FFT_DISPLAY_PIXELS, { displayPixels }); + //set_setting(SETTING_FFT_DB_OFFSET, { fftOffset }); + //set_setting(SETTING_FFT_DB_RANGE, { fftRange }); + //device_info.MaximumSampleRate + //availableSampleRates + std::cerr << "SpyServer: Maximum Sample Rate: " << device_info.MaximumSampleRate << std::endl; + for (unsigned int i = device_info.MinimumIQDecimation; i<=device_info.DecimationStageCount; i++) { + uint32_t sr = device_info.MaximumSampleRate / (1 << i); + _sample_rates.push_back( std::pair((double)sr, i ) ); + } + std::sort(_sample_rates.begin(), _sample_rates.end()); +} + +bool spyserver_source_c::set_setting(uint32_t settingType, std::vector params) { + std::vector argBytes; + if (params.size() > 0) { + argBytes = std::vector(sizeof(SettingType) + params.size() * sizeof(uint32_t)); + uint8_t *settingBytes = (uint8_t *) &settingType; + for (unsigned int i=0; i(); + } + + return send_command(CMD_SET_SETTING, argBytes); +} + +bool spyserver_source_c::say_hello() { + const uint8_t *protocolVersionBytes = (const uint8_t *) &ProtocolVersion; + const uint8_t *softwareVersionBytes = (const uint8_t *) SoftwareID.c_str(); + std::vector args = std::vector(sizeof(ProtocolVersion) + SoftwareID.size()); + + std::memcpy(&args[0], protocolVersionBytes, sizeof(ProtocolVersion)); + std::memcpy(&args[0] + sizeof(ProtocolVersion), softwareVersionBytes, SoftwareID.size()); + + return send_command(CMD_HELLO, args); +} + +void spyserver_source_c::cleanup() { + device_info.DeviceType = 0; + device_info.DeviceSerial = 0; + device_info.DecimationStageCount = 0; + device_info.GainStageCount = 0; + device_info.MaximumSampleRate = 0; + device_info.MaximumBandwidth = 0; + device_info.MaximumGainIndex = 0; + device_info.MinimumFrequency = 0; + device_info.MaximumFrequency = 0; + + _gain = 0; + _digitalGain = 0; + //displayCenterFrequency = 0; + //device_center_frequency = 0; + //displayDecimationStageCount = 0; + //channel_decimation_stage_count = 0; + //minimum_tunable_frequency = 0; + //maximum_tunable_frequency = 0; + can_control = false; + got_device_info = false; + got_sync_info = false; + + last_sequence_number = ((uint32_t)-1); + dropped_buffers = 0; + down_stream_bytes = 0; + + parser_phase = AcquiringHeader; + parser_position = 0; + + streaming = false; + terminated = true; +} + + +void spyserver_source_c::thread_loop() { + parser_phase = AcquiringHeader; + parser_position = 0; + + char buffer[BufferSize]; + try { + while(!terminated) { + if (terminated) { + break; + } + uint32_t availableData = client.available_data(); + if (availableData > 0) { + availableData = availableData > BufferSize ? BufferSize : availableData; + client.receive_data(buffer, availableData); + parse_message(buffer, availableData); + } + } + } catch (std::exception &e) { + std::cerr << "SpyServer: Error on ThreadLoop: " << e.what() << std::endl; + } + if (body_buffer != NULL) { + delete[] body_buffer; + body_buffer = NULL; + } + + cleanup(); +} + +void spyserver_source_c::parse_message(char *buffer, uint32_t len) { + down_stream_bytes++; + + int consumed; + while (len > 0 && !terminated) { + if (parser_phase == AcquiringHeader) { + while (parser_phase == AcquiringHeader && len > 0) { + consumed = parse_header(buffer, len); + buffer += consumed; + len -= consumed; + } + + if (parser_phase == ReadingData) { + uint8_t client_major = (SPYSERVER_PROTOCOL_VERSION >> 24) & 0xFF; + uint8_t client_minor = (SPYSERVER_PROTOCOL_VERSION >> 16) & 0xFF; + + uint8_t server_major = (header.ProtocolID >> 24) & 0xFF; + uint8_t server_minor = (header.ProtocolID >> 16) & 0xFF; + //uint16_t server_build = (header.ProtocolID & 0xFFFF); + + if (client_major != server_major || client_minor != server_minor) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + "Server is running an unsupported protocol version."); + } + + if (header.BodySize > SPYSERVER_MAX_MESSAGE_BODY_SIZE) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + "The server is probably buggy."); + } + + if (body_buffer == NULL || body_buffer_length < header.BodySize) { + if (body_buffer != NULL) { + delete[] body_buffer; + } + + body_buffer = new uint8_t[header.BodySize]; + } + } + } + + if (parser_phase == ReadingData) { + consumed = parse_body(buffer, len); + buffer += consumed; + len -= consumed; + + if (parser_phase == AcquiringHeader) { + if (header.MessageType != MSG_TYPE_DEVICE_INFO && header.MessageType != MSG_TYPE_CLIENT_SYNC) { + int32_t gap = header.SequenceNumber - last_sequence_number - 1; + last_sequence_number = header.SequenceNumber; + dropped_buffers += gap; + if (gap > 0) { + std::cerr << "SpyServer: Lost " << gap << " frames from SpyServer!"; + } + } + handle_new_message(); + } + } + } +} + +int spyserver_source_c::parse_header(char *buffer, uint32_t length) { + auto consumed = 0; + + while (length > 0) { + int to_write = std::min((uint32_t)(sizeof(MessageHeader) - parser_position), length); + std::memcpy(&header + parser_position, buffer, to_write); + length -= to_write; + buffer += to_write; + parser_position += to_write; + consumed += to_write; + if (parser_position == sizeof(MessageHeader)) { + parser_position = 0; + if (header.BodySize > 0) { + parser_phase = ReadingData; + } + + return consumed; + } + } + + return consumed; +} + +int spyserver_source_c::parse_body(char* buffer, uint32_t length) { + auto consumed = 0; + + while (length > 0) { + int to_write = std::min((int) header.BodySize - parser_position, length); + std::memcpy(body_buffer + parser_position, buffer, to_write); + length -= to_write; + buffer += to_write; + parser_position += to_write; + consumed += to_write; + + if (parser_position == header.BodySize) { + parser_position = 0; + parser_phase = AcquiringHeader; + return consumed; + } + } + + return consumed; +} + +bool spyserver_source_c::send_command(uint32_t cmd, std::vector args) { + if (!is_connected) { + return false; + } + + bool result; + uint32_t headerLen = sizeof(CommandHeader); + uint16_t argLen = args.size(); + uint8_t *buffer = new uint8_t[headerLen + argLen]; + + CommandHeader header; + header.CommandType = cmd; + header.BodySize = argLen; + + for (uint32_t i=0; i 0) { + for (uint16_t i=0; icapacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples / 2); + + for (size_t i=0; i < to_copy; i++) + { + _fifo->push_back(gr_complex(*sample - 128.f / 128.f, *(sample+1) - 128.f / 128.f)); + sample += 2; + } + _fifo_lock.unlock(); + if (to_copy) { + _samp_avail.notify_one(); + } + + if (to_copy < num_samples) + std::cerr << "O" << std::flush; +} + +void spyserver_source_c::process_int16_samples() { + size_t n_avail, to_copy, num_samples = (header.BodySize / 2) / 2; + + _fifo_lock.lock(); + + int16_t *sample = (int16_t *)body_buffer; + + n_avail = _fifo->capacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples); + + for (size_t i=0; i < to_copy; i++) + { + _fifo->push_back(gr_complex(*sample / 32768.f, *(sample+1) / 32768.f)); + sample += 2; + } + _fifo_lock.unlock(); + if (to_copy) { + _samp_avail.notify_one(); + } + + if (to_copy < num_samples) + std::cerr << "O" << std::flush; +} + +void spyserver_source_c::process_float_samples() { + size_t n_avail, to_copy, num_samples = (header.BodySize / 4) / 2; + _fifo_lock.lock(); + + float *sample = (float *)body_buffer; + + n_avail = _fifo->capacity() - _fifo->size(); + to_copy = (n_avail < num_samples ? n_avail : num_samples); + + for (size_t i=0; i < to_copy; i++) + { + _fifo->push_back(gr_complex(*sample, *(sample+1))); + sample += 2; + } + _fifo_lock.unlock(); + if (to_copy) { + _samp_avail.notify_one(); + } +} + +void spyserver_source_c::set_stream_state() { + set_setting(SETTING_STREAMING_ENABLED, {(unsigned int)(streaming ? 1 : 0)}); +} + +double spyserver_source_c::set_sample_rate(double sampleRate) { + if (sampleRate <= 0xFFFFFFFF) { + std::cerr << "SpyServer: Setting sample rate to " << sampleRate << std::endl; + for (unsigned int i=0; i<_sample_rates.size(); i++) { + if (_sample_rates[i].first == sampleRate) { + channel_decimation_stage_count = _sample_rates[i].second; + set_setting(SETTING_IQ_DECIMATION, {channel_decimation_stage_count}); + _sample_rate = sampleRate; + return get_sample_rate(); + } + } + } + std::cerr << "SpyServer: Sample rate not supported: " << sampleRate << std::endl; + std::cerr << "SpyServer: Supported Sample Rates: " << std::endl; + for (std::pair sr: _sample_rates) { + std::cerr << "SpyServer: " << sr.first << std::endl; + } + + throw std::runtime_error(boost::str( boost::format("Unsupported samplerate: %gM") % (sampleRate/1e6) ) ); +} + +double spyserver_source_c::set_center_freq(double centerFrequency, size_t chan) { + if (centerFrequency <= 0xFFFFFFFF) { + channel_center_frequency = (uint32_t) centerFrequency; + set_setting(SETTING_IQ_FREQUENCY, {channel_center_frequency}); + return centerFrequency; + } + + std::cerr << boost::format("Unsupported center frequency: %gM") % (centerFrequency/1e6) << std::endl; + + return this->get_center_freq(chan); +} + +void spyserver_source_c::process_uint8_fft() { + // TODO + // // std::cerr << "UInt8 FFT Samples processing not implemented!!!" << std::endl; +} + +/* + * Our virtual destructor. + */ +spyserver_source_c::~spyserver_source_c () +{ + disconnect(); + if (_fifo) + { + delete _fifo; + _fifo = NULL; + } + delete[] header_data; + header_data = NULL; +} + +bool spyserver_source_c::start() +{ + if (!streaming) { + std::cerr << "SpyServer: Starting Streaming" << std::endl; + streaming = true; + down_stream_bytes = 0; + set_stream_state(); + return true; + } + return false; +} + +bool spyserver_source_c::stop() +{ + if (streaming) { + std::cerr << "SpyServer: Stopping Streaming" << std::endl; + streaming = false; + down_stream_bytes = 0; + set_stream_state(); + return true; + } + return false; +} + +int spyserver_source_c::work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ) +{ + gr_complex *out = (gr_complex *)output_items[0]; + + if ( ! streaming ) + return WORK_DONE; + + boost::unique_lock lock(_fifo_lock); + + /* Wait until we have the requested number of samples */ + int n_samples_avail = _fifo->size(); + + while (n_samples_avail < noutput_items) { + _samp_avail.wait(lock); + n_samples_avail = _fifo->size(); + } + + for(int i = 0; i < noutput_items; ++i) { + out[i] = _fifo->at(0); + _fifo->pop_front(); + } + + //std::cerr << "-" << std::flush; + + return noutput_items; +} + +std::vector spyserver_source_c::get_devices(bool fake) +{ + std::vector devices; + std::string label; + if ( fake ) + { + std::string args = "spyserver=0,host=localhost,port=5555"; + args += ",label='Spyserver Client'"; + devices.push_back( args ); + } + + return devices; +} + +size_t spyserver_source_c::get_num_channels() +{ + return 1; +} + +osmosdr::meta_range_t spyserver_source_c::get_sample_rates() +{ + osmosdr::meta_range_t range; + for (size_t i = 0; i < _sample_rates.size(); i++) + range += osmosdr::range_t( _sample_rates[i].first ); + + return range; +} + +double spyserver_source_c::get_sample_rate() +{ + return _sample_rate; +} + +osmosdr::freq_range_t spyserver_source_c::get_freq_range( size_t chan ) +{ + osmosdr::freq_range_t range; + range += osmosdr::range_t( minimum_tunable_frequency, maximum_tunable_frequency ); + + return range; +} + +double spyserver_source_c::get_center_freq( size_t chan ) +{ + return _center_freq; +} + +double spyserver_source_c::set_freq_corr( double ppm, size_t chan ) +{ + return get_freq_corr( chan ); +} + +double spyserver_source_c::get_freq_corr( size_t chan ) +{ + return 0; +} + +std::vector spyserver_source_c::get_gain_names( size_t chan ) +{ + std::vector< std::string > names; + if (can_control) { + names += "LNA"; + } + names += "Digital"; + + return names; +} + +osmosdr::gain_range_t spyserver_source_c::get_gain_range( size_t chan ) +{ + return osmosdr::gain_range_t( 0, 16, 1 ); +} + +osmosdr::gain_range_t spyserver_source_c::get_gain_range( const std::string & name, size_t chan ) +{ + if (name == "Digital") { + return osmosdr::gain_range_t( 0, 1, 1 ); + } + return get_gain_range(chan); +} + +bool spyserver_source_c::set_gain_mode( bool automatic, size_t chan ) +{ + return get_gain_mode(chan); +} + +bool spyserver_source_c::get_gain_mode( size_t chan ) +{ + return false; +} + +double spyserver_source_c::set_gain( double gain, size_t chan ) +{ + if (can_control) { + _gain = gain; + set_setting(SETTING_GAIN, {(uint32_t)gain}); + } else { + std::cerr << "Spyserver: The server does not allow you to change the gains." << std::endl; + } + + return _gain; +} + +double spyserver_source_c::set_lna_gain( double gain, size_t chan) +{ + return set_gain(gain, chan); +} + +double spyserver_source_c::set_gain( double gain, const std::string & name, size_t chan) +{ + if (name == "Digital") { + _digitalGain = gain; + set_setting(SETTING_IQ_DIGITAL_GAIN, {((uint32_t)gain) * 0xFFFFFFFF}); + return _gain; + } + return set_gain(gain, chan); +} + +double spyserver_source_c::get_gain( size_t chan ) +{ + return chan == 0 ? _gain : _digitalGain; + return _gain; +} + +double spyserver_source_c::get_gain( const std::string & name, size_t chan ) +{ + + if (name == "Digital") { + return _digitalGain; + } + return get_gain(chan); +} + +double spyserver_source_c::set_mix_gain(double gain, size_t chan) +{ + return _gain; +} + +double spyserver_source_c::set_if_gain(double gain, size_t chan) +{ + return _gain; +} + +std::vector< std::string > spyserver_source_c::get_antennas( size_t chan ) +{ + std::vector< std::string > antennas; + + antennas += get_antenna( chan ); + + return antennas; +} + +std::string spyserver_source_c::set_antenna( const std::string & antenna, size_t chan ) +{ + return get_antenna( chan ); +} + +std::string spyserver_source_c::get_antenna( size_t chan ) +{ + return "RX"; +} + +double spyserver_source_c::set_bandwidth( double bandwidth, size_t chan ) +{ + return get_bandwidth( chan ); +} + +double spyserver_source_c::get_bandwidth( size_t chan ) +{ + return _sample_rate; +} + +osmosdr::freq_range_t spyserver_source_c::get_bandwidth_range( size_t chan ) +{ + osmosdr::freq_range_t bandwidths; + + bandwidths += osmosdr::range_t( get_bandwidth( chan ) ); + + return bandwidths; +} + +void spyserver_source_c::set_biast( bool enabled ) { + +} + +bool spyserver_source_c::get_biast() { + return false; +} \ No newline at end of file --- /dev/null +++ lib/spyserver/spyserver_source_c.h @@ -0,0 +1,206 @@ +/* -*- c++ -*- */ +/* + * Copyright 2013 Dimitri Stolnikov + * + * This file is part of GNU Radio + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ +#ifndef INCLUDED_SPYSERVER_SOURCE_C_H +#define INCLUDED_SPYSERVER_SOURCE_C_H + +#include +#include +#include +#include +#include + +#include + +#include "source_iface.h" +#include "spyserver_protocol.h" +#include "tcp_client.h" + +class spyserver_source_c; + +/* + * We use boost::shared_ptr's instead of raw pointers for all access + * to gr::blocks (and many other data structures). The shared_ptr gets + * us transparent reference counting, which greatly simplifies storage + * management issues. This is especially helpful in our hybrid + * C++ / Python system. + * + * See http://www.boost.org/libs/smart_ptr/smart_ptr.htm + * + * As a convention, the _sptr suffix indicates a boost::shared_ptr + */ +typedef boost::shared_ptr spyserver_source_c_sptr; + +/*! + * \brief Return a shared_ptr to a new instance of spyserver_source_c. + * + * To avoid accidental use of raw pointers, spyserver_source_c's + * constructor is private. make_spyserver_source_c is the public + * interface for creating new instances. + */ +spyserver_source_c_sptr make_spyserver_source_c (const std::string & args = ""); + +/*! + * \brief Provides a stream of complex samples. + * \ingroup block + */ +class spyserver_source_c : + public gr::sync_block, + public source_iface +{ +private: + // The friend declaration allows make_spyserver_source_c to + // access the private constructor. + + friend spyserver_source_c_sptr make_spyserver_source_c (const std::string & args); + + /*! + * \brief Provides a stream of complex samples. + */ + spyserver_source_c (const std::string & args); // private constructor + + +public: + ~spyserver_source_c (); // public destructor + + bool start(); + bool stop(); + + int work( int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items ); + + static std::vector< std::string > get_devices(bool fake = false); + + size_t get_num_channels( void ); + + osmosdr::meta_range_t get_sample_rates( void ); + double set_sample_rate( double rate ); + double get_sample_rate( void ); + + osmosdr::freq_range_t get_freq_range( size_t chan = 0 ); + double set_center_freq( double freq, size_t chan = 0 ); + double get_center_freq( size_t chan = 0 ); + double set_freq_corr( double ppm, size_t chan = 0 ); + double get_freq_corr( size_t chan = 0 ); + + std::vector get_gain_names( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( size_t chan = 0 ); + osmosdr::gain_range_t get_gain_range( const std::string & name, size_t chan = 0 ); + bool set_gain_mode( bool automatic, size_t chan = 0 ); + bool get_gain_mode( size_t chan = 0 ); + double set_gain( double gain, size_t chan = 0 ); + double set_gain( double gain, const std::string & name, size_t chan = 0 ); + double get_gain( size_t chan = 0 ); + double get_gain( const std::string & name, size_t chan = 0 ); + + double set_lna_gain( double gain, size_t chan = 0 ); + double set_mix_gain(double gain, size_t chan = 0 ); + double set_if_gain( double gain, size_t chan = 0 ); + double set_bb_gain( double gain, size_t chan = 0 ) { return set_mix_gain(gain, chan); }; + + std::vector< std::string > get_antennas( size_t chan = 0 ); + std::string set_antenna( const std::string & antenna, size_t chan = 0 ); + std::string get_antenna( size_t chan = 0 ); + + double set_bandwidth( double bandwidth, size_t chan = 0 ); + double get_bandwidth( size_t chan = 0 ); + osmosdr::freq_range_t get_bandwidth_range( size_t chan = 0 ); + + void set_biast( bool enabled ); + bool get_biast(); + +private: + static constexpr unsigned int BufferSize = 64 * 1024; + const uint32_t ProtocolVersion = SPYSERVER_PROTOCOL_VERSION; + const std::string SoftwareID = std::string("gr-osmosdr"); + const std::string NameNoDevice = std::string("SpyServer - No Device"); + const std::string NameAirspyOne = std::string("SpyServer - Airspy One"); + const std::string NameAirspyHF = std::string("SpyServer - Airspy HF+"); + const std::string NameRTLSDR = std::string("SpyServer - RTLSDR"); + const std::string NameUnknown = std::string("SpyServer - Unknown Device"); + + uint32_t minimum_tunable_frequency; + uint32_t maximum_tunable_frequency; + uint32_t device_center_frequency; + uint32_t channel_center_frequency; + uint32_t channel_decimation_stage_count; + tcp_client client; + + void connect(); + void disconnect(); + void thread_loop(); + bool say_hello(); + void cleanup(); + void on_connect(); + + bool set_setting(uint32_t settingType, std::vector params); + bool send_command(uint32_t cmd, std::vector args); + void parse_message(char *buffer, uint32_t len); + int parse_header(char *buffer, uint32_t len); + int parse_body(char *buffer, uint32_t len); + void process_device_info(); + void process_client_sync(); + void process_uint8_samples(); + void process_int16_samples(); + void process_float_samples(); + void process_uint8_fft(); + void handle_new_message(); + void set_stream_state(); + + std::atomic_bool terminated; + std::atomic_bool streaming; + std::atomic_bool got_device_info; + std::atomic_bool got_sync_info; + std::atomic_bool can_control; + std::atomic_bool is_connected; + std::thread *receiver_thread; + + uint32_t dropped_buffers; + std::atomic down_stream_bytes; + + uint8_t *header_data; + uint8_t *body_buffer; + uint64_t body_buffer_length; + uint32_t parser_position; + uint32_t last_sequence_number; + + std::string ip; + int port; + + DeviceInfo device_info; + MessageHeader header; + + uint32_t streaming_mode; + uint32_t parser_phase; + + boost::circular_buffer *_fifo; + boost::mutex _fifo_lock; + boost::condition_variable _samp_avail; + + std::vector< std::pair > _sample_rates; + double _sample_rate; + double _center_freq; + double _gain; + double _digitalGain; +}; + +#endif /* INCLUDED_SPYSERVER_SOURCE_C_H */ --- /dev/null +++ lib/spyserver/tcp_client.cc @@ -0,0 +1,163 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Lucas Teske + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "tcp_client.h" + +#include +#include +#include + +#ifdef _WIN32 +# include +# include +# include +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32.lib") +# endif +# ifndef MSG_WAITALL +# define MSG_WAITALL (1 << 3) +# endif +#else +# include +# include +# include +# include +# include +# include +# include +# define ioctlsocket ioctl +#endif +#if defined(_WIN32) || defined(__APPLE__) + #ifndef MSG_NOSIGNAL + #define MSG_NOSIGNAL 0 + #endif +#endif + +#ifdef _WIN32 +std::atomic_bool tcp_client::initialized(false); +std::atomic_uint tcp_client::sockCount(0); + +void tcp_client::socket_initialize() { + if (!initialized) { + initialized = true; + sockCount = 1; + WSADATA wsa_data; + WSAStartup(MAKEWORD(1, 1), &wsa_data); + } else { + sockCount++; + } +} +#endif + +tcp_client::tcp_client(std::string addr, int port) +{ + this->port = port; + #ifdef _WIN32 + socket_initialize(); + #endif + hostent * record = gethostbyname(addr.c_str()); + if (record == NULL) { + throw std::runtime_error( std::string(__FUNCTION__) + " " + + "Cannot resolve: " + addr ); + } + in_addr * address = (in_addr *)record->h_addr; + memset(&socketAddr, 0x00, sizeof(sockaddr_in)); + socketAddr.sin_addr = *address; +} + +void tcp_client::connect_conn() { + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + throw std::runtime_error("Socket Error Code " + std::to_string(errno)); + } + + socketAddr.sin_family = AF_INET; + socketAddr.sin_port = htons(port); + int x = connect(s, (struct sockaddr *) &socketAddr, sizeof(socketAddr)); + if (x < 0) { + throw std::runtime_error("Socket Error Code " + std::to_string(errno)); + } +} + +void tcp_client::close_conn() { + if (s > 0) { + int status = 0; +#ifdef _WIN32 + status = shutdown(s, SD_BOTH); + if (status == 0) { + status = closesocket(s); + } +#else + status = shutdown(s, 2); + if (status == 0) { + status = close(s); + } +#endif + } +} + +tcp_client::~tcp_client() { + #ifdef _WIN32 + sockCount--; + if (!sockCount) { + WSACleanup(); + } + #endif +} + +void tcp_client::receive_data(char *data, int length) { + long n = recv(s, data, length, MSG_WAITALL); + if (n == 0) { + throw std::runtime_error("Client Disconnected"); + } else if (n != length) { + throw std::runtime_error("Socket Error Code " + std::to_string(errno)); + } +} + +void tcp_client::send_data(char * data, int length) { + int n = send(s, data, length, MSG_NOSIGNAL); + if (n == 0) { + throw std::runtime_error("Client Disconnected"); + } else if (n != length) { + throw std::runtime_error("Socket Error Code " + std::to_string(errno)); + } +} + +uint64_t tcp_client::available_data() { + if (s < 0) { + return 0; + } + + unsigned long bytesAvailable = 0; + int ret = ioctlsocket(s, FIONREAD, &bytesAvailable); + + switch (ret) { + case EINVAL: + case EFAULT: + case ENOTTY: + throw std::runtime_error("Socket Error Code " + std::to_string(ret)); + break; + case EBADF: + throw std::runtime_error("Client Disconnected"); + break; + } + + return bytesAvailable; +} \ No newline at end of file --- /dev/null +++ lib/spyserver/tcp_client.h @@ -0,0 +1,82 @@ +/* -*- c++ -*- */ +/* + * Copyright 2018 Lucas Teske + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef TCPCLIENT_H_ +#define TCPCLIENT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include +#else +# include +# include +#endif + + +#if defined(__GNUC__) || defined(__MINGW32__) +#include +#endif + + +class tcp_client { +private: + int port; + #ifdef _WIN32 + static std::atomic_bool initialized; + static std::atomic_uint sockCount; + void socket_initialize(); + #endif +protected: + struct sockaddr_in socketAddr; + int s; +public: + tcp_client() {} + tcp_client(std::string addr, int port); + ~tcp_client(); + + void connect_conn(); + void close_conn(); + + void receive_data(char *data, int length); + void send_data(char *data, int length); + uint64_t available_data(); + + inline void wait_for_data(uint64_t bytes, uint32_t timeout) { + uint32_t checkTime = (int) time(NULL); + while (available_data() < bytes) { + if (((int) time(NULL)) - checkTime > timeout) { + return; + } + + std::this_thread::sleep_for(std::chrono::microseconds(10)); + } + } +}; + +#endif /* TCPCLIENT_H_ */