[hamradio-commits] [soapyremote] 01/02: New upstream version 0.3.1
Andreas E. Bombe
aeb at moszumanska.debian.org
Thu Oct 20 00:55:49 UTC 2016
This is an automated email from the git hooks/post-receive script.
aeb pushed a commit to branch master
in repository soapyremote.
commit e30cf278eb1c98321bf3d48d1431354b3c8c2a10
Author: Andreas Bombe <aeb at debian.org>
Date: Mon Sep 19 20:31:13 2016 +0200
New upstream version 0.3.1
---
.travis.yml | 42 +
CMakeLists.txt | 44 +
Changelog.txt | 41 +
LICENSE_1_0.txt | 23 +
README.md | 19 +
client/CMakeLists.txt | 14 +
client/ClientStreamData.cpp | 167 ++++
client/ClientStreamData.hpp | 52 ++
client/LogAcceptor.cpp | 181 ++++
client/LogAcceptor.hpp | 21 +
client/Registration.cpp | 173 ++++
client/Settings.cpp | 1360 ++++++++++++++++++++++++++++
client/SoapyClient.hpp | 357 ++++++++
client/Streaming.cpp | 466 ++++++++++
common/CMakeLists.txt | 50 +
common/SoapyHTTPUtils.cpp | 52 ++
common/SoapyHTTPUtils.hpp | 42 +
common/SoapyInfoUtils.hpp | 22 +
common/SoapyInfoUtils.in.cpp | 87 ++
common/SoapyRPCPacker.cpp | 206 +++++
common/SoapyRPCPacker.hpp | 117 +++
common/SoapyRPCSocket.cpp | 448 +++++++++
common/SoapyRPCSocket.hpp | 157 ++++
common/SoapyRPCUnpacker.cpp | 282 ++++++
common/SoapyRPCUnpacker.hpp | 115 +++
common/SoapyRemoteConfig.hpp | 19 +
common/SoapyRemoteDefs.hpp | 285 ++++++
common/SoapySSDPEndpoint.cpp | 359 ++++++++
common/SoapySSDPEndpoint.hpp | 87 ++
common/SoapySocketDefs.in.hpp | 120 +++
common/SoapyStreamEndpoint.cpp | 380 ++++++++
common/SoapyStreamEndpoint.hpp | 159 ++++
common/SoapyURLUtils.cpp | 217 +++++
common/SoapyURLUtils.hpp | 87 ++
debian/changelog | 29 +
debian/compat | 1 +
debian/control | 35 +
debian/copyright | 32 +
debian/docs | 1 +
debian/rules | 17 +
debian/soapysdr-server.install | 1 +
debian/soapysdr0.5-2-module-remote.install | 1 +
debian/source/format | 1 +
server/CMakeLists.txt | 36 +
server/ClientHandler.cpp | 1358 +++++++++++++++++++++++++++
server/ClientHandler.hpp | 44 +
server/LogForwarding.cpp | 59 ++
server/LogForwarding.hpp | 18 +
server/ServerListener.cpp | 108 +++
server/ServerStreamData.cpp | 223 +++++
server/ServerStreamData.hpp | 65 ++
server/SoapyServer.cpp | 121 +++
server/SoapyServer.hpp | 41 +
server/ThreadPrioHelper.hpp | 13 +
server/ThreadPrioUnix.cpp | 38 +
server/ThreadPrioWindows.cpp | 28 +
server/msvc/getopt.h | 607 +++++++++++++
57 files changed, 9128 insertions(+)
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..5e33312
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,42 @@
+########################################################################
+## Travis CI config for SoapyRemote
+##
+## * installs SoapySDR from source
+## * confirms build and install
+## * checks that drivers load
+########################################################################
+
+sudo: required
+dist: trusty
+
+language: cpp
+compiler: gcc
+
+env:
+ global:
+ - INSTALL_PREFIX=/usr/local
+ - SOAPY_SDR_BRANCH=master
+ matrix:
+ - BUILD_TYPE=Debug
+ - BUILD_TYPE=Release
+
+install:
+ # install SoapySDR from source
+ - git clone https://github.com/pothosware/SoapySDR.git
+ - pushd SoapySDR
+ - git checkout ${SOAPY_SDR_BRANCH}
+ - mkdir build && cd build
+ - cmake ../ -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DENABLE_PYTHON=OFF -DENABLE_PYTHON3=OFF
+ - make && sudo make install
+ - popd
+
+script:
+ - mkdir build && cd build
+ - cmake ../ -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${BUILD_TYPE}
+ - make && sudo make install
+ # print info about the install
+ - export LD_LIBRARY_PATH=${INSTALL_PREFIX}/lib:${LD_LIBRARY_PATH}
+ - export PATH=${INSTALL_PREFIX}/bin:${PATH}
+ - SoapySDRUtil --info
+ - SoapySDRUtil --check=remote
+ - SoapySDRServer --help
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..f74ce3c
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,44 @@
+########################################################################
+# Build Soapy SDR remote support
+########################################################################
+cmake_minimum_required(VERSION 2.8.7)
+project(SoapyRemote CXX C)
+
+# select build type to get optimization flags
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE "Release")
+ message(STATUS "Build type not specified: defaulting to release.")
+endif(NOT CMAKE_BUILD_TYPE)
+set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "")
+
+#find soapy sdr
+find_package(SoapySDR "0.4.0" NO_MODULE)
+
+#enable c++11 features
+if(CMAKE_COMPILER_IS_GNUCXX)
+
+ #C++11 is a required language feature for this project
+ include(CheckCXXCompilerFlag)
+ CHECK_CXX_COMPILER_FLAG("-std=c++11" HAS_STD_CXX11)
+ if(HAS_STD_CXX11)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+ else(HAS_STD_CXX11)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
+ endif()
+
+ #Thread support enabled (not the same as -lpthread)
+ list(APPEND SoapySDR_LIBRARIES -pthread)
+
+endif(CMAKE_COMPILER_IS_GNUCXX)
+
+#enable c++11 extensions for OSX
+if (APPLE)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wc++11-extensions")
+endif(APPLE)
+
+#common headers used by client and server
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/common)
+
+add_subdirectory(common)
+add_subdirectory(client)
+add_subdirectory(server)
diff --git a/Changelog.txt b/Changelog.txt
new file mode 100644
index 0000000..5a58ccf
--- /dev/null
+++ b/Changelog.txt
@@ -0,0 +1,41 @@
+Release 0.3.1 (2016-09-01)
+==========================
+
+- Update debian files for SoapySDR module ABI format
+- Preserve packet+burst boundaries in server readStream() endpoint
+
+Release 0.3.0 (2016-07-10)
+==========================
+
+- Support for named register interface API
+- Support for getChannelInfo() API call
+- Moved time source calls to time API section
+- Support for getBandwidthRange() API call
+- Support for channel-specific settings API
+
+Release 0.2.1 (2016-04-21)
+==========================
+
+- Fixed element send size when using multi-channel streams
+- Fixed bug in SoapyRPCUnpacker for std::vector<size_t>
+- Fix use of error code in reportError() with strerror_r
+
+Release 0.2.0 (2015-11-21)
+==========================
+
+- Use formatToSize() from SoapySDR library
+- Support CU8 native conversion to CF32
+- Implement SSDP for automatic server discovery
+- Use native format for automatic selection
+- Support API to query tune argument info
+- Support API to query sensors meta info
+- Support API to query setting argument info
+- Support API to query stream argument info
+- Support API to query stream format types
+- Support API call to query clock rates
+- Support API call to query AGC mode
+
+Release 0.1.0 (2015-10-10)
+==========================
+
+- First release of SoapyRemote plugin module and server application
diff --git a/LICENSE_1_0.txt b/LICENSE_1_0.txt
new file mode 100644
index 0000000..36b7cd9
--- /dev/null
+++ b/LICENSE_1_0.txt
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6c16ff5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+# Use any Soapy SDR remotely
+
+##Build Status
+
+- Travis: [](https://travis-ci.org/pothosware/SoapyRemote)
+
+## Dependencies
+
+* SoapySDR - https://github.com/pothosware/SoapySDR/wiki
+
+## Documentation
+
+* https://github.com/pothosware/SoapyRemote/wiki
+
+## Licensing information
+
+Use, modification and distribution is subject to the Boost Software
+License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+http://www.boost.org/LICENSE_1_0.txt)
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
new file mode 100644
index 0000000..9fac9bf
--- /dev/null
+++ b/client/CMakeLists.txt
@@ -0,0 +1,14 @@
+########################################################################
+# Build the client plugin module
+########################################################################
+SOAPY_SDR_MODULE_UTIL(
+ TARGET remoteSupport
+ SOURCES
+ Registration.cpp
+ Settings.cpp
+ Streaming.cpp
+ LogAcceptor.cpp
+ ClientStreamData.cpp
+ LIBRARIES
+ SoapySDRRemoteCommon
+)
diff --git a/client/ClientStreamData.cpp b/client/ClientStreamData.cpp
new file mode 100644
index 0000000..7f46170
--- /dev/null
+++ b/client/ClientStreamData.cpp
@@ -0,0 +1,167 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "ClientStreamData.hpp"
+#include "SoapyStreamEndpoint.hpp"
+#include <cstring> //memcpy
+#include <cassert>
+#include <cstdint>
+
+ClientStreamData::ClientStreamData(void):
+ streamId(-1),
+ endpoint(nullptr),
+ readHandle(0),
+ readElemsLeft(0),
+ scaleFactor(0.0),
+ convertType(CONVERT_MEMCPY)
+{
+ return;
+}
+
+void ClientStreamData::convertRecvBuffs(void * const *buffs, const size_t numElems)
+{
+ assert(endpoint != nullptr);
+ assert(endpoint->getElemSize() != 0);
+ assert(endpoint->getNumChans() != 0);
+ assert(not recvBuffs.empty());
+
+ switch (convertType)
+ {
+ ///////////////////////////
+ case CONVERT_MEMCPY:
+ ///////////////////////////
+ {
+ size_t elemSize = endpoint->getElemSize();
+ for (size_t i = 0; i < recvBuffs.size(); i++)
+ {
+ std::memcpy(buffs[i], recvBuffs[i], numElems*elemSize);
+ }
+ }
+ break;
+
+ ///////////////////////////
+ case CONVERT_CF32_CS16:
+ ///////////////////////////
+ {
+ const float scale = float(1.0/scaleFactor);
+ for (size_t i = 0; i < recvBuffs.size(); i++)
+ {
+ auto in = (short *)recvBuffs[i];
+ auto out = (float *)buffs[i];
+ for (size_t j = 0; j < numElems*2; j++)
+ {
+ out[j] = float(in[j])*scale;
+ }
+ }
+ }
+ break;
+
+ ///////////////////////////
+ case CONVERT_CF32_CS8:
+ ///////////////////////////
+ {
+ const float scale = float(1.0/scaleFactor);
+ for (size_t i = 0; i < recvBuffs.size(); i++)
+ {
+ auto in = (int8_t *)recvBuffs[i];
+ auto out = (float *)buffs[i];
+ for (size_t j = 0; j < numElems*2; j++)
+ {
+ out[j] = float(in[j])*scale;
+ }
+ }
+ }
+ break;
+
+ ///////////////////////////
+ case CONVERT_CF32_CU8:
+ ///////////////////////////
+ {
+ const float scale = float(1.0/scaleFactor);
+ for (size_t i = 0; i < recvBuffs.size(); i++)
+ {
+ auto in = (int8_t *)recvBuffs[i];
+ auto out = (float *)buffs[i];
+ for (size_t j = 0; j < numElems*2; j++)
+ {
+ out[j] = float(in[j]-127)*scale;
+ }
+ }
+ }
+ break;
+ }
+}
+
+void ClientStreamData::convertSendBuffs(const void * const *buffs, const size_t numElems)
+{
+ assert(endpoint != nullptr);
+ assert(endpoint->getElemSize() != 0);
+ assert(endpoint->getNumChans() != 0);
+ assert(not sendBuffs.empty());
+
+ switch (convertType)
+ {
+ ///////////////////////////
+ case CONVERT_MEMCPY:
+ ///////////////////////////
+ {
+ size_t elemSize = endpoint->getElemSize();
+ for (size_t i = 0; i < sendBuffs.size(); i++)
+ {
+ std::memcpy(sendBuffs[i], buffs[i], numElems*elemSize);
+ }
+ }
+ break;
+
+ ///////////////////////////
+ case CONVERT_CF32_CS16:
+ ///////////////////////////
+ {
+ float scale = float(scaleFactor);
+ for (size_t i = 0; i < sendBuffs.size(); i++)
+ {
+ auto in = (float *)buffs[i];
+ auto out = (short *)sendBuffs[i];
+ for (size_t j = 0; j < numElems*2; j++)
+ {
+ out[j] = short(in[j]*scale);
+ }
+ }
+ }
+ break;
+
+ ///////////////////////////
+ case CONVERT_CF32_CS8:
+ ///////////////////////////
+ {
+ float scale = float(scaleFactor);
+ for (size_t i = 0; i < sendBuffs.size(); i++)
+ {
+ auto in = (float *)buffs[i];
+ auto out = (int8_t *)sendBuffs[i];
+ for (size_t j = 0; j < numElems*2; j++)
+ {
+ out[j] = int8_t(in[j]*scale);
+ }
+ }
+ }
+ break;
+
+ ///////////////////////////
+ case CONVERT_CF32_CU8:
+ ///////////////////////////
+ {
+ float scale = float(scaleFactor);
+ for (size_t i = 0; i < sendBuffs.size(); i++)
+ {
+ auto in = (float *)buffs[i];
+ auto out = (int8_t *)sendBuffs[i];
+ for (size_t j = 0; j < numElems*2; j++)
+ {
+ out[j] = int8_t(in[j]*scale) + 127;
+ }
+ }
+ }
+ break;
+ }
+}
diff --git a/client/ClientStreamData.hpp b/client/ClientStreamData.hpp
new file mode 100644
index 0000000..84c12e5
--- /dev/null
+++ b/client/ClientStreamData.hpp
@@ -0,0 +1,52 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRPCSocket.hpp"
+#include <vector>
+#include <string>
+
+class SoapyStreamEndpoint;
+
+enum ConvertTypes
+{
+ CONVERT_MEMCPY,
+ CONVERT_CF32_CS16,
+ CONVERT_CF32_CS8,
+ CONVERT_CF32_CU8,
+};
+
+struct ClientStreamData
+{
+ ClientStreamData(void);
+
+ //string formats in use
+ std::string localFormat;
+ std::string remoteFormat;
+
+ //this ID identifies the stream to the remote host
+ int streamId;
+
+ //datagram socket for stream endpoint
+ SoapyRPCSocket streamSock;
+
+ //datagram socket for status endpoint
+ SoapyRPCSocket statusSock;
+
+ //local side of the stream endpoint
+ SoapyStreamEndpoint *endpoint;
+
+ //buffer pointers to read/write API
+ std::vector<const void *> recvBuffs;
+ std::vector<void *> sendBuffs;
+
+ //read stream remainder tracking
+ size_t readHandle;
+ size_t readElemsLeft;
+
+ //converter implementations
+ double scaleFactor;
+ ConvertTypes convertType;
+ void convertRecvBuffs(void * const *buffs, const size_t numElems);
+ void convertSendBuffs(const void * const *buffs, const size_t numElems);
+};
diff --git a/client/LogAcceptor.cpp b/client/LogAcceptor.cpp
new file mode 100644
index 0000000..9ba2520
--- /dev/null
+++ b/client/LogAcceptor.cpp
@@ -0,0 +1,181 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "LogAcceptor.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyRPCSocket.hpp"
+#include "SoapyRPCPacker.hpp"
+#include "SoapyRPCUnpacker.hpp"
+#include <SoapySDR/Logger.hpp>
+#include <csignal> //sig_atomic_t
+#include <cassert>
+#include <mutex>
+#include <thread>
+#include <map>
+
+/***********************************************************************
+ * Log acceptor thread implementation
+ **********************************************************************/
+struct LogAcceptorThreadData
+{
+ LogAcceptorThreadData(void):
+ done(true),
+ thread(nullptr),
+ useCount(0)
+ {
+ return;
+ }
+
+ ~LogAcceptorThreadData(void)
+ {
+ this->shutdown();
+ }
+
+ void activate(void);
+
+ void shutdown(void);
+
+ void handlerLoop(void);
+
+ SoapyRPCSocket client;
+ std::string url;
+ sig_atomic_t done;
+ std::thread *thread;
+ sig_atomic_t useCount;
+};
+
+void LogAcceptorThreadData::activate(void)
+{
+ client = SoapyRPCSocket();
+ int ret = client.connect(url);
+ if (ret != 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::connect() FAIL: %s", client.lastErrorMsg());
+ done = true;
+ return;
+ }
+
+ try
+ {
+ //startup forwarding
+ SoapyRPCPacker packerStart(client);
+ packerStart & SOAPY_REMOTE_START_LOG_FORWARDING;
+ packerStart();
+ SoapyRPCUnpacker unpackerStart(client);
+ done = false;
+ thread = new std::thread(&LogAcceptorThreadData::handlerLoop, this);
+ }
+ catch (const std::exception &ex)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::reactivate() ", ex.what());
+ done = true;
+ }
+}
+
+void LogAcceptorThreadData::shutdown(void)
+{
+ try
+ {
+ //shutdown forwarding (ignore reply)
+ SoapyRPCPacker packerStop(client);
+ packerStop & SOAPY_REMOTE_STOP_LOG_FORWARDING;
+ packerStop();
+
+ //graceful disconnect (ignore reply)
+ SoapyRPCPacker packerHangup(client);
+ packerHangup & SOAPY_REMOTE_HANGUP;
+ packerHangup();
+ }
+ catch (const std::exception &ex)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::shutdown() ", ex.what());
+ }
+
+ //the thread will exit due to the requests above
+ assert(thread != nullptr);
+ thread->join();
+ delete thread;
+ done = true;
+ client = SoapyRPCSocket();
+}
+
+void LogAcceptorThreadData::handlerLoop(void)
+{
+ try
+ {
+ //loop while active to relay messages to logger
+ while (true)
+ {
+ SoapyRPCUnpacker unpackerLogMsg(client);
+ if (unpackerLogMsg.done()) break; //got stop reply
+ char logLevel = 0;
+ std::string message;
+ unpackerLogMsg & logLevel;
+ unpackerLogMsg & message;
+ SoapySDR::log(SoapySDR::LogLevel(logLevel), message);
+ }
+ }
+ catch (const std::exception &ex)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyLogAcceptor::handlerLoop() ", ex.what());
+ }
+
+ done = true;
+}
+
+/***********************************************************************
+ * log acceptor threads and subscription tracking
+ **********************************************************************/
+static std::mutex logMutex;
+
+//unique server id to thread data
+static std::map<std::string, LogAcceptorThreadData> handlers;
+
+//cleanup completed and restart on errors
+static void threadMaintenance(void)
+{
+ auto it = handlers.begin();
+ while (it != handlers.end())
+ {
+ auto &data = it->second;
+
+ //first time, or error occurred
+ if (data.done) data.activate();
+
+ //no subscribers, erase
+ if (data.useCount == 0) handlers.erase(it++);
+
+ //next element
+ else ++it;
+ }
+}
+
+/***********************************************************************
+ * client subscription hooks
+ **********************************************************************/
+SoapyLogAcceptor::SoapyLogAcceptor(const std::string &url, SoapyRPCSocket &sock)
+{
+ SoapyRPCPacker packer(sock);
+ packer & SOAPY_REMOTE_GET_SERVER_ID;
+ packer();
+ SoapyRPCUnpacker unpacker(sock);
+ unpacker & _serverId;
+
+ std::lock_guard<std::mutex> lock(logMutex);
+
+ auto &data = handlers[_serverId];
+ data.useCount++;
+ data.url = url;
+
+ threadMaintenance();
+}
+
+SoapyLogAcceptor::~SoapyLogAcceptor(void)
+{
+ std::lock_guard<std::mutex> lock(logMutex);
+
+ auto &data = handlers.at(_serverId);
+ data.useCount--;
+
+ threadMaintenance();
+}
diff --git a/client/LogAcceptor.hpp b/client/LogAcceptor.hpp
new file mode 100644
index 0000000..77a868f
--- /dev/null
+++ b/client/LogAcceptor.hpp
@@ -0,0 +1,21 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include <string>
+
+class SoapyRPCSocket;
+
+/*!
+ * Create a log acceptor to subscribe to log events from the remote server.
+ * The acceptor avoids redundant threads by reference counting subscribers.
+ */
+class SoapyLogAcceptor
+{
+public:
+ SoapyLogAcceptor(const std::string &url, SoapyRPCSocket &sock);
+ ~SoapyLogAcceptor(void);
+
+private:
+ std::string _serverId;
+};
diff --git a/client/Registration.cpp b/client/Registration.cpp
new file mode 100644
index 0000000..6e66f6d
--- /dev/null
+++ b/client/Registration.cpp
@@ -0,0 +1,173 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapyClient.hpp"
+#include "LogAcceptor.hpp"
+#include "SoapySSDPEndpoint.hpp"
+#include "SoapyURLUtils.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyRPCPacker.hpp"
+#include "SoapyRPCUnpacker.hpp"
+#include <SoapySDR/Registry.hpp>
+#include <SoapySDR/Logger.hpp>
+#include <thread>
+
+/***********************************************************************
+ * Args translator for nested keywords
+ **********************************************************************/
+static SoapySDR::Kwargs translateArgs(const SoapySDR::Kwargs &args)
+{
+ SoapySDR::Kwargs argsOut;
+
+ //stop infinite loops with special keyword
+ argsOut[SOAPY_REMOTE_KWARG_STOP] = "";
+
+ //copy all non-remote keys
+ for (auto &pair : args)
+ {
+ if (pair.first == "driver") continue; //don't propagate local driver filter
+ if (pair.first == "type") continue; //don't propagate local sub-type filter
+ if (pair.first.find(SOAPY_REMOTE_KWARG_PREFIX) == std::string::npos)
+ {
+ argsOut[pair.first] = pair.second;
+ }
+ }
+
+ //write all remote keys with prefix stripped
+ for (auto &pair : args)
+ {
+ if (pair.first.find(SOAPY_REMOTE_KWARG_PREFIX) == 0)
+ {
+ static const size_t offset = std::string(SOAPY_REMOTE_KWARG_PREFIX).size();
+ argsOut[pair.first.substr(offset)] = pair.second;
+ }
+ }
+
+ return argsOut;
+}
+
+/***********************************************************************
+ * Discovery routine -- connect to server when key specified
+ **********************************************************************/
+static std::vector<SoapySDR::Kwargs> findRemote(const SoapySDR::Kwargs &args)
+{
+ std::vector<SoapySDR::Kwargs> result;
+
+ if (args.count(SOAPY_REMOTE_KWARG_STOP) != 0) return result;
+
+ //no remote specified, use the discovery protocol
+ if (args.count("remote") == 0)
+ {
+ //On non-windows platforms the endpoint instance can last the
+ //duration of the process because it can be cleaned up safely.
+ //Windows has issues cleaning up threads and sockets on exit.
+ #ifndef _MSC_VER
+ static
+ #endif //_MSC_VER
+ auto ssdpEndpoint = SoapySSDPEndpoint::getInstance();
+
+ //enable forces new search queries
+ ssdpEndpoint->enablePeriodicSearch(true);
+
+ //wait maximum timeout for replies
+ std::this_thread::sleep_for(std::chrono::microseconds(SOAPY_REMOTE_SOCKET_TIMEOUT_US));
+
+ for (const auto &url : SoapySSDPEndpoint::getInstance()->getServerURLs())
+ {
+ auto argsWithURL = args;
+ argsWithURL["remote"] = url;
+ const auto subResult = findRemote(argsWithURL);
+ result.insert(result.end(), subResult.begin(), subResult.end());
+ }
+
+ return result;
+ }
+
+ //otherwise connect to a specific url and enumerate
+ auto url = SoapyURL(args.at("remote"));
+
+ //default url parameters when not specified
+ if (url.getScheme().empty()) url.setScheme("tcp");
+ if (url.getService().empty()) url.setService(SOAPY_REMOTE_DEFAULT_SERVICE);
+
+ //try to connect to the remote server
+ SoapySocketSession sess;
+ SoapyRPCSocket s;
+ int ret = s.connect(url.toString());
+ if (ret != 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyRemote::find() -- connect(%s) FAIL: %s", url.toString().c_str(), s.lastErrorMsg());
+ return result;
+ }
+
+ //find transaction
+ try
+ {
+ SoapyLogAcceptor logAcceptor(url.toString(), s);
+
+ SoapyRPCPacker packer(s);
+ packer & SOAPY_REMOTE_FIND;
+ packer & translateArgs(args);
+ packer();
+ SoapyRPCUnpacker unpacker(s);
+ unpacker & result;
+
+ //graceful disconnect
+ SoapyRPCPacker packerHangup(s);
+ packerHangup & SOAPY_REMOTE_HANGUP;
+ packerHangup();
+ SoapyRPCUnpacker unpackerHangup(s);
+ }
+ catch (const std::exception &ex)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyRemote::find() -- transact FAIL: %s", ex.what());
+ }
+
+ //remove instances of the stop key from the result
+ for (auto &resultArgs : result)
+ {
+ resultArgs.erase(SOAPY_REMOTE_KWARG_STOP);
+ if (resultArgs.count("driver") != 0)
+ {
+ resultArgs["remote:driver"] = resultArgs.at("driver");
+ resultArgs.erase("driver");
+ }
+ if (resultArgs.count("type") != 0)
+ {
+ resultArgs["remote:type"] = resultArgs.at("type");
+ resultArgs.erase("type");
+ }
+ resultArgs["remote"] = url.toString();
+ }
+
+ return result;
+}
+
+/***********************************************************************
+ * Factory routine -- connect to server and create remote device
+ **********************************************************************/
+static SoapySDR::Device *makeRemote(const SoapySDR::Kwargs &args)
+{
+ if (args.count(SOAPY_REMOTE_KWARG_STOP) != 0) //probably wont happen
+ {
+ throw std::runtime_error("SoapyRemoteDevice() -- factory loop");
+ }
+
+ if (args.count("remote") == 0)
+ {
+ throw std::runtime_error("SoapyRemoteDevice() -- missing URL");
+ }
+
+ auto url = SoapyURL(args.at("remote"));
+
+ //default url parameters when not specified
+ if (url.getScheme().empty()) url.setScheme("tcp");
+ if (url.getService().empty()) url.setService(SOAPY_REMOTE_DEFAULT_SERVICE);
+
+ return new SoapyRemoteDevice(url.toString(), translateArgs(args));
+}
+
+/***********************************************************************
+ * Registration
+ **********************************************************************/
+static SoapySDR::Registry registerRemote("remote", &findRemote, &makeRemote, SOAPY_SDR_ABI_VERSION);
diff --git a/client/Settings.cpp b/client/Settings.cpp
new file mode 100644
index 0000000..3e1fd93
--- /dev/null
+++ b/client/Settings.cpp
@@ -0,0 +1,1360 @@
+// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapyClient.hpp"
+#include "LogAcceptor.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyRPCPacker.hpp"
+#include "SoapyRPCUnpacker.hpp"
+#include <SoapySDR/Logger.hpp>
+#include <stdexcept>
+
+//lazy fix for the const call issue -- FIXME
+#define _mutex const_cast<std::mutex &>(_mutex)
+#define _sock const_cast<SoapyRPCSocket &>(_sock)
+
+/*******************************************************************
+ * Constructor
+ ******************************************************************/
+
+SoapyRemoteDevice::SoapyRemoteDevice(const std::string &url, const SoapySDR::Kwargs &args):
+ _logAcceptor(nullptr)
+{
+ //try to connect to the remote server
+ int ret = _sock.connect(url);
+ if (ret != 0)
+ {
+ throw std::runtime_error("SoapyRemoteDevice("+url+") -- connect FAIL: " + _sock.lastErrorMsg());
+ }
+
+ //connect the log acceptor
+ _logAcceptor = new SoapyLogAcceptor(url, _sock);
+
+ //acquire device instance
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_MAKE;
+ packer & args;
+ packer();
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+SoapyRemoteDevice::~SoapyRemoteDevice(void)
+{
+ //cant throw in the destructor
+ try
+ {
+ //release device instance
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_UNMAKE;
+ packer();
+ SoapyRPCUnpacker unpacker(_sock);
+
+ //graceful disconnect
+ SoapyRPCPacker packerHangup(_sock);
+ packerHangup & SOAPY_REMOTE_HANGUP;
+ packerHangup();
+ SoapyRPCUnpacker unpackerHangup(_sock);
+ }
+ catch (const std::exception &ex)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "~SoapyRemoteDevice() FAIL: %s", ex.what());
+ }
+
+ //disconnect the log acceptor (does not throw)
+ delete _logAcceptor;
+}
+
+/*******************************************************************
+ * Identification API
+ ******************************************************************/
+
+std::string SoapyRemoteDevice::getDriverKey(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_DRIVER_KEY;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+std::string SoapyRemoteDevice::getHardwareKey(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_HARDWARE_KEY;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::Kwargs SoapyRemoteDevice::getHardwareInfo(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_HARDWARE_INFO;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::Kwargs result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Channels API
+ ******************************************************************/
+
+void SoapyRemoteDevice::setFrontendMapping(const int direction, const std::string &mapping)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_FRONTEND_MAPPING;
+ packer & char(direction);
+ packer & mapping;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::string SoapyRemoteDevice::getFrontendMapping(const int direction) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_FRONTEND_MAPPING;
+ packer & char(direction);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+size_t SoapyRemoteDevice::getNumChannels(const int direction) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_NUM_CHANNELS;
+ packer & char(direction);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ int result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::Kwargs SoapyRemoteDevice::getChannelInfo(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_CHANNEL_INFO;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::Kwargs result;
+ unpacker & result;
+ return result;
+}
+
+bool SoapyRemoteDevice::getFullDuplex(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_FULL_DUPLEX;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ bool result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Antenna API
+ ******************************************************************/
+
+std::vector<std::string> SoapyRemoteDevice::listAntennas(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_ANTENNAS;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setAntenna(const int direction, const size_t channel, const std::string &name)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_ANTENNA;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::string SoapyRemoteDevice::getAntenna(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_ANTENNA;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Frontend corrections API
+ ******************************************************************/
+
+bool SoapyRemoteDevice::hasDCOffsetMode(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_HAS_DC_OFFSET_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ bool result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setDCOffsetMode(const int direction, const size_t channel, const bool automatic)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_DC_OFFSET_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer & automatic;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+bool SoapyRemoteDevice::getDCOffsetMode(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_DC_OFFSET_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ bool result;
+ unpacker & result;
+ return result;
+}
+
+bool SoapyRemoteDevice::hasDCOffset(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_HAS_DC_OFFSET;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ bool result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setDCOffset(const int direction, const size_t channel, const std::complex<double> &offset)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_DC_OFFSET;
+ packer & char(direction);
+ packer & int(channel);
+ packer & offset;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::complex<double> SoapyRemoteDevice::getDCOffset(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_DC_OFFSET;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::complex<double> result;
+ unpacker & result;
+ return result;
+}
+
+bool SoapyRemoteDevice::hasIQBalance(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_HAS_IQ_BALANCE_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ bool result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setIQBalance(const int direction, const size_t channel, const std::complex<double> &balance)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_IQ_BALANCE_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer & balance;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::complex<double> SoapyRemoteDevice::getIQBalance(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_IQ_BALANCE_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::complex<double> result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Gain API
+ ******************************************************************/
+
+std::vector<std::string> SoapyRemoteDevice::listGains(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_GAINS;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+bool SoapyRemoteDevice::hasGainMode(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_HAS_GAIN_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ bool result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setGainMode(const int direction, const size_t channel, const bool automatic)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_GAIN_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer & automatic;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+bool SoapyRemoteDevice::getGainMode(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_GAIN_MODE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ bool result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setGain(const int direction, const size_t channel, const double value)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_GAIN;
+ packer & char(direction);
+ packer & int(channel);
+ packer & value;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+void SoapyRemoteDevice::setGain(const int direction, const size_t channel, const std::string &name, const double value)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_GAIN_ELEMENT;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer & value;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+double SoapyRemoteDevice::getGain(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_GAIN;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ double result;
+ unpacker & result;
+ return result;
+}
+
+double SoapyRemoteDevice::getGain(const int direction, const size_t channel, const std::string &name) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_GAIN_ELEMENT;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ double result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::Range SoapyRemoteDevice::getGainRange(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_GAIN_RANGE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::Range result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::Range SoapyRemoteDevice::getGainRange(const int direction, const size_t channel, const std::string &name) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_GAIN_RANGE_ELEMENT;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::Range result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Frequency API
+ ******************************************************************/
+
+void SoapyRemoteDevice::setFrequency(const int direction, const size_t channel, const double frequency, const SoapySDR::Kwargs &args)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_FREQUENCY;
+ packer & char(direction);
+ packer & int(channel);
+ packer & frequency;
+ packer & args;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+void SoapyRemoteDevice::setFrequency(const int direction, const size_t channel, const std::string &name, const double frequency, const SoapySDR::Kwargs &args)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_FREQUENCY_COMPONENT;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer & frequency;
+ packer & args;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+double SoapyRemoteDevice::getFrequency(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_FREQUENCY;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ double result;
+ unpacker & result;
+ return result;
+}
+
+double SoapyRemoteDevice::getFrequency(const int direction, const size_t channel, const std::string &name) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_FREQUENCY_COMPONENT;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ double result;
+ unpacker & result;
+ return result;
+}
+
+std::vector<std::string> SoapyRemoteDevice::listFrequencies(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_FREQUENCIES;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::RangeList SoapyRemoteDevice::getFrequencyRange(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_FREQUENCY_RANGE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::RangeList result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::RangeList SoapyRemoteDevice::getFrequencyRange(const int direction, const size_t channel, const std::string &name) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_FREQUENCY_RANGE_COMPONENT;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::RangeList result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::ArgInfoList SoapyRemoteDevice::getFrequencyArgsInfo(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_FREQUENCY_ARGS_INFO;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::ArgInfoList result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Sample Rate API
+ ******************************************************************/
+
+void SoapyRemoteDevice::setSampleRate(const int direction, const size_t channel, const double rate)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_SAMPLE_RATE;
+ packer & char(direction);
+ packer & int(channel);
+ packer & rate;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+double SoapyRemoteDevice::getSampleRate(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_SAMPLE_RATE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ double result;
+ unpacker & result;
+ return result;
+}
+
+std::vector<double> SoapyRemoteDevice::listSampleRates(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_SAMPLE_RATES;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<double> result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Bandwidth API
+ ******************************************************************/
+
+void SoapyRemoteDevice::setBandwidth(const int direction, const size_t channel, const double bw)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_BANDWIDTH;
+ packer & char(direction);
+ packer & int(channel);
+ packer & bw;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+double SoapyRemoteDevice::getBandwidth(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_BANDWIDTH;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ double result;
+ unpacker & result;
+ return result;
+}
+
+std::vector<double> SoapyRemoteDevice::listBandwidths(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_BANDWIDTHS;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<double> result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::RangeList SoapyRemoteDevice::getBandwidthRange(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_BANDWIDTH_RANGE;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::RangeList result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Clocking API
+ ******************************************************************/
+
+void SoapyRemoteDevice::setMasterClockRate(const double rate)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_MASTER_CLOCK_RATE;
+ packer & rate;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+double SoapyRemoteDevice::getMasterClockRate(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_MASTER_CLOCK_RATE;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ double result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::RangeList SoapyRemoteDevice::getMasterClockRates(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_MASTER_CLOCK_RATES;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::RangeList result;
+ unpacker & result;
+ return result;
+}
+
+std::vector<std::string> SoapyRemoteDevice::listClockSources(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_CLOCK_SOURCES;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setClockSource(const std::string &source)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_CLOCK_SOURCE;
+ packer & source;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::string SoapyRemoteDevice::getClockSource(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_CLOCK_SOURCE;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Time API
+ ******************************************************************/
+
+std::vector<std::string> SoapyRemoteDevice::listTimeSources(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_TIME_SOURCES;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setTimeSource(const std::string &source)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_TIME_SOURCE;
+ packer & source;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::string SoapyRemoteDevice::getTimeSource(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_TIME_SOURCE;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+bool SoapyRemoteDevice::hasHardwareTime(const std::string &what) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_HAS_HARDWARE_TIME;
+ packer & what;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ bool result;
+ unpacker & result;
+ return result;
+}
+
+long long SoapyRemoteDevice::getHardwareTime(const std::string &what) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_HARDWARE_TIME;
+ packer & what;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ long long result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::setHardwareTime(const long long timeNs, const std::string &what)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_HARDWARE_TIME;
+ packer & timeNs;
+ packer & what;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+void SoapyRemoteDevice::setCommandTime(const long long timeNs, const std::string &what)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SET_COMMAND_TIME;
+ packer & timeNs;
+ packer & what;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+/*******************************************************************
+ * Sensor API
+ ******************************************************************/
+
+std::vector<std::string> SoapyRemoteDevice::listSensors(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_SENSORS;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::ArgInfo SoapyRemoteDevice::getSensorInfo(const std::string &name) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_SENSOR_INFO;
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::ArgInfo result;
+ unpacker & result;
+ return result;
+}
+
+std::string SoapyRemoteDevice::readSensor(const std::string &name) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_SENSOR;
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+std::vector<std::string> SoapyRemoteDevice::listSensors(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_CHANNEL_SENSORS;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::ArgInfo SoapyRemoteDevice::getSensorInfo(const int direction, const size_t channel, const std::string &name) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_CHANNEL_SENSOR_INFO;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::ArgInfo result;
+ unpacker & result;
+ return result;
+}
+
+std::string SoapyRemoteDevice::readSensor(const int direction, const size_t channel, const std::string &name) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_CHANNEL_SENSOR;
+ packer & char(direction);
+ packer & int(channel);
+ packer & name;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * Register API
+ ******************************************************************/
+
+std::vector<std::string> SoapyRemoteDevice::listRegisterInterfaces(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_REGISTER_INTERFACES;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::writeRegister(const std::string &name, const unsigned addr, const unsigned value)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_REGISTER_NAMED;
+ packer & name;
+ packer & int(addr);
+ packer & int(value);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+unsigned SoapyRemoteDevice::readRegister(const std::string &name, const unsigned addr) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_REGISTER_NAMED;
+ packer & name;
+ packer & int(addr);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ int result;
+ unpacker & result;
+ return unsigned(result);
+}
+
+void SoapyRemoteDevice::writeRegister(const unsigned addr, const unsigned value)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_REGISTER;
+ packer & int(addr);
+ packer & int(value);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+unsigned SoapyRemoteDevice::readRegister(const unsigned addr) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_REGISTER;
+ packer & int(addr);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ int result;
+ unpacker & result;
+ return unsigned(result);
+}
+
+/*******************************************************************
+ * Settings API
+ ******************************************************************/
+
+SoapySDR::ArgInfoList SoapyRemoteDevice::getSettingInfo(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_SETTING_INFO;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::ArgInfoList result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::writeSetting(const std::string &key, const std::string &value)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_SETTING;
+ packer & key;
+ packer & value;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::string SoapyRemoteDevice::readSetting(const std::string &key) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_SETTING;
+ packer & key;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+SoapySDR::ArgInfoList SoapyRemoteDevice::getSettingInfo(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_CHANNEL_SETTING_INFO;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapySDR::ArgInfoList result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::writeSetting(const int direction, const size_t channel, const std::string &key, const std::string &value)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_CHANNEL_SETTING;
+ packer & char(direction);
+ packer & int(channel);
+ packer & key;
+ packer & value;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::string SoapyRemoteDevice::readSetting(const int direction, const size_t channel, const std::string &key) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_CHANNEL_SETTING;
+ packer & char(direction);
+ packer & int(channel);
+ packer & key;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * GPIO API
+ ******************************************************************/
+
+std::vector<std::string> SoapyRemoteDevice::listGPIOBanks(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_GPIO_BANKS;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::writeGPIO(const std::string &bank, const unsigned value)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_GPIO;
+ packer & bank;
+ packer & int(value);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+void SoapyRemoteDevice::writeGPIO(const std::string &bank, const unsigned value, const unsigned mask)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_GPIO_MASKED;
+ packer & bank;
+ packer & int(value);
+ packer & int(mask);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+unsigned SoapyRemoteDevice::readGPIO(const std::string &bank) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_GPIO;
+ packer & bank;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ int result;
+ unpacker & result;
+ return unsigned(result);
+}
+
+void SoapyRemoteDevice::writeGPIODir(const std::string &bank, const unsigned dir)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_GPIO_DIR;
+ packer & bank;
+ packer & int(dir);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+void SoapyRemoteDevice::writeGPIODir(const std::string &bank, const unsigned dir, const unsigned mask)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_GPIO_DIR_MASKED;
+ packer & bank;
+ packer & int(dir);
+ packer & int(mask);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+unsigned SoapyRemoteDevice::readGPIODir(const std::string &bank) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_GPIO_DIR;
+ packer & bank;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ int result;
+ unpacker & result;
+ return unsigned(result);
+}
+
+/*******************************************************************
+ * I2C API
+ ******************************************************************/
+
+void SoapyRemoteDevice::writeI2C(const int addr, const std::string &data)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_I2C;
+ packer & int(addr);
+ packer & data;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::string SoapyRemoteDevice::readI2C(const int addr, const size_t numBytes)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_I2C;
+ packer & int(addr);
+ packer & int(numBytes);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
+
+/*******************************************************************
+ * SPI API
+ ******************************************************************/
+
+unsigned SoapyRemoteDevice::transactSPI(const int addr, const unsigned data, const size_t numBits)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_TRANSACT_SPI;
+ packer & int(addr);
+ packer & int(data);
+ packer & int(numBits);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ int result;
+ unpacker & result;
+ return unsigned(result);
+}
+
+/*******************************************************************
+ * UART API
+ ******************************************************************/
+
+std::vector<std::string> SoapyRemoteDevice::listUARTs(void) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_LIST_UARTS;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+void SoapyRemoteDevice::writeUART(const std::string &which, const std::string &data)
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_WRITE_UART;
+ packer & which;
+ packer & data;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+}
+
+std::string SoapyRemoteDevice::readUART(const std::string &which, const long timeoutUs) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_READ_UART;
+ packer & which;
+ packer & int(timeoutUs);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ return result;
+}
diff --git a/client/SoapyClient.hpp b/client/SoapyClient.hpp
new file mode 100644
index 0000000..8e5eb62
--- /dev/null
+++ b/client/SoapyClient.hpp
@@ -0,0 +1,357 @@
+// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRPCSocket.hpp"
+#include <SoapySDR/Device.hpp>
+#include <mutex>
+
+class SoapyLogAcceptor;
+
+class SoapyRemoteDevice : public SoapySDR::Device
+{
+public:
+ SoapyRemoteDevice(const std::string &url, const SoapySDR::Kwargs &args);
+
+ ~SoapyRemoteDevice(void);
+
+ /*******************************************************************
+ * Identification API
+ ******************************************************************/
+
+ std::string getDriverKey(void) const;
+
+ std::string getHardwareKey(void) const;
+
+ SoapySDR::Kwargs getHardwareInfo(void) const;
+
+ /*******************************************************************
+ * Channels API
+ ******************************************************************/
+
+ void setFrontendMapping(const int direction, const std::string &mapping);
+
+ std::string getFrontendMapping(const int direction) const;
+
+ size_t getNumChannels(const int direction) const;
+
+ SoapySDR::Kwargs getChannelInfo(const int direction, const size_t channel) const;
+
+ bool getFullDuplex(const int direction, const size_t channel) const;
+
+ /*******************************************************************
+ * Stream API
+ ******************************************************************/
+
+ std::vector<std::string> __getRemoteOnlyStreamFormats(const int direction, const size_t channel) const;
+
+ std::vector<std::string> getStreamFormats(const int direction, const size_t channel) const;
+
+ std::string getNativeStreamFormat(const int direction, const size_t channel, double &fullScale) const;
+
+ SoapySDR::ArgInfoList getStreamArgsInfo(const int direction, const size_t channel) const;
+
+ SoapySDR::Stream *setupStream(
+ const int direction,
+ const std::string &format,
+ const std::vector<size_t> &channels,
+ const SoapySDR::Kwargs &args);
+
+ void closeStream(SoapySDR::Stream *stream);
+
+ size_t getStreamMTU(SoapySDR::Stream *stream) const;
+
+ int activateStream(
+ SoapySDR::Stream *stream,
+ const int flags,
+ const long long timeNs,
+ const size_t numElems);
+
+ int deactivateStream(
+ SoapySDR::Stream *stream,
+ const int flags,
+ const long long timeNs);
+
+ int readStream(
+ SoapySDR::Stream *stream,
+ void * const *buffs,
+ const size_t numElems,
+ int &flags,
+ long long &timeNs,
+ const long timeoutUs);
+
+ int writeStream(
+ SoapySDR::Stream *stream,
+ const void * const *buffs,
+ const size_t numElems,
+ int &flags,
+ const long long timeNs,
+ const long timeoutUs);
+
+ int readStreamStatus(
+ SoapySDR::Stream *stream,
+ size_t &chanMask,
+ int &flags,
+ long long &timeNs,
+ const long timeoutUs);
+
+ /*******************************************************************
+ * Direct buffer access API
+ ******************************************************************/
+
+ size_t getNumDirectAccessBuffers(SoapySDR::Stream *stream);
+
+ int getDirectAccessBufferAddrs(SoapySDR::Stream *stream, const size_t handle, void **buffs);
+
+ int acquireReadBuffer(
+ SoapySDR::Stream *stream,
+ size_t &handle,
+ const void **buffs,
+ int &flags,
+ long long &timeNs,
+ const long timeoutUs);
+
+ void releaseReadBuffer(
+ SoapySDR::Stream *stream,
+ const size_t handle);
+
+ int acquireWriteBuffer(
+ SoapySDR::Stream *stream,
+ size_t &handle,
+ void **buffs,
+ const long timeoutUs);
+
+ void releaseWriteBuffer(
+ SoapySDR::Stream *stream,
+ const size_t handle,
+ const size_t numElems,
+ int &flags,
+ const long long timeNs);
+
+ /*******************************************************************
+ * Antenna API
+ ******************************************************************/
+
+ std::vector<std::string> listAntennas(const int direction, const size_t channel) const;
+
+ void setAntenna(const int direction, const size_t channel, const std::string &name);
+
+ std::string getAntenna(const int direction, const size_t channel) const;
+
+ /*******************************************************************
+ * Frontend corrections API
+ ******************************************************************/
+
+ bool hasDCOffsetMode(const int direction, const size_t channel) const;
+
+ void setDCOffsetMode(const int direction, const size_t channel, const bool automatic);
+
+ bool getDCOffsetMode(const int direction, const size_t channel) const;
+
+ bool hasDCOffset(const int direction, const size_t channel) const;
+
+ void setDCOffset(const int direction, const size_t channel, const std::complex<double> &offset);
+
+ std::complex<double> getDCOffset(const int direction, const size_t channel) const;
+
+ bool hasIQBalance(const int direction, const size_t channel) const;
+
+ void setIQBalance(const int direction, const size_t channel, const std::complex<double> &balance);
+
+ std::complex<double> getIQBalance(const int direction, const size_t channel) const;
+
+ /*******************************************************************
+ * Gain API
+ ******************************************************************/
+
+ std::vector<std::string> listGains(const int direction, const size_t channel) const;
+
+ bool hasGainMode(const int direction, const size_t channel) const;
+
+ void setGainMode(const int direction, const size_t channel, const bool automatic);
+
+ bool getGainMode(const int direction, const size_t channel) const;
+
+ void setGain(const int direction, const size_t channel, const double value);
+
+ void setGain(const int direction, const size_t channel, const std::string &name, const double value);
+
+ double getGain(const int direction, const size_t channel) const;
+
+ double getGain(const int direction, const size_t channel, const std::string &name) const;
+
+ SoapySDR::Range getGainRange(const int direction, const size_t channel) const;
+
+ SoapySDR::Range getGainRange(const int direction, const size_t channel, const std::string &name) const;
+
+ /*******************************************************************
+ * Frequency API
+ ******************************************************************/
+
+ void setFrequency(const int direction, const size_t channel, const double frequency, const SoapySDR::Kwargs &args);
+
+ void setFrequency(const int direction, const size_t channel, const std::string &name, const double frequency, const SoapySDR::Kwargs &args);
+
+ double getFrequency(const int direction, const size_t channel) const;
+
+ double getFrequency(const int direction, const size_t channel, const std::string &name) const;
+
+ std::vector<std::string> listFrequencies(const int direction, const size_t channel) const;
+
+ SoapySDR::RangeList getFrequencyRange(const int direction, const size_t channel) const;
+
+ SoapySDR::RangeList getFrequencyRange(const int direction, const size_t channel, const std::string &name) const;
+
+ SoapySDR::ArgInfoList getFrequencyArgsInfo(const int direction, const size_t channel) const;
+
+ /*******************************************************************
+ * Sample Rate API
+ ******************************************************************/
+
+ void setSampleRate(const int direction, const size_t channel, const double rate);
+
+ double getSampleRate(const int direction, const size_t channel) const;
+
+ std::vector<double> listSampleRates(const int direction, const size_t channel) const;
+
+ /*******************************************************************
+ * Bandwidth API
+ ******************************************************************/
+
+ void setBandwidth(const int direction, const size_t channel, const double bw);
+
+ double getBandwidth(const int direction, const size_t channel) const;
+
+ std::vector<double> listBandwidths(const int direction, const size_t channel) const;
+
+ SoapySDR::RangeList getBandwidthRange(const int direction, const size_t channel) const;
+
+ /*******************************************************************
+ * Clocking API
+ ******************************************************************/
+
+ void setMasterClockRate(const double rate);
+
+ double getMasterClockRate(void) const;
+
+ SoapySDR::RangeList getMasterClockRates(void) const;
+
+ std::vector<std::string> listClockSources(void) const;
+
+ void setClockSource(const std::string &source);
+
+ std::string getClockSource(void) const;
+
+ /*******************************************************************
+ * Time API
+ ******************************************************************/
+
+ std::vector<std::string> listTimeSources(void) const;
+
+ void setTimeSource(const std::string &source);
+
+ std::string getTimeSource(void) const;
+
+ bool hasHardwareTime(const std::string &what) const;
+
+ long long getHardwareTime(const std::string &what) const;
+
+ void setHardwareTime(const long long timeNs, const std::string &what);
+
+ void setCommandTime(const long long timeNs, const std::string &what);
+
+ /*******************************************************************
+ * Sensor API
+ ******************************************************************/
+
+ std::vector<std::string> listSensors(void) const;
+
+ SoapySDR::ArgInfo getSensorInfo(const std::string &name) const;
+
+ std::string readSensor(const std::string &name) const;
+
+ std::vector<std::string> listSensors(const int direction, const size_t channel) const;
+
+ SoapySDR::ArgInfo getSensorInfo(const int direction, const size_t channel, const std::string &name) const;
+
+ std::string readSensor(const int direction, const size_t channel, const std::string &name) const;
+
+ /*******************************************************************
+ * Register API
+ ******************************************************************/
+
+ std::vector<std::string> listRegisterInterfaces(void) const;
+
+ void writeRegister(const std::string &name, const unsigned addr, const unsigned value);
+
+ unsigned readRegister(const std::string &name, const unsigned addr) const;
+
+ void writeRegister(const unsigned addr, const unsigned value);
+
+ unsigned readRegister(const unsigned addr) const;
+
+ /*******************************************************************
+ * Settings API
+ ******************************************************************/
+
+ SoapySDR::ArgInfoList getSettingInfo(void) const;
+
+ void writeSetting(const std::string &key, const std::string &value);
+
+ std::string readSetting(const std::string &key) const;
+
+ SoapySDR::ArgInfoList getSettingInfo(const int direction, const size_t channel) const;
+
+ void writeSetting(const int direction, const size_t channel, const std::string &key, const std::string &value);
+
+ std::string readSetting(const int direction, const size_t channel, const std::string &key) const;
+
+ /*******************************************************************
+ * GPIO API
+ ******************************************************************/
+
+ std::vector<std::string> listGPIOBanks(void) const;
+
+ void writeGPIO(const std::string &bank, const unsigned value);
+
+ void writeGPIO(const std::string &bank, const unsigned value, const unsigned mask);
+
+ unsigned readGPIO(const std::string &bank) const;
+
+ void writeGPIODir(const std::string &bank, const unsigned dir);
+
+ void writeGPIODir(const std::string &bank, const unsigned dir, const unsigned mask);
+
+ unsigned readGPIODir(const std::string &bank) const;
+
+ /*******************************************************************
+ * I2C API
+ ******************************************************************/
+
+ void writeI2C(const int addr, const std::string &data);
+
+ std::string readI2C(const int addr, const size_t numBytes);
+
+ /*******************************************************************
+ * SPI API
+ ******************************************************************/
+
+ unsigned transactSPI(const int addr, const unsigned data, const size_t numBits);
+
+ /*******************************************************************
+ * UART API
+ ******************************************************************/
+
+ std::vector<std::string> listUARTs(void) const;
+
+ void writeUART(const std::string &which, const std::string &data);
+
+ std::string readUART(const std::string &which, const long timeoutUs) const;
+
+private:
+ SoapySocketSession _sess;
+ SoapyRPCSocket _sock;
+ SoapyLogAcceptor *_logAcceptor;
+ std::mutex _mutex;
+};
diff --git a/client/Streaming.cpp b/client/Streaming.cpp
new file mode 100644
index 0000000..c143368
--- /dev/null
+++ b/client/Streaming.cpp
@@ -0,0 +1,466 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Logger.hpp>
+#include <SoapySDR/Formats.hpp>
+#include "SoapyClient.hpp"
+#include "ClientStreamData.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyURLUtils.hpp"
+#include "SoapyRPCPacker.hpp"
+#include "SoapyRPCUnpacker.hpp"
+#include "SoapyStreamEndpoint.hpp"
+#include <algorithm> //std::min, std::find
+
+//lazy fix for the const call issue -- FIXME
+#define _mutex const_cast<std::mutex &>(_mutex)
+#define _sock const_cast<SoapyRPCSocket &>(_sock)
+
+std::vector<std::string> SoapyRemoteDevice::__getRemoteOnlyStreamFormats(const int direction, const size_t channel) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_STREAM_FORMATS;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::vector<std::string> result;
+ unpacker & result;
+ return result;
+}
+
+std::vector<std::string> SoapyRemoteDevice::getStreamFormats(const int direction, const size_t channel) const
+{
+ auto formats = __getRemoteOnlyStreamFormats(direction, channel);
+
+ //add complex floats when a conversion is possible
+ const bool hasCF32 = std::find(formats.begin(), formats.end(), SOAPY_SDR_CF32) != formats.end();
+ const bool hasCS16 = std::find(formats.begin(), formats.end(), SOAPY_SDR_CS16) != formats.end();
+ const bool hasCS8 = std::find(formats.begin(), formats.end(), SOAPY_SDR_CS8) != formats.end();
+ const bool hasCU8 = std::find(formats.begin(), formats.end(), SOAPY_SDR_CU8) != formats.end();
+ if (not hasCF32 and (hasCS16 or hasCS8 or hasCU8)) formats.push_back(SOAPY_SDR_CF32);
+
+ return formats;
+}
+
+std::string SoapyRemoteDevice::getNativeStreamFormat(const int direction, const size_t channel, double &fullScale) const
+{
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_NATIVE_STREAM_FORMAT;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string result;
+ unpacker & result;
+ unpacker & fullScale;
+ return result;
+}
+
+SoapySDR::ArgInfoList SoapyRemoteDevice::getStreamArgsInfo(const int direction, const size_t channel) const
+{
+ //get the remote arguments first (careful with lock scope)
+ SoapySDR::ArgInfoList result;
+ {
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_GET_STREAM_ARGS_INFO;
+ packer & char(direction);
+ packer & int(channel);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ unpacker & result;
+ }
+
+ //insert SoapyRemote stream arguments
+ double fullScale = 0.0;
+ SoapySDR::ArgInfo formatArg;
+ formatArg.key = "remote:format";
+ formatArg.value = this->getNativeStreamFormat(direction, channel, fullScale);
+ formatArg.name = "Remote Format";
+ formatArg.description = "The stream format used on the remote device.";
+ formatArg.type = SoapySDR::ArgInfo::STRING;
+ formatArg.options = __getRemoteOnlyStreamFormats(direction, channel);
+ result.push_back(formatArg);
+
+ SoapySDR::ArgInfo scaleArg;
+ scaleArg.key = "remote:scale";
+ scaleArg.value = std::to_string(fullScale);
+ scaleArg.name = "Remote Scale";
+ scaleArg.description = "The factor used to scale remote samples to full-scale floats.";
+ scaleArg.type = SoapySDR::ArgInfo::FLOAT;
+ result.push_back(scaleArg);
+
+ SoapySDR::ArgInfo mtuArg;
+ mtuArg.key = "remote:mtu";
+ mtuArg.value = std::to_string(SOAPY_REMOTE_DEFAULT_ENDPOINT_MTU);
+ mtuArg.name = "Remote MTU";
+ mtuArg.units = "bytes";
+ mtuArg.description = "The maximum datagram transfer size in bytes.";
+ mtuArg.type = SoapySDR::ArgInfo::INT;
+ result.push_back(mtuArg);
+
+ SoapySDR::ArgInfo windowArg;
+ windowArg.key = "remote:window";
+ windowArg.value = std::to_string(SOAPY_REMOTE_DEFAULT_ENDPOINT_WINDOW);
+ windowArg.name = "Remote Window";
+ windowArg.units = "bytes";
+ windowArg.description = "The size of the kernel socket buffer in bytes.";
+ windowArg.type = SoapySDR::ArgInfo::INT;
+ result.push_back(windowArg);
+
+ SoapySDR::ArgInfo priorityArg;
+ priorityArg.key = "remote:priority";
+ priorityArg.value = std::to_string(SOAPY_REMOTE_DEFAULT_THREAD_PRIORITY);
+ priorityArg.name = "Remote Priority";
+ priorityArg.description = "Specify the scheduling priority of the server forwarding threads.";
+ priorityArg.type = SoapySDR::ArgInfo::FLOAT;
+ priorityArg.range = SoapySDR::Range(-1.0, 1.0);
+ result.push_back(priorityArg);
+
+ return result;
+}
+
+SoapySDR::Stream *SoapyRemoteDevice::setupStream(
+ const int direction,
+ const std::string &localFormat,
+ const std::vector<size_t> &channels_,
+ const SoapySDR::Kwargs &args)
+{
+ //default to channel 0 when not specified
+ //the channels vector cannot be empty
+ //its used for stream endpoint allocation
+ auto channels = channels_;
+ if (channels.empty()) channels.push_back(0);
+
+ //use the remote device's native stream format and scale factor when the conversion is supported
+ double nativeScaleFactor = 0.0;
+ auto nativeFormat = this->getNativeStreamFormat(direction, channels.front(), nativeScaleFactor);
+ const bool useNative = (localFormat == nativeFormat) or
+ (localFormat == SOAPY_SDR_CF32 and nativeFormat == SOAPY_SDR_CS16) or
+ (localFormat == SOAPY_SDR_CF32 and nativeFormat == SOAPY_SDR_CS8) or
+ (localFormat == SOAPY_SDR_CF32 and nativeFormat == SOAPY_SDR_CU8);
+
+ //use the native format when the conversion is supported,
+ //otherwise use the client's local format for the default
+ auto remoteFormat = useNative?nativeFormat:localFormat;
+ const auto remoteFormatIt = args.find(SOAPY_REMOTE_KWARG_FORMAT);
+ if (remoteFormatIt != args.end()) remoteFormat = remoteFormatIt->second;
+
+ //use the native scale factor when the remote format is native,
+ //otherwise the default scale factor is the max signed integer
+ double scaleFactor = (remoteFormat == nativeFormat)?nativeScaleFactor:double(1 << ((SoapySDR::formatToSize(remoteFormat)*4)-1));
+ const auto scaleFactorIt = args.find(SOAPY_REMOTE_KWARG_SCALAR);
+ if (scaleFactorIt != args.end()) scaleFactor = std::stod(scaleFactorIt->second);
+
+ size_t mtu = SOAPY_REMOTE_DEFAULT_ENDPOINT_MTU;
+ const auto mtuIt = args.find(SOAPY_REMOTE_KWARG_MTU);
+ if (mtuIt != args.end()) mtu = size_t(std::stod(mtuIt->second));
+
+ size_t window = SOAPY_REMOTE_DEFAULT_ENDPOINT_WINDOW;
+ const auto windowIt = args.find(SOAPY_REMOTE_KWARG_WINDOW);
+ if (windowIt != args.end()) window = size_t(std::stod(windowIt->second));
+
+ SoapySDR::logf(SOAPY_SDR_INFO, "SoapyRemote::setup%sStream(remoteFormat=%s, localFormat=%s, scaleFactor=%g, mtu=%d, window=%d)",
+ (direction == SOAPY_SDR_RX)?"Rx":"Tx", remoteFormat.c_str(), localFormat.c_str(), scaleFactor, int(mtu), int(window));
+
+ //check supported formats
+ ConvertTypes convertType = CONVERT_MEMCPY;
+ if (localFormat == remoteFormat) convertType = CONVERT_MEMCPY;
+ else if (localFormat == SOAPY_SDR_CF32 and remoteFormat == SOAPY_SDR_CS16) convertType = CONVERT_CF32_CS16;
+ else if (localFormat == SOAPY_SDR_CF32 and remoteFormat == SOAPY_SDR_CS8) convertType = CONVERT_CF32_CS8;
+ else if (localFormat == SOAPY_SDR_CF32 and remoteFormat == SOAPY_SDR_CU8) convertType = CONVERT_CF32_CU8;
+ else throw std::runtime_error(
+ "SoapyRemote::setupStream() conversion not supported;"
+ "localFormat="+localFormat+", remoteFormat="+remoteFormat);
+
+ //allocate new local stream data
+ ClientStreamData *data = new ClientStreamData();
+ data->localFormat = localFormat;
+ data->remoteFormat = remoteFormat;
+ data->recvBuffs.resize(channels.size());
+ data->sendBuffs.resize(channels.size());
+ data->convertType = convertType;
+ data->scaleFactor = scaleFactor;
+
+ //extract socket node information
+ const auto localNode = SoapyURL(_sock.getsockname()).getNode();
+ const auto remoteNode = SoapyURL(_sock.getpeername()).getNode();
+
+ //bind the stream socket to an automatic port
+ const auto bindURL = SoapyURL("udp", localNode, "0").toString();
+ int ret = data->streamSock.bind(bindURL);
+ if (ret != 0)
+ {
+ const std::string errorMsg = data->streamSock.lastErrorMsg();
+ delete data;
+ throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
+ }
+ SoapySDR::logf(SOAPY_SDR_INFO, "Client side stream bound to %s", data->streamSock.getsockname().c_str());
+ const auto clientBindPort = SoapyURL(data->streamSock.getsockname()).getService();
+
+ //bind the status socket to an automatic port
+ ret = data->statusSock.bind(bindURL);
+ if (ret != 0)
+ {
+ const std::string errorMsg = data->statusSock.lastErrorMsg();
+ delete data;
+ throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
+ }
+ SoapySDR::logf(SOAPY_SDR_INFO, "Client side status bound to %s", data->streamSock.getsockname().c_str());
+ const auto statusBindPort = SoapyURL(data->statusSock.getsockname()).getService();
+
+ //setup the remote end of the stream
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_SETUP_STREAM;
+ packer & char(direction);
+ packer & remoteFormat;
+ packer & channels;
+ packer & args;
+ packer & clientBindPort;
+ packer & statusBindPort;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ std::string serverBindPort;
+ unpacker & data->streamId;
+ unpacker & serverBindPort;
+
+ //connect the stream socket to the specified port
+ const auto connectURL = SoapyURL("udp", remoteNode, serverBindPort).toString();
+ ret = data->streamSock.connect(connectURL);
+ if (ret != 0)
+ {
+ const std::string errorMsg = data->streamSock.lastErrorMsg();
+ delete data;
+ throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+ }
+ SoapySDR::logf(SOAPY_SDR_INFO, "Client side stream connected to %s", data->streamSock.getpeername().c_str());
+
+ //create endpoint
+ data->endpoint = new SoapyStreamEndpoint(data->streamSock, data->statusSock,
+ direction == SOAPY_SDR_RX, channels.size(), SoapySDR::formatToSize(remoteFormat), mtu, window);
+
+ return (SoapySDR::Stream *)data;
+}
+
+void SoapyRemoteDevice::closeStream(SoapySDR::Stream *stream)
+{
+ auto data = (ClientStreamData *)stream;
+
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_CLOSE_STREAM;
+ packer & data->streamId;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+
+ //cleanup local stream data
+ delete data->endpoint;
+ delete data;
+}
+
+size_t SoapyRemoteDevice::getStreamMTU(SoapySDR::Stream *stream) const
+{
+ auto data = (ClientStreamData *)stream;
+ return data->endpoint->getBuffSize();
+ return SoapySDR::Device::getStreamMTU(stream);
+}
+
+int SoapyRemoteDevice::activateStream(
+ SoapySDR::Stream *stream,
+ const int flags,
+ const long long timeNs,
+ const size_t numElems)
+{
+ auto data = (ClientStreamData *)stream;
+
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_ACTIVATE_STREAM;
+ packer & data->streamId;
+ packer & flags;
+ packer & timeNs;
+ packer & int(numElems);
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ int result = 0;
+ unpacker & result;
+ return result;
+}
+
+int SoapyRemoteDevice::deactivateStream(
+ SoapySDR::Stream *stream,
+ const int flags,
+ const long long timeNs)
+{
+ auto data = (ClientStreamData *)stream;
+
+ std::lock_guard<std::mutex> lock(_mutex);
+ SoapyRPCPacker packer(_sock);
+ packer & SOAPY_REMOTE_DEACTIVATE_STREAM;
+ packer & data->streamId;
+ packer & flags;
+ packer & timeNs;
+ packer();
+
+ SoapyRPCUnpacker unpacker(_sock);
+ int result = 0;
+ unpacker & result;
+ return result;
+}
+
+int SoapyRemoteDevice::readStream(
+ SoapySDR::Stream *stream,
+ void * const *buffs,
+ const size_t numElems,
+ int &flags,
+ long long &timeNs,
+ const long timeoutUs)
+{
+ auto data = (ClientStreamData *)stream;
+
+ //call into direct buffer access (when there is no remainder)
+ if (data->readElemsLeft == 0)
+ {
+ int ret = this->acquireReadBuffer(stream, data->readHandle, data->recvBuffs.data(), flags, timeNs, timeoutUs);
+ if (ret < 0) return ret;
+ data->readElemsLeft = size_t(ret);
+ }
+
+ //convert the buffer
+ size_t numSamples = std::min(numElems, data->readElemsLeft);
+ data->convertRecvBuffs(buffs, numSamples);
+ data->readElemsLeft -= numSamples;
+
+ //completed the buffer, release its handle
+ if (data->readElemsLeft == 0)
+ {
+ this->releaseReadBuffer(stream, data->readHandle);
+ }
+
+ //increment pointers for the remainder conversion
+ else
+ {
+ flags |= SOAPY_SDR_MORE_FRAGMENTS;
+ const size_t offsetBytes = data->endpoint->getElemSize()*numSamples;
+ for (size_t i = 0; i < data->recvBuffs.size(); i++)
+ {
+ data->recvBuffs[i] = ((char *)data->recvBuffs[i]) + offsetBytes;
+ }
+ }
+
+ return numSamples;
+}
+
+int SoapyRemoteDevice::writeStream(
+ SoapySDR::Stream *stream,
+ const void * const *buffs,
+ const size_t numElems,
+ int &flags,
+ const long long timeNs,
+ const long timeoutUs)
+{
+ auto data = (ClientStreamData *)stream;
+
+ //acquire from direct buffer access
+ size_t handle = 0;
+ int ret = this->acquireWriteBuffer(stream, handle, data->sendBuffs.data(), timeoutUs);
+ if (ret < 0) return ret;
+
+ //only end burst if the last sample can be released
+ const size_t numSamples = std::min<size_t>(ret, numElems);
+ if (numSamples < numElems) flags &= ~(SOAPY_SDR_END_BURST);
+
+ //convert the samples
+ data->convertSendBuffs(buffs, numSamples);
+
+ //release to direct buffer access
+ this->releaseWriteBuffer(stream, handle, numSamples, flags, timeNs);
+ return numSamples;
+}
+
+int SoapyRemoteDevice::readStreamStatus(
+ SoapySDR::Stream *stream,
+ size_t &chanMask,
+ int &flags,
+ long long &timeNs,
+ const long timeoutUs)
+{
+ auto data = (ClientStreamData *)stream;
+ auto ep = data->endpoint;
+ if (not ep->waitStatus(timeoutUs)) return SOAPY_SDR_TIMEOUT;
+ return ep->readStatus(chanMask, flags, timeNs);
+}
+
+/*******************************************************************
+ * Direct buffer access API
+ ******************************************************************/
+
+size_t SoapyRemoteDevice::getNumDirectAccessBuffers(SoapySDR::Stream *stream)
+{
+ auto data = (ClientStreamData *)stream;
+ return data->endpoint->getNumBuffs();
+}
+
+int SoapyRemoteDevice::getDirectAccessBufferAddrs(SoapySDR::Stream *stream, const size_t handle, void **buffs)
+{
+ auto data = (ClientStreamData *)stream;
+ data->endpoint->getAddrs(handle, buffs);
+ return 0;
+}
+
+int SoapyRemoteDevice::acquireReadBuffer(
+ SoapySDR::Stream *stream,
+ size_t &handle,
+ const void **buffs,
+ int &flags,
+ long long &timeNs,
+ const long timeoutUs)
+{
+ auto data = (ClientStreamData *)stream;
+ auto ep = data->endpoint;
+ if (not ep->waitRecv(timeoutUs)) return SOAPY_SDR_TIMEOUT;
+ return ep->acquireRecv(handle, buffs, flags, timeNs);
+}
+
+void SoapyRemoteDevice::releaseReadBuffer(
+ SoapySDR::Stream *stream,
+ const size_t handle)
+{
+ auto data = (ClientStreamData *)stream;
+ auto ep = data->endpoint;
+ return ep->releaseRecv(handle);
+}
+
+int SoapyRemoteDevice::acquireWriteBuffer(
+ SoapySDR::Stream *stream,
+ size_t &handle,
+ void **buffs,
+ const long timeoutUs)
+{
+ auto data = (ClientStreamData *)stream;
+ auto ep = data->endpoint;
+ if (not ep->waitSend(timeoutUs)) return SOAPY_SDR_TIMEOUT;
+ return ep->acquireSend(handle, buffs);
+}
+
+void SoapyRemoteDevice::releaseWriteBuffer(
+ SoapySDR::Stream *stream,
+ const size_t handle,
+ const size_t numElems,
+ int &flags,
+ const long long timeNs)
+{
+ auto data = (ClientStreamData *)stream;
+ auto ep = data->endpoint;
+ return ep->releaseSend(handle, numElems, flags, timeNs);
+}
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
new file mode 100644
index 0000000..8967291
--- /dev/null
+++ b/common/CMakeLists.txt
@@ -0,0 +1,50 @@
+########################################################################
+# Build the remote support library
+########################################################################
+set(COMMON_SOURCES
+ SoapyURLUtils.cpp
+ SoapyRPCSocket.cpp
+ SoapyRPCPacker.cpp
+ SoapyRPCUnpacker.cpp
+ SoapyStreamEndpoint.cpp
+ SoapyHTTPUtils.cpp
+ SoapySSDPEndpoint.cpp
+)
+
+#configure ssdp defines
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/SoapyInfoUtils.in.cpp
+ ${CMAKE_CURRENT_BINARY_DIR}/SoapyInfoUtils.cpp
+ at ONLY)
+list(APPEND COMMON_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/SoapyInfoUtils.cpp)
+
+#check for platform-specific network header
+include(CheckIncludeFiles)
+CHECK_INCLUDE_FILES(winsock2.h HAS_WINSOCK2_H)
+CHECK_INCLUDE_FILES(ws2tcpip.h HAS_WS2TCPIP_H)
+CHECK_INCLUDE_FILES(netdb.h HAS_NETDB_H)
+CHECK_INCLUDE_FILES(unistd.h HAS_UNISTD_H)
+CHECK_INCLUDE_FILES(netinet/in.h HAS_NETINET_IN_H)
+CHECK_INCLUDE_FILES(netinet/tcp.h HAS_NETINET_TCP_H)
+CHECK_INCLUDE_FILES(sys/types.h HAS_SYS_TYPES_H)
+CHECK_INCLUDE_FILES(sys/socket.h HAS_SYS_SOCKET_H)
+CHECK_INCLUDE_FILES(arpa/inet.h HAS_ARPA_INET_H)
+CHECK_INCLUDE_FILES(ifaddrs.h HAS_IFADDRS_H)
+CHECK_INCLUDE_FILES(net/if.h HAS_NET_IF_H)
+
+#network libraries
+if (WIN32)
+ list(APPEND SoapySDR_LIBRARIES ws2_32)
+endif (WIN32)
+
+#create private include header for network compatibility
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/SoapySocketDefs.in.hpp
+ ${CMAKE_CURRENT_BINARY_DIR}/SoapySocketDefs.hpp)
+
+#build a static library
+include_directories(${SoapySDR_INCLUDE_DIRS})
+add_library(SoapySDRRemoteCommon STATIC ${COMMON_SOURCES})
+target_link_libraries(SoapySDRRemoteCommon ${SoapySDR_LIBRARIES})
+set_property(TARGET SoapySDRRemoteCommon PROPERTY POSITION_INDEPENDENT_CODE TRUE)
diff --git a/common/SoapyHTTPUtils.cpp b/common/SoapyHTTPUtils.cpp
new file mode 100644
index 0000000..388ba88
--- /dev/null
+++ b/common/SoapyHTTPUtils.cpp
@@ -0,0 +1,52 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapyHTTPUtils.hpp"
+#include <cctype>
+
+SoapyHTTPHeader::SoapyHTTPHeader(const std::string &line0)
+{
+ _storage = line0 + "\r\n";
+}
+
+void SoapyHTTPHeader::addField(const std::string &key, const std::string &value)
+{
+ _storage += key + ": " + value + "\r\n";
+}
+
+void SoapyHTTPHeader::finalize(void)
+{
+ _storage += "\r\n";
+}
+
+SoapyHTTPHeader::SoapyHTTPHeader(const void *buff, const size_t length)
+{
+ _storage = std::string((const char *)buff, length);
+}
+
+std::string SoapyHTTPHeader::getLine0(void) const
+{
+ const auto pos = _storage.find("\r\n");
+ if (pos == std::string::npos) return "";
+ return _storage.substr(0, pos);
+}
+
+std::string SoapyHTTPHeader::getField(const std::string &key) const
+{
+ //find the field start
+ const std::string fieldStart("\r\n"+key+":");
+ auto pos = _storage.find(fieldStart);
+ if (pos == std::string::npos) return "";
+
+ //offset from field start
+ pos += fieldStart.length();
+
+ //offset from whitespace
+ while (std::isspace(_storage.at(pos))) pos++;
+
+ //find the field end
+ const auto end = _storage.find("\r\n", pos);
+ if (end == std::string::npos) return "";
+
+ return _storage.substr(pos, end-pos);
+}
diff --git a/common/SoapyHTTPUtils.hpp b/common/SoapyHTTPUtils.hpp
new file mode 100644
index 0000000..4cb4e7a
--- /dev/null
+++ b/common/SoapyHTTPUtils.hpp
@@ -0,0 +1,42 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRemoteConfig.hpp"
+#include <string>
+
+class SOAPY_REMOTE_API SoapyHTTPHeader
+{
+public:
+
+ //! Create an HTTP header given request/response line
+ SoapyHTTPHeader(const std::string &line0);
+
+ //! Add a key/value field to the header
+ void addField(const std::string &key, const std::string &value);
+
+ //! Done adding fields to the header
+ void finalize(void);
+
+ //! Create an HTTP from a received datagram
+ SoapyHTTPHeader(const void *buff, const size_t length);
+
+ //! Get the request/response line
+ std::string getLine0(void) const;
+
+ //! Read a field from the HTTP header (empty when missing)
+ std::string getField(const std::string &key) const;
+
+ const void *data(void) const
+ {
+ return _storage.data();
+ }
+
+ size_t size(void) const
+ {
+ return _storage.size();
+ }
+
+private:
+ std::string _storage;
+};
diff --git a/common/SoapyInfoUtils.hpp b/common/SoapyInfoUtils.hpp
new file mode 100644
index 0000000..4c3428b
--- /dev/null
+++ b/common/SoapyInfoUtils.hpp
@@ -0,0 +1,22 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRemoteConfig.hpp"
+#include <string>
+
+namespace SoapyInfo
+{
+ //! Get the hostname
+ SOAPY_REMOTE_API std::string getHostName(void);
+
+ /*!
+ * Generate a type1 UUID based on the current time and host ID.
+ */
+ SOAPY_REMOTE_API std::string generateUUID1(void);
+
+ /*!
+ * Get the user agent string for this build.
+ */
+ SOAPY_REMOTE_API std::string getUserAgent(void);
+};
diff --git a/common/SoapyInfoUtils.in.cpp b/common/SoapyInfoUtils.in.cpp
new file mode 100644
index 0000000..a29e684
--- /dev/null
+++ b/common/SoapyInfoUtils.in.cpp
@@ -0,0 +1,87 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapySocketDefs.hpp"
+#include "SoapyInfoUtils.hpp"
+#include <cstdlib> //rand
+#include <chrono>
+
+#ifdef _MSC_VER
+
+#define getpid() GetCurrentProcessId()
+
+static DWORD gethostid(void)
+{
+ char szVolName[MAX_PATH];
+ char szFileSysName[80];
+ DWORD dwSerialNumber;
+ DWORD dwMaxComponentLen;
+ DWORD dwFileSysFlags;
+ GetVolumeInformation(
+ "C:\\", szVolName, MAX_PATH,
+ &dwSerialNumber, &dwMaxComponentLen,
+ &dwFileSysFlags, szFileSysName, sizeof(szFileSysName));
+ return dwSerialNumber;
+}
+
+#endif //_MSC_VER
+
+std::string SoapyInfo::getHostName(void)
+{
+ std::string hostname;
+ char hostnameBuff[128];
+ int ret = gethostname(hostnameBuff, sizeof(hostnameBuff));
+ if (ret == 0) hostname = std::string(hostnameBuff);
+ else hostname = "unknown";
+ return hostname;
+}
+
+std::string SoapyInfo::generateUUID1(void)
+{
+ //64-bit timestamp in nanoseconds
+ const auto timeSinceEpoch = std::chrono::high_resolution_clock::now().time_since_epoch();
+ const auto timeNanoseconds = std::chrono::duration_cast<std::chrono::nanoseconds>(timeSinceEpoch);
+ const unsigned long long timeNs64 = timeNanoseconds.count();
+
+ //clock sequence (random)
+ const unsigned short clockSeq16 = short(std::rand());
+
+ //rather than node, use the host id and pid
+ const unsigned short pid16 = short(getpid());
+ const unsigned int hid32 = int(gethostid());
+
+ //load up the UUID bytes
+ unsigned char bytes[16];
+ bytes[0] = (unsigned char)(timeNs64 >> 24);
+ bytes[1] = (unsigned char)(timeNs64 >> 16);
+ bytes[2] = (unsigned char)(timeNs64 >> 8);
+ bytes[3] = (unsigned char)(timeNs64 >> 0);
+ bytes[4] = (unsigned char)(timeNs64 >> 40);
+ bytes[5] = (unsigned char)(timeNs64 >> 32);
+ bytes[6] = (unsigned char)(((timeNs64 >> 56) & 0x0F) | 0x10); //variant
+ bytes[7] = (unsigned char)(timeNs64 >> 48);
+ bytes[8] = (unsigned char)(((clockSeq16 >> 8) & 0x3F) | 0x80); //reserved
+ bytes[9] = (unsigned char)(clockSeq16 >> 0);
+ bytes[10] = (unsigned char)(pid16 >> 8);
+ bytes[11] = (unsigned char)(pid16 >> 0);
+ bytes[12] = (unsigned char)(hid32 >> 24);
+ bytes[13] = (unsigned char)(hid32 >> 16);
+ bytes[14] = (unsigned char)(hid32 >> 8);
+ bytes[15] = (unsigned char)(hid32 >> 0);
+
+ //load fields into the buffer
+ char buff[37];
+ const int ret = sprintf(buff,
+ "%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-"
+ "%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
+ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
+ bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]);
+
+ if (ret > 0) return std::string(buff, size_t(ret));
+ return ""; //failed
+}
+
+SOAPY_REMOTE_API std::string SoapyInfo::getUserAgent(void)
+{
+ return "@CMAKE_SYSTEM_NAME@ UPnP/1.1 SoapyRemote/@SoapySDR_VERSION@";
+}
diff --git a/common/SoapyRPCPacker.cpp b/common/SoapyRPCPacker.cpp
new file mode 100644
index 0000000..e768aff
--- /dev/null
+++ b/common/SoapyRPCPacker.cpp
@@ -0,0 +1,206 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapySocketDefs.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyRPCSocket.hpp"
+#include "SoapyRPCPacker.hpp"
+#include <cfloat> //DBL_MANT_DIG
+#include <cmath> //frexp
+#include <cstring> //memcpy
+#include <cstdlib> //malloc
+#include <algorithm> //min, max
+#include <stdexcept>
+
+SoapyRPCPacker::SoapyRPCPacker(SoapyRPCSocket &sock):
+ _sock(sock),
+ _message(NULL),
+ _size(0),
+ _capacity(0)
+{
+ //default allocation
+ this->ensureSpace(512);
+
+ //allot space for the header (filled in by send)
+ SoapyRPCHeader header;
+ this->pack(&header, sizeof(header));
+}
+
+SoapyRPCPacker::~SoapyRPCPacker(void)
+{
+ free(_message);
+ _message = NULL;
+}
+
+void SoapyRPCPacker::send(void)
+{
+ //load the trailer
+ SoapyRPCTrailer trailer;
+ trailer.trailerWord = htonl(SoapyRPCTrailerWord);
+ this->pack(&trailer, sizeof(trailer));
+
+ //load the header
+ SoapyRPCHeader *header = (SoapyRPCHeader *)_message;
+ header->headerWord = htonl(SoapyRPCHeaderWord);
+ header->version = htonl(SoapyRPCVersion);
+ header->length = htonl(_size);
+
+ //send the entire message
+ size_t bytesSent = 0;
+ while (bytesSent != _size)
+ {
+ const size_t toSend = std::min<size_t>(SOAPY_REMOTE_SOCKET_BUFFMAX, _size-bytesSent);
+ int ret = _sock.send(_message+bytesSent, toSend);
+ if (ret < 0)
+ {
+ throw std::runtime_error("SoapyRPCPacker::send() FAIL: "+std::string(_sock.lastErrorMsg()));
+ }
+ bytesSent += ret;
+ }
+}
+
+void SoapyRPCPacker::ensureSpace(const size_t length)
+{
+ if (_size+length <= _capacity) return;
+ const size_t newSize = std::max(_capacity*2, _size+length);
+ _message = (char *)realloc(_message, newSize);
+}
+
+void SoapyRPCPacker::pack(const void *buff, const size_t length)
+{
+ this->ensureSpace(length);
+ std::memcpy(_message+_size, buff, length);
+ _size += length;
+}
+
+void SoapyRPCPacker::operator&(const char value)
+{
+ *this & SOAPY_REMOTE_CHAR;
+ this->pack(value);
+}
+
+void SoapyRPCPacker::operator&(const bool value)
+{
+ *this & SOAPY_REMOTE_BOOL;
+ char out = value?1:0;
+ this->pack(out);
+}
+
+void SoapyRPCPacker::operator&(const int value)
+{
+ *this & SOAPY_REMOTE_INT32;
+ int out = htonl(value);
+ this->pack(&out, sizeof(out));
+}
+
+void SoapyRPCPacker::operator&(const long long value)
+{
+ *this & SOAPY_REMOTE_INT64;
+ long long out = htonll(value);
+ this->pack(&out, sizeof(out));
+}
+
+void SoapyRPCPacker::operator&(const double value)
+{
+ *this & SOAPY_REMOTE_FLOAT64;
+ int exp = 0;
+ const double x = std::frexp(value, &exp);
+ const long long man = (long long)std::ldexp(x, DBL_MANT_DIG);
+ *this & exp;
+ *this & man;
+}
+
+void SoapyRPCPacker::operator&(const std::complex<double> &value)
+{
+ *this & SOAPY_REMOTE_COMPLEX128;
+ *this & value.real();
+ *this & value.imag();
+}
+
+void SoapyRPCPacker::operator&(const std::string &value)
+{
+ *this & SOAPY_REMOTE_STRING;
+ *this & int(value.size());
+ this->pack(value.c_str(), value.size());
+}
+
+void SoapyRPCPacker::operator&(const SoapySDR::Range &value)
+{
+ *this & SOAPY_REMOTE_RANGE;
+ *this & value.minimum();
+ *this & value.maximum();
+}
+
+void SoapyRPCPacker::operator&(const SoapySDR::RangeList &value)
+{
+ *this & SOAPY_REMOTE_RANGE_LIST;
+ *this & int(value.size());
+ for (size_t i = 0; i < value.size(); i++) *this & value[i];
+}
+
+void SoapyRPCPacker::operator&(const std::vector<std::string> &value)
+{
+ *this & SOAPY_REMOTE_STRING_LIST;
+ *this & int(value.size());
+ for (size_t i = 0; i < value.size(); i++) *this & value[i];
+}
+
+void SoapyRPCPacker::operator&(const std::vector<double> &value)
+{
+ *this & SOAPY_REMOTE_FLOAT64_LIST;
+ *this & int(value.size());
+ for (size_t i = 0; i < value.size(); i++) *this & value[i];
+}
+
+void SoapyRPCPacker::operator&(const SoapySDR::Kwargs &value)
+{
+ *this & SOAPY_REMOTE_KWARGS;
+ *this & int(value.size());
+ for (auto it = value.begin(); it != value.end(); ++it)
+ {
+ *this & it->first;
+ *this & it->second;
+ }
+}
+
+void SoapyRPCPacker::operator&(const SoapySDR::KwargsList &value)
+{
+ *this & SOAPY_REMOTE_KWARGS_LIST;
+ *this & int(value.size());
+ for (size_t i = 0; i < value.size(); i++) *this & value[i];
+}
+
+void SoapyRPCPacker::operator&(const std::vector<size_t> &value)
+{
+ *this & SOAPY_REMOTE_SIZE_LIST;
+ *this & int(value.size());
+ for (size_t i = 0; i < value.size(); i++) *this & int(value[i]);
+}
+
+void SoapyRPCPacker::operator&(const SoapySDR::ArgInfo &value)
+{
+ *this & SOAPY_REMOTE_ARG_INFO;
+ *this & value.key;
+ *this & value.value;
+ *this & value.name;
+ *this & value.description;
+ *this & value.units;
+ *this & int(value.type);
+ *this & value.range;
+ *this & value.options;
+ *this & value.optionNames;
+}
+
+void SoapyRPCPacker::operator&(const SoapySDR::ArgInfoList &value)
+{
+ *this & SOAPY_REMOTE_ARG_INFO_LIST;
+ *this & int(value.size());
+ for (size_t i = 0; i < value.size(); i++) *this & value[i];
+}
+
+void SoapyRPCPacker::operator&(const std::exception &value)
+{
+ *this & SOAPY_REMOTE_EXCEPTION;
+ std::string msg(value.what());
+ *this & msg;
+}
diff --git a/common/SoapyRPCPacker.hpp b/common/SoapyRPCPacker.hpp
new file mode 100644
index 0000000..d6b0734
--- /dev/null
+++ b/common/SoapyRPCPacker.hpp
@@ -0,0 +1,117 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRemoteConfig.hpp"
+#include <SoapySDR/Types.hpp>
+#include <vector>
+#include <complex>
+#include <string>
+#include <stdexcept>
+
+class SoapyRPCSocket;
+
+/*!
+ * The packer object accepts primitive Soapy SDR types
+ * and encodes them into a portable network RPC format.
+ */
+class SOAPY_REMOTE_API SoapyRPCPacker
+{
+public:
+ SoapyRPCPacker(SoapyRPCSocket &sock);
+
+ ~SoapyRPCPacker(void);
+
+ //! Shortcut operator for send
+ void operator()(void)
+ {
+ this->send();
+ }
+
+ //! Send the message when packing is complete
+ void send(void);
+
+ //! Pack a binary blob
+ void pack(const void *buff, const size_t length);
+
+ //! Pack a single byte
+ void pack(const char byte)
+ {
+ this->ensureSpace(1);
+ _message[_size] = byte;
+ _size++;
+ }
+
+ //! Pack the call
+ void operator&(const SoapyRemoteCalls value)
+ {
+ *this & SOAPY_REMOTE_CALL;
+ *this & int(value);
+ }
+
+ //! Pack the type
+ void operator&(const SoapyRemoteTypes value)
+ {
+ this->pack(char(value));
+ }
+
+ //! Pack a character
+ void operator&(const char value);
+
+ //! Pack a boolean
+ void operator&(const bool value);
+
+ //! Pack a 32-bit integer
+ void operator&(const int value);
+
+ //! Pack a 64-bit integer
+ void operator&(const long long value);
+
+ //! Pack a double float
+ void operator&(const double value);
+
+ //! Pack a complex double float
+ void operator&(const std::complex<double> &value);
+
+ //! Pack a string
+ void operator&(const std::string &value);
+
+ //! Pack a range
+ void operator&(const SoapySDR::Range &value);
+
+ //! Pack a list of ranges
+ void operator&(const SoapySDR::RangeList &value);
+
+ //! Pack a list of strings
+ void operator&(const std::vector<std::string> &value);
+
+ //! Pack a list of double floats
+ void operator&(const std::vector<double> &value);
+
+ //! Pack a kwargs dictionary
+ void operator&(const SoapySDR::Kwargs &value);
+
+ //! Pack a list of kwargs
+ void operator&(const SoapySDR::KwargsList &value);
+
+ //! Pack a list of sizes
+ void operator&(const std::vector<size_t> &value);
+
+ //! Pack an arg info structure
+ void operator&(const SoapySDR::ArgInfo &value);
+
+ //! Pack a list of arg infos
+ void operator&(const SoapySDR::ArgInfoList &value);
+
+ //! Pack an exception
+ void operator&(const std::exception &value);
+
+private:
+
+ void ensureSpace(const size_t length);
+
+ SoapyRPCSocket &_sock;
+ char *_message;
+ size_t _size;
+ size_t _capacity;
+};
diff --git a/common/SoapyRPCSocket.cpp b/common/SoapyRPCSocket.cpp
new file mode 100644
index 0000000..7bdbad8
--- /dev/null
+++ b/common/SoapyRPCSocket.cpp
@@ -0,0 +1,448 @@
+// Copyright (c) 2015-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapySocketDefs.hpp"
+#include "SoapyRPCSocket.hpp"
+#include "SoapyURLUtils.hpp"
+#include <SoapySDR/Logger.hpp>
+#include <cstring> //strerror
+#include <cerrno> //errno
+#include <mutex>
+
+static std::mutex sessionMutex;
+static size_t sessionCount = 0;
+
+SoapySocketSession::SoapySocketSession(void)
+{
+ std::lock_guard<std::mutex> lock(sessionMutex);
+ sessionCount++;
+ if (sessionCount > 1) return;
+
+ #ifdef _MSC_VER
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ wVersionRequested = MAKEWORD(2, 2);
+ int ret = WSAStartup(wVersionRequested, &wsaData);
+ if (ret != 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapySocketSession::WSAStartup: %d", ret);
+ }
+ #endif
+}
+
+SoapySocketSession::~SoapySocketSession(void)
+{
+ std::lock_guard<std::mutex> lock(sessionMutex);
+ sessionCount--;
+ if (sessionCount > 0) return;
+
+ #ifdef _MSC_VER
+ WSACleanup();
+ #endif
+}
+
+void SoapyRPCSocket::setDefaultTcpSockOpts(void)
+{
+ if (this->null()) return;
+
+ int one = 1;
+ int ret = ::setsockopt(_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&one, sizeof(one));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(TCP_NODELAY)");
+ }
+
+ #ifdef TCP_QUICKACK
+ ret = ::setsockopt(_sock, IPPROTO_TCP, TCP_QUICKACK, (const char *)&one, sizeof(one));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(TCP_QUICKACK)");
+ }
+ #endif //TCP_QUICKACK
+}
+
+SoapyRPCSocket::SoapyRPCSocket(void):
+ _sock(INVALID_SOCKET)
+{
+ return;
+}
+
+SoapyRPCSocket::SoapyRPCSocket(const std::string &url):
+ _sock(INVALID_SOCKET)
+{
+ SoapyURL urlObj(url);
+ SockAddrData addr;
+ const auto errorMsg = urlObj.toSockAddr(addr);
+
+ if (not errorMsg.empty())
+ {
+ this->reportError("getaddrinfo("+url+")", errorMsg);
+ }
+ else
+ {
+ _sock = ::socket(addr.addr()->sa_family, urlObj.getType(), 0);
+ }
+}
+
+SoapyRPCSocket::~SoapyRPCSocket(void)
+{
+ if (this->close() != 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapyRPCSocket::~SoapyRPCSocket: %s", this->lastErrorMsg());
+ }
+}
+
+bool SoapyRPCSocket::null(void)
+{
+ return _sock == INVALID_SOCKET;
+}
+
+int SoapyRPCSocket::close(void)
+{
+ if (this->null()) return 0;
+ int ret = ::closesocket(_sock);
+ _sock = INVALID_SOCKET;
+ return ret;
+}
+
+int SoapyRPCSocket::bind(const std::string &url)
+{
+ SoapyURL urlObj(url);
+ SockAddrData addr;
+ const auto errorMsg = urlObj.toSockAddr(addr);
+ if (not errorMsg.empty())
+ {
+ this->reportError("getaddrinfo("+url+")", errorMsg);
+ return -1;
+ }
+
+ if (this->null()) _sock = ::socket(addr.addr()->sa_family, urlObj.getType(), 0);
+ if (this->null()) return -1;
+
+ //setup reuse address
+ int one = 1;
+ int ret = ::setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&one, sizeof(one));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(SO_REUSEADDR)");
+ }
+
+ #ifdef __APPLE__
+ ret = ::setsockopt(_sock, SOL_SOCKET, SO_REUSEPORT, (const char *)&one, sizeof(one));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(SO_REUSEPORT)");
+ }
+ #endif //__APPLE__
+
+ if (urlObj.getType() == SOCK_STREAM) this->setDefaultTcpSockOpts();
+
+ ret = ::bind(_sock, addr.addr(), addr.addrlen());
+ if (ret == -1) this->reportError("bind("+url+")");
+ return ret;
+}
+
+int SoapyRPCSocket::listen(int backlog)
+{
+ int ret = ::listen(_sock, backlog);
+ if (ret == -1) this->reportError("listen()");
+ return ret;
+}
+
+SoapyRPCSocket *SoapyRPCSocket::accept(void)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ int client = ::accept(_sock, (struct sockaddr*)&addr, &addrlen);
+ if (client == INVALID_SOCKET) return NULL;
+ SoapyRPCSocket *clientSock = new SoapyRPCSocket();
+ clientSock->_sock = client;
+ clientSock->setDefaultTcpSockOpts();
+ return clientSock;
+}
+
+int SoapyRPCSocket::connect(const std::string &url)
+{
+ SoapyURL urlObj(url);
+ SockAddrData addr;
+ const auto errorMsg = urlObj.toSockAddr(addr);
+ if (not errorMsg.empty())
+ {
+ this->reportError("getaddrinfo("+url+")", errorMsg);
+ return -1;
+ }
+
+ if (this->null()) _sock = ::socket(addr.addr()->sa_family, urlObj.getType(), 0);
+ if (this->null()) return -1;
+ if (urlObj.getType() == SOCK_STREAM) this->setDefaultTcpSockOpts();
+
+ int ret = ::connect(_sock, addr.addr(), addr.addrlen());
+ if (ret == -1) this->reportError("connect("+url+")");
+ return ret;
+}
+
+/*!
+ * OSX doesn't support automatic ipv6mr_interface = 0.
+ * The following code attempts to work around this issue
+ * by manually selecting a multicast capable interface.
+ */
+static int getDefaultIfaceIndex(void)
+{
+ #ifdef __APPLE__
+
+ //find the first available multicast interfaces
+ int loIface = 0, enIface = 0;
+ struct ifaddrs *ifa = nullptr;
+ getifaddrs(&ifa);
+ while (ifa != nullptr)
+ {
+ const bool isIPv6 = ifa->ifa_addr->sa_family == AF_INET6;
+ const bool isUp = ((ifa->ifa_flags & IFF_UP) != 0);
+ const bool isLoopback = ((ifa->ifa_flags & IFF_LOOPBACK) != 0);
+ const bool isMulticast = ((ifa->ifa_flags & IFF_MULTICAST) != 0);
+ const int ifaceIndex = if_nametoindex(ifa->ifa_name);
+ SoapySDR::logf(SOAPY_SDR_DEBUG, "Interface: #%d(%s) ipv6=%d, up=%d, lb=%d, mcast=%d",
+ ifaceIndex, ifa->ifa_name, isIPv6, isUp, isLoopback, isMulticast);
+ if (isIPv6 and isUp and isLoopback and isMulticast and loIface == 0) loIface = ifaceIndex;
+ if (isIPv6 and isUp and not isLoopback and isMulticast and enIface == 0) enIface = ifaceIndex;
+ ifa = ifa->ifa_next;
+ }
+ freeifaddrs(ifa);
+ SoapySDR::logf(SOAPY_SDR_DEBUG, "Default loopback: #%d, default ethernet #%d", loIface, enIface);
+
+ //prefer discovered regular interface over loopback
+ if (enIface != 0) return enIface;
+ if (loIface != 0) return loIface;
+ #endif //__APPLE__
+
+ return 0;
+}
+
+int SoapyRPCSocket::multicastJoin(const std::string &group, const bool loop, const int ttl, int iface)
+{
+ /*
+ * Multicast join docs:
+ * http://www.tldp.org/HOWTO/Multicast-HOWTO-6.html
+ * http://www.tenouk.com/Module41c.html
+ */
+
+ //lookup group url
+ SoapyURL urlObj(group);
+ SockAddrData addr;
+ const auto errorMsg = urlObj.toSockAddr(addr);
+ if (not errorMsg.empty())
+ {
+ this->reportError("getaddrinfo("+group+")", errorMsg);
+ return -1;
+ }
+
+ //create socket if null
+ if (this->null()) _sock = ::socket(addr.addr()->sa_family, SOCK_DGRAM, 0);
+ if (this->null()) return -1;
+ int ret = 0;
+
+ int loopInt = loop?1:0;
+
+ switch(addr.addr()->sa_family)
+ {
+ case AF_INET: {
+
+ //setup IP_MULTICAST_LOOP
+ ret = ::setsockopt(_sock, IPPROTO_IP, IP_MULTICAST_LOOP, (const char *)&loopInt, sizeof(loopInt));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(IP_MULTICAST_LOOP)");
+ return -1;
+ }
+
+ //setup IP_MULTICAST_TTL
+ ret = ::setsockopt(_sock, IPPROTO_IP, IP_MULTICAST_TTL, (const char *)&ttl, sizeof(ttl));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(IP_MULTICAST_TTL)");
+ return -1;
+ }
+
+ //setup IP_ADD_MEMBERSHIP
+ auto *addr_in = (const struct sockaddr_in *)addr.addr();
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr = addr_in->sin_addr;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ ret = ::setsockopt(_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)&mreq, sizeof(mreq));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(IP_ADD_MEMBERSHIP)");
+ return -1;
+ }
+ break;
+ }
+ case AF_INET6: {
+
+ //setup IPV6_MULTICAST_LOOP
+ ret = ::setsockopt(_sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const char *)&loopInt, sizeof(loopInt));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(IPV6_MULTICAST_LOOP)");
+ return -1;
+ }
+
+ //setup IPV6_MULTICAST_HOPS
+ ret = ::setsockopt(_sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char *)&ttl, sizeof(ttl));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(IPV6_MULTICAST_HOPS)");
+ return -1;
+ }
+
+ //setup IPV6_MULTICAST_IF
+ if (iface == 0) iface = getDefaultIfaceIndex();
+ if (iface != 0)
+ {
+ ret = ::setsockopt(_sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const char *)&iface, sizeof(iface));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(IPV6_MULTICAST_IF)");
+ return -1;
+ }
+ }
+
+ //setup IPV6_ADD_MEMBERSHIP
+ auto *addr_in6 = (const struct sockaddr_in6 *)addr.addr();
+ struct ipv6_mreq mreq6;
+ mreq6.ipv6mr_multiaddr = addr_in6->sin6_addr;
+ mreq6.ipv6mr_interface = iface;
+ ret = ::setsockopt(_sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (const char *)&mreq6, sizeof(mreq6));
+ if (ret != 0)
+ {
+ this->reportError("setsockopt(IPV6_ADD_MEMBERSHIP)");
+ return -1;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int SoapyRPCSocket::send(const void *buf, size_t len, int flags)
+{
+ int ret = ::send(_sock, (const char *)buf, int(len), flags);
+ if (ret == -1) this->reportError("send()");
+ return ret;
+}
+
+int SoapyRPCSocket::recv(void *buf, size_t len, int flags)
+{
+ int ret = ::recv(_sock, (char *)buf, int(len), flags);
+ if (ret == -1) this->reportError("recv()");
+ return ret;
+}
+
+int SoapyRPCSocket::sendto(const void *buf, size_t len, const std::string &url, int flags)
+{
+ SockAddrData addr; SoapyURL(url).toSockAddr(addr);
+ int ret = ::sendto(_sock, (char *)buf, int(len), flags, addr.addr(), addr.addrlen());
+ if (ret == -1) this->reportError("sendto("+url+")");
+ return ret;
+}
+
+int SoapyRPCSocket::recvfrom(void *buf, size_t len, std::string &url, int flags)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ int ret = ::recvfrom(_sock, (char *)buf, int(len), flags, (struct sockaddr*)&addr, &addrlen);
+ if (ret == -1) this->reportError("recvfrom()");
+ else url = SoapyURL(SockAddrData((struct sockaddr *)&addr, addrlen)).toString();
+ return ret;
+}
+
+bool SoapyRPCSocket::selectRecv(const long timeoutUs)
+{
+ struct timeval tv;
+ tv.tv_sec = timeoutUs / 1000000;
+ tv.tv_usec = timeoutUs % 1000000;
+
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(_sock, &readfds);
+
+ int ret = ::select(_sock+1, &readfds, NULL, NULL, &tv);
+ if (ret == -1) this->reportError("select()");
+ return ret == 1;
+}
+
+static std::string errToString(const int err)
+{
+ char buff[1024];
+ #ifdef _MSC_VER
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&buff, sizeof(buff), NULL);
+ return buff;
+ #else
+ //http://linux.die.net/man/3/strerror_r
+ #if ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE) || __APPLE__
+ strerror_r(err, buff, sizeof(buff));
+ #else
+ //this version may decide to use its own internal string
+ return strerror_r(err, buff, sizeof(buff));
+ #endif
+ return buff;
+ #endif
+}
+
+void SoapyRPCSocket::reportError(const std::string &what)
+{
+ const int err = SOCKET_ERRNO;
+ if (err == 0) _lastErrorMsg = what;
+ else this->reportError(what, std::to_string(err) + ": " + errToString(err));
+}
+
+void SoapyRPCSocket::reportError(const std::string &what, const std::string &errorMsg)
+{
+ _lastErrorMsg = what + " [" + errorMsg + "]";
+}
+
+std::string SoapyRPCSocket::getsockname(void)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ int ret = ::getsockname(_sock, (struct sockaddr *)&addr, &addrlen);
+ if (ret == -1) this->reportError("getsockname()");
+ if (ret != 0) return "";
+ return SoapyURL(SockAddrData((struct sockaddr *)&addr, addrlen)).toString();
+}
+
+std::string SoapyRPCSocket::getpeername(void)
+{
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ int ret = ::getpeername(_sock, (struct sockaddr *)&addr, &addrlen);
+ if (ret == -1) this->reportError("getpeername()");
+ if (ret != 0) return "";
+ return SoapyURL(SockAddrData((struct sockaddr *)&addr, addrlen)).toString();
+}
+
+int SoapyRPCSocket::setBuffSize(const bool isRecv, const size_t numBytes)
+{
+ int opt = int(numBytes);
+ int ret = ::setsockopt(_sock, SOL_SOCKET, isRecv?SO_RCVBUF:SO_SNDBUF, (const char *)&opt, sizeof(opt));
+ if (ret == -1) this->reportError("setsockopt("+std::string(isRecv?"SO_RCVBUF":"SO_SNDBUF")+")");
+ return ret;
+}
+
+int SoapyRPCSocket::getBuffSize(const bool isRecv)
+{
+ int opt = 0;
+ socklen_t optlen = sizeof(opt);
+ int ret = ::getsockopt(_sock, SOL_SOCKET, isRecv?SO_RCVBUF:SO_SNDBUF, (char *)&opt, &optlen);
+ if (ret == -1) this->reportError("getsockopt("+std::string(isRecv?"SO_RCVBUF":"SO_SNDBUF")+")");
+ if (ret != 0) return ret;
+
+ //adjustment for linux kernel socket buffer doubling for bookkeeping
+ #ifdef __linux
+ opt = opt/2;
+ #endif
+
+ return opt;
+}
diff --git a/common/SoapyRPCSocket.hpp b/common/SoapyRPCSocket.hpp
new file mode 100644
index 0000000..48d4361
--- /dev/null
+++ b/common/SoapyRPCSocket.hpp
@@ -0,0 +1,157 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRemoteConfig.hpp"
+#include <cstddef>
+#include <string>
+
+class SockAddrData;
+
+/*!
+ * Create one instance of the session per process to use sockets.
+ */
+class SOAPY_REMOTE_API SoapySocketSession
+{
+public:
+ SoapySocketSession(void);
+ ~SoapySocketSession(void);
+};
+
+/*!
+ * A simple socket wrapper with a TCP-like socket API.
+ * The implementation may be swapped out in the future.
+ */
+class SOAPY_REMOTE_API SoapyRPCSocket
+{
+public:
+ SoapyRPCSocket(void);
+
+ /*!
+ * Make the underlying socket (but does not bind or connect).
+ * This function is called automatically by bind and connect,
+ * however it can be used to test if a protocol is possible.
+ */
+ SoapyRPCSocket(const std::string &url);
+
+ ~SoapyRPCSocket(void);
+
+ /*!
+ * Is the socket null?
+ * The default constructor makes a null socket.
+ * The socket is non null after bind or connect,
+ * and after accept returns a successful socket.
+ */
+ bool null(void);
+
+ /*!
+ * Explicit close the socket, also done by destructor.
+ */
+ int close(void);
+
+ /*!
+ * Server bind.
+ * URL examples:
+ * 0.0.0.0:1234
+ * [::]:1234
+ */
+ int bind(const std::string &url);
+
+ /*!
+ * Server listen.
+ */
+ int listen(int backlog);
+
+ /*!
+ * Server accept connection.
+ * Socket will be null on failure.
+ * Caller owns the client socket.
+ */
+ SoapyRPCSocket *accept(void);
+
+ /*!
+ * Client connect.
+ * URL examples:
+ * 10.10.1.123:1234
+ * [2001:db8:0:1]:1234
+ * hostname:1234
+ */
+ int connect(const std::string &url);
+
+ /*!
+ * Join a multi-cast group.
+ * \param group the url for the multicast group and port number
+ * \param loop specify to receive local loopback
+ * \param ttl specify time to live for send packets
+ * \param iface the IPv6 interface index or 0 for automatic
+ */
+ int multicastJoin(const std::string &group, const bool loop = true, const int ttl = 1, const int iface = 0);
+
+ /*!
+ * Send the buffer and return bytes sent or error.
+ */
+ int send(const void *buf, size_t len, int flags = 0);
+
+ /*!
+ * Receive into buffer and return bytes received or error.
+ */
+ int recv(void *buf, size_t len, int flags = 0);
+
+ /*!
+ * Send to a specific destination.
+ */
+ int sendto(const void *buf, size_t len, const std::string &url, int flags = 0);
+
+ /*!
+ * Receive from an unconnected socket.
+ */
+ int recvfrom(void *buf, size_t len, std::string &url, int flags = 0);
+
+ /*!
+ * Wait for recv to become ready with timeout.
+ * Return true for ready, false for timeout.
+ */
+ bool selectRecv(const long timeoutUs);
+
+ /*!
+ * Query the last error message as a string.
+ */
+ const char *lastErrorMsg(void) const
+ {
+ return _lastErrorMsg.c_str();
+ }
+
+ /*!
+ * Get the URL of the local socket.
+ * Return an empty string on error.
+ */
+ std::string getsockname(void);
+
+ /*!
+ * Get the URL of the remote socket.
+ * Return an empty string on error.
+ */
+ std::string getpeername(void);
+
+ /*!
+ * Set the socket buffer size in bytes.
+ * \param isRecv true for RCVBUF, false for SNDBUF
+ * \return 0 for success or negative error code.
+ */
+ int setBuffSize(const bool isRecv, const size_t numBytes);
+
+ /*!
+ * Get the socket buffer size in bytes.
+ * \param isRecv true for RCVBUF, false for SNDBUF
+ * \return the actual size set or negative error code.
+ */
+ int getBuffSize(const bool isRecv);
+
+private:
+ int _sock;
+ std::string _lastErrorMsg;
+
+ void reportError(const std::string &what, const std::string &errorMsg);
+ void reportError(const std::string &what);
+ void setDefaultTcpSockOpts(void);
+};
diff --git a/common/SoapyRPCUnpacker.cpp b/common/SoapyRPCUnpacker.cpp
new file mode 100644
index 0000000..7f7c2e1
--- /dev/null
+++ b/common/SoapyRPCUnpacker.cpp
@@ -0,0 +1,282 @@
+// Copyright (c) 2015-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapySocketDefs.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyRPCSocket.hpp"
+#include "SoapyRPCUnpacker.hpp"
+#include <SoapySDR/Logger.hpp>
+#include <cfloat> //DBL_MANT_DIG
+#include <cmath> //ldexp
+#include <cstring> //memcpy
+#include <cstdlib> //malloc
+#include <algorithm> //min, max
+#include <stdexcept>
+
+SoapyRPCUnpacker::SoapyRPCUnpacker(SoapyRPCSocket &sock, const bool autoRecv):
+ _sock(sock),
+ _message(NULL),
+ _offset(0),
+ _capacity(0)
+{
+ if (autoRecv) this->recv();
+}
+
+SoapyRPCUnpacker::~SoapyRPCUnpacker(void)
+{
+ free(_message);
+ _message = NULL;
+ _offset += sizeof(SoapyRPCTrailer); //consume trailer
+ if (_offset != _capacity)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "~SoapyRPCUnpacker: Unconsumed payload bytes %d", int(_capacity-_offset));
+ }
+}
+
+void SoapyRPCUnpacker::recv(void)
+{
+ //receive the header
+ SoapyRPCHeader header;
+ int ret = _sock.recv(&header, sizeof(header), MSG_WAITALL);
+ if (ret != sizeof(header))
+ {
+ throw std::runtime_error("SoapyRPCUnpacker::recv(header) FAIL: "+std::string(_sock.lastErrorMsg()));
+ }
+
+ //inspect and parse the header
+ if (ntohl(header.headerWord) != SoapyRPCHeaderWord)
+ {
+ throw std::runtime_error("SoapyRPCUnpacker::recv() FAIL: header word");
+ }
+ //TODO ignoring the version for now
+ //the check may need to be delicate with the version major, minor vs patch number
+ const size_t length = ntohl(header.length);
+ if (length <= sizeof(SoapyRPCHeader) + sizeof(SoapyRPCTrailer))
+ {
+ throw std::runtime_error("SoapyRPCUnpacker::recv() FAIL: header length");
+ }
+
+ //receive the remaining payload
+ _capacity = length - sizeof(SoapyRPCHeader);
+ _message = (char *)malloc(_capacity);
+ size_t bytesReceived = 0;
+ while (bytesReceived != _capacity)
+ {
+ const size_t toRecv = std::min<size_t>(SOAPY_REMOTE_SOCKET_BUFFMAX, _capacity-bytesReceived);
+ ret = _sock.recv(_message+bytesReceived, toRecv);
+ if (ret < 0)
+ {
+ throw std::runtime_error("SoapyRPCUnpacker::recv(payload) FAIL: "+std::string(_sock.lastErrorMsg()));
+ }
+ bytesReceived += ret;
+ }
+
+ //check the trailer
+ SoapyRPCTrailer trailer;
+ std::memcpy(&trailer, _message + _capacity - sizeof(SoapyRPCTrailer), sizeof(trailer));
+ if (ntohl(trailer.trailerWord) != SoapyRPCTrailerWord)
+ {
+ throw std::runtime_error("SoapyRPCUnpacker::recv() FAIL: trailer word");
+ }
+
+ //auto-consume void
+ if (this->peekType() == SOAPY_REMOTE_VOID)
+ {
+ SoapyRemoteTypes type;
+ *this & type;
+ }
+
+ //check for exceptions
+ else if (this->peekType() == SOAPY_REMOTE_EXCEPTION)
+ {
+ SoapyRemoteTypes type;
+ std::string errorMsg;
+ *this & type;
+ *this & errorMsg;
+ throw std::runtime_error("RemoteError: "+errorMsg);
+ }
+}
+
+void SoapyRPCUnpacker::unpack(void *buff, const size_t length)
+{
+ std::memcpy(buff, this->unpack(length), length);
+}
+
+void *SoapyRPCUnpacker::unpack(const size_t length)
+{
+ if (_offset + length > _capacity - sizeof(SoapyRPCTrailer))
+ {
+ throw std::runtime_error("SoapyRPCUnpacker::unpack() OVER-CONSUME");
+ }
+ void *buff = _message+_offset;
+ _offset += length;
+ return buff;
+}
+
+bool SoapyRPCUnpacker::done(void) const
+{
+ return (_offset + sizeof(SoapyRPCTrailer)) == _capacity;
+}
+
+#define UNPACK_TYPE_HELPER(expected) \
+ SoapyRemoteTypes type; *this & type; \
+ if (type != expected) {throw std::runtime_error("SoapyRPCUnpacker type check FAIL:" #expected);} else {}
+
+void SoapyRPCUnpacker::operator&(SoapyRemoteCalls &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_CALL);
+ int call = 0;
+ *this & call;
+ value = SoapyRemoteCalls(call);
+}
+
+void SoapyRPCUnpacker::operator&(char &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_CHAR);
+ value = this->unpack();
+}
+
+void SoapyRPCUnpacker::operator&(bool &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_BOOL);
+ char in = this->unpack();
+ value = (in == 0)?false:true;
+}
+
+void SoapyRPCUnpacker::operator&(int &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_INT32);
+ this->unpack(&value, sizeof(value));
+ value = ntohl(value);
+}
+
+void SoapyRPCUnpacker::operator&(long long &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_INT64);
+ this->unpack(&value, sizeof(value));
+ value = ntohll(value);
+}
+
+void SoapyRPCUnpacker::operator&(double &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_FLOAT64);
+ int exp = 0;
+ long long man = 0;
+ *this & exp;
+ *this & man;
+ value = std::ldexp(double(man), exp-DBL_MANT_DIG);
+}
+
+void SoapyRPCUnpacker::operator&(std::complex<double> &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_COMPLEX128);
+ double r = 0.0, i = 0.0;
+ *this & r;
+ *this & i;
+ value = std::complex<double>(r, i);
+}
+
+void SoapyRPCUnpacker::operator&(std::string &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_STRING);
+ int size = 0;
+ *this & size;
+ value = std::string((const char *)this->unpack(size), size);
+}
+
+void SoapyRPCUnpacker::operator&(SoapySDR::Range &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_RANGE);
+ double minimum = 0.0, maximum = 0.0;
+ *this & minimum;
+ *this & maximum;
+ value = SoapySDR::Range(minimum, maximum);
+}
+
+void SoapyRPCUnpacker::operator&(SoapySDR::RangeList &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_RANGE_LIST);
+ int size = 0;
+ *this & size;
+ value.resize(size);
+ for (size_t i = 0; i < size_t(size); i++) *this & value[i];
+}
+
+void SoapyRPCUnpacker::operator&(std::vector<std::string> &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_STRING_LIST);
+ int size = 0;
+ *this & size;
+ value.resize(size);
+ for (size_t i = 0; i < size_t(size); i++) *this & value[i];
+}
+
+void SoapyRPCUnpacker::operator&(std::vector<double> &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_FLOAT64_LIST);
+ int size = 0;
+ *this & size;
+ value.resize(size);
+ for (size_t i = 0; i < size_t(size); i++) *this & value[i];
+}
+
+void SoapyRPCUnpacker::operator&(SoapySDR::Kwargs &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_KWARGS);
+ int size = 0;
+ *this & size;
+ value.clear();
+ for (size_t i = 0; i < size_t(size); i++)
+ {
+ std::string key, val;
+ *this & key;
+ *this & val;
+ value[key] = val;
+ }
+}
+
+void SoapyRPCUnpacker::operator&(SoapySDR::KwargsList &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_KWARGS_LIST);
+ int size = 0;
+ *this & size;
+ value.resize(size);
+ for (size_t i = 0; i < size_t(size); i++) *this & value[i];
+}
+
+void SoapyRPCUnpacker::operator&(std::vector<size_t> &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_SIZE_LIST);
+ int size = 0;
+ *this & size;
+ value.resize(size);
+ for (size_t i = 0; i < value.size(); i++)
+ {
+ *this & size;
+ value[i] = size;
+ }
+}
+
+void SoapyRPCUnpacker::operator&(SoapySDR::ArgInfo &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_ARG_INFO);
+ *this & value.key;
+ *this & value.value;
+ *this & value.name;
+ *this & value.description;
+ *this & value.units;
+ int intType = 0; *this & intType;
+ value.type = SoapySDR::ArgInfo::Type(intType);
+ *this & value.range;
+ *this & value.options;
+ *this & value.optionNames;
+}
+
+void SoapyRPCUnpacker::operator&(SoapySDR::ArgInfoList &value)
+{
+ UNPACK_TYPE_HELPER(SOAPY_REMOTE_ARG_INFO_LIST);
+ int size = 0;
+ *this & size;
+ value.resize(size);
+ for (size_t i = 0; i < size_t(size); i++) *this & value[i];
+}
diff --git a/common/SoapyRPCUnpacker.hpp b/common/SoapyRPCUnpacker.hpp
new file mode 100644
index 0000000..6fee795
--- /dev/null
+++ b/common/SoapyRPCUnpacker.hpp
@@ -0,0 +1,115 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRemoteConfig.hpp"
+#include <SoapySDR/Types.hpp>
+#include <vector>
+#include <complex>
+#include <string>
+
+class SoapyRPCSocket;
+
+/*!
+ * The unpacker object receives a complete RPC message,
+ * and unpacks the message into primitive Soapy SDR types.
+ */
+class SOAPY_REMOTE_API SoapyRPCUnpacker
+{
+public:
+ SoapyRPCUnpacker(SoapyRPCSocket &sock, const bool autoRecv = true);
+
+ ~SoapyRPCUnpacker(void);
+
+ //! Receive a complete RPC message
+ void recv(void);
+
+ //! Unpack a binary blob of known size
+ void unpack(void *buff, const size_t length);
+
+ //! Copy-less version of unpack
+ void *unpack(const size_t length);
+
+ //! Unpack a single byte
+ char unpack(void)
+ {
+ char byte = _message[_offset];
+ _offset++;
+ return byte;
+ }
+
+ //! Done when no data is left to unpack
+ bool done(void) const;
+
+ //! View the next type without consuming
+ SoapyRemoteTypes peekType(void) const
+ {
+ return SoapyRemoteTypes(_message[_offset]);
+ }
+
+ //! Unpack the call
+ void operator&(SoapyRemoteCalls &value);
+
+ //! Unpack the type
+ void operator&(SoapyRemoteTypes &value)
+ {
+ value = SoapyRemoteTypes(this->unpack());
+ }
+
+ //! Unpack a character
+ void operator&(char &value);
+
+ //! Unpack a boolean
+ void operator&(bool &value);
+
+ //! Unpack a 32-bit integer
+ void operator&(int &value);
+
+ //! Unpack a 64-bit integer
+ void operator&(long long &value);
+
+ //! Unpack a double float
+ void operator&(double &value);
+
+ //! Unpack a complex double float
+ void operator&(std::complex<double> &value);
+
+ //! Unpack a string
+ void operator&(std::string &value);
+
+ //! Unpack a range
+ void operator&(SoapySDR::Range &value);
+
+ //! Unpack a list of ranges
+ void operator&(SoapySDR::RangeList &value);
+
+ //! Unpack a list of strings
+ void operator&(std::vector<std::string> &value);
+
+ //! Unpack a list of double floats
+ void operator&(std::vector<double> &value);
+
+ //! Unpack a kwargs dictionary
+ void operator&(SoapySDR::Kwargs &value);
+
+ //! Unpack a list of kwargs
+ void operator&(SoapySDR::KwargsList &value);
+
+ //! Unpack a list of sizes
+ void operator&(std::vector<size_t> &value);
+
+ //! Unpack an arg info structure
+ void operator&(SoapySDR::ArgInfo &value);
+
+ //! Unpack a list of arg infos
+ void operator&(SoapySDR::ArgInfoList &value);
+
+private:
+
+ void ensureSpace(const size_t length);
+
+ SoapyRPCSocket &_sock;
+ char *_message;
+ size_t _offset;
+ size_t _capacity;
+};
diff --git a/common/SoapyRemoteConfig.hpp b/common/SoapyRemoteConfig.hpp
new file mode 100644
index 0000000..ef2179c
--- /dev/null
+++ b/common/SoapyRemoteConfig.hpp
@@ -0,0 +1,19 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include <SoapySDR/Config.hpp>
+
+/***********************************************************************
+ * API export defines
+ **********************************************************************/
+#ifdef SOAPY_REMOTE_DLL // defined if SOAPY is compiled as a DLL
+ #ifdef SOAPY_REMOTE_DLL_EXPORTS // defined if we are building the SOAPY DLL (instead of using it)
+ #define SOAPY_REMOTE_API SOAPY_SDR_HELPER_DLL_EXPORT
+ #else
+ #define SOAPY_REMOTE_API SOAPY_SDR_HELPER_DLL_IMPORT
+ #endif // SOAPY_REMOTE_DLL_EXPORTS
+ #define SOAPY_REMOTE_LOCAL SOAPY_SDR_HELPER_DLL_LOCAL
+#else // SOAPY_REMOTE_DLL is not defined: this means SOAPY is a static lib.
+ #define SOAPY_REMOTE_API SOAPY_SDR_HELPER_DLL_EXPORT
+#endif // SOAPY_REMOTE_DLL
diff --git a/common/SoapyRemoteDefs.hpp b/common/SoapyRemoteDefs.hpp
new file mode 100644
index 0000000..23ebe16
--- /dev/null
+++ b/common/SoapyRemoteDefs.hpp
@@ -0,0 +1,285 @@
+// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRemoteConfig.hpp"
+
+/***********************************************************************
+ * Key-words and their defaults
+ **********************************************************************/
+//! Use this magic stop key in the server to prevent infinite loops
+#define SOAPY_REMOTE_KWARG_STOP "soapy_remote_no_deeper"
+
+//! Use this key prefix to pass in args that will become local
+#define SOAPY_REMOTE_KWARG_PREFIX "remote:"
+
+//! Stream args key to set the format on the remote server
+#define SOAPY_REMOTE_KWARG_FORMAT (SOAPY_REMOTE_KWARG_PREFIX "format")
+
+//! Stream args key to set the scalar for local float conversions
+#define SOAPY_REMOTE_KWARG_SCALAR (SOAPY_REMOTE_KWARG_PREFIX "scalar")
+
+//! Stream args key to set the buffer MTU bytes for network transfers
+#define SOAPY_REMOTE_KWARG_MTU (SOAPY_REMOTE_KWARG_PREFIX "mtu")
+
+/*!
+ * Default stream transfer size (under network MTU).
+ * Larger transfer sizes may not be supported in hardware
+ * or may require tweaks to the system configuration.
+ */
+#define SOAPY_REMOTE_DEFAULT_ENDPOINT_MTU 1500
+
+/*!
+ * Stream args key to set the very large socket buffer size in bytes.
+ * This sets the socket buffer size as well as the flow control window.
+ */
+#define SOAPY_REMOTE_KWARG_WINDOW (SOAPY_REMOTE_KWARG_PREFIX "window")
+
+/*!
+ * Default number of bytes in socket buffer.
+ * Larger buffer sizes may not be supported or
+ * may require tweaks to the system configuration.
+ */
+#ifdef __APPLE__ //large buffer size causes crash
+#define SOAPY_REMOTE_DEFAULT_ENDPOINT_WINDOW (16*1024)
+#else
+#define SOAPY_REMOTE_DEFAULT_ENDPOINT_WINDOW (42*1024*1024)
+#endif
+
+/*!
+ * Stream args key to set the priority of the forwarding threads.
+ * Priority ranges: -1.0 (low), 0.0 (normal), and 1.0 (high)
+ */
+#define SOAPY_REMOTE_KWARG_PRIORITY (SOAPY_REMOTE_KWARG_PREFIX "priority")
+
+//! Default thread priority is elevated for stream forwarding
+#define SOAPY_REMOTE_DEFAULT_THREAD_PRIORITY double(0.5)
+
+/***********************************************************************
+ * Socket defaults
+ **********************************************************************/
+
+//! The default bind port for the remote server
+#define SOAPY_REMOTE_DEFAULT_SERVICE "55132"
+
+//! Use this timeout for every socket poll loop
+#define SOAPY_REMOTE_SOCKET_TIMEOUT_US (50*1000) //50 ms
+
+//! Backlog count for the server socket listen
+#define SOAPY_REMOTE_LISTEN_BACKLOG 100
+
+/*!
+ * The number of buffers that can be acquired.
+ * This is the number of buffers for the direct access API.
+ * The socket is doing all of the actual buffering,
+ * this just allows the user to get some flexibility
+ * with the direct access API. Otherwise, most client code
+ * will acquire and immediately release the same handle.
+ */
+#define SOAPY_REMOTE_ENDPOINT_NUM_BUFFS 8
+
+/*!
+ * The maximum buffer size for single socket call.
+ * Use this in the packer and unpacker TCP code.
+ * Larger buffers may crash some socket implementations.
+ * The implementation should loop until completed.
+ */
+#define SOAPY_REMOTE_SOCKET_BUFFMAX 4096
+
+/***********************************************************************
+ * RPC structures and constants
+ **********************************************************************/
+//major, minor, patch when this was last updated
+//bump the version number when changes are made
+static const unsigned int SoapyRPCVersion = 0x000300;
+
+enum SoapyRemoteTypes
+{
+ SOAPY_REMOTE_CHAR = 0,
+ SOAPY_REMOTE_BOOL = 1,
+ SOAPY_REMOTE_INT32 = 2,
+ SOAPY_REMOTE_INT64 = 3,
+ SOAPY_REMOTE_FLOAT64 = 4,
+ SOAPY_REMOTE_COMPLEX128 = 5,
+ SOAPY_REMOTE_STRING = 6,
+ SOAPY_REMOTE_RANGE = 7,
+ SOAPY_REMOTE_RANGE_LIST = 8,
+ SOAPY_REMOTE_STRING_LIST = 9,
+ SOAPY_REMOTE_FLOAT64_LIST = 10,
+ SOAPY_REMOTE_KWARGS = 11,
+ SOAPY_REMOTE_KWARGS_LIST = 12,
+ SOAPY_REMOTE_EXCEPTION = 13,
+ SOAPY_REMOTE_VOID = 14,
+ SOAPY_REMOTE_CALL = 15,
+ SOAPY_REMOTE_SIZE_LIST = 16,
+ SOAPY_REMOTE_ARG_INFO = 17,
+ SOAPY_REMOTE_ARG_INFO_LIST = 18,
+ SOAPY_REMOTE_TYPE_MAX = 19,
+};
+
+enum SoapyRemoteCalls
+{
+ //factory
+ SOAPY_REMOTE_FIND = 0,
+ SOAPY_REMOTE_MAKE = 1,
+ SOAPY_REMOTE_UNMAKE = 2,
+ SOAPY_REMOTE_HANGUP = 3,
+
+ //logger
+ SOAPY_REMOTE_GET_SERVER_ID = 20,
+ SOAPY_REMOTE_START_LOG_FORWARDING = 21,
+ SOAPY_REMOTE_STOP_LOG_FORWARDING = 22,
+
+ //identification
+ SOAPY_REMOTE_GET_DRIVER_KEY = 100,
+ SOAPY_REMOTE_GET_HARDWARE_KEY = 101,
+ SOAPY_REMOTE_GET_HARDWARE_INFO = 102,
+
+ //channels
+ SOAPY_REMOTE_SET_FRONTEND_MAPPING = 200,
+ SOAPY_REMOTE_GET_FRONTEND_MAPPING = 201,
+ SOAPY_REMOTE_GET_NUM_CHANNELS = 202,
+ SOAPY_REMOTE_GET_FULL_DUPLEX = 203,
+ SOAPY_REMOTE_GET_CHANNEL_INFO = 204,
+
+ //stream
+ SOAPY_REMOTE_SETUP_STREAM = 300,
+ SOAPY_REMOTE_CLOSE_STREAM = 301,
+ SOAPY_REMOTE_ACTIVATE_STREAM = 302,
+ SOAPY_REMOTE_DEACTIVATE_STREAM = 303,
+ SOAPY_REMOTE_GET_STREAM_FORMATS = 304,
+ SOAPY_REMOTE_GET_NATIVE_STREAM_FORMAT = 305,
+ SOAPY_REMOTE_GET_STREAM_ARGS_INFO = 306,
+
+ //antenna
+ SOAPY_REMOTE_LIST_ANTENNAS = 500,
+ SOAPY_REMOTE_SET_ANTENNA = 501,
+ SOAPY_REMOTE_GET_ANTENNA = 502,
+
+ //corrections
+ SOAPY_REMOTE_HAS_DC_OFFSET_MODE = 600,
+ SOAPY_REMOTE_SET_DC_OFFSET_MODE = 601,
+ SOAPY_REMOTE_GET_DC_OFFSET_MODE = 602,
+ SOAPY_REMOTE_HAS_DC_OFFSET = 603,
+ SOAPY_REMOTE_SET_DC_OFFSET = 604,
+ SOAPY_REMOTE_GET_DC_OFFSET = 605,
+ SOAPY_REMOTE_HAS_IQ_BALANCE_MODE = 606,
+ SOAPY_REMOTE_SET_IQ_BALANCE_MODE = 607,
+ SOAPY_REMOTE_GET_IQ_BALANCE_MODE = 608,
+
+ //gain
+ SOAPY_REMOTE_LIST_GAINS = 700,
+ SOAPY_REMOTE_SET_GAIN_MODE = 701,
+ SOAPY_REMOTE_GET_GAIN_MODE = 702,
+ SOAPY_REMOTE_SET_GAIN = 703,
+ SOAPY_REMOTE_SET_GAIN_ELEMENT = 704,
+ SOAPY_REMOTE_GET_GAIN = 705,
+ SOAPY_REMOTE_GET_GAIN_ELEMENT = 706,
+ SOAPY_REMOTE_GET_GAIN_RANGE = 707,
+ SOAPY_REMOTE_GET_GAIN_RANGE_ELEMENT = 708,
+ SOAPY_REMOTE_HAS_GAIN_MODE = 709,
+
+ //frequency
+ SOAPY_REMOTE_SET_FREQUENCY = 800,
+ SOAPY_REMOTE_SET_FREQUENCY_COMPONENT = 801,
+ SOAPY_REMOTE_GET_FREQUENCY = 802,
+ SOAPY_REMOTE_GET_FREQUENCY_COMPONENT = 803,
+ SOAPY_REMOTE_LIST_FREQUENCIES = 804,
+ SOAPY_REMOTE_GET_FREQUENCY_RANGE = 805,
+ SOAPY_REMOTE_GET_FREQUENCY_RANGE_COMPONENT = 806,
+ SOAPY_REMOTE_GET_FREQUENCY_ARGS_INFO = 807,
+
+ //sample rate
+ SOAPY_REMOTE_SET_SAMPLE_RATE = 900,
+ SOAPY_REMOTE_GET_SAMPLE_RATE = 901,
+ SOAPY_REMOTE_LIST_SAMPLE_RATES = 902,
+
+ //bandwidth
+ SOAPY_REMOTE_SET_BANDWIDTH = 903,
+ SOAPY_REMOTE_GET_BANDWIDTH = 904,
+ SOAPY_REMOTE_LIST_BANDWIDTHS = 905,
+ SOAPY_REMOTE_GET_BANDWIDTH_RANGE = 906,
+
+ //clocking
+ SOAPY_REMOTE_SET_MASTER_CLOCK_RATE = 1000,
+ SOAPY_REMOTE_GET_MASTER_CLOCK_RATE = 1001,
+ SOAPY_REMOTE_LIST_CLOCK_SOURCES = 1002,
+ SOAPY_REMOTE_SET_CLOCK_SOURCE = 1003,
+ SOAPY_REMOTE_GET_CLOCK_SOURCE = 1004,
+ SOAPY_REMOTE_GET_MASTER_CLOCK_RATES = 1008,
+
+ //time
+ SOAPY_REMOTE_LIST_TIME_SOURCES = 1005,
+ SOAPY_REMOTE_SET_TIME_SOURCE = 1006,
+ SOAPY_REMOTE_GET_TIME_SOURCE = 1007,
+ SOAPY_REMOTE_HAS_HARDWARE_TIME = 1100,
+ SOAPY_REMOTE_GET_HARDWARE_TIME = 1101,
+ SOAPY_REMOTE_SET_HARDWARE_TIME = 1102,
+ SOAPY_REMOTE_SET_COMMAND_TIME = 1103,
+
+ //sensors
+ SOAPY_REMOTE_LIST_SENSORS = 1200,
+ SOAPY_REMOTE_READ_SENSOR = 1201,
+ SOAPY_REMOTE_LIST_CHANNEL_SENSORS = 1202,
+ SOAPY_REMOTE_READ_CHANNEL_SENSOR = 1203,
+ SOAPY_REMOTE_GET_SENSOR_INFO = 1204,
+ SOAPY_REMOTE_GET_CHANNEL_SENSOR_INFO = 1205,
+
+ //registers
+ SOAPY_REMOTE_WRITE_REGISTER = 1300,
+ SOAPY_REMOTE_READ_REGISTER = 1301,
+ SOAPY_REMOTE_LIST_REGISTER_INTERFACES = 1302,
+ SOAPY_REMOTE_WRITE_REGISTER_NAMED = 1303,
+ SOAPY_REMOTE_READ_REGISTER_NAMED = 1304,
+
+ //settings
+ SOAPY_REMOTE_WRITE_SETTING = 1400,
+ SOAPY_REMOTE_READ_SETTING = 1401,
+ SOAPY_REMOTE_GET_SETTING_INFO = 1402,
+ SOAPY_REMOTE_WRITE_CHANNEL_SETTING = 1403,
+ SOAPY_REMOTE_READ_CHANNEL_SETTING = 1404,
+ SOAPY_REMOTE_GET_CHANNEL_SETTING_INFO = 1405,
+
+ //gpio
+ SOAPY_REMOTE_LIST_GPIO_BANKS = 1500,
+ SOAPY_REMOTE_WRITE_GPIO = 1501,
+ SOAPY_REMOTE_WRITE_GPIO_MASKED = 1502,
+ SOAPY_REMOTE_READ_GPIO = 1503,
+ SOAPY_REMOTE_WRITE_GPIO_DIR = 1504,
+ SOAPY_REMOTE_WRITE_GPIO_DIR_MASKED = 1505,
+ SOAPY_REMOTE_READ_GPIO_DIR = 1506,
+
+ //i2c
+ SOAPY_REMOTE_WRITE_I2C = 1600,
+ SOAPY_REMOTE_READ_I2C = 1601,
+
+ //spi
+ SOAPY_REMOTE_TRANSACT_SPI = 1700,
+
+ //uart
+ SOAPY_REMOTE_LIST_UARTS = 1801,
+ SOAPY_REMOTE_WRITE_UART = 1802,
+ SOAPY_REMOTE_READ_UART = 1803,
+};
+
+#define SOAPY_PACKET_WORD32(str) \
+ ((unsigned int)(str[0]) << 24) | \
+ ((unsigned int)(str[1]) << 16) | \
+ ((unsigned int)(str[2]) << 8) | \
+ ((unsigned int)(str[3]) << 0)
+
+static const unsigned int SoapyRPCHeaderWord = SOAPY_PACKET_WORD32("SRPC");
+static const unsigned int SoapyRPCTrailerWord = SOAPY_PACKET_WORD32("CPRS");
+
+struct SoapyRPCHeader
+{
+ unsigned int headerWord; //!< header word to identify this protocol
+ unsigned int version; //!< version number for protocol compatibility
+ unsigned int length; //!< complete packet length in bytes
+};
+
+struct SoapyRPCTrailer
+{
+ unsigned int trailerWord; //!< trailer word to identify this protocol
+};
diff --git a/common/SoapySSDPEndpoint.cpp b/common/SoapySSDPEndpoint.cpp
new file mode 100644
index 0000000..4aa4ab4
--- /dev/null
+++ b/common/SoapySSDPEndpoint.cpp
@@ -0,0 +1,359 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+/*
+ * Docs and examples:
+ * https://stackoverflow.com/questions/13382469/ssdp-protocol-implementation
+ * http://buildingskb.schneider-electric.com/view.php?AID=15197
+ * http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf
+ */
+
+#include <SoapySDR/Logger.hpp>
+#include "SoapySSDPEndpoint.hpp"
+#include "SoapyURLUtils.hpp"
+#include "SoapyInfoUtils.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyHTTPUtils.hpp"
+#include "SoapyRPCSocket.hpp"
+#include <thread>
+#include <ctime>
+#include <cctype>
+#include <set>
+
+//! IPv4 multi-cast address for SSDP communications
+#define SSDP_MULTICAST_ADDR_IPV4 "239.255.255.250"
+
+//! IPv6 multi-cast address for SSDP communications
+#define SSDP_MULTICAST_ADDR_IPV6 "ff02::c"
+
+//! UDP service port number for SSDP communications
+#define SSDP_UDP_PORT_NUMBER "1900"
+
+//! service and notify target identification string
+#define SOAPY_REMOTE_TARGET "urn:schemas-pothosware-com:service:soapyRemote:1"
+
+//! How often search and notify packets are triggered
+#define TRIGGER_TIMEOUT_SECONDS 60
+
+//! The default duration of an entry in the USN cache
+#define CACHE_DURATION_SECONDS 120
+
+//! Service is active, use with multicast NOTIFY
+#define NTS_ALIVE "ssdp:alive"
+
+//! Service stopped, use with multicast NOTIFY
+#define NTS_BYEBYE "ssdp:byebye"
+
+struct SoapySSDPEndpointData
+{
+ SoapyRPCSocket sock;
+ std::string groupURL;
+ std::thread *thread;
+ std::chrono::high_resolution_clock::time_point lastTimeSearch;
+ std::chrono::high_resolution_clock::time_point lastTimeNotify;
+};
+
+static std::string timeNowGMT(void)
+{
+ char buff[128];
+ auto t = std::time(nullptr);
+ size_t len = std::strftime(buff, sizeof(buff), "%c %Z", std::localtime(&t));
+ return std::string(buff, len);
+}
+
+std::shared_ptr<SoapySSDPEndpoint> SoapySSDPEndpoint::getInstance(void)
+{
+ static std::mutex singletonMutex;
+ std::lock_guard<std::mutex> lock(singletonMutex);
+ static std::weak_ptr<SoapySSDPEndpoint> epWeak;
+ auto epShared = epWeak.lock();
+ if (not epShared) epShared.reset(new SoapySSDPEndpoint());
+ epWeak = epShared;
+ return epShared;
+}
+
+SoapySSDPEndpoint::SoapySSDPEndpoint(void):
+ serviceRegistered(false),
+ periodicSearchEnabled(false),
+ periodicNotifyEnabled(false),
+ done(false)
+{
+ const bool isIPv6Supported = not SoapyRPCSocket(SoapyURL("tcp", "::", "0").toString()).null();
+ this->spawnHandler("0.0.0.0", SSDP_MULTICAST_ADDR_IPV4);
+ if (isIPv6Supported) this->spawnHandler("::", SSDP_MULTICAST_ADDR_IPV6);
+}
+
+SoapySSDPEndpoint::~SoapySSDPEndpoint(void)
+{
+ done = true;
+ for (auto &data : handlers)
+ {
+ data->thread->join();
+ delete data->thread;
+ delete data;
+ }
+}
+
+void SoapySSDPEndpoint::registerService(const std::string &uuid, const std::string &service)
+{
+ std::lock_guard<std::mutex> lock(mutex);
+ this->serviceRegistered = true;
+ this->uuid = uuid;
+ this->service = service;
+}
+
+void SoapySSDPEndpoint::enablePeriodicSearch(const bool enable)
+{
+ std::lock_guard<std::mutex> lock(mutex);
+ periodicSearchEnabled = enable;
+ for (auto &data : handlers) this->sendSearchHeader(data);
+}
+
+void SoapySSDPEndpoint::enablePeriodicNotify(const bool enable)
+{
+ std::lock_guard<std::mutex> lock(mutex);
+ periodicNotifyEnabled = enable;
+ for (auto &data : handlers) this->sendNotifyHeader(data, NTS_ALIVE);
+}
+
+std::vector<std::string> SoapySSDPEndpoint::getServerURLs(void)
+{
+ std::lock_guard<std::mutex> lock(mutex);
+ std::vector<std::string> serverURLs;
+ for (auto &pair : usnToURL) serverURLs.push_back(pair.second.first);
+ return serverURLs;
+}
+
+void SoapySSDPEndpoint::spawnHandler(const std::string &bindAddr, const std::string &groupAddr)
+{
+ //static list of blacklisted groups
+ //if we fail to join a group, its blacklisted
+ //so future instances wont get the same error
+ //thread-safe protected by the get instance call
+ static std::set<std::string> blacklistedGroups;
+
+ //check the blacklist
+ if (blacklistedGroups.find(groupAddr) != blacklistedGroups.end())
+ {
+ SoapySDR::logf(SOAPY_SDR_DEBUG, "SoapySSDPEndpoint::spawnHandler(%s) group blacklisted due to previous error", groupAddr.c_str());
+ return;
+ }
+
+ auto data = new SoapySSDPEndpointData();
+ auto &sock = data->sock;
+
+ const auto groupURL = SoapyURL("udp", groupAddr, SSDP_UDP_PORT_NUMBER).toString();
+ int ret = sock.multicastJoin(groupURL);
+ if (ret != 0)
+ {
+ blacklistedGroups.insert(groupAddr);
+ SoapySDR::logf(SOAPY_SDR_WARNING, "SoapySSDPEndpoint failed join group %s\n %s", groupURL.c_str(), sock.lastErrorMsg());
+ delete data;
+ return;
+ }
+
+ const auto bindURL = SoapyURL("udp", bindAddr, SSDP_UDP_PORT_NUMBER).toString();
+ ret = sock.bind(bindURL);
+ if (ret != 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapySSDPEndpoint::bind(%s) failed\n %s", bindURL.c_str(), sock.lastErrorMsg());
+ delete data;
+ return;
+ }
+
+ data->groupURL = groupURL;
+ data->thread = new std::thread(&SoapySSDPEndpoint::handlerLoop, this, data);
+ handlers.push_back(data);
+}
+
+void SoapySSDPEndpoint::handlerLoop(SoapySSDPEndpointData *data)
+{
+ auto &sock = data->sock;
+
+ std::string recvAddr;
+ char recvBuff[SOAPY_REMOTE_DEFAULT_ENDPOINT_MTU];
+
+ while (not done)
+ {
+ //receive SSDP traffic
+ if (sock.selectRecv(SOAPY_REMOTE_SOCKET_TIMEOUT_US))
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ int ret = sock.recvfrom(recvBuff, sizeof(recvBuff), recvAddr);
+ if (ret < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapySSDPEndpoint::recvfrom() = %d\n %s", ret, sock.lastErrorMsg());
+ return;
+ }
+
+ //parse the HTTP header
+ SoapyHTTPHeader header(recvBuff, size_t(ret));
+ if (header.getLine0() == "M-SEARCH * HTTP/1.1") this->handleSearchRequest(data, header, recvAddr);
+ if (header.getLine0() == "HTTP/1.1 200 OK") this->handleSearchResponse(data, header, recvAddr);
+ if (header.getLine0() == "NOTIFY * HTTP/1.1") this->handleNotifyRequest(data, header, recvAddr);
+ }
+
+ //locked for all non-blocking routines below
+ std::lock_guard<std::mutex> lock(mutex);
+ const auto timeNow = std::chrono::high_resolution_clock::now();
+ const auto triggerExpired = timeNow + std::chrono::seconds(TRIGGER_TIMEOUT_SECONDS);
+
+ //remove old cache entries
+ auto it = usnToURL.begin();
+ while (it != usnToURL.end())
+ {
+ auto &expires = it->second.second;
+ if (expires > timeNow) ++it;
+ else usnToURL.erase(it++);
+ }
+
+ //check trigger for periodic search
+ if (periodicSearchEnabled and data->lastTimeSearch > triggerExpired)
+ {
+ this->sendSearchHeader(data);
+ }
+
+ //check trigger for periodic notify
+ if (periodicNotifyEnabled and data->lastTimeNotify > triggerExpired)
+ {
+ this->sendNotifyHeader(data, NTS_ALIVE);
+ }
+ }
+
+ //disconnect notification when done
+ if (done)
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ this->sendNotifyHeader(data, NTS_BYEBYE);
+ }
+}
+
+void SoapySSDPEndpoint::sendHeader(SoapyRPCSocket &sock, const SoapyHTTPHeader &header, const std::string &addr)
+{
+ int ret = sock.sendto(header.data(), header.size(), addr);
+ if (ret != int(header.size()))
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "SoapySSDPEndpoint::sendTo(%s) = %d\n %s", addr.c_str(), ret, sock.lastErrorMsg());
+ }
+}
+
+void SoapySSDPEndpoint::sendSearchHeader(SoapySSDPEndpointData *data)
+{
+ auto hostURL = SoapyURL(data->groupURL);
+ hostURL.setScheme(""); //no scheme name
+
+ SoapyHTTPHeader header("M-SEARCH * HTTP/1.1");
+ header.addField("HOST", hostURL.toString());
+ header.addField("MAN", "\"ssdp:discover\"");
+ header.addField("MX", "2");
+ header.addField("ST", SOAPY_REMOTE_TARGET);
+ header.addField("USER-AGENT", SoapyInfo::getUserAgent());
+ header.finalize();
+ this->sendHeader(data->sock, header, data->groupURL);
+ data->lastTimeSearch = std::chrono::high_resolution_clock::now();
+}
+
+void SoapySSDPEndpoint::sendNotifyHeader(SoapySSDPEndpointData *data, const std::string &nts)
+{
+ if (not serviceRegistered) return; //do we have a service to advertise?
+
+ auto hostURL = SoapyURL(data->groupURL);
+ hostURL.setScheme(""); //no scheme name
+
+ SoapyHTTPHeader header("NOTIFY * HTTP/1.1");
+ header.addField("HOST", hostURL.toString());
+ if (nts == NTS_ALIVE)
+ {
+ header.addField("CACHE-CONTROL", "max-age=" + std::to_string(CACHE_DURATION_SECONDS));
+ header.addField("LOCATION", SoapyURL("tcp", SoapyInfo::getHostName(), service).toString());
+ }
+ header.addField("SERVER", SoapyInfo::getUserAgent());
+ header.addField("NT", SOAPY_REMOTE_TARGET);
+ header.addField("USN", "uuid:"+uuid+"::"+SOAPY_REMOTE_TARGET);
+ header.addField("NTS", nts);
+ header.finalize();
+ this->sendHeader(data->sock, header, data->groupURL);
+ data->lastTimeNotify = std::chrono::high_resolution_clock::now();
+}
+
+void SoapySSDPEndpoint::handleSearchRequest(SoapySSDPEndpointData *data, const SoapyHTTPHeader &request, const std::string &recvAddr)
+{
+ if (not serviceRegistered) return; //do we have a service to advertise?
+
+ if (request.getField("MAN") != "\"ssdp:discover\"") return;
+ const auto st = request.getField("ST");
+ const bool stForUs = (st == "ssdp:all" or st == SOAPY_REMOTE_TARGET or st == "uuid:"+uuid);
+ if (not stForUs) return;
+
+ //send a unicast response HTTP header
+ SoapyHTTPHeader response("HTTP/1.1 200 OK");
+ response.addField("CACHE-CONTROL", "max-age=" + std::to_string(CACHE_DURATION_SECONDS));
+ response.addField("DATE", timeNowGMT());
+ response.addField("EXT", "");
+ response.addField("LOCATION", SoapyURL("tcp", SoapyInfo::getHostName(), service).toString());
+ response.addField("SERVER", SoapyInfo::getUserAgent());
+ response.addField("ST", SOAPY_REMOTE_TARGET);
+ response.addField("USN", "uuid:"+uuid+"::"+SOAPY_REMOTE_TARGET);
+ response.finalize();
+ this->sendHeader(data->sock, response, recvAddr);
+
+ //The unicast response may not be received if the destination has multiple SSDP clients
+ //because only one client on the destination host will actually receive the datagram.
+ //To work around this limitation, a multicast notification packet is sent as well;
+ //which will be received by all clients at the destination as well as other hosts.
+ this->sendNotifyHeader(data, NTS_ALIVE);
+}
+
+static int getCacheDuration(const SoapyHTTPHeader &header)
+{
+ const auto cacheControl = header.getField("CACHE-CONTROL");
+ if (cacheControl.empty()) return CACHE_DURATION_SECONDS;
+
+ const auto maxAgePos = cacheControl.find("max-age");
+ const auto equalsPos = cacheControl.find("=");
+ if (maxAgePos == std::string::npos) return CACHE_DURATION_SECONDS;
+ if (equalsPos == std::string::npos) return CACHE_DURATION_SECONDS;
+ if (maxAgePos > equalsPos) return CACHE_DURATION_SECONDS;
+ auto valuePos = equalsPos + 1;
+ while (std::isspace(cacheControl.at(valuePos))) valuePos++;
+
+ const auto maxAge = cacheControl.substr(valuePos);
+ try {return std::stoul(maxAge);}
+ catch (...) {return CACHE_DURATION_SECONDS;}
+}
+
+void SoapySSDPEndpoint::handleSearchResponse(SoapySSDPEndpointData *data, const SoapyHTTPHeader &header, const std::string &recvAddr)
+{
+ if (header.getField("ST") != SOAPY_REMOTE_TARGET) return;
+ this->handleRegisterService(data, header, recvAddr);
+}
+
+void SoapySSDPEndpoint::handleNotifyRequest(SoapySSDPEndpointData *data, const SoapyHTTPHeader &header, const std::string &recvAddr)
+{
+ if (header.getField("NT") != SOAPY_REMOTE_TARGET) return;
+ this->handleRegisterService(data, header, recvAddr);
+}
+
+void SoapySSDPEndpoint::handleRegisterService(SoapySSDPEndpointData *, const SoapyHTTPHeader &header, const std::string &recvAddr)
+{
+ //extract usn
+ const auto usn = header.getField("USN");
+ if (usn.empty()) return;
+
+ //handle byebye from notification packets
+ if (header.getField("NTS") == NTS_BYEBYE)
+ {
+ usnToURL.erase(usn);
+ return;
+ }
+
+ //format the server's url
+ const auto location = header.getField("LOCATION");
+ if (location.empty()) return;
+ const SoapyURL serverURL("tcp", SoapyURL(recvAddr).getNode(), SoapyURL(location).getService());
+ SoapySDR::logf(SOAPY_SDR_DEBUG, "SoapyRemote discovered %s", serverURL.toString().c_str());
+
+ //register the server
+ const auto expires = std::chrono::high_resolution_clock::now() + std::chrono::seconds(getCacheDuration(header));
+ usnToURL[usn] = std::make_pair(serverURL.toString(), expires);
+}
diff --git a/common/SoapySSDPEndpoint.hpp b/common/SoapySSDPEndpoint.hpp
new file mode 100644
index 0000000..0203111
--- /dev/null
+++ b/common/SoapySSDPEndpoint.hpp
@@ -0,0 +1,87 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRPCSocket.hpp"
+#include <map>
+#include <string>
+#include <csignal> //sig_atomic_t
+#include <chrono>
+#include <mutex>
+#include <vector>
+#include <memory>
+
+class SoapyHTTPHeader;
+struct SoapySSDPEndpointData;
+
+/*!
+ * Service an SSDP endpoint to:
+ * keep track of discovered servers of interest,
+ * and to respond to discovery packets for us.
+ */
+class SoapySSDPEndpoint
+{
+public:
+
+ //! Get a singleton instance of the endpoint
+ static std::shared_ptr<SoapySSDPEndpoint> getInstance(void);
+
+ /*!
+ * Create a discovery endpoint
+ */
+ SoapySSDPEndpoint(void);
+
+ ~SoapySSDPEndpoint(void);
+
+ /*!
+ * Allow the endpoint to advertise that its running the RPC service
+ */
+ void registerService(const std::string &uuid, const std::string &service);
+
+ /*!
+ * Enable the client endpoint to search for running services.
+ */
+ void enablePeriodicSearch(const bool enable);
+
+ /*!
+ * Enable the server to send periodic notification messages.
+ */
+ void enablePeriodicNotify(const bool enable);
+
+ //! Get a list of all active server URLs
+ std::vector<std::string> getServerURLs(void);
+
+private:
+ SoapySocketSession sess;
+
+ //protection between threads
+ std::mutex mutex;
+
+ //discovered services
+ std::map<std::string, std::pair<std::string, std::chrono::high_resolution_clock::time_point>> usnToURL;
+
+ //service settings
+ bool serviceRegistered;
+ std::string uuid;
+ std::string service;
+
+ //configured messages
+ bool periodicSearchEnabled;
+ bool periodicNotifyEnabled;
+
+ //server data
+ std::vector<SoapySSDPEndpointData *> handlers;
+
+ //signal done to the thread
+ sig_atomic_t done;
+
+ void spawnHandler(const std::string &bindAddr, const std::string &groupAddr);
+ void handlerLoop(SoapySSDPEndpointData *data);
+ void sendHeader(SoapyRPCSocket &sock, const SoapyHTTPHeader &header, const std::string &addr);
+ void sendSearchHeader(SoapySSDPEndpointData *data);
+ void sendNotifyHeader(SoapySSDPEndpointData *data, const std::string &nts);
+ void handleSearchRequest(SoapySSDPEndpointData *data, const SoapyHTTPHeader &header, const std::string &addr);
+ void handleSearchResponse(SoapySSDPEndpointData *data, const SoapyHTTPHeader &header, const std::string &addr);
+ void handleNotifyRequest(SoapySSDPEndpointData *data, const SoapyHTTPHeader &header, const std::string &addr);
+ void handleRegisterService(SoapySSDPEndpointData *, const SoapyHTTPHeader &header, const std::string &recvAddr);
+};
diff --git a/common/SoapySocketDefs.in.hpp b/common/SoapySocketDefs.in.hpp
new file mode 100644
index 0000000..9356ed8
--- /dev/null
+++ b/common/SoapySocketDefs.in.hpp
@@ -0,0 +1,120 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+// ** This header should be included first, to avoid compile errors.
+// ** At least in the case of the windows header files.
+
+// This header helps to abstract network differences between platforms.
+// Including the correct headers for various network APIs.
+// And providing various typedefs and definitions when missing.
+
+#pragma once
+
+/***********************************************************************
+ * Windows socket headers
+ **********************************************************************/
+#cmakedefine HAS_WINSOCK2_H
+#ifdef HAS_WINSOCK2_H
+#include <winsock2.h> //htonll
+#endif //HAS_WINSOCK2_H
+
+#cmakedefine HAS_WS2TCPIP_H
+#ifdef HAS_WS2TCPIP_H
+#include <ws2tcpip.h> //addrinfo
+typedef int socklen_t;
+#endif //HAS_WS2TCPIP_H
+
+/***********************************************************************
+ * unix socket headers
+ **********************************************************************/
+#cmakedefine HAS_UNISTD_H
+#ifdef HAS_UNISTD_H
+#include <unistd.h> //close
+#define closesocket close
+#endif //HAS_UNISTD_H
+
+#cmakedefine HAS_NETDB_H
+#ifdef HAS_NETDB_H
+#include <netdb.h> //addrinfo
+#endif //HAS_NETDB_H
+
+#cmakedefine HAS_NETINET_IN_H
+#ifdef HAS_NETINET_IN_H
+#include <netinet/in.h>
+#endif //HAS_NETINET_IN_H
+
+#cmakedefine HAS_NETINET_TCP_H
+#ifdef HAS_NETINET_TCP_H
+#include <netinet/tcp.h>
+#endif //HAS_NETINET_TCP_H
+
+#cmakedefine HAS_SYS_TYPES_H
+#ifdef HAS_SYS_TYPES_H
+#include <sys/types.h>
+#endif //HAS_SYS_TYPES_H
+
+#cmakedefine HAS_SYS_SOCKET_H
+#ifdef HAS_SYS_SOCKET_H
+#include <sys/socket.h>
+#endif //HAS_SYS_SOCKET_H
+
+#cmakedefine HAS_ARPA_INET_H
+#ifdef HAS_ARPA_INET_H
+#include <arpa/inet.h> //inet_ntop
+#endif //HAS_ARPA_INET_H
+
+#cmakedefine HAS_IFADDRS_H
+#ifdef HAS_IFADDRS_H
+#include <ifaddrs.h> //getifaddrs
+#endif //HAS_IFADDRS_H
+
+#cmakedefine HAS_NET_IF_H
+#ifdef HAS_NET_IF_H
+#include <net/if.h> //if_nametoindex
+#endif //HAS_NET_IF_H
+
+/***********************************************************************
+ * htonll and ntohll for GCC
+ **********************************************************************/
+#if defined(__GNUC__) && !defined(htonll)
+ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ #define htonll(x) __builtin_bswap64(x)
+ #else //big endian
+ #define htonll(x) (x)
+ #endif //little endian
+#endif //__GNUC__ and not htonll
+
+#if defined(__GNUC__) && !defined(ntohll)
+ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ #define ntohll(x) __builtin_bswap64(x)
+ #else //big endian
+ #define ntohll(x) (x)
+ #endif //little endian
+#endif //__GNUC__ and not ntohll
+
+/***********************************************************************
+ * socket type definitions
+ **********************************************************************/
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET -1
+#endif //INVALID_SOCKET
+
+/***********************************************************************
+ * socket errno
+ **********************************************************************/
+#ifdef _MSC_VER
+#define SOCKET_ERRNO WSAGetLastError()
+#else
+#define SOCKET_ERRNO errno
+#endif
+
+/***********************************************************************
+ * OSX compatibility
+ **********************************************************************/
+#if !defined(IPV6_ADD_MEMBERSHIP) && defined(IPV6_JOIN_GROUP)
+#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
+#endif
+
+#if !defined(IPV6_DROP_MEMBERSHIP) && defined(IPV6_LEAVE_GROUP)
+#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
+#endif
diff --git a/common/SoapyStreamEndpoint.cpp b/common/SoapyStreamEndpoint.cpp
new file mode 100644
index 0000000..4705031
--- /dev/null
+++ b/common/SoapyStreamEndpoint.cpp
@@ -0,0 +1,380 @@
+// Copyright (c) 2015-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Errors.hpp>
+#include <SoapySDR/Logger.hpp>
+#include "SoapyStreamEndpoint.hpp"
+#include "SoapyRPCSocket.hpp"
+#include "SoapyURLUtils.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapySocketDefs.hpp"
+#include <cassert>
+#include <cstdint>
+
+#define HEADER_SIZE sizeof(StreamDatagramHeader)
+
+//use the larger IPv6 header size
+#define PROTO_HEADER_SIZE (40 + 8) //IPv6 + UDP
+
+struct StreamDatagramHeader
+{
+ uint32_t bytes; //!< total number of bytes in datagram
+ uint32_t sequence; //!< sequence count for flow control
+ uint32_t elems; //!< number of elements or error code
+ int flags; //!< flags associated with this datagram
+ long long time; //!< time associated with this datagram
+};
+
+SoapyStreamEndpoint::SoapyStreamEndpoint(
+ SoapyRPCSocket &streamSock,
+ SoapyRPCSocket &statusSock,
+ const bool isRecv,
+ const size_t numChans,
+ const size_t elemSize,
+ const size_t mtu,
+ const size_t window):
+ _streamSock(streamSock),
+ _statusSock(statusSock),
+ _xferSize(mtu-PROTO_HEADER_SIZE),
+ _numChans(numChans),
+ _elemSize(elemSize),
+ _buffSize(((_xferSize-HEADER_SIZE)/numChans)/elemSize),
+ _numBuffs(SOAPY_REMOTE_ENDPOINT_NUM_BUFFS),
+ _nextHandleAcquire(0),
+ _nextHandleRelease(0),
+ _numHandlesAcquired(0),
+ _lastSendSequence(0),
+ _lastRecvSequence(0),
+ _maxInFlightSeqs(0),
+ _receiveInitial(false),
+ _triggerAckWindow(0)
+{
+ assert(not _streamSock.null());
+
+ //allocate buffer data and default state
+ _buffData.resize(_numBuffs);
+ for (auto &data : _buffData)
+ {
+ data.acquired = false;
+ data.buff.resize(_xferSize);
+ data.buffs.resize(_numChans);
+ for (size_t i = 0; i < _numChans; i++)
+ {
+ size_t offsetBytes = HEADER_SIZE+(i*_buffSize*_elemSize);
+ data.buffs[i] = (void*)(data.buff.data()+offsetBytes);
+ }
+ }
+
+ //endpoints require a large socket buffer in the data direction
+ int ret = _streamSock.setBuffSize(isRecv, window);
+ if (ret != 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint resize socket buffer to %d KiB failed\n %s", int(window/1024), _streamSock.lastErrorMsg());
+ }
+
+ //log when the size is not expected, users may have to tweak system parameters
+ int actualWindow = _streamSock.getBuffSize(isRecv);
+ if (actualWindow < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint get socket buffer size failed\n %s", _streamSock.lastErrorMsg());
+ actualWindow = window;
+ }
+ else if (size_t(actualWindow) < window)
+ {
+ SoapySDR::logf(SOAPY_SDR_WARNING, "StreamEndpoint resize socket buffer: set %d KiB, got %d KiB", int(window/1024), int(actualWindow/1024));
+ }
+
+ //print summary
+ SoapySDR::logf(SOAPY_SDR_INFO, "Configured %s endpoint: dgram=%d bytes, %d elements @ %d bytes, window=%d KiB",
+ isRecv?"receiver":"sender", int(_xferSize), int(_buffSize*_numChans), int(_elemSize), int(actualWindow/1024));
+
+ //calculate flow control window
+ if (isRecv)
+ {
+ //calculate maximum in-flight sequences allowed
+ _maxInFlightSeqs = actualWindow/mtu;
+
+ //calculate the flow control ACK conditions
+ _triggerAckWindow = _maxInFlightSeqs/_numBuffs;
+
+ //send gratuitous ack to set sender's window
+ this->sendACK();
+ }
+ else
+ {
+ //_maxInFlightSeqs set by flow control packet
+ }
+}
+
+SoapyStreamEndpoint::~SoapyStreamEndpoint(void)
+{
+ return;
+}
+
+void SoapyStreamEndpoint::sendACK(void)
+{
+ StreamDatagramHeader header;
+ header.bytes = htonl(sizeof(header));
+ header.sequence = htonl(_lastRecvSequence);
+ header.elems = htonl(_maxInFlightSeqs);
+ header.flags = htonl(0);
+ header.time = htonll(0);
+
+ //send the flow control ACK
+ int ret = _streamSock.send(&header, sizeof(header));
+ if (ret < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::sendACK(), FAILED %s", _streamSock.lastErrorMsg());
+ }
+ else if (size_t(ret) != sizeof(header))
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::sendACK(%d bytes), FAILED %d", int(sizeof(header)), ret);
+ }
+
+ //update last flow control ACK state
+ _lastSendSequence = _lastRecvSequence;
+}
+
+void SoapyStreamEndpoint::recvACK(void)
+{
+ StreamDatagramHeader header;
+ int ret = _streamSock.recv(&header, sizeof(header));
+ if (ret < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::recvACK(), FAILED %s", _streamSock.lastErrorMsg());
+ }
+ _receiveInitial = true;
+
+ //check the header
+ size_t bytes = ntohl(header.bytes);
+ if (bytes > size_t(ret))
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::recvACK(%d bytes), FAILED %d", int(bytes), ret);
+ }
+
+ _lastRecvSequence = ntohl(header.sequence);
+ _maxInFlightSeqs = ntohl(header.elems);
+}
+
+/***********************************************************************
+ * receive endpoint implementation
+ **********************************************************************/
+bool SoapyStreamEndpoint::waitRecv(const long timeoutUs)
+{
+ //send gratuitous ack until something is received
+ if (not _receiveInitial) this->sendACK();
+ return _streamSock.selectRecv(timeoutUs);
+}
+
+int SoapyStreamEndpoint::acquireRecv(size_t &handle, const void **buffs, int &flags, long long &timeNs)
+{
+ //no available handles, the user is hoarding them...
+ if (_numHandlesAcquired == _buffData.size())
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::acquireRecv() -- all buffers acquired");
+ return SOAPY_SDR_STREAM_ERROR;
+ }
+
+ //grab the current handle
+ handle = _nextHandleAcquire;
+ auto &data = _buffData[handle];
+
+ //receive into the buffer
+ assert(not _streamSock.null());
+ int ret = _streamSock.recv(data.buff.data(), data.buff.size());
+ if (ret < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::acquireRecv(), FAILED %s", _streamSock.lastErrorMsg());
+ return SOAPY_SDR_STREAM_ERROR;
+ }
+ _receiveInitial = true;
+
+ //check the header
+ auto header = (const StreamDatagramHeader*)data.buff.data();
+ size_t bytes = ntohl(header->bytes);
+ if (bytes > size_t(ret))
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::acquireRecv(%d bytes), FAILED %d\n"
+ "This MTU setting may be unachievable. Check network configuration.", int(bytes), ret);
+ return SOAPY_SDR_STREAM_ERROR;
+ }
+ const int numElemsOrErr = int(ntohl(header->elems));
+
+ //dropped or out of order packets
+ //TODO return an error code, more than a notification
+ if (uint32_t(_lastRecvSequence) != uint32_t(ntohl(header->sequence)))
+ {
+ SoapySDR::log(SOAPY_SDR_SSI, "S");
+ }
+
+ //update flow control
+ _lastRecvSequence = ntohl(header->sequence)+1;
+
+ //has there been at least trigger window number of sequences since the last ACK?
+ if (uint32_t(_lastRecvSequence-_lastSendSequence) >= _triggerAckWindow)
+ {
+ this->sendACK();
+ }
+
+ //increment for next handle
+ if (numElemsOrErr >= 0)
+ {
+ data.acquired = true;
+ _nextHandleAcquire = (_nextHandleAcquire + 1)%_numBuffs;
+ _numHandlesAcquired++;
+ }
+
+ //set output parameters
+ this->getAddrs(handle, (void **)buffs);
+ flags = ntohl(header->flags);
+ timeNs = ntohll(header->time);
+ return numElemsOrErr;
+}
+
+void SoapyStreamEndpoint::releaseRecv(const size_t handle)
+{
+ auto &data = _buffData[handle];
+ data.acquired = false;
+
+ //actually release in order of handle index
+ while (_numHandlesAcquired != 0)
+ {
+ if (_buffData[_nextHandleRelease].acquired) break;
+ _nextHandleRelease = (_nextHandleRelease + 1)%_numBuffs;
+ _numHandlesAcquired--;
+ }
+}
+
+/***********************************************************************
+ * send endpoint implementation
+ **********************************************************************/
+bool SoapyStreamEndpoint::waitSend(const long timeoutUs)
+{
+ //are we within the allowed number of sequences in flight?
+ while (not _receiveInitial or uint32_t(_lastSendSequence-_lastRecvSequence) >= _maxInFlightSeqs)
+ {
+ //wait for a flow control ACK to arrive
+ if (not _streamSock.selectRecv(timeoutUs)) return false;
+
+ //exhaustive receive without timeout
+ while (_streamSock.selectRecv(0)) this->recvACK();
+ }
+
+ return true;
+}
+
+int SoapyStreamEndpoint::acquireSend(size_t &handle, void **buffs)
+{
+ //no available handles, the user is hoarding them...
+ if (_numHandlesAcquired == _buffData.size())
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::acquireSend() -- all buffers acquired");
+ return SOAPY_SDR_STREAM_ERROR;
+ }
+
+ //grab the current handle
+ handle = _nextHandleAcquire;
+ auto &data = _buffData[handle];
+
+ //increment for next handle
+ data.acquired = true;
+ _nextHandleAcquire = (_nextHandleAcquire + 1)%_numBuffs;
+ _numHandlesAcquired++;
+
+ //set output parameters
+ this->getAddrs(handle, buffs);
+ return int(_buffSize);
+}
+
+void SoapyStreamEndpoint::releaseSend(const size_t handle, const int numElemsOrErr, int &flags, const long long timeNs)
+{
+ auto &data = _buffData[handle];
+ data.acquired = false;
+
+ //The first N-1 channels must be complete buffSize sends
+ //due to the pointer allocation at initialization time.
+ //The last channel can be shortened to the available numElems.
+ const size_t totalElems = ((_numChans-1)*_buffSize) + numElemsOrErr;
+
+ //load the header
+ auto header = (StreamDatagramHeader*)data.buff.data();
+ size_t bytes = HEADER_SIZE + ((numElemsOrErr < 0)?0:(totalElems*_elemSize));
+ header->bytes = htonl(bytes);
+ header->sequence = htonl(_lastSendSequence++);
+ header->elems = htonl(numElemsOrErr);
+ header->flags = htonl(flags);
+ header->time = htonll(timeNs);
+
+ //send from the buffer
+ assert(not _streamSock.null());
+ int ret = _streamSock.send(data.buff.data(), bytes);
+ if (ret < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::releaseSend(), FAILED %s", _streamSock.lastErrorMsg());
+ }
+ else if (size_t(ret) != bytes)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::releaseSend(%d bytes), FAILED %d", int(bytes), ret);
+ }
+
+ //actually release in order of handle index
+ while (_numHandlesAcquired != 0)
+ {
+ if (_buffData[_nextHandleRelease].acquired) break;
+ _nextHandleRelease = (_nextHandleRelease + 1)%_numBuffs;
+ _numHandlesAcquired--;
+ }
+}
+
+/***********************************************************************
+ * status endpoint implementation -- used by both directions
+ **********************************************************************/
+bool SoapyStreamEndpoint::waitStatus(const long timeoutUs)
+{
+ return _statusSock.selectRecv(timeoutUs);
+}
+
+int SoapyStreamEndpoint::readStatus(size_t &chanMask, int &flags, long long &timeNs)
+{
+ StreamDatagramHeader header;
+ //read the status
+ assert(not _statusSock.null());
+ int ret = _statusSock.recv(&header, sizeof(header));
+ if (ret < 0) return SOAPY_SDR_STREAM_ERROR;
+
+ //check the header
+ size_t bytes = ntohl(header.bytes);
+ if (bytes > size_t(ret))
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::readStatus(%d bytes), FAILED %d", int(bytes), ret);
+ return SOAPY_SDR_STREAM_ERROR;
+ }
+
+ //set output parameters
+ chanMask = ntohl(header.sequence);
+ flags = ntohl(header.flags);
+ timeNs = ntohll(header.time);
+ return int(ntohl(header.elems));
+}
+
+void SoapyStreamEndpoint::writeStatus(const int code, const size_t chanMask, const int flags, const long long timeNs)
+{
+ StreamDatagramHeader header;
+ header.bytes = htonl(sizeof(header));
+ header.sequence = htonl(chanMask);
+ header.flags = htonl(flags);
+ header.time = htonll(timeNs);
+ header.elems = htonl(code);
+
+ //send the status
+ assert(not _statusSock.null());
+ int ret = _statusSock.send(&header, sizeof(header));
+ if (ret < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::writeStatus(), FAILED %s", _statusSock.lastErrorMsg());
+ }
+ else if (size_t(ret) != sizeof(header))
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "StreamEndpoint::writeStatus(%d bytes), FAILED %d", int(sizeof(header)), ret);
+ }
+}
diff --git a/common/SoapyStreamEndpoint.hpp b/common/SoapyStreamEndpoint.hpp
new file mode 100644
index 0000000..93587ea
--- /dev/null
+++ b/common/SoapyStreamEndpoint.hpp
@@ -0,0 +1,159 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRemoteConfig.hpp"
+#include <cstddef>
+#include <vector>
+
+class SoapyRPCSocket;
+
+/*!
+ * The stream endpoint supports a windowed link datagram protocol.
+ * This endpoint can be operated in only one mode: receive or send,
+ * and must be paired with another differently configured endpoint.
+ */
+class SOAPY_REMOTE_API SoapyStreamEndpoint
+{
+public:
+ SoapyStreamEndpoint(
+ SoapyRPCSocket &streamSock,
+ SoapyRPCSocket &statusSock,
+ const bool isRecv,
+ const size_t numChans,
+ const size_t elemSize,
+ const size_t mtu,
+ const size_t window);
+
+ ~SoapyStreamEndpoint(void);
+
+ //! How many channels configured
+ size_t getNumChans(void) const
+ {
+ return _numChans;
+ }
+
+ //! Element size in bytes
+ size_t getElemSize(void) const
+ {
+ return _elemSize;
+ }
+
+ //! Actual buffer size in elements
+ size_t getBuffSize(void) const
+ {
+ return _buffSize;
+ }
+
+ //! Actual number of buffers
+ size_t getNumBuffs(void) const
+ {
+ return _numBuffs;
+ }
+
+ //! Query handle addresses
+ void getAddrs(const size_t handle, void **buffs) const
+ {
+ for (size_t i = 0; i < _numChans; i++)
+ {
+ buffs[i] = _buffData[handle].buffs[i];
+ }
+ }
+
+ /*******************************************************************
+ * receive endpoint API
+ ******************************************************************/
+
+ /*!
+ * Wait for a datagram to arrive at the socket
+ * return true when ready for false for timeout.
+ */
+ bool waitRecv(const long timeoutUs);
+
+ /*!
+ * Acquire a receive buffer with metadata.
+ * return the number of elements or error code
+ */
+ int acquireRecv(size_t &handle, const void **buffs, int &flags, long long &timeNs);
+
+ /*!
+ * Release the buffer when done.
+ */
+ void releaseRecv(const size_t handle);
+
+ /*******************************************************************
+ * send endpoint API
+ ******************************************************************/
+
+ /*!
+ * Wait for the flow control to allow transmission.
+ * return true when ready for false for timeout.
+ */
+ bool waitSend(const long timeoutUs);
+
+ /*!
+ * Acquire a receive buffer with metadata.
+ */
+ int acquireSend(size_t &handle, void **buffs);
+
+ /*!
+ * Release the buffer when done.
+ * pass in the number of elements or error code
+ */
+ void releaseSend(const size_t handle, const int numElemsOrErr, int &flags, const long long timeNs);
+
+ /*******************************************************************
+ * status endpoint API -- used by both directions
+ ******************************************************************/
+
+ /*!
+ * Wait for a status message to arrive
+ */
+ bool waitStatus(const long timeoutUs);
+
+ /*!
+ * Read the stream status data.
+ * Return 0 or error code.
+ */
+ int readStatus(size_t &chanMask, int &flags, long long &timeNs);
+
+ /*!
+ * Write the stream status from the forwarder.
+ */
+ void writeStatus(const int code, const size_t chanMask, const int flags, const long long timeNs);
+
+private:
+ SoapyRPCSocket &_streamSock;
+ SoapyRPCSocket &_statusSock;
+ const size_t _xferSize;
+ const size_t _numChans;
+ const size_t _elemSize;
+ const size_t _buffSize;
+ const size_t _numBuffs;
+
+ struct BufferData
+ {
+ std::vector<char> buff; //actual POD
+ std::vector<void *> buffs; //pointers
+ bool acquired;
+ };
+ std::vector<BufferData> _buffData;
+
+ //acquire+release tracking
+ size_t _nextHandleAcquire;
+ size_t _nextHandleRelease;
+ size_t _numHandlesAcquired;
+
+ //sequence tracking
+ size_t _lastSendSequence;
+ size_t _lastRecvSequence;
+ size_t _maxInFlightSeqs;
+ bool _receiveInitial;
+
+ //how often to send a flow control ACK? (recv only)
+ size_t _triggerAckWindow;
+
+ //flow control helpers
+ void sendACK(void);
+ void recvACK(void);
+};
diff --git a/common/SoapyURLUtils.cpp b/common/SoapyURLUtils.cpp
new file mode 100644
index 0000000..bf45e0c
--- /dev/null
+++ b/common/SoapyURLUtils.cpp
@@ -0,0 +1,217 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapySocketDefs.hpp"
+#include "SoapyURLUtils.hpp"
+#include <cstring> //memset
+#include <string>
+#include <cassert>
+
+SockAddrData::SockAddrData(void)
+{
+ return;
+}
+
+SockAddrData::SockAddrData(const struct sockaddr *addr, const int addrlen)
+{
+ _storage.resize(addrlen);
+ std::memcpy(_storage.data(), addr, addrlen);
+}
+
+const struct sockaddr *SockAddrData::addr(void) const
+{
+ return (const struct sockaddr *)_storage.data();
+}
+
+size_t SockAddrData::addrlen(void) const
+{
+ return _storage.size();
+}
+
+SoapyURL::SoapyURL(void)
+{
+ return;
+}
+
+SoapyURL::SoapyURL(const std::string &scheme, const std::string &node, const std::string &service):
+ _scheme(scheme),
+ _node(node),
+ _service(service)
+{
+ return;
+}
+
+SoapyURL::SoapyURL(const std::string &url)
+{
+ //extract the scheme
+ std::string urlRest = url;
+ auto schemeEnd = url.find("://");
+ if (schemeEnd != std::string::npos)
+ {
+ _scheme = url.substr(0, schemeEnd);
+ urlRest = url.substr(schemeEnd+3);
+ }
+
+ //extract node name and service port
+ bool inBracket = false;
+ bool inService = false;
+ for (size_t i = 0; i < urlRest.size(); i++)
+ {
+ const char ch = urlRest[i];
+ if (inBracket and ch == ']')
+ {
+ inBracket = false;
+ continue;
+ }
+ if (not inBracket and ch == '[')
+ {
+ inBracket = true;
+ continue;
+ }
+ if (inBracket)
+ {
+ _node += ch;
+ continue;
+ }
+ if (inService)
+ {
+ _service += ch;
+ continue;
+ }
+ if (not inService and ch == ':')
+ {
+ inService = true;
+ continue;
+ }
+ if (not inService)
+ {
+ _node += ch;
+ continue;
+ }
+ }
+}
+
+SoapyURL::SoapyURL(const SockAddrData &addr)
+{
+ char *s = NULL;
+ switch(addr.addr()->sa_family)
+ {
+ case AF_INET: {
+ auto *addr_in = (const struct sockaddr_in *)addr.addr();
+ s = (char *)malloc(INET_ADDRSTRLEN);
+ inet_ntop(AF_INET, (void *)&(addr_in->sin_addr), s, INET_ADDRSTRLEN);
+ _node = s;
+ _service = std::to_string(ntohs(addr_in->sin_port));
+ break;
+ }
+ case AF_INET6: {
+ auto *addr_in6 = (const struct sockaddr_in6 *)addr.addr();
+ s = (char *)malloc(INET6_ADDRSTRLEN);
+ inet_ntop(AF_INET6, (void *)&(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
+ _node = s;
+ //support scoped address node
+ if (addr_in6->sin6_scope_id != 0)
+ {
+ _node += "%" + std::to_string(addr_in6->sin6_scope_id);
+ }
+ _service = std::to_string(ntohs(addr_in6->sin6_port));
+ break;
+ }
+ default:
+ break;
+ }
+ free(s);
+}
+
+std::string SoapyURL::toSockAddr(SockAddrData &addr) const
+{
+ SockAddrData result;
+
+ //unspecified service, cant continue
+ if (_service.empty()) return "service not specified";
+
+ //configure the hint
+ struct addrinfo hints, *servinfo = NULL;
+ std::memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
+ hints.ai_socktype = this->getType();
+
+ //get address info
+ int ret = getaddrinfo(_node.c_str(), _service.c_str(), &hints, &servinfo);
+ if (ret != 0) return gai_strerror(ret);
+
+ //iterate through possible matches
+ struct addrinfo *p = NULL;
+ for (p = servinfo; p != NULL; p = p->ai_next)
+ {
+ //eliminate unsupported family types
+ if (p->ai_family != AF_INET and p->ai_family != AF_INET6) continue;
+
+ //found a match
+ assert(p->ai_family == p->ai_addr->sa_family);
+ addr = SockAddrData(p->ai_addr, p->ai_addrlen);
+ break;
+ }
+
+ //cleanup
+ freeaddrinfo(servinfo);
+
+ //no results
+ if (p == NULL) return "no lookup results";
+
+ return ""; //OK
+}
+
+std::string SoapyURL::toString(void) const
+{
+ std::string url;
+
+ //add the scheme
+ if (not _scheme.empty()) url += _scheme + "://";
+
+ //add the node with ipv6 escape brackets
+ if (_node.find(":") != std::string::npos) url += "[" + _node + "]";
+ else url += _node;
+
+ //and the service
+ if (not _service.empty()) url += ":" + _service;
+
+ return url;
+}
+
+std::string SoapyURL::getScheme(void) const
+{
+ return _scheme;
+}
+
+std::string SoapyURL::getNode(void) const
+{
+ return _node;
+}
+
+std::string SoapyURL::getService(void) const
+{
+ return _service;
+}
+
+void SoapyURL::setScheme(const std::string &scheme)
+{
+ _scheme = scheme;
+}
+
+void SoapyURL::setNode(const std::string &node)
+{
+ _node = node;
+}
+
+void SoapyURL::setService(const std::string &service)
+{
+ _service = service;
+}
+
+int SoapyURL::getType(void) const
+{
+ if (_scheme == "tcp") return SOCK_STREAM;
+ if (_scheme == "udp") return SOCK_DGRAM;
+ return SOCK_STREAM; //assume
+}
diff --git a/common/SoapyURLUtils.hpp b/common/SoapyURLUtils.hpp
new file mode 100644
index 0000000..aeeeb57
--- /dev/null
+++ b/common/SoapyURLUtils.hpp
@@ -0,0 +1,87 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRemoteConfig.hpp"
+#include <cstddef>
+#include <string>
+#include <vector>
+
+//forward declares
+struct sockaddr;
+
+//! A simple storage class for a sockaddr
+class SOAPY_REMOTE_API SockAddrData
+{
+public:
+ //! Create an empty socket address
+ SockAddrData(void);
+
+ //! Create a socket address from a pointer and length
+ SockAddrData(const struct sockaddr *addr, const int addrlen);
+
+ //! Get a pointer to the underlying data
+ const struct sockaddr *addr(void) const;
+
+ //! Length of the underlying structure
+ size_t addrlen(void) const;
+
+private:
+ std::vector<char> _storage;
+};
+
+/*!
+ * URL parsing, manipulation, lookup.
+ */
+class SOAPY_REMOTE_API SoapyURL
+{
+public:
+ //! Create empty url object
+ SoapyURL(void);
+
+ //! Create URL from components
+ SoapyURL(const std::string &scheme, const std::string &node, const std::string &service);
+
+ //! Parse from url markup string
+ SoapyURL(const std::string &url);
+
+ //! Create URL from socket address
+ SoapyURL(const SockAddrData &addr);
+
+ /*!
+ * Convert to socket address + resolve address.
+ * Return the error message on failure.
+ */
+ std::string toSockAddr(SockAddrData &addr) const;
+
+ /*!
+ * Convert to URL string markup.
+ */
+ std::string toString(void) const;
+
+ //! Get the scheme
+ std::string getScheme(void) const;
+
+ //! Get the node
+ std::string getNode(void) const;
+
+ //! Get the service
+ std::string getService(void) const;
+
+ //! Set the scheme
+ void setScheme(const std::string &scheme);
+
+ //! Set the node
+ void setNode(const std::string &node);
+
+ //! Set the service
+ void setService(const std::string &service);
+
+ //! Get the socket type from the scheme
+ int getType(void) const;
+
+private:
+ std::string _scheme;
+ std::string _node;
+ std::string _service;
+};
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..c46379c
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,29 @@
+soapyremote (0.3.1) unstable; urgency=low
+
+ * Release 0.3.1 (2016-09-01)
+
+ -- Josh Blum <josh at pothosware.com> Thu, 01 Sep 2016 21:02:42 -0700
+
+soapyremote (0.3.0) unstable; urgency=low
+
+ * Release 0.3.0 (2016-07-10)
+
+ -- Josh Blum <josh at pothosware.com> Sun, 10 Jul 2016 16:48:12 -0700
+
+soapyremote (0.2.1) unstable; urgency=low
+
+ * Release 0.2.1 (2016-04-21)
+
+ -- Josh Blum <josh at pothosware.com> Thu, 21 Apr 2016 14:50:37 -0400
+
+soapyremote (0.2.0) unstable; urgency=low
+
+ * Release 0.2.0 (2015-11-21)
+
+ -- Josh Blum <josh at pothosware.com> Thu, 15 Oct 2015 19:29:23 -0700
+
+soapyremote (0.1.0) unstable; urgency=low
+
+ * Release 0.1.0 (2015-10-10)
+
+ -- Josh Blum <josh at pothosware.com> Sat, 10 Oct 2015 11:08:35 -0700
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..16c85cc
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,35 @@
+Source: soapyremote
+Section: libs
+Priority: optional
+Maintainer: Josh Blum <josh at pothosware.com>
+Build-Depends:
+ debhelper (>= 9.0.0),
+ cmake,
+ libsoapysdr-dev (>= 0.3.0)
+Standards-Version: 3.9.8
+Homepage: https://github.com/pothosware/SoapyRemote/wiki
+Vcs-Git: https://github.com/pothosware/SoapyRemote.git
+Vcs-Browser: https://github.com/pothosware/SoapyRemote
+
+Package: soapysdr0.5-2-module-remote
+Architecture: any
+Multi-Arch: same
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Soapy Remote - Remote device support for Soapy SDR.
+ A Soapy module that supports remote devices within the Soapy API.
+
+Package: soapysdr-module-remote
+Architecture: all
+Depends: soapysdr0.5-2-module-remote, ${misc:Depends}
+Description: Soapy Remote - Remote device support for Soapy SDR.
+ A Soapy module that supports remote devices within the Soapy API.
+ .
+ This is an empty dependency package that pulls in the remote module
+ for the default version of libsoapysdr.
+
+Package: soapysdr-server
+Section: libs
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Soapy Remote - Remote device support for Soapy SDR.
+ The SoapySDRServer server application for remote devices.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..372433f
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,32 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: soapyremote
+Source: https://github.com/pothosware/SoapyRemote/wiki
+
+Files: *
+Copyright:
+ Copyright (c) 2015-2016 Josh Blum <josh at pothosware.com>
+ Copyright (c) 2016-2016 Bastille Networks
+License: BSL-1.0
+ Boost Software License - Version 1.0 - August 17th, 2003
+ .
+ Permission is hereby granted, free of charge, to any person or organization
+ obtaining a copy of the software and accompanying documentation covered by
+ this license (the "Software") to use, reproduce, display, distribute,
+ execute, and transmit the Software, and to prepare derivative works of the
+ Software, and to permit third-parties to whom the Software is furnished to
+ do so, all subject to the following:
+ .
+ The copyright notices in the Software and this entire statement, including
+ the above license grant, this restriction and the following disclaimer,
+ must be included in all copies of the Software, in whole or in part, and
+ all derivative works of the Software, unless such copies or derivative
+ works are solely in the form of machine-executable object code generated by
+ a source language processor.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..b43bf86
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+README.md
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..6eb429c
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,17 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
+export DEB_HOST_MULTIARCH
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+%:
+ dh $@ --buildsystem=cmake --parallel
+
+override_dh_auto_configure:
+ dh_auto_configure -- -DLIB_SUFFIX="/$(DEB_HOST_MULTIARCH)"
+
+override_dh_installchangelogs:
+ dh_installchangelogs Changelog.txt
diff --git a/debian/soapysdr-server.install b/debian/soapysdr-server.install
new file mode 100644
index 0000000..c703cf8
--- /dev/null
+++ b/debian/soapysdr-server.install
@@ -0,0 +1 @@
+usr/bin/
diff --git a/debian/soapysdr0.5-2-module-remote.install b/debian/soapysdr0.5-2-module-remote.install
new file mode 100644
index 0000000..56a9b13
--- /dev/null
+++ b/debian/soapysdr0.5-2-module-remote.install
@@ -0,0 +1 @@
+usr/lib/*/SoapySDR/modules*
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
new file mode 100644
index 0000000..4ad4bcd
--- /dev/null
+++ b/server/CMakeLists.txt
@@ -0,0 +1,36 @@
+########################################################################
+# Thread config support
+########################################################################
+find_library(
+ RT_LIBRARIES
+ NAMES rt
+ PATHS /usr/lib /usr/lib64
+)
+
+if (RT_LIBRARIES)
+ list(APPEND SoapySDR_LIBRARIES ${RT_LIBRARIES})
+endif()
+
+if(WIN32)
+ list(APPEND SOAPY_SERVER_SOURCES ThreadPrioWindows.cpp)
+elseif(UNIX)
+ list(APPEND SOAPY_SERVER_SOURCES ThreadPrioUnix.cpp)
+endif()
+
+########################################################################
+# Build the remote server application
+########################################################################
+list(APPEND SOAPY_SERVER_SOURCES
+ SoapyServer.cpp
+ ServerListener.cpp
+ ClientHandler.cpp
+ LogForwarding.cpp
+ ServerStreamData.cpp
+)
+if (MSVC)
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/msvc)
+endif ()
+include_directories(${SoapySDR_INCLUDE_DIRS})
+add_executable(SoapySDRServer ${SOAPY_SERVER_SOURCES})
+target_link_libraries(SoapySDRServer ${SoapySDR_LIBRARIES} SoapySDRRemoteCommon)
+install(TARGETS SoapySDRServer DESTINATION bin)
diff --git a/server/ClientHandler.cpp b/server/ClientHandler.cpp
new file mode 100644
index 0000000..f5e0a11
--- /dev/null
+++ b/server/ClientHandler.cpp
@@ -0,0 +1,1358 @@
+// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+#include "ClientHandler.hpp"
+#include "ServerStreamData.hpp"
+#include "LogForwarding.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyURLUtils.hpp"
+#include "SoapyRPCSocket.hpp"
+#include "SoapyRPCPacker.hpp"
+#include "SoapyRPCUnpacker.hpp"
+#include "SoapyStreamEndpoint.hpp"
+#include <SoapySDR/Device.hpp>
+#include <SoapySDR/Logger.hpp>
+#include <SoapySDR/Formats.hpp>
+#include <SoapySDR/Version.hpp>
+#include <iostream>
+#include <mutex>
+
+//! The device factory make and unmake requires a process-wide mutex
+static std::mutex factoryMutex;
+
+/***********************************************************************
+ * Client handler constructor
+ **********************************************************************/
+SoapyClientHandler::SoapyClientHandler(SoapyRPCSocket &sock, const std::string &uuid):
+ _sock(sock),
+ _uuid(uuid),
+ _dev(nullptr),
+ _logForwarder(nullptr),
+ _nextStreamId(0)
+{
+ return;
+}
+
+SoapyClientHandler::~SoapyClientHandler(void)
+{
+ //stop all stream threads and close streams
+ for (auto &data : _streamData)
+ {
+ data.second.stopThreads();
+ _dev->closeStream(data.second.stream);
+ }
+
+ //release the device handle if we have it
+ if (_dev != nullptr)
+ {
+ std::lock_guard<std::mutex> lock(factoryMutex);
+ SoapySDR::Device::unmake(_dev);
+ _dev = nullptr;
+ }
+
+ //finally stop and cleanup log forwarding
+ delete _logForwarder;
+}
+
+/***********************************************************************
+ * Transaction handler
+ **********************************************************************/
+bool SoapyClientHandler::handleOnce(void)
+{
+ if (not _sock.selectRecv(SOAPY_REMOTE_SOCKET_TIMEOUT_US)) return true;
+
+ //receive the client's request
+ SoapyRPCUnpacker unpacker(_sock);
+ SoapyRPCPacker packer(_sock);
+
+ //handle the client's request
+ bool again = true;
+ try
+ {
+ again = this->handleOnce(unpacker, packer);
+ }
+ catch (const std::exception &ex)
+ {
+ packer & ex;
+ }
+
+ //send the result back
+ packer();
+
+ return again;
+}
+
+/***********************************************************************
+ * Handler dispatcher implementation
+ **********************************************************************/
+bool SoapyClientHandler::handleOnce(SoapyRPCUnpacker &unpacker, SoapyRPCPacker &packer)
+{
+ SoapyRemoteCalls call;
+ unpacker & call;
+
+ switch (call)
+ {
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_FIND:
+ ////////////////////////////////////////////////////////////////////
+ {
+ SoapySDR::Kwargs args;
+ unpacker & args;
+ packer & SoapySDR::Device::enumerate(args);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_MAKE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ SoapySDR::Kwargs args;
+ unpacker & args;
+ std::lock_guard<std::mutex> lock(factoryMutex);
+ if (_dev == nullptr) _dev = SoapySDR::Device::make(args);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_UNMAKE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ //stop all stream threads and close streams
+ if (not _streamData.empty())
+ {
+ SoapySDR::log(SOAPY_SDR_WARNING, "Performing automatic closeStream() before Device unmake.");
+ }
+ for (auto &data : _streamData)
+ {
+ data.second.stopThreads();
+ _dev->closeStream(data.second.stream);
+ }
+ _streamData.clear();
+
+ std::lock_guard<std::mutex> lock(factoryMutex);
+ if (_dev != nullptr) SoapySDR::Device::unmake(_dev);
+ _dev = nullptr;
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_HANGUP:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_SERVER_ID:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _uuid;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_START_LOG_FORWARDING:
+ ////////////////////////////////////////////////////////////////////
+ {
+ if (_logForwarder == nullptr) _logForwarder = new SoapyLogForwarder(_sock);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_STOP_LOG_FORWARDING:
+ ////////////////////////////////////////////////////////////////////
+ {
+ if (_logForwarder != nullptr) delete _logForwarder;
+ _logForwarder = nullptr;
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_DRIVER_KEY:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->getDriverKey();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_HARDWARE_KEY:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->getHardwareKey();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_HARDWARE_INFO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->getHardwareInfo();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_FRONTEND_MAPPING:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ std::string mapping;
+ unpacker & direction;
+ unpacker & mapping;
+ _dev->setFrontendMapping(direction, mapping);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_FRONTEND_MAPPING:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ unpacker & direction;
+ packer & _dev->getFrontendMapping(direction);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_NUM_CHANNELS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ unpacker & direction;
+ packer & int(_dev->getNumChannels(direction));
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_FULL_DUPLEX:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getFullDuplex(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_CHANNEL_INFO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ #ifdef SOAPY_SDR_API_HAS_GET_CHANNEL_INFO
+ packer & _dev->getChannelInfo(direction, channel);
+ #else
+ SoapySDR::Kwargs result;
+ packer & result;
+ #endif
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_STREAM_FORMATS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getStreamFormats(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_NATIVE_STREAM_FORMAT:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+
+ double fullScale = 0.0;
+ packer & _dev->getNativeStreamFormat(direction, channel, fullScale);
+ packer & fullScale;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_STREAM_ARGS_INFO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getStreamArgsInfo(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SETUP_STREAM:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ std::string format;
+ std::vector<size_t> channels;
+ SoapySDR::Kwargs args;
+ std::string clientBindPort;
+ std::string statusBindPort;
+ unpacker & direction;
+ unpacker & format;
+ unpacker & channels;
+ unpacker & args;
+ unpacker & clientBindPort;
+ unpacker & statusBindPort;
+
+ //parse args for buffer configuration
+ size_t mtu = SOAPY_REMOTE_DEFAULT_ENDPOINT_MTU;
+ const auto mtuIt = args.find(SOAPY_REMOTE_KWARG_MTU);
+ if (mtuIt != args.end()) mtu = size_t(std::stod(mtuIt->second));
+
+ size_t window = SOAPY_REMOTE_DEFAULT_ENDPOINT_WINDOW;
+ const auto windowIt = args.find(SOAPY_REMOTE_KWARG_WINDOW);
+ if (windowIt != args.end()) window = size_t(std::stod(windowIt->second));
+
+ double priority = SOAPY_REMOTE_DEFAULT_THREAD_PRIORITY;
+ const auto priorityIt = args.find(SOAPY_REMOTE_KWARG_PRIORITY);
+ if (priorityIt != args.end()) priority = std::stod(priorityIt->second);
+
+ //create stream
+ auto stream = _dev->setupStream(direction, format, channels, args);
+
+ //load data structure
+ auto &data = _streamData[_nextStreamId];
+ data.streamId = _nextStreamId++;
+ data.device = _dev;
+ data.stream = stream;
+ data.format = format;
+ for (const auto chan : channels) data.chanMask |= (1 << chan);
+ data.priority = priority;
+
+ //extract socket node information
+ const auto localNode = SoapyURL(_sock.getsockname()).getNode();
+ const auto remoteNode = SoapyURL(_sock.getpeername()).getNode();
+
+ //bind the stream socket to an automatic port
+ const auto bindURL = SoapyURL("udp", localNode, "0").toString();
+ int ret = data.streamSock.bind(bindURL);
+ if (ret != 0)
+ {
+ const std::string errorMsg = data.streamSock.lastErrorMsg();
+ _streamData.erase(data.streamId);
+ throw std::runtime_error("SoapyRemote::setupStream("+bindURL+") -- bind FAIL: " + errorMsg);
+ }
+ SoapySDR::logf(SOAPY_SDR_INFO, "Server side stream bound to %s", data.streamSock.getsockname().c_str());
+ const auto serverBindPort = SoapyURL(data.streamSock.getsockname()).getService();
+
+ //connect the stream socket to the specified port
+ auto connectURL = SoapyURL("udp", remoteNode, clientBindPort).toString();
+ ret = data.streamSock.connect(connectURL);
+ if (ret != 0)
+ {
+ const std::string errorMsg = data.streamSock.lastErrorMsg();
+ _streamData.erase(data.streamId);
+ throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+ }
+ SoapySDR::logf(SOAPY_SDR_INFO, "Server side stream connected to %s", data.streamSock.getpeername().c_str());
+
+ //connect the status socket to the specified port
+ connectURL = SoapyURL("udp", remoteNode, statusBindPort).toString();
+ ret = data.statusSock.connect(connectURL);
+ if (ret != 0)
+ {
+ const std::string errorMsg = data.statusSock.lastErrorMsg();
+ _streamData.erase(data.streamId);
+ throw std::runtime_error("SoapyRemote::setupStream("+connectURL+") -- connect FAIL: " + errorMsg);
+ }
+ SoapySDR::logf(SOAPY_SDR_INFO, "Server side status connected to %s", data.statusSock.getpeername().c_str());
+
+ //create endpoint
+ data.endpoint = new SoapyStreamEndpoint(data.streamSock, data.statusSock,
+ direction == SOAPY_SDR_TX, channels.size(), SoapySDR::formatToSize(format), mtu, window);
+
+ //start worker thread, this is not backwards,
+ //receive from device means using a send endpoint
+ //transmit to device means using a recv endpoint
+ if (direction == SOAPY_SDR_RX) data.startSendThread();
+ if (direction == SOAPY_SDR_TX) data.startRecvThread();
+ data.startStatThread();
+
+ packer & data.streamId;
+ packer & serverBindPort;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_CLOSE_STREAM:
+ ////////////////////////////////////////////////////////////////////
+ {
+ int streamId = 0;
+ unpacker & streamId;
+
+ //cleanup data and stop worker thread
+ auto &data = _streamData.at(streamId);
+ data.stopThreads();
+ _dev->closeStream(data.stream);
+ _streamData.erase(streamId);
+
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_ACTIVATE_STREAM:
+ ////////////////////////////////////////////////////////////////////
+ {
+ int streamId = 0;
+ int flags = 0;
+ long long timeNs = 0;
+ int numElems = 0;
+ unpacker & streamId;
+ unpacker & flags;
+ unpacker & timeNs;
+ unpacker & numElems;
+
+ auto &data = _streamData.at(streamId);
+ packer & _dev->activateStream(data.stream, flags, timeNs, size_t(numElems));
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_DEACTIVATE_STREAM:
+ ////////////////////////////////////////////////////////////////////
+ {
+ int streamId = 0;
+ int flags = 0;
+ long long timeNs = 0;
+ unpacker & streamId;
+ unpacker & flags;
+ unpacker & timeNs;
+
+ auto &data = _streamData.at(streamId);
+ packer & _dev->deactivateStream(data.stream, flags, timeNs);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_ANTENNAS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->listAntennas(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_ANTENNA:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ _dev->setAntenna(direction, channel, name);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_ANTENNA:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getAntenna(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_HAS_DC_OFFSET_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->hasDCOffsetMode(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_DC_OFFSET_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ bool automatic = false;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & automatic;
+ _dev->setDCOffsetMode(direction, channel, automatic);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_DC_OFFSET_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getDCOffsetMode(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_HAS_DC_OFFSET:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->hasDCOffset(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_DC_OFFSET:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::complex<double> offset;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & offset;
+ _dev->setDCOffset(direction, channel, offset);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_DC_OFFSET:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getDCOffset(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_HAS_IQ_BALANCE_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->hasIQBalance(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_IQ_BALANCE_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::complex<double> balance;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & balance;
+ _dev->setIQBalance(direction, channel, balance);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_IQ_BALANCE_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getIQBalance(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_GAINS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->listGains(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_HAS_GAIN_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->hasGainMode(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_GAIN_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ bool automatic = false;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & automatic;
+ _dev->setGainMode(direction, channel, automatic);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_GAIN_MODE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getGainMode(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_GAIN:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ double value = 0;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & value;
+ _dev->setGain(direction, channel, value);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_GAIN_ELEMENT:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ double value = 0;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ unpacker & value;
+ _dev->setGain(direction, channel, name, value);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_GAIN:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getGain(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_GAIN_ELEMENT:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ packer & _dev->getGain(direction, channel, name);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_GAIN_RANGE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getGainRange(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_GAIN_RANGE_ELEMENT:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ packer & _dev->getGainRange(direction, channel, name);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_FREQUENCY:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ double value = 0;
+ SoapySDR::Kwargs args;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & value;
+ unpacker & args;
+ _dev->setFrequency(direction, channel, value, args);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_FREQUENCY_COMPONENT:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ double value = 0;
+ SoapySDR::Kwargs args;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ unpacker & value;
+ unpacker & args;
+ _dev->setFrequency(direction, channel, name, value, args);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_FREQUENCY:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getFrequency(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_FREQUENCY_COMPONENT:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ packer & _dev->getFrequency(direction, channel, name);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_FREQUENCIES:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->listFrequencies(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_FREQUENCY_RANGE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getFrequencyRange(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_FREQUENCY_RANGE_COMPONENT:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ packer & _dev->getFrequencyRange(direction, channel, name);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_FREQUENCY_ARGS_INFO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getFrequencyArgsInfo(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_SAMPLE_RATE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ double rate = 0;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & rate;
+ _dev->setSampleRate(direction, channel, rate);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_SAMPLE_RATE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getSampleRate(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_SAMPLE_RATES:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->listSampleRates(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_BANDWIDTH:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ double bw = 0;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & bw;
+ _dev->setBandwidth(direction, channel, bw);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_BANDWIDTH:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->getBandwidth(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_BANDWIDTHS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->listBandwidths(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_BANDWIDTH_RANGE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ #ifdef SOAPY_SDR_API_HAS_GET_BANDWIDTH_RANGE
+ packer & _dev->getBandwidthRange(direction, channel);
+ #else
+ SoapySDR::RangeList result;
+ packer & result;
+ #endif
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_MASTER_CLOCK_RATE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ double rate = 0;
+ unpacker & rate;
+ _dev->setMasterClockRate(rate);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_MASTER_CLOCK_RATE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->getMasterClockRate();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_MASTER_CLOCK_RATES:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->getMasterClockRates();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_CLOCK_SOURCES:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->listClockSources();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_CLOCK_SOURCE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string source;
+ unpacker & source;
+ _dev->setClockSource(source);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_CLOCK_SOURCE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->getClockSource();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_TIME_SOURCES:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->listTimeSources();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_TIME_SOURCE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string source;
+ unpacker & source;
+ _dev->setTimeSource(source);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_TIME_SOURCE:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->getTimeSource();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_HAS_HARDWARE_TIME:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string what;
+ unpacker & what;
+ packer & _dev->hasHardwareTime(what);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_HARDWARE_TIME:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string what;
+ unpacker & what;
+ packer & _dev->getHardwareTime(what);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_HARDWARE_TIME:
+ ////////////////////////////////////////////////////////////////////
+ {
+ long long timeNs = 0;
+ std::string what;
+ unpacker & timeNs;
+ unpacker & what;
+ _dev->setHardwareTime(timeNs, what);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_SET_COMMAND_TIME:
+ ////////////////////////////////////////////////////////////////////
+ {
+ long long timeNs = 0;
+ std::string what;
+ unpacker & timeNs;
+ unpacker & what;
+ _dev->setCommandTime(timeNs, what);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_SENSORS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->listSensors();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_SENSOR_INFO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string name;
+ unpacker & name;
+ packer & _dev->getSensorInfo(name);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_SENSOR:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string name;
+ unpacker & name;
+ packer & _dev->readSensor(name);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_CHANNEL_SENSORS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ packer & _dev->listSensors(direction, channel);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_CHANNEL_SENSOR_INFO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ packer & _dev->getSensorInfo(direction, channel, name);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_CHANNEL_SENSOR:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string name;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & name;
+ packer & _dev->readSensor(direction, channel, name);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_REGISTER:
+ ////////////////////////////////////////////////////////////////////
+ {
+ int addr = 0;
+ int value = 0;
+ unpacker & addr;
+ unpacker & value;
+ _dev->writeRegister(unsigned(addr), unsigned(value));
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_REGISTER:
+ ////////////////////////////////////////////////////////////////////
+ {
+ int addr = 0;
+ unpacker & addr;
+ packer & int(_dev->readRegister(unsigned(addr)));
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_REGISTER_INTERFACES:
+ ////////////////////////////////////////////////////////////////////
+ {
+ #ifdef SOAPY_SDR_API_HAS_NAMED_REGISTER_API
+ packer & _dev->listRegisterInterfaces();
+ #else
+ std::vector<std::string> result;
+ packer & result;
+ #endif
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_REGISTER_NAMED:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string name;
+ int addr = 0;
+ int value = 0;
+ unpacker & name;
+ unpacker & addr;
+ unpacker & value;
+ #ifdef SOAPY_SDR_API_HAS_NAMED_REGISTER_API
+ _dev->writeRegister(name, unsigned(addr), unsigned(value));
+ #endif
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_REGISTER_NAMED:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string name;
+ int addr = 0;
+ unpacker & name;
+ unpacker & addr;
+ #ifdef SOAPY_SDR_API_HAS_NAMED_REGISTER_API
+ packer & int(_dev->readRegister(name, unsigned(addr)));
+ #else
+ int result = 0;
+ packer & result;
+ #endif
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_SETTING_INFO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->getSettingInfo();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_SETTING:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string key;
+ std::string value;
+ unpacker & key;
+ unpacker & value;
+ _dev->writeSetting(key, value);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_SETTING:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string key;
+ unpacker & key;
+ packer & _dev->readSetting(key);
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_GET_CHANNEL_SETTING_INFO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ unpacker & direction;
+ unpacker & channel;
+ #ifdef SOAPY_SDR_API_HAS_CHANNEL_SETTINGS
+ packer & _dev->getSettingInfo(direction, channel);
+ #else
+ SoapySDR::ArgInfoList info;
+ packer & info;
+ #endif
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_CHANNEL_SETTING:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string key;
+ std::string value;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & key;
+ unpacker & value;
+ #ifdef SOAPY_SDR_API_HAS_CHANNEL_SETTINGS
+ _dev->writeSetting(direction, channel, key, value);
+ #endif
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_CHANNEL_SETTING:
+ ////////////////////////////////////////////////////////////////////
+ {
+ char direction = 0;
+ int channel = 0;
+ std::string key;
+ unpacker & direction;
+ unpacker & channel;
+ unpacker & key;
+ #ifdef SOAPY_SDR_API_HAS_CHANNEL_SETTINGS
+ packer & _dev->readSetting(direction, channel, key);
+ #else
+ std::string value;
+ packer & value;
+ #endif
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_GPIO_BANKS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->listGPIOBanks();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_GPIO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string bank;
+ int value = 0;
+ unpacker & bank;
+ unpacker & value;
+ _dev->writeGPIO(bank, unsigned(value));
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_GPIO_MASKED:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string bank;
+ int value = 0;
+ int mask = 0;
+ unpacker & bank;
+ unpacker & value;
+ unpacker & mask;
+ _dev->writeGPIO(bank, unsigned(value), unsigned(mask));
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_GPIO:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string bank;
+ unpacker & bank;
+ packer & int(_dev->readGPIO(bank));
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_GPIO_DIR:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string bank;
+ int dir = 0;
+ unpacker & bank;
+ unpacker & dir;
+ _dev->writeGPIODir(bank, unsigned(dir));
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_GPIO_DIR_MASKED:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string bank;
+ int dir = 0;
+ int mask = 0;
+ unpacker & bank;
+ unpacker & dir;
+ unpacker & mask;
+ _dev->writeGPIODir(bank, unsigned(dir), unsigned(mask));
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_GPIO_DIR:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string bank;
+ unpacker & bank;
+ packer & int(_dev->readGPIODir(bank));
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_I2C:
+ ////////////////////////////////////////////////////////////////////
+ {
+ int addr = 0;
+ std::string data;
+ unpacker & addr;
+ unpacker & data;
+ _dev->writeI2C(addr, data);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_I2C:
+ ////////////////////////////////////////////////////////////////////
+ {
+ int addr = 0;
+ int numBytes = 0;
+ unpacker & addr;
+ unpacker & numBytes;
+ packer & _dev->readI2C(addr, unsigned(numBytes));
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_TRANSACT_SPI:
+ ////////////////////////////////////////////////////////////////////
+ {
+ int addr = 0;
+ int data = 0;
+ int numBits = 0;
+ unpacker & addr;
+ unpacker & data;
+ unpacker & numBits;
+ packer & int(_dev->transactSPI(addr, unsigned(data), size_t(numBits)));
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_LIST_UARTS:
+ ////////////////////////////////////////////////////////////////////
+ {
+ packer & _dev->listUARTs();
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_WRITE_UART:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string which;
+ std::string data;
+ unpacker & which;
+ unpacker & data;
+ _dev->writeUART(which, data);
+ packer & SOAPY_REMOTE_VOID;
+ } break;
+
+ ////////////////////////////////////////////////////////////////////
+ case SOAPY_REMOTE_READ_UART:
+ ////////////////////////////////////////////////////////////////////
+ {
+ std::string which;
+ int timeoutUs = 0;
+ unpacker & which;
+ unpacker & timeoutUs;
+ packer & _dev->readUART(which, long(timeoutUs));
+ } break;
+
+ default: throw std::runtime_error(
+ "SoapyClientHandler::handleOnce("+std::to_string(int(call))+") unknown call");
+ }
+
+ return call != SOAPY_REMOTE_HANGUP;
+}
diff --git a/server/ClientHandler.hpp b/server/ClientHandler.hpp
new file mode 100644
index 0000000..eb952f5
--- /dev/null
+++ b/server/ClientHandler.hpp
@@ -0,0 +1,44 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include <cstddef>
+#include <string>
+#include <map>
+
+class SoapyRPCSocket;
+class SoapyRPCPacker;
+class SoapyRPCUnpacker;
+class SoapyLogForwarder;
+class ServerStreamData;
+
+namespace SoapySDR
+{
+ class Device;
+}
+
+/*!
+ * The client handler manages a remote client.
+ */
+class SoapyClientHandler
+{
+public:
+ SoapyClientHandler(SoapyRPCSocket &sock, const std::string &uuid);
+
+ ~SoapyClientHandler(void);
+
+ //handle once, return true to call again
+ bool handleOnce(void);
+
+private:
+ bool handleOnce(SoapyRPCUnpacker &unpacker, SoapyRPCPacker &packer);
+
+ SoapyRPCSocket &_sock;
+ const std::string _uuid;
+ SoapySDR::Device *_dev;
+ SoapyLogForwarder *_logForwarder;
+
+ //stream tracking
+ int _nextStreamId;
+ std::map<int, ServerStreamData> _streamData;
+};
diff --git a/server/LogForwarding.cpp b/server/LogForwarding.cpp
new file mode 100644
index 0000000..71dc453
--- /dev/null
+++ b/server/LogForwarding.cpp
@@ -0,0 +1,59 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "LogForwarding.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyRPCPacker.hpp"
+#include <SoapySDR/Logger.hpp>
+#include <mutex>
+#include <set>
+
+/***********************************************************************
+ * socket subscribers for log forwarding
+ **********************************************************************/
+static std::mutex subscribersMutex;
+
+static std::set<SoapyRPCSocket *> subscribers;
+
+/***********************************************************************
+ * custom log handling
+ **********************************************************************/
+static void handleLogMessage(const SoapySDRLogLevel logLevel, const char *message)
+{
+ std::string strMessage(message);
+
+ std::lock_guard<std::mutex> lock(subscribersMutex);
+ for (auto sock : subscribers)
+ {
+ try
+ {
+ SoapyRPCPacker packer(*sock);
+ packer & char(logLevel);
+ packer & strMessage;
+ packer();
+ }
+ catch (...)
+ {
+ //ignored
+ }
+ }
+}
+
+/***********************************************************************
+ * subscriber reregistration entry points
+ **********************************************************************/
+SoapyLogForwarder::SoapyLogForwarder(SoapyRPCSocket &sock):
+ _sock(sock)
+{
+ std::lock_guard<std::mutex> lock(subscribersMutex);
+ subscribers.insert(&_sock);
+
+ //register the log handler, its safe to re-register every time
+ SoapySDR::registerLogHandler(&handleLogMessage);
+}
+
+SoapyLogForwarder::~SoapyLogForwarder(void)
+{
+ std::lock_guard<std::mutex> lock(subscribersMutex);
+ subscribers.erase(&_sock);
+}
diff --git a/server/LogForwarding.hpp b/server/LogForwarding.hpp
new file mode 100644
index 0000000..8d977f6
--- /dev/null
+++ b/server/LogForwarding.hpp
@@ -0,0 +1,18 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRPCSocket.hpp"
+
+/*!
+ * Create a log forwarder to subscribe to log events from the local logger.
+ */
+class SoapyLogForwarder
+{
+public:
+ SoapyLogForwarder(SoapyRPCSocket &sock);
+ ~SoapyLogForwarder(void);
+
+private:
+ SoapyRPCSocket &_sock;
+};
diff --git a/server/ServerListener.cpp b/server/ServerListener.cpp
new file mode 100644
index 0000000..16fd148
--- /dev/null
+++ b/server/ServerListener.cpp
@@ -0,0 +1,108 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapyServer.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "ClientHandler.hpp"
+#include "SoapyRPCSocket.hpp"
+#include <thread>
+#include <iostream>
+
+/***********************************************************************
+ * Server thread implementation
+ **********************************************************************/
+SoapyServerThreadData::SoapyServerThreadData(void):
+ done(false),
+ thread(nullptr),
+ client(nullptr)
+{
+ return;
+}
+
+SoapyServerThreadData::~SoapyServerThreadData(void)
+{
+ done = true;
+ if (thread != nullptr)
+ {
+ thread->join();
+ }
+ delete thread;
+ if (client != nullptr)
+ {
+ std::cout << "SoapyServerListener::close()" << std::endl;
+ }
+ delete client;
+}
+
+void SoapyServerThreadData::handlerLoop(void)
+{
+ SoapyClientHandler handler(*client, uuid);
+
+ try
+ {
+ while (handler.handleOnce())
+ {
+ if (done) break;
+ }
+ }
+ catch (const std::exception &ex)
+ {
+ std::cerr << "SoapyServerListener::handlerLoop() FAIL: " << ex.what() << std::endl;
+ }
+
+ done = true;
+}
+
+/***********************************************************************
+ * Socket listener constructor
+ **********************************************************************/
+SoapyServerListener::SoapyServerListener(SoapyRPCSocket &sock, const std::string &uuid):
+ _sock(sock),
+ _uuid(uuid),
+ _handlerId(0)
+{
+ return;
+}
+
+SoapyServerListener::~SoapyServerListener(void)
+{
+ auto it = _handlers.begin();
+ while (it != _handlers.end())
+ {
+ _handlers.erase(it++);
+ }
+}
+
+/***********************************************************************
+ * Client socket acceptor
+ **********************************************************************/
+void SoapyServerListener::handleOnce(void)
+{
+ //cleanup completed threads
+ auto it = _handlers.begin();
+ while (it != _handlers.end())
+ {
+ auto &data = it->second;
+ if (not data.done) ++it;
+ else _handlers.erase(it++);
+ }
+
+ //wait with timeout for the server socket to become ready to accept
+ if (not _sock.selectRecv(SOAPY_REMOTE_SOCKET_TIMEOUT_US)) return;
+
+ SoapyRPCSocket *client = _sock.accept();
+ if (client == NULL)
+ {
+ std::cerr << "SoapyServerListener::accept() FAIL:" << _sock.lastErrorMsg() << std::endl;
+ return;
+ }
+ std::cout << "SoapyServerListener::accept(" << client->getpeername() << ")" << std::endl;
+
+ //setup the thread data
+ auto &data = _handlers[_handlerId++];
+ data.client = client;
+ data.uuid = _uuid;
+
+ //spawn a new thread
+ data.thread = new std::thread(&SoapyServerThreadData::handlerLoop, &data);
+}
diff --git a/server/ServerStreamData.cpp b/server/ServerStreamData.cpp
new file mode 100644
index 0000000..a44e600
--- /dev/null
+++ b/server/ServerStreamData.cpp
@@ -0,0 +1,223 @@
+// Copyright (c) 2015-2015 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+#include "ServerStreamData.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyStreamEndpoint.hpp"
+#include <SoapySDR/Device.hpp>
+#include <SoapySDR/Logger.hpp>
+#include <algorithm> //min
+#include <thread>
+#include <vector>
+#include <cassert>
+
+template <typename T>
+void incrementBuffs(std::vector<T> &buffs, size_t numElems, size_t elemSize)
+{
+ for (auto &buff : buffs)
+ {
+ buff = T(size_t(buff) + (numElems*elemSize));
+ }
+}
+
+ServerStreamData::ServerStreamData(void):
+ device(nullptr),
+ stream(nullptr),
+ chanMask(0),
+ priority(0.0),
+ streamId(-1),
+ endpoint(nullptr),
+ streamThread(nullptr),
+ statusThread(nullptr),
+ done(true)
+{
+ return;
+}
+
+void ServerStreamData::startSendThread(void)
+{
+ assert(streamId != -1);
+ done = false;
+ streamThread = new std::thread(&ServerStreamData::sendEndpointWork, this);
+}
+
+void ServerStreamData::startRecvThread(void)
+{
+ assert(streamId != -1);
+ done = false;
+ streamThread = new std::thread(&ServerStreamData::recvEndpointWork, this);
+}
+
+void ServerStreamData::startStatThread(void)
+{
+ assert(streamId != -1);
+ done = false;
+ statusThread = new std::thread(&ServerStreamData::statEndpointWork, this);
+}
+
+void ServerStreamData::stopThreads(void)
+{
+ done = true;
+ assert(streamThread != nullptr);
+ assert(statusThread != nullptr);
+ streamThread->join();
+ statusThread->join();
+ delete streamThread;
+ delete statusThread;
+}
+
+static void setThreadPrioWithLogging(const double priority)
+{
+ const auto errorMsg = setThreadPrio(priority);
+ if (not errorMsg.empty()) SoapySDR::logf(SOAPY_SDR_WARNING,
+ "Set thread priority %g failed: %s", priority, errorMsg.c_str());
+}
+
+void ServerStreamData::recvEndpointWork(void)
+{
+ setThreadPrioWithLogging(priority);
+ assert(endpoint != nullptr);
+ assert(endpoint->getElemSize() != 0);
+ assert(endpoint->getNumChans() != 0);
+
+ //setup worker data structures
+ int ret = 0;
+ size_t handle = 0;
+ int flags = 0;
+ long long timeNs = 0;
+ const auto elemSize = endpoint->getElemSize();
+ std::vector<const void *> buffs(endpoint->getNumChans());
+
+ //loop forever until signaled done
+ //1) wait on the endpoint to become ready
+ //2) acquire the recv buffer from the endpoint
+ //3) write to the device stream from the endpoint buffer
+ //4) release the buffer back to the endpoint
+ while (not done)
+ {
+ if (not endpoint->waitRecv(SOAPY_REMOTE_SOCKET_TIMEOUT_US)) continue;
+ ret = endpoint->acquireRecv(handle, buffs.data(), flags, timeNs);
+ if (ret < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "Server-side receive endpoint: %s; worker quitting...", streamSock.lastErrorMsg());
+ return;
+ }
+
+ //loop to write to device
+ size_t elemsLeft = size_t(ret);
+ while (not done)
+ {
+ ret = device->writeStream(stream, buffs.data(), elemsLeft, flags, timeNs, SOAPY_REMOTE_SOCKET_TIMEOUT_US);
+ if (ret == SOAPY_SDR_TIMEOUT) continue;
+ if (ret < 0)
+ {
+ endpoint->writeStatus(ret, chanMask, flags, timeNs);
+ break; //discard after error, this may have been invalid flags or time
+ }
+ elemsLeft -= ret;
+ incrementBuffs(buffs, ret, elemSize);
+ if (elemsLeft == 0) break;
+ flags &= ~(SOAPY_SDR_HAS_TIME); //clear time for subsequent writes
+ }
+
+ //release the buffer back to the endpoint
+ endpoint->releaseRecv(handle);
+ }
+}
+
+void ServerStreamData::sendEndpointWork(void)
+{
+ setThreadPrioWithLogging(priority);
+ assert(endpoint != nullptr);
+ assert(endpoint->getElemSize() != 0);
+ assert(endpoint->getNumChans() != 0);
+
+ //setup worker data structures
+ int ret = 0;
+ size_t handle = 0;
+ int flags = 0;
+ long long timeNs = 0;
+ const auto elemSize = endpoint->getElemSize();
+ std::vector<void *> buffs(endpoint->getNumChans());
+ const size_t mtuElems = device->getStreamMTU(stream);
+
+ //loop forever until signaled done
+ //1) waits on the endpoint to become ready
+ //2) acquire the send buffer from the endpoint
+ //3) read from the device stream into the endpoint buffer
+ //4) release the buffer back to the endpoint (sends)
+ while (not done)
+ {
+ if (not endpoint->waitSend(SOAPY_REMOTE_SOCKET_TIMEOUT_US)) continue;
+ ret = endpoint->acquireSend(handle, buffs.data());
+ if (ret < 0)
+ {
+ SoapySDR::logf(SOAPY_SDR_ERROR, "Server-side send endpoint: %s; worker quitting...", streamSock.lastErrorMsg());
+ return;
+ }
+
+ //Read only up to MTU size with a timeout for minimal waiting.
+ //In the next section we will continue the read with non-blocking.
+ size_t elemsLeft = size_t(ret);
+ size_t elemsRead = 0;
+ while (not done)
+ {
+ flags = 0; //flags is an in/out parameter and must be cleared for consistency
+ const size_t numElems = std::min(mtuElems, elemsLeft);
+ ret = device->readStream(stream, buffs.data(), numElems, flags, timeNs, SOAPY_REMOTE_SOCKET_TIMEOUT_US);
+ if (ret == SOAPY_SDR_TIMEOUT) continue;
+ if (ret < 0)
+ {
+ //ret will be propagated to remote endpoint
+ break;
+ }
+ elemsLeft -= ret;
+ elemsRead += ret;
+ incrementBuffs(buffs, ret, elemSize);
+ break;
+ }
+
+ //fill remaining buffer with no timeout
+ //This is a latency optimization to forward to the host ASAP,
+ //but to use the full bandwidth when more data is available.
+ //Do not allow this optimization when end of burst or single packet mode to preserve boundaries
+ if (elemsRead != 0 and elemsLeft != 0 and (flags & (SOAPY_SDR_END_BURST | SOAPY_SDR_ONE_PACKET)) == 0)
+ {
+ int flags1 = 0;
+ long long timeNs1 = 0;
+ ret = device->readStream(stream, buffs.data(), elemsLeft, flags1, timeNs1, 0);
+ if (ret == SOAPY_SDR_TIMEOUT) ret = 0; //timeouts OK
+ if (ret > 0)
+ {
+ elemsLeft -= ret;
+ elemsRead += ret;
+ }
+ }
+
+ //release the buffer with flags and time from the first read
+ //if any read call returned an error, forward the error instead
+ endpoint->releaseSend(handle, (ret < 0)?ret:elemsRead, flags, timeNs);
+ }
+}
+
+void ServerStreamData::statEndpointWork(void)
+{
+ assert(endpoint != nullptr);
+
+ int ret = 0;
+ size_t chanMask = 0;
+ int flags = 0;
+ long long timeNs = 0;
+
+ while (not done)
+ {
+ ret = device->readStreamStatus(stream, chanMask, flags, timeNs, SOAPY_REMOTE_SOCKET_TIMEOUT_US);
+ if (ret == SOAPY_SDR_TIMEOUT) continue;
+ endpoint->writeStatus(ret, chanMask, flags, timeNs);
+
+ //exit the thread if stream status is not supported
+ //but only after reporting this to the local endpoint
+ if (ret == SOAPY_SDR_NOT_SUPPORTED) return;
+ }
+}
diff --git a/server/ServerStreamData.hpp b/server/ServerStreamData.hpp
new file mode 100644
index 0000000..c9f7ef2
--- /dev/null
+++ b/server/ServerStreamData.hpp
@@ -0,0 +1,65 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include "SoapyRPCSocket.hpp"
+#include "ThreadPrioHelper.hpp"
+#include <csignal> //sig_atomic_t
+#include <string>
+#include <thread>
+
+class SoapyStreamEndpoint;
+
+namespace SoapySDR
+{
+ class Device;
+ class Stream;
+}
+
+/*!
+ * Server-side stream data for client handler.
+ * This class manages a recv/send endpoint,
+ * and a thread to handle that endpoint.
+ */
+class ServerStreamData
+{
+public:
+ ServerStreamData(void);
+
+ SoapySDR::Device *device;
+ SoapySDR::Stream *stream;
+ std::string format;
+ size_t chanMask;
+ double priority;
+
+ //this ID identifies the stream to the remote host
+ int streamId;
+
+ //datagram socket for stream endpoint
+ SoapyRPCSocket streamSock;
+
+ //datagram socket for status endpoint
+ SoapyRPCSocket statusSock;
+
+ //remote side of the stream endpoint
+ SoapyStreamEndpoint *endpoint;
+
+ //hooks to start/stop work
+ void startSendThread(void);
+ void startRecvThread(void);
+ void startStatThread(void);
+ void stopThreads(void);
+
+ //worker implementations
+ void recvEndpointWork(void);
+ void sendEndpointWork(void);
+ void statEndpointWork(void);
+
+private:
+ //worker thread for this stream
+ std::thread *streamThread;
+ std::thread *statusThread;
+
+ //signal done to the thread
+ sig_atomic_t done;
+};
diff --git a/server/SoapyServer.cpp b/server/SoapyServer.cpp
new file mode 100644
index 0000000..22038bc
--- /dev/null
+++ b/server/SoapyServer.cpp
@@ -0,0 +1,121 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "SoapyServer.hpp"
+#include "SoapyRemoteDefs.hpp"
+#include "SoapyURLUtils.hpp"
+#include "SoapyInfoUtils.hpp"
+#include "SoapyRPCSocket.hpp"
+#include "SoapySSDPEndpoint.hpp"
+#include <cstdlib>
+#include <cstddef>
+#include <iostream>
+#include <getopt.h>
+#include <csignal>
+
+/***********************************************************************
+ * Print help message
+ **********************************************************************/
+static int printHelp(void)
+{
+ std::cout << "Usage SoapySDRServer [options]" << std::endl;
+ std::cout << " Options summary:" << std::endl;
+ std::cout << " --help \t\t\t\t Print this help message" << std::endl;
+ std::cout << " --bind \t\t\t\t Bind and serve forever" << std::endl;
+ std::cout << std::endl;
+ return EXIT_SUCCESS;
+}
+
+/***********************************************************************
+ * Signal handler for Ctrl + C
+ **********************************************************************/
+static sig_atomic_t serverDone = false;
+void sigIntHandler(const int)
+{
+ std::cout << "Caught Ctrl+C, shutting down the server..." << std::endl;
+ serverDone = true;
+}
+
+/***********************************************************************
+ * Launch the server
+ **********************************************************************/
+static int runServer(void)
+{
+ SoapySocketSession sess;
+ const bool isIPv6Supported = not SoapyRPCSocket(SoapyURL("tcp", "::", "0").toString()).null();
+ const auto defaultBindNode = isIPv6Supported?"::":"0.0.0.0";
+
+ //extract url from user input or generate automatically
+ const bool optargHasURL = (optarg != NULL and not std::string(optarg).empty());
+ auto url = (optargHasURL)? SoapyURL(optarg) : SoapyURL("tcp", defaultBindNode, "");
+
+ //default url parameters when not specified
+ if (url.getScheme().empty()) url.setScheme("tcp");
+ if (url.getService().empty()) url.setService(SOAPY_REMOTE_DEFAULT_SERVICE);
+
+ //this UUID identifies the server process
+ const auto serverUUID = SoapyInfo::generateUUID1();
+ std::cout << serverUUID << std::endl;
+
+ std::cout << "Launching the server... " << url.toString() << std::endl;
+ SoapyRPCSocket s;
+ if (s.bind(url.toString()) != 0)
+ {
+ std::cerr << "Server socket bind FAIL: " << s.lastErrorMsg() << std::endl;
+ return EXIT_FAILURE;
+ }
+ std::cout << "Server bound to " << s.getsockname() << std::endl;
+ s.listen(SOAPY_REMOTE_LISTEN_BACKLOG);
+ auto serverListener = new SoapyServerListener(s, serverUUID);
+
+ std::cout << "Launching discovery server... " << std::endl;
+ auto ssdpEndpoint = SoapySSDPEndpoint::getInstance();
+ ssdpEndpoint->registerService(serverUUID, url.getService());
+ ssdpEndpoint->enablePeriodicNotify(true);
+
+ std::cout << "Press Ctrl+C to stop the server" << std::endl;
+ signal(SIGINT, sigIntHandler);
+ while (not serverDone) serverListener->handleOnce();
+ ssdpEndpoint->enablePeriodicNotify(false);
+ ssdpEndpoint.reset();
+
+ std::cout << "Shutdown client handler threads" << std::endl;
+ delete serverListener;
+ s.close();
+
+ std::cout << "Cleanup complete, exiting" << std::endl;
+ return EXIT_SUCCESS;
+}
+
+/***********************************************************************
+ * Parse and dispatch options
+ **********************************************************************/
+int main(int argc, char *argv[])
+{
+ std::cout << "######################################################" << std::endl;
+ std::cout << "## Soapy Server -- Use any Soapy SDR remotely" << std::endl;
+ std::cout << "######################################################" << std::endl;
+ std::cout << std::endl;
+
+ /*******************************************************************
+ * parse command line options
+ ******************************************************************/
+ static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"bind", optional_argument, 0, 'b'},
+ {0, 0, 0, 0}
+ };
+ int long_index = 0;
+ int option = 0;
+ while ((option = getopt_long_only(argc, argv, "", long_options, &long_index)) != -1)
+ {
+ switch (option)
+ {
+ case 'h': return printHelp();
+ case 'b': return runServer();
+ }
+ }
+
+ //unknown or unspecified options, do help...
+ return printHelp();
+}
diff --git a/server/SoapyServer.hpp b/server/SoapyServer.hpp
new file mode 100644
index 0000000..9f5fb0b
--- /dev/null
+++ b/server/SoapyServer.hpp
@@ -0,0 +1,41 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include <csignal> //sig_atomic_t
+#include <string>
+#include <thread>
+#include <map>
+
+class SoapyRPCSocket;
+
+//! Client handler data
+struct SoapyServerThreadData
+{
+ SoapyServerThreadData(void);
+ ~SoapyServerThreadData(void);
+ void handlerLoop(void);
+ sig_atomic_t done;
+ std::thread *thread;
+ SoapyRPCSocket *client;
+ std::string uuid;
+};
+
+/*!
+ * The server listener class accepts clients and spawns threads.
+ */
+class SoapyServerListener
+{
+public:
+ SoapyServerListener(SoapyRPCSocket &sock, const std::string &uuid);
+
+ ~SoapyServerListener(void);
+
+ void handleOnce(void);
+
+private:
+ SoapyRPCSocket &_sock;
+ const std::string _uuid;
+ size_t _handlerId;
+ std::map<size_t, SoapyServerThreadData> _handlers;
+};
diff --git a/server/ThreadPrioHelper.hpp b/server/ThreadPrioHelper.hpp
new file mode 100644
index 0000000..8c04642
--- /dev/null
+++ b/server/ThreadPrioHelper.hpp
@@ -0,0 +1,13 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+
+#include <string>
+
+/*! Set the thread priority:
+ * -1.0 (low), 0.0 (normal), and 1.0 (high)
+ * Return a empty string on success,
+ * otherwise an error message.
+ */
+std::string setThreadPrio(const double prio);
diff --git a/server/ThreadPrioUnix.cpp b/server/ThreadPrioUnix.cpp
new file mode 100644
index 0000000..edcf4ca
--- /dev/null
+++ b/server/ThreadPrioUnix.cpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "ThreadPrioHelper.hpp"
+#include <cstring> //memset, strerror
+#include <cerrno> //errno
+#include <sched.h>
+
+#ifdef __APPLE__
+#include <thread>
+#endif
+
+std::string setThreadPrio(const double prio)
+{
+ //no negative priorities supported on this OS
+ if (prio <= 0.0) return "";
+
+ //determine priority bounds
+ const int policy(SCHED_RR);
+ const int maxPrio = sched_get_priority_max(policy);
+ if (maxPrio < 0) return strerror(errno);
+ const int minPrio = sched_get_priority_min(policy);
+ if (minPrio < 0) return strerror(errno);
+
+ //set realtime priority and prio number
+ struct sched_param param;
+ std::memset(¶m, 0, sizeof(param));
+ param.sched_priority = minPrio + int(prio * (maxPrio-minPrio));
+
+#ifdef __APPLE__
+ pthread_t tID = pthread_self(); // ID of this thread
+ if (pthread_setschedparam(tID, policy, ¶m) != 0) return strerror(errno);
+#else
+ if (sched_setscheduler(0, policy, ¶m) != 0) return strerror(errno);
+#endif
+
+ return "";
+}
diff --git a/server/ThreadPrioWindows.cpp b/server/ThreadPrioWindows.cpp
new file mode 100644
index 0000000..cca4600
--- /dev/null
+++ b/server/ThreadPrioWindows.cpp
@@ -0,0 +1,28 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "ThreadPrioHelper.hpp"
+#include <windows.h>
+
+std::string setThreadPrio(const double prio)
+{
+ int nPriority(THREAD_PRIORITY_NORMAL);
+
+ if (prio > 0)
+ {
+ if (prio > +0.75) nPriority = THREAD_PRIORITY_TIME_CRITICAL;
+ else if (prio > +0.50) nPriority = THREAD_PRIORITY_HIGHEST;
+ else if (prio > +0.25) nPriority = THREAD_PRIORITY_ABOVE_NORMAL;
+ else nPriority = THREAD_PRIORITY_NORMAL;
+ }
+ else
+ {
+ if (prio < -0.75) nPriority = THREAD_PRIORITY_IDLE;
+ else if (prio < -0.50) nPriority = THREAD_PRIORITY_LOWEST;
+ else if (prio < -0.25) nPriority = THREAD_PRIORITY_BELOW_NORMAL;
+ else nPriority = THREAD_PRIORITY_NORMAL;
+ }
+
+ if (SetThreadPriority(GetCurrentThread(), nPriority)) return "";
+ return "SetThreadPriority() fail";
+}
diff --git a/server/msvc/getopt.h b/server/msvc/getopt.h
new file mode 100644
index 0000000..6c834b4
--- /dev/null
+++ b/server/msvc/getopt.h
@@ -0,0 +1,607 @@
+#ifndef __GETOPT_H__
+/**
+ * DISCLAIMER
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the mingw-w64 runtime package.
+ *
+ * The mingw-w64 runtime package and its code is distributed in the hope that it
+ * will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
+ * IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
+ * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#pragma warning(disable:4996)
+
+#define __GETOPT_H__
+
+/* All the headers include this file. */
+#include <crtdefs.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
+
+#ifdef REPLACE_GETOPT
+int opterr = 1; /* if error message should be printed */
+int optind = 1; /* index into parent argv vector */
+int optopt = '?'; /* character checked for validity */
+#undef optreset /* see getopt.h */
+#define optreset __mingw_optreset
+int optreset; /* reset getopt */
+char *optarg; /* argument associated with option */
+#endif
+
+//extern int optind; /* index of first non-option in argv */
+//extern int optopt; /* single option character, as parsed */
+//extern int opterr; /* flag to enable built-in diagnostics... */
+// /* (user may set to zero, to suppress) */
+//
+//extern char *optarg; /* pointer to argument of current option */
+
+#define PRINT_ERROR ((opterr) && (*options != ':'))
+
+#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
+#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
+#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
+
+/* return values */
+#define BADCH (int)'?'
+#define BADARG ((*options == ':') ? (int)':' : (int)'?')
+#define INORDER (int)1
+
+#ifndef __CYGWIN__
+#define __progname __argv[0]
+#else
+extern char __declspec(dllimport) *__progname;
+#endif
+
+#ifdef __CYGWIN__
+static char EMSG[] = "";
+#else
+#define EMSG ""
+#endif
+
+static int getopt_internal(int, char * const *, const char *,
+ const struct option *, int *, int);
+static int parse_long_options(char * const *, const char *,
+ const struct option *, int *, int);
+static int gcd(int, int);
+static void permute_args(int, int, int, char * const *);
+
+static char *place = EMSG; /* option letter processing */
+
+/* XXX: set optreset to 1 rather than these two */
+static int nonopt_start = -1; /* first non option argument (for permute) */
+static int nonopt_end = -1; /* first option after non options (for permute) */
+
+/* Error messages */
+static const char recargchar[] = "option requires an argument -- %c";
+static const char recargstring[] = "option requires an argument -- %s";
+static const char ambig[] = "ambiguous option -- %.*s";
+static const char noarg[] = "option doesn't take an argument -- %.*s";
+static const char illoptchar[] = "unknown option -- %c";
+static const char illoptstring[] = "unknown option -- %s";
+
+static void
+_vwarnx(const char *fmt,va_list ap)
+{
+ (void)fprintf(stderr,"%s: ",__progname);
+ if (fmt != NULL)
+ (void)vfprintf(stderr,fmt,ap);
+ (void)fprintf(stderr,"\n");
+}
+
+static void
+warnx(const char *fmt,...)
+{
+ va_list ap;
+ va_start(ap,fmt);
+ _vwarnx(fmt,ap);
+ va_end(ap);
+}
+
+/*
+ * Compute the greatest common divisor of a and b.
+ */
+static int
+gcd(int a, int b)
+{
+ int c;
+
+ c = a % b;
+ while (c != 0) {
+ a = b;
+ b = c;
+ c = a % b;
+ }
+
+ return (b);
+}
+
+/*
+ * Exchange the block from nonopt_start to nonopt_end with the block
+ * from nonopt_end to opt_end (keeping the same order of arguments
+ * in each block).
+ */
+static void
+permute_args(int panonopt_start, int panonopt_end, int opt_end,
+ char * const *nargv)
+{
+ int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
+ char *swap;
+
+ /*
+ * compute lengths of blocks and number and size of cycles
+ */
+ nnonopts = panonopt_end - panonopt_start;
+ nopts = opt_end - panonopt_end;
+ ncycle = gcd(nnonopts, nopts);
+ cyclelen = (opt_end - panonopt_start) / ncycle;
+
+ for (i = 0; i < ncycle; i++) {
+ cstart = panonopt_end+i;
+ pos = cstart;
+ for (j = 0; j < cyclelen; j++) {
+ if (pos >= panonopt_end)
+ pos -= nnonopts;
+ else
+ pos += nopts;
+ swap = nargv[pos];
+ /* LINTED const cast */
+ ((char **) nargv)[pos] = nargv[cstart];
+ /* LINTED const cast */
+ ((char **)nargv)[cstart] = swap;
+ }
+ }
+}
+
+#ifdef REPLACE_GETOPT
+/*
+ * getopt --
+ * Parse argc/argv argument vector.
+ *
+ * [eventually this will replace the BSD getopt]
+ */
+int
+getopt(int nargc, char * const *nargv, const char *options)
+{
+
+ /*
+ * We don't pass FLAG_PERMUTE to getopt_internal() since
+ * the BSD getopt(3) (unlike GNU) has never done this.
+ *
+ * Furthermore, since many privileged programs call getopt()
+ * before dropping privileges it makes sense to keep things
+ * as simple (and bug-free) as possible.
+ */
+ return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
+}
+#endif /* REPLACE_GETOPT */
+
+//extern int getopt(int nargc, char * const *nargv, const char *options);
+
+#ifdef _BSD_SOURCE
+/*
+ * BSD adds the non-standard `optreset' feature, for reinitialisation
+ * of `getopt' parsing. We support this feature, for applications which
+ * proclaim their BSD heritage, before including this header; however,
+ * to maintain portability, developers are advised to avoid it.
+ */
+# define optreset __mingw_optreset
+extern int optreset;
+#endif
+#ifdef __cplusplus
+}
+#endif
+/*
+ * POSIX requires the `getopt' API to be specified in `unistd.h';
+ * thus, `unistd.h' includes this header. However, we do not want
+ * to expose the `getopt_long' or `getopt_long_only' APIs, when
+ * included in this manner. Thus, close the standard __GETOPT_H__
+ * declarations block, and open an additional __GETOPT_LONG_H__
+ * specific block, only when *not* __UNISTD_H_SOURCED__, in which
+ * to declare the extended API.
+ */
+#endif /* !defined(__GETOPT_H__) */
+
+#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)
+#define __GETOPT_LONG_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct option /* specification for a long form option... */
+{
+ const char *name; /* option name, without leading hyphens */
+ int has_arg; /* does it take an argument? */
+ int *flag; /* where to save its status, or NULL */
+ int val; /* its associated status value */
+};
+
+enum /* permitted values for its `has_arg' field... */
+{
+ no_argument = 0, /* option never takes an argument */
+ required_argument, /* option always requires an argument */
+ optional_argument /* option may take an argument */
+};
+
+/*
+ * parse_long_options --
+ * Parse long options in argc/argv argument vector.
+ * Returns -1 if short_too is set and the option does not match long_options.
+ */
+static int
+parse_long_options(char * const *nargv, const char *options,
+ const struct option *long_options, int *idx, int short_too)
+{
+ char *current_argv, *has_equal;
+ size_t current_argv_len;
+ int i, ambiguous, match;
+
+#define IDENTICAL_INTERPRETATION(_x, _y) \
+ (long_options[(_x)].has_arg == long_options[(_y)].has_arg && \
+ long_options[(_x)].flag == long_options[(_y)].flag && \
+ long_options[(_x)].val == long_options[(_y)].val)
+
+ current_argv = place;
+ match = -1;
+ ambiguous = 0;
+
+ optind++;
+
+ if ((has_equal = strchr(current_argv, '=')) != NULL) {
+ /* argument found (--option=arg) */
+ current_argv_len = has_equal - current_argv;
+ has_equal++;
+ } else
+ current_argv_len = strlen(current_argv);
+
+ for (i = 0; long_options[i].name; i++) {
+ /* find matching long option */
+ if (strncmp(current_argv, long_options[i].name,
+ current_argv_len))
+ continue;
+
+ if (strlen(long_options[i].name) == current_argv_len) {
+ /* exact match */
+ match = i;
+ ambiguous = 0;
+ break;
+ }
+ /*
+ * If this is a known short option, don't allow
+ * a partial match of a single character.
+ */
+ if (short_too && current_argv_len == 1)
+ continue;
+
+ if (match == -1) /* partial match */
+ match = i;
+ else if (!IDENTICAL_INTERPRETATION(i, match))
+ ambiguous = 1;
+ }
+ if (ambiguous) {
+ /* ambiguous abbreviation */
+ if (PRINT_ERROR)
+ warnx(ambig, (int)current_argv_len,
+ current_argv);
+ optopt = 0;
+ return (BADCH);
+ }
+ if (match != -1) { /* option found */
+ if (long_options[match].has_arg == no_argument
+ && has_equal) {
+ if (PRINT_ERROR)
+ warnx(noarg, (int)current_argv_len,
+ current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless of flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ return (BADARG);
+ }
+ if (long_options[match].has_arg == required_argument ||
+ long_options[match].has_arg == optional_argument) {
+ if (has_equal)
+ optarg = has_equal;
+ else if (long_options[match].has_arg ==
+ required_argument) {
+ /*
+ * optional argument doesn't use next nargv
+ */
+ optarg = nargv[optind++];
+ }
+ }
+ if ((long_options[match].has_arg == required_argument)
+ && (optarg == NULL)) {
+ /*
+ * Missing argument; leading ':' indicates no error
+ * should be generated.
+ */
+ if (PRINT_ERROR)
+ warnx(recargstring,
+ current_argv);
+ /*
+ * XXX: GNU sets optopt to val regardless of flag
+ */
+ if (long_options[match].flag == NULL)
+ optopt = long_options[match].val;
+ else
+ optopt = 0;
+ --optind;
+ return (BADARG);
+ }
+ } else { /* unknown option */
+ if (short_too) {
+ --optind;
+ return (-1);
+ }
+ if (PRINT_ERROR)
+ warnx(illoptstring, current_argv);
+ optopt = 0;
+ return (BADCH);
+ }
+ if (idx)
+ *idx = match;
+ if (long_options[match].flag) {
+ *long_options[match].flag = long_options[match].val;
+ return (0);
+ } else
+ return (long_options[match].val);
+#undef IDENTICAL_INTERPRETATION
+}
+
+/*
+ * getopt_internal --
+ * Parse argc/argv argument vector. Called by user level routines.
+ */
+static int
+getopt_internal(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx, int flags)
+{
+ char *oli; /* option letter list index */
+ int optchar, short_too;
+ static int posixly_correct = -1;
+
+ if (options == NULL)
+ return (-1);
+
+ /*
+ * XXX Some GNU programs (like cvs) set optind to 0 instead of
+ * XXX using optreset. Work around this braindamage.
+ */
+ if (optind == 0)
+ optind = optreset = 1;
+
+ /*
+ * Disable GNU extensions if POSIXLY_CORRECT is set or options
+ * string begins with a '+'.
+ *
+ * CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or
+ * optreset != 0 for GNU compatibility.
+ */
+ if (posixly_correct == -1 || optreset != 0)
+ posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
+ if (*options == '-')
+ flags |= FLAG_ALLARGS;
+ else if (posixly_correct || *options == '+')
+ flags &= ~FLAG_PERMUTE;
+ if (*options == '+' || *options == '-')
+ options++;
+
+ optarg = NULL;
+ if (optreset)
+ nonopt_start = nonopt_end = -1;
+start:
+ if (optreset || !*place) { /* update scanning pointer */
+ optreset = 0;
+ if (optind >= nargc) { /* end of argument vector */
+ place = EMSG;
+ if (nonopt_end != -1) {
+ /* do permutation, if we have to */
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ else if (nonopt_start != -1) {
+ /*
+ * If we skipped non-options, set optind
+ * to the first of them.
+ */
+ optind = nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return (-1);
+ }
+ if (*(place = nargv[optind]) != '-' ||
+ (place[1] == '\0' && strchr(options, '-') == NULL)) {
+ place = EMSG; /* found non-option */
+ if (flags & FLAG_ALLARGS) {
+ /*
+ * GNU extension:
+ * return non-option as argument to option 1
+ */
+ optarg = nargv[optind++];
+ return (INORDER);
+ }
+ if (!(flags & FLAG_PERMUTE)) {
+ /*
+ * If no permutation wanted, stop parsing
+ * at first non-option.
+ */
+ return (-1);
+ }
+ /* do permutation */
+ if (nonopt_start == -1)
+ nonopt_start = optind;
+ else if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ nonopt_start = optind -
+ (nonopt_end - nonopt_start);
+ nonopt_end = -1;
+ }
+ optind++;
+ /* process next argument */
+ goto start;
+ }
+ if (nonopt_start != -1 && nonopt_end == -1)
+ nonopt_end = optind;
+
+ /*
+ * If we have "-" do nothing, if "--" we are done.
+ */
+ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
+ optind++;
+ place = EMSG;
+ /*
+ * We found an option (--), so if we skipped
+ * non-options, we have to permute.
+ */
+ if (nonopt_end != -1) {
+ permute_args(nonopt_start, nonopt_end,
+ optind, nargv);
+ optind -= nonopt_end - nonopt_start;
+ }
+ nonopt_start = nonopt_end = -1;
+ return (-1);
+ }
+ }
+
+ /*
+ * Check long options if:
+ * 1) we were passed some
+ * 2) the arg is not just "-"
+ * 3) either the arg starts with -- we are getopt_long_only()
+ */
+ if (long_options != NULL && place != nargv[optind] &&
+ (*place == '-' || (flags & FLAG_LONGONLY))) {
+ short_too = 0;
+ if (*place == '-')
+ place++; /* --foo long option */
+ else if (*place != ':' && strchr(options, *place) != NULL)
+ short_too = 1; /* could be short option too */
+
+ optchar = parse_long_options(nargv, options, long_options,
+ idx, short_too);
+ if (optchar != -1) {
+ place = EMSG;
+ return (optchar);
+ }
+ }
+
+ if ((optchar = (int)*place++) == (int)':' ||
+ (optchar == (int)'-' && *place != '\0') ||
+ (oli = (char*)strchr(options, optchar)) == NULL) {
+ /*
+ * If the user specified "-" and '-' isn't listed in
+ * options, return -1 (non-option) as per POSIX.
+ * Otherwise, it is an unknown option character (or ':').
+ */
+ if (optchar == (int)'-' && *place == '\0')
+ return (-1);
+ if (!*place)
+ ++optind;
+ if (PRINT_ERROR)
+ warnx(illoptchar, optchar);
+ optopt = optchar;
+ return (BADCH);
+ }
+ if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
+ /* -W long-option */
+ if (*place) /* no space */
+ /* NOTHING */;
+ else if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ warnx(recargchar, optchar);
+ optopt = optchar;
+ return (BADARG);
+ } else /* white space */
+ place = nargv[optind];
+ optchar = parse_long_options(nargv, options, long_options,
+ idx, 0);
+ place = EMSG;
+ return (optchar);
+ }
+ if (*++oli != ':') { /* doesn't take argument */
+ if (!*place)
+ ++optind;
+ } else { /* takes (optional) argument */
+ optarg = NULL;
+ if (*place) /* no white space */
+ optarg = place;
+ else if (oli[1] != ':') { /* arg not optional */
+ if (++optind >= nargc) { /* no arg */
+ place = EMSG;
+ if (PRINT_ERROR)
+ warnx(recargchar, optchar);
+ optopt = optchar;
+ return (BADARG);
+ } else
+ optarg = nargv[optind];
+ }
+ place = EMSG;
+ ++optind;
+ }
+ /* dump back option letter */
+ return (optchar);
+}
+
+/*
+ * getopt_long --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt_long(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx)
+{
+
+ return (getopt_internal(nargc, nargv, options, long_options, idx,
+ FLAG_PERMUTE));
+}
+
+/*
+ * getopt_long_only --
+ * Parse argc/argv argument vector.
+ */
+int
+getopt_long_only(int nargc, char * const *nargv, const char *options,
+ const struct option *long_options, int *idx)
+{
+
+ return (getopt_internal(nargc, nargv, options, long_options, idx,
+ FLAG_PERMUTE|FLAG_LONGONLY));
+}
+
+//extern int getopt_long(int nargc, char * const *nargv, const char *options,
+// const struct option *long_options, int *idx);
+//extern int getopt_long_only(int nargc, char * const *nargv, const char *options,
+// const struct option *long_options, int *idx);
+/*
+ * Previous MinGW implementation had...
+ */
+#ifndef HAVE_DECL_GETOPT
+/*
+ * ...for the long form API only; keep this for compatibility.
+ */
+# define HAVE_DECL_GETOPT 1
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/soapyremote.git
More information about the pkg-hamradio-commits
mailing list