gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

commit 41767be56615edd9bf45f90badba83d77d966a14
parent e92c15905936615e74361daea3178b64251ed15f
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date:   Sun,  8 Dec 2024 17:27:48 +0100

add DSP bridge

Diffstat:
Mbase.cmake | 1+
Mscripts/synths.cmake | 3+++
Msource/CMakeLists.txt | 6++++++
Msource/baseLib/CMakeLists.txt | 1+
Msource/baseLib/binarystream.h | 8++++++++
Msource/baseLib/commandline.cpp | 55++++---------------------------------------------------
Msource/baseLib/commandline.h | 20++------------------
Msource/baseLib/configFile.cpp | 48+++++++++++++++++++++++++++---------------------
Msource/baseLib/configFile.h | 13+++----------
Msource/baseLib/md5.cpp | 9+++++----
Msource/baseLib/md5.h | 1+
Asource/baseLib/propertyMap.cpp | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/baseLib/propertyMap.h | 35+++++++++++++++++++++++++++++++++++
Asource/bridge/CMakeLists.txt | 3+++
Asource/bridge/bridgeLib/CMakeLists.txt | 26++++++++++++++++++++++++++
Asource/bridge/bridgeLib/audioBuffers.cpp | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/audioBuffers.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/command.cpp | 22++++++++++++++++++++++
Asource/bridge/bridgeLib/command.h | 16++++++++++++++++
Asource/bridge/bridgeLib/commandReader.cpp | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/commandReader.h | 34++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/commandStruct.cpp | 0
Asource/bridge/bridgeLib/commandStruct.h | 18++++++++++++++++++
Asource/bridge/bridgeLib/commandWriter.cpp | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/commandWriter.h | 30++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/commands.cpp | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/commands.h | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/error.cpp | 0
Asource/bridge/bridgeLib/error.h | 21+++++++++++++++++++++
Asource/bridge/bridgeLib/pluginDesc.cpp | 9+++++++++
Asource/bridge/bridgeLib/pluginDesc.h | 10++++++++++
Asource/bridge/bridgeLib/tcpConnection.cpp | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/tcpConnection.h | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/bridgeLib/types.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/CMakeLists.txt | 24++++++++++++++++++++++++
Asource/bridge/client/deviceConnection.cpp | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/deviceConnection.h | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/export.cpp | 1+
Asource/bridge/client/export.h | 24++++++++++++++++++++++++
Asource/bridge/client/plugin.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/remoteDevice.cpp | 288+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/remoteDevice.h | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/serverList.cpp | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/serverList.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/types.h | 1+
Asource/bridge/client/udpClient.cpp | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/client/udpClient.h | 26++++++++++++++++++++++++++
Asource/bridge/server/CMakeLists.txt | 26++++++++++++++++++++++++++
Asource/bridge/server/bridgeServer.cpp | 43+++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/clientConnection.cpp | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/clientConnection.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/config.cpp | 43+++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/config.h | 20++++++++++++++++++++
Asource/bridge/server/import.cpp | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/import.h | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/romPool.cpp | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/romPool.h | 35+++++++++++++++++++++++++++++++++++
Asource/bridge/server/server.cpp | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/server.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/udpServer.cpp | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/bridge/server/udpServer.h | 14++++++++++++++
Msource/juce.cmake | 55++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msource/jucePluginEditorLib/pluginEditorState.cpp | 46++++++++++++++++++++++++++++++++++++++++++++++
Msource/jucePluginEditorLib/pluginEditorState.h | 4++++
Msource/jucePluginLib/CMakeLists.txt | 2+-
Msource/jucePluginLib/dummydevice.cpp | 4++++
Msource/jucePluginLib/dummydevice.h | 2++
Msource/jucePluginLib/processor.cpp | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msource/jucePluginLib/processor.h | 33+++++++++++++++++++++++++++++++++
Msource/jucePluginLib/processorPropertiesInit.h | 1+
Msource/jucePluginLib/types.h | 7+++++++
Msource/mqJucePlugin/PluginProcessor.cpp | 16+++++++++++++++-
Msource/mqJucePlugin/PluginProcessor.h | 2++
Asource/mqJucePlugin/serverPlugin.cpp | 9+++++++++
Msource/mqLib/device.cpp | 6+++++-
Msource/mqLib/device.h | 2+-
Msource/mqLib/microq.cpp | 15+++++++++++++--
Msource/mqLib/microq.h | 2+-
Msource/mqLib/mqmc.cpp | 2+-
Msource/mqLib/rom.cpp | 14++++++++++++--
Msource/mqLib/rom.h | 4++++
Msource/mqTestConsole/mqTestConsole.cpp | 24+++++++++---------------
Asource/networkLib/CMakeLists.txt | 38++++++++++++++++++++++++++++++++++++++
Asource/networkLib/exception.cpp | 5+++++
Asource/networkLib/exception.h | 26++++++++++++++++++++++++++
Asource/networkLib/logging.cpp | 39+++++++++++++++++++++++++++++++++++++++
Asource/networkLib/logging.h | 37+++++++++++++++++++++++++++++++++++++
Asource/networkLib/networkThread.cpp | 40++++++++++++++++++++++++++++++++++++++++
Asource/networkLib/networkThread.h | 27+++++++++++++++++++++++++++
Asource/networkLib/stream.cpp | 0
Asource/networkLib/stream.h | 15+++++++++++++++
Asource/networkLib/tcpClient.cpp | 41+++++++++++++++++++++++++++++++++++++++++
Asource/networkLib/tcpClient.h | 19+++++++++++++++++++
Asource/networkLib/tcpConnection.cpp | 32++++++++++++++++++++++++++++++++
Asource/networkLib/tcpConnection.h | 30++++++++++++++++++++++++++++++
Asource/networkLib/tcpServer.cpp | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/networkLib/tcpServer.h | 17+++++++++++++++++
Asource/networkLib/tcpStream.cpp | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/networkLib/tcpStream.h | 32++++++++++++++++++++++++++++++++
Asource/networkLib/udpClient.cpp | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/networkLib/udpClient.h | 30++++++++++++++++++++++++++++++
Asource/networkLib/udpServer.cpp | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/networkLib/udpServer.h | 25+++++++++++++++++++++++++
Msource/nord/n2x/n2xJucePlugin/n2xPluginProcessor.cpp | 16+++++++++++++++-
Msource/nord/n2x/n2xJucePlugin/n2xPluginProcessor.h | 2++
Asource/nord/n2x/n2xJucePlugin/serverPlugin.cpp | 9+++++++++
Msource/nord/n2x/n2xLib/n2xdevice.cpp | 10++++------
Msource/nord/n2x/n2xLib/n2xdevice.h | 4+---
Msource/nord/n2x/n2xLib/n2xhardware.cpp | 14++++++++++++--
Msource/nord/n2x/n2xLib/n2xhardware.h | 2+-
Msource/nord/n2x/n2xLib/n2xrom.cpp | 8+++++++-
Msource/nord/n2x/n2xLib/n2xrom.h | 1+
Msource/nord/n2x/n2xLib/n2xromdata.cpp | 8++++++++
Msource/nord/n2x/n2xLib/n2xromdata.h | 1+
Asource/osTIrusJucePlugin/serverPlugin.cpp | 9+++++++++
Asource/osirusJucePlugin/serverPlugin.cpp | 9+++++++++
Asource/ptypes/CMakeLists.txt | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pasync.cxx | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pasync.h | 542+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/patomic.cxx | 377+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pcomponent.cxx | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pcset.cxx | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pcsetdbg.cxx | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pexcept.cxx | 35+++++++++++++++++++++++++++++++++++
Asource/ptypes/pfatal.cxx | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pfdxstm.cxx | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pinet.h | 400+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pinfile.cxx | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pinfilter.cxx | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pinmem.cxx | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pinstm.cxx | 350+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pintee.cxx | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/piobase.cxx | 388+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pipbase.cxx | 421+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pipmsg.cxx | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pipmsgsv.cxx | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pipstm.cxx | 248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pipstmsv.cxx | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pipsvbase.cxx | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pmd5.cxx | 517+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pmem.cxx | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pmsgq.cxx | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pmtxtable.cxx | 42++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pnpipe.cxx | 290+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pnpserver.cxx | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pobjlist.cxx | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/poutfile.cxx | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/poutfilter.cxx | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/poutmem.cxx | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/poutstm.cxx | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ppipe.cxx | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ppodlist.cxx | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pport.h | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pputf.cxx | 287+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/prwlock.cxx | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/psemaphore.cxx | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstdio.cxx | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstrcase.cxx | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstrconv.cxx | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstreams.h | 807+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstring.cxx | 289++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstrlist.cxx | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstrmanip.cxx | 281+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstrtoi.cxx | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pstrutils.cxx | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ptextmap.cxx | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pthread.cxx | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ptime.cxx | 325+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ptime.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ptimedsem.cxx | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ptrigger.cxx | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ptypes.h | 1159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/ptypes_test.cxx | 1622+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/punit.cxx | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/punknown.cxx | 21+++++++++++++++++++++
Asource/ptypes/pvariant.cxx | 641+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/ptypes/pversion.cxx | 15+++++++++++++++
Msource/synthLib/device.cpp | 18+++++++++++++++---
Msource/synthLib/device.h | 34++++++++++++++++++++++++++++------
Msource/synthLib/deviceTypes.h | 6++++--
Msource/synthLib/plugin.cpp | 29++++++++++++++++++-----------
Msource/synthLib/plugin.h | 10+++++++---
Msource/synthLib/resamplerInOut.cpp | 4++++
Msource/virusJucePlugin/Leds.cpp | 1+
Msource/virusJucePlugin/VirusProcessor.cpp | 19+++++++++++++++----
Msource/virusJucePlugin/VirusProcessor.h | 3+--
Msource/virusLib/device.cpp | 29++++++++++++++++++++---------
Msource/virusLib/device.h | 6+++---
Msource/virusLib/romfile.h | 2++
Msource/wLib/wDevice.cpp | 6+++++-
Msource/wLib/wDevice.h | 2++
Msource/wLib/wRom.h | 4++--
Msource/xtJucePlugin/PluginProcessor.cpp | 19++++++++++++++++++-
Msource/xtJucePlugin/PluginProcessor.h | 1+
Asource/xtJucePlugin/serverPlugin.cpp | 9+++++++++
Msource/xtLib/xt.cpp | 4++--
Msource/xtLib/xt.h | 2+-
Msource/xtLib/xtDevice.cpp | 2+-
Msource/xtLib/xtDevice.h | 2+-
Msource/xtLib/xtHardware.cpp | 11+++++++++--
Msource/xtLib/xtHardware.h | 4++--
Msource/xtLib/xtRom.h | 4++--
Msource/xtLib/xtRomLoader.cpp | 2+-
Msource/xtLib/xtUc.cpp | 2+-
Msource/xtTestConsole/xtTestConsole.cpp | 2+-
205 files changed, 18928 insertions(+), 211 deletions(-)

diff --git a/base.cmake b/base.cmake @@ -34,6 +34,7 @@ if(MSVC) set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG") set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} /LTCG /DEBUG") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} /LTCG /DEBUG") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG /DEBUG") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /SAFESEH:NO") diff --git a/scripts/synths.cmake b/scripts/synths.cmake @@ -4,6 +4,7 @@ set(synths gearmulator_SYNTH_VAVRA gearmulator_SYNTH_XENIA gearmulator_SYNTH_NODALRED2X + gearmulator_COMPONENT_DSPBRIDGE ) set(gearmulator_SYNTH_OSIRUS_name Osirus) @@ -11,12 +12,14 @@ set(gearmulator_SYNTH_OSTIRUS_name OsTIrus) set(gearmulator_SYNTH_VAVRA_name Vavra) set(gearmulator_SYNTH_XENIA_name Xenia) set(gearmulator_SYNTH_NODALRED2X_name NodalRed2x) +set(gearmulator_COMPONENT_DSPBRIDGE_name DSPBridge) set(gearmulator_SYNTH_OSIRUS_folder osirus) set(gearmulator_SYNTH_OSTIRUS_folder ostirus) set(gearmulator_SYNTH_VAVRA_folder vavra) set(gearmulator_SYNTH_XENIA_folder xenia) set(gearmulator_SYNTH_NODALRED2X_folder nodalred2x) +set(gearmulator_COMPONENT_DSPBRIDGE_folder dspbridge) macro(validateToggle NAME) if(NOT DEFINED ${NAME} OR (NOT ${${NAME}} STREQUAL "on" AND NOT ${${NAME}} STREQUAL "off")) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt @@ -39,6 +39,12 @@ add_subdirectory(libresample) include(macsetup.cmake) include(skins.cmake) +# ----------------- network bridge + +add_subdirectory(ptypes EXCLUDE_FROM_ALL) +add_subdirectory(networkLib EXCLUDE_FROM_ALL) +add_subdirectory(bridge) + # ----------------- Try to install VST2 SDK include(findvst2.cmake) diff --git a/source/baseLib/CMakeLists.txt b/source/baseLib/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES configFile.cpp configFile.h hybridcontainer.h md5.cpp md5.h + propertyMap.cpp propertyMap.h semaphore.h ) diff --git a/source/baseLib/binarystream.h b/source/baseLib/binarystream.h @@ -103,6 +103,8 @@ namespace baseLib return !eof(); } + auto& getVector() { return m_vector; } + private: size_t size() const { return m_fixedSize ? m_size : m_vector.size(); } @@ -149,6 +151,10 @@ namespace baseLib { } + explicit BinaryStream(const size_t _capacity) : StreamBuffer(_capacity) + { + } + template<typename T> explicit BinaryStream(const std::vector<T>& _data) { Base::write(reinterpret_cast<const uint8_t*>(_data.data()), _data.size() * sizeof(T)); @@ -208,6 +214,8 @@ namespace baseLib void setWritePos(const uint32_t _pos) { seekp(_pos); } void setReadPos(const uint32_t _pos) { seekg(_pos); } + + using StreamBuffer::getVector; // ___________________________________ // write diff --git a/source/baseLib/commandline.cpp b/source/baseLib/commandline.cpp @@ -1,8 +1,5 @@ #include"commandline.h" -#include <cmath> -#include <stdexcept> - namespace baseLib { CommandLine::CommandLine(const int _argc, char* _argv[]) @@ -18,7 +15,7 @@ namespace baseLib if (arg[0] == '-') { if (!currentArg.empty()) - m_args.insert(currentArg); + add(currentArg); currentArg = arg.substr(1); } @@ -26,61 +23,17 @@ namespace baseLib { if (!currentArg.empty()) { - m_argsWithValues.insert(std::make_pair(currentArg, arg)); + add(currentArg, arg); currentArg.clear(); } else { - m_args.insert(arg); + add(arg); } } } if (!currentArg.empty()) - m_args.insert(currentArg); - } - - std::string CommandLine::tryGet(const std::string& _key, const std::string& _value) const - { - const auto it = m_argsWithValues.find(_key); - if (it == m_argsWithValues.end()) - return _value; - return it->second; - } - - std::string CommandLine::get(const std::string& _key, const std::string& _default/* = {}*/) const - { - const auto it = m_argsWithValues.find(_key); - if (it == m_argsWithValues.end()) - return _default; - return it->second; - } - - float CommandLine::getFloat(const std::string& _key, const float _default/* = 0.0f*/) const - { - const std::string stringResult = get(_key); - - if (stringResult.empty()) - return _default; - - const double result = atof(stringResult.c_str()); - if (std::isinf(result) || std::isnan(result)) - { - return _default; - } - return static_cast<float>(result); - } - - int CommandLine::getInt(const std::string& _key, const int _default/* = 0*/) const - { - const std::string stringResult = get(_key); - if (stringResult.empty()) - return _default; - return atoi(stringResult.c_str()); - } - - bool CommandLine::contains(const std::string& _key) const - { - return m_args.find(_key) != m_args.end() || m_argsWithValues.find(_key) != m_argsWithValues.end(); + add(currentArg); } } \ No newline at end of file diff --git a/source/baseLib/commandline.h b/source/baseLib/commandline.h @@ -1,28 +1,12 @@ #pragma once -#include <string> -#include <map> -#include <set> +#include "propertyMap.h" namespace baseLib { - class CommandLine + class CommandLine : public PropertyMap { public: CommandLine(int _argc, char* _argv[]); - - std::string tryGet(const std::string& _key, const std::string& _value = std::string()) const; - - std::string get(const std::string& _key, const std::string& _default = {}) const; - - float getFloat(const std::string& _key, float _default = 0.0f) const; - - int getInt(const std::string& _key, int _default = 0) const; - - bool contains(const std::string& _key) const; - - private: - std::map<std::string, std::string> m_argsWithValues; - std::set<std::string> m_args; }; } diff --git a/source/baseLib/configFile.cpp b/source/baseLib/configFile.cpp @@ -4,27 +4,30 @@ namespace baseLib { - static bool needsTrim(char _c) + namespace { - switch (_c) + bool needsTrim(const char _c) { - case ' ': - case '\r': - case '\n': - case '\t': - return true; - default: - return false; + switch (_c) + { + case ' ': + case '\r': + case '\n': + case '\t': + return true; + default: + return false; + } } - } - static std::string trim(std::string _s) - { - while (!_s.empty() && needsTrim(_s.front())) - _s = _s.substr(1); - while (!_s.empty() && needsTrim(_s.back())) - _s = _s.substr(0, _s.size() - 1); - return _s; + std::string trim(std::string _s) + { + while (!_s.empty() && needsTrim(_s.front())) + _s = _s.substr(1); + while (!_s.empty() && needsTrim(_s.back())) + _s = _s.substr(0, _s.size() - 1); + return _s; + } } ConfigFile::ConfigFile(const char* _filename) @@ -32,7 +35,7 @@ namespace baseLib std::ifstream file(_filename, std::ios::in); if (!file.is_open()) - throw std::runtime_error("Failed to open config file " + std::string(_filename)); + return; std::string line; @@ -53,7 +56,11 @@ namespace baseLib const auto key = trim(line.substr(0, posEq)); const auto val = trim(line.substr(posEq + 1)); - m_values.emplace_back(key, val); + add(key, val); } } -} -\ No newline at end of file + + ConfigFile::ConfigFile(const std::string& _filename) : ConfigFile(_filename.c_str()) + { + } +} diff --git a/source/baseLib/configFile.h b/source/baseLib/configFile.h @@ -1,20 +1,13 @@ #pragma once -#include <string> -#include <vector> +#include "propertyMap.h" namespace baseLib { - class ConfigFile + class ConfigFile : public PropertyMap { public: explicit ConfigFile(const char* _filename); - - const std::vector<std::pair<std::string, std::string>>& getValues() const - { - return m_values; - } - private: - std::vector<std::pair<std::string, std::string>> m_values; + explicit ConfigFile(const std::string& _filename); }; } diff --git a/source/baseLib/md5.cpp b/source/baseLib/md5.cpp @@ -121,14 +121,15 @@ namespace baseLib _h3 += d; } + } - // cleanup - // free(msg); + MD5::MD5(const std::vector<uint8_t>& _data) : MD5(_data.data(), static_cast<uint32_t>(_data.size())) + { } - MD5::MD5(const std::vector<uint8_t>& _data) + MD5::MD5(const uint8_t* _data, const uint32_t _size) { - md5(m_h[0], m_h[1], m_h[2], m_h[3], _data.data(), static_cast<uint32_t>(_data.size())); + md5(m_h[0], m_h[1], m_h[2], m_h[3], _data, _size); } std::string MD5::toString() const diff --git a/source/baseLib/md5.h b/source/baseLib/md5.h @@ -47,6 +47,7 @@ namespace baseLib } explicit MD5(const std::vector<uint8_t>& _data); + explicit MD5(const uint8_t* _data, uint32_t _size); MD5() : m_h({0,0,0,0}) {} diff --git a/source/baseLib/propertyMap.cpp b/source/baseLib/propertyMap.cpp @@ -0,0 +1,86 @@ +#include "propertyMap.h" + +#include <cmath> + +namespace baseLib +{ + bool PropertyMap::add(const std::string& _key, const std::string& _value) + { + if (m_argsWithValues.find(_key) != m_argsWithValues.end()) + return false; + m_argsWithValues.insert(std::make_pair(_key, _value)); + return true; + } + + bool PropertyMap::add(const std::string& _key) + { + return m_args.insert(_key).second; + } + + bool PropertyMap::add(const PropertyMap& _other, const bool _overwrite) + { + bool changed = false; + + for (const auto& [key, val] : _other.getArgsWithValues()) + { + if (_overwrite || m_argsWithValues.find(key) == m_argsWithValues.end()) + { + m_argsWithValues[key] = val; + changed = true; + } + } + for (const auto& a : _other.getArgs()) + { + if (_overwrite || m_args.find(a) == m_args.end()) + { + m_args.insert(a); + changed = true; + } + } + return changed; + } + + std::string PropertyMap::tryGet(const std::string& _key, const std::string& _value) const + { + const auto it = m_argsWithValues.find(_key); + if (it == m_argsWithValues.end()) + return _value; + return it->second; + } + + std::string PropertyMap::get(const std::string& _key, const std::string& _default/* = {}*/) const + { + const auto it = m_argsWithValues.find(_key); + if (it == m_argsWithValues.end()) + return _default; + return it->second; + } + + float PropertyMap::getFloat(const std::string& _key, const float _default/* = 0.0f*/) const + { + const std::string stringResult = get(_key); + + if (stringResult.empty()) + return _default; + + const double result = atof(stringResult.c_str()); + if (std::isinf(result) || std::isnan(result)) + { + return _default; + } + return static_cast<float>(result); + } + + int PropertyMap::getInt(const std::string& _key, const int _default/* = 0*/) const + { + const std::string stringResult = get(_key); + if (stringResult.empty()) + return _default; + return atoi(stringResult.c_str()); + } + + bool PropertyMap::contains(const std::string& _key) const + { + return m_args.find(_key) != m_args.end() || m_argsWithValues.find(_key) != m_argsWithValues.end(); + } +} diff --git a/source/baseLib/propertyMap.h b/source/baseLib/propertyMap.h @@ -0,0 +1,35 @@ +#pragma once + +#include <string> +#include <map> +#include <set> + +namespace baseLib +{ + class PropertyMap + { + public: + bool add(const std::string& _key, const std::string& _value); + bool add(const std::string& _key); + bool add(const PropertyMap& _other, bool _overwrite = false); + + std::string tryGet(const std::string& _key, const std::string& _value = std::string()) const; + + std::string get(const std::string& _key, const std::string& _default = {}) const; + + float getFloat(const std::string& _key, float _default = 0.0f) const; + + int getInt(const std::string& _key, int _default = 0) const; + + bool contains(const std::string& _key) const; + + const auto& getArgsWithValues() const { return m_argsWithValues; } + const auto& getArgs() const { return m_args; } + + bool empty() const { return m_argsWithValues.empty() && m_args.empty(); } + + private: + std::map<std::string, std::string> m_argsWithValues; + std::set<std::string> m_args; + }; +} diff --git a/source/bridge/CMakeLists.txt b/source/bridge/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(bridgeLib EXCLUDE_FROM_ALL) +add_subdirectory(client) +add_subdirectory(server) diff --git a/source/bridge/bridgeLib/CMakeLists.txt b/source/bridge/bridgeLib/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) + +project(bridgeLib) + +add_library(bridgeLib STATIC) + +set(SOURCES + audioBuffers.cpp audioBuffers.h + command.cpp command.h + commandReader.cpp commandReader.h + commands.cpp commands.h + commandStruct.cpp commandStruct.h + commandWriter.cpp commandWriter.h + error.cpp error.h + tcpConnection.cpp tcpConnection.h + types.h +) + +target_sources(bridgeLib PRIVATE ${SOURCES}) + +source_group("source" FILES ${SOURCES}) + +target_link_libraries(bridgeLib PUBLIC networkLib synthLib) + +target_include_directories(bridgeLib PUBLIC ../) +set_property(TARGET bridgeLib PROPERTY FOLDER "Bridge") diff --git a/source/bridge/bridgeLib/audioBuffers.cpp b/source/bridge/bridgeLib/audioBuffers.cpp @@ -0,0 +1,70 @@ +#include "audioBuffers.h" + +namespace bridgeLib +{ + AudioBuffers::AudioBuffers() = default; + + void AudioBuffers::writeInput(const synthLib::TAudioInputs& _inputs, const uint32_t _size) + { + m_inputSize += _size; + + for(size_t c=0; c<_inputs.size(); ++c) + { + auto& in = _inputs[c]; + + if(!in) + continue; + + for(uint32_t i=0; i<_size; ++i) + m_inputBuffers[c].push_back(in[i]); + } + } + + void AudioBuffers::readInput(const uint32_t _channel, std::vector<float>& _data, const uint32_t _numSamples) + { + for(uint32_t i=0; i<_numSamples; ++i) + _data[i] = m_inputBuffers[_channel].pop_front(); + } + + void AudioBuffers::readOutput(const synthLib::TAudioOutputs& _outputs, const uint32_t _size) + { + assert(m_outputSize >= _size); + m_outputSize -= _size; + + for(size_t c=0; c<_outputs.size(); ++c) + { + auto* out = _outputs[c]; + + if(!out) + continue; + + for(uint32_t i=0; i<_size; ++i) + out[i] = m_outputBuffers[c].pop_front(); + } + } + + void AudioBuffers::writeOutput(const uint32_t _channel, const std::vector<float>& _data, const uint32_t _numSamples) + { + for(uint32_t i=0; i<_numSamples; ++i) + m_outputBuffers[_channel].push_back(_data[i]); + } + + void AudioBuffers::setLatency(const uint32_t _newLatency, const uint32_t _numSamplesToKeep) + { + while(_newLatency > m_latency) + { + for (auto& in : m_inputBuffers) + in.push_back(0.0f); + ++m_latency; + ++m_inputSize; + } + + while(_newLatency < m_latency && m_inputBuffers.front().size() > _numSamplesToKeep) + { + for (auto& in : m_inputBuffers) + in.pop_front(); + --m_latency; + --m_inputSize; + } + } +} diff --git a/source/bridge/bridgeLib/audioBuffers.h b/source/bridge/bridgeLib/audioBuffers.h @@ -0,0 +1,50 @@ +#pragma once + +#include "baseLib/binarystream.h" +#include "dsp56kEmu/ringbuffer.h" + +#include "synthLib/audioTypes.h" + +namespace bridgeLib +{ + class AudioBuffers + { + public: + static constexpr uint32_t BufferSize = 16384; + + using RingBufferIn = dsp56k::RingBuffer<float, BufferSize, false, true>; + using RingBufferOut = dsp56k::RingBuffer<float, BufferSize, false, true>; + + AudioBuffers(); + + uint32_t getInputSize() const { return m_inputSize; } + uint32_t getOutputSize() const { return m_outputSize; } + auto getLatency() const { return m_latency; } + + void onInputRead(const uint32_t _size) + { + assert(m_inputSize >= _size); + m_inputSize -= _size; + } + + void onOutputWritten(const uint32_t _size) + { + m_outputSize += _size; + } + + void writeInput(const synthLib::TAudioInputs& _inputs, uint32_t _size); + void readInput(uint32_t _channel, std::vector<float>& _data, uint32_t _numSamples); + + void readOutput(const synthLib::TAudioOutputs& _outputs, uint32_t _size); + void writeOutput(uint32_t _channel, const std::vector<float>& _data, uint32_t _numSamples); + void setLatency(uint32_t _newLatency, uint32_t _numSamplesToKeep); + + private: + std::array<RingBufferIn, std::tuple_size_v<synthLib::TAudioInputs>> m_inputBuffers; + std::array<RingBufferOut, std::tuple_size_v<synthLib::TAudioOutputs>> m_outputBuffers; + + uint32_t m_inputSize = 0; + uint32_t m_outputSize = 0; + uint32_t m_latency = 0; + }; +} diff --git a/source/bridge/bridgeLib/command.cpp b/source/bridge/bridgeLib/command.cpp @@ -0,0 +1,22 @@ +#include "command.h" + +namespace bridgeLib +{ + enum class HostEndian + { + Big, + Little + }; + + constexpr HostEndian hostEndian() + { + constexpr uint32_t test32 = 0x01020304; + constexpr uint8_t test8 = static_cast<const uint8_t&>(test32); + + static_assert(test8 == 0x01 || test8 == 0x04, "unable to determine endianess"); + + return test8 == 0x01 ? HostEndian::Big : HostEndian::Little; + } + + static_assert(hostEndian() == HostEndian::Little, "big endian systems not supported"); +} diff --git a/source/bridge/bridgeLib/command.h b/source/bridge/bridgeLib/command.h @@ -0,0 +1,16 @@ +#pragma once + +#include <cstdint> +#include <string> + +namespace bridgeLib +{ + template<size_t N, std::enable_if_t<N == 5, void*> = nullptr> + constexpr uint32_t cmd(const char (&_cmd)[N]) + { + return (static_cast<uint32_t>(_cmd[0]) << 24) | + (static_cast<uint32_t>(_cmd[1]) << 16) | + (static_cast<uint32_t>(_cmd[2]) << 8) | + (static_cast<uint32_t>(_cmd[3])); + } +} diff --git a/source/bridge/bridgeLib/commandReader.cpp b/source/bridge/bridgeLib/commandReader.cpp @@ -0,0 +1,57 @@ +#include "commandReader.h" + +#include "command.h" +#include "dsp56300/source/dsp56kEmu/logging.h" +#include "networkLib/stream.h" + +namespace bridgeLib +{ + CommandReader::CommandReader(CommandCallback&& _callback) : m_stream(512 * 1024), m_commandCallback(std::move(_callback)) + { + } + + void CommandReader::read(networkLib::Stream& _stream) + { + // read command (4 bytes) + char temp[5]{0,0,0,0,0}; + + _stream.read(temp, 4); + const auto command = static_cast<Command>(cmd(temp)); + + // read size (4 bytes) + uint32_t size; + _stream.read(&size, sizeof(size)); + + // read data (n bytes) + m_stream.getVector().resize(size); + _stream.read(m_stream.getVector().data(), size); + +// LOG("Recv cmd " << commandToString(command) << ", len " << size); + m_stream.setReadPos(0); + handleCommand(command, m_stream); + } + + void CommandReader::read(baseLib::BinaryStream& _in) + { + // read command (4 bytes) + char temp[5]{0,0,0,0,0}; + + _in.read(temp[0]); + _in.read(temp[1]); + _in.read(temp[2]); + _in.read(temp[3]); + + const auto command = cmd(temp); + + // read size (4 bytes) + const uint32_t size = _in.read<uint32_t>(); + + // read data (n bytes) + m_stream.getVector().resize(size); + for(size_t i=0; i<size; ++i) + m_stream.getVector()[i] = _in.read<uint8_t>(); + + m_stream.setReadPos(0); + handleCommand(static_cast<Command>(command), m_stream); + } +} diff --git a/source/bridge/bridgeLib/commandReader.h b/source/bridge/bridgeLib/commandReader.h @@ -0,0 +1,34 @@ +#pragma once + +#include "commands.h" + +#include "baseLib/binarystream.h" + +namespace networkLib +{ + class Stream; +} + +namespace bridgeLib +{ + class CommandReader + { + public: + using CommandCallback = std::function<void(Command, baseLib::BinaryStream&)>; + + CommandReader(CommandCallback&& _callback); + virtual ~CommandReader() = default; + + void read(networkLib::Stream& _stream); + void read(baseLib::BinaryStream& _in); + + virtual void handleCommand(Command _command, baseLib::BinaryStream& _in) + { + m_commandCallback(_command, _in); + } + + private: + baseLib::BinaryStream m_stream; + CommandCallback m_commandCallback; + }; +} diff --git a/source/bridge/bridgeLib/commandStruct.cpp b/source/bridge/bridgeLib/commandStruct.cpp diff --git a/source/bridge/bridgeLib/commandStruct.h b/source/bridge/bridgeLib/commandStruct.h @@ -0,0 +1,18 @@ +#pragma once + +namespace baseLib +{ + class BinaryStream; +} + +namespace bridgeLib +{ + class CommandStruct + { + public: + virtual ~CommandStruct() = default; + + virtual baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const = 0; + virtual baseLib::BinaryStream& read(baseLib::BinaryStream& _s) = 0; + }; +} diff --git a/source/bridge/bridgeLib/commandWriter.cpp b/source/bridge/bridgeLib/commandWriter.cpp @@ -0,0 +1,64 @@ +#include "commandWriter.h" + +#include <array> + +#include "networkLib/stream.h" + +namespace bridgeLib +{ + CommandWriter::CommandWriter() : m_stream(512 * 1024) + { + } + + baseLib::BinaryStream& CommandWriter::build(const Command _command) + { + m_stream.setWritePos(0); + m_command = _command; + + return m_stream; + } + + baseLib::BinaryStream& CommandWriter::build(const Command _command, const CommandStruct& _data) + { + return _data.write(build(_command)); + } + + void CommandWriter::write(networkLib::Stream& _stream, const bool _flush) + { + // send command (4 bytes) + std::array<char,5> buf; + commandToBuffer(buf, m_command); + _stream.write(buf.data(), 4); + + // send size (4 bytes) + const auto size = m_stream.getWritePos(); + _stream.write(&size, sizeof(size)); + + // send data (size bytes) + const auto* data = m_stream.getVector().data(); + _stream.write(data, size); + + if(_flush) + _stream.flush(); + } + + void CommandWriter::write(baseLib::BinaryStream& _out) + { + // send command (4 bytes) + std::array<char,5> buf; + commandToBuffer(buf, m_command); + _out.write(buf[0]); + _out.write(buf[1]); + _out.write(buf[2]); + _out.write(buf[3]); + + // send size (4 bytes) + const auto size = m_stream.getWritePos(); + _out.write(size); + + // send data (size bytes) + const auto* data = m_stream.getVector().data(); + for(uint32_t i=0; i<size; ++i) + _out.write(m_stream.getVector()[i]); + } +} diff --git a/source/bridge/bridgeLib/commandWriter.h b/source/bridge/bridgeLib/commandWriter.h @@ -0,0 +1,30 @@ +#pragma once + +#include "commands.h" + +#include "baseLib/binarystream.h" + +namespace networkLib +{ + class Stream; +} + +namespace bridgeLib +{ + class CommandWriter + { + public: + CommandWriter(); + + baseLib::BinaryStream& build(Command _command); + + baseLib::BinaryStream& build(const Command _command, const CommandStruct& _data); + + void write(networkLib::Stream& _stream, bool _flush = true); + void write(baseLib::BinaryStream& _out); + + private: + baseLib::BinaryStream m_stream; + Command m_command = Command::Invalid; + }; +} diff --git a/source/bridge/bridgeLib/commands.cpp b/source/bridge/bridgeLib/commands.cpp @@ -0,0 +1,187 @@ +#include "commands.h" + +#include <array> + +#include "baseLib/binarystream.h" + +namespace bridgeLib +{ + std::string commandToString(const Command _command) + { + std::array<char, 5> temp; + commandToBuffer(temp, _command); + return {temp.data()}; + } + + void commandToBuffer(std::array<char, 5>& _buffer, Command _command) + { + const auto c = static_cast<uint32_t>(_command); + _buffer[0] = static_cast<char>((c >> 24) & 0xff); + _buffer[1] = static_cast<char>((c >> 16) & 0xff); + _buffer[2] = static_cast<char>((c >> 8) & 0xff); + _buffer[3] = static_cast<char>((c) & 0xff); + _buffer[4] = 0; + } + + baseLib::BinaryStream& Error::write(baseLib::BinaryStream& _s) const + { + _s.write<uint32_t>(static_cast<uint32_t>(code)); + _s.write(msg); + return _s; + } + + baseLib::BinaryStream& Error::read(baseLib::BinaryStream& _s) + { + code = static_cast<ErrorCode>(_s.read<uint32_t>()); + msg = _s.readString(); + return _s; + } + + baseLib::BinaryStream& ServerInfo::write(baseLib::BinaryStream& _s) const + { + _s.write(protocolVersion); + _s.write(portUdp); + _s.write(portTcp); + return _s; + } + + baseLib::BinaryStream& ServerInfo::read(baseLib::BinaryStream& _s) + { + _s.read(protocolVersion); + _s.read(portUdp); + _s.read(portTcp); + return _s; + } + + baseLib::BinaryStream& PluginDesc::write(baseLib::BinaryStream& _s) const + { + _s.write(protocolVersion); + _s.write(pluginName); + _s.write(pluginVersion); + _s.write(plugin4CC); + _s.write(sessionId); + return _s; + } + + baseLib::BinaryStream& PluginDesc::read(baseLib::BinaryStream& _s) + { + _s.read(protocolVersion); + pluginName = _s.readString(); + _s.read(pluginVersion); + plugin4CC = _s.readString(); + _s.read(sessionId); + return _s; + } + + baseLib::BinaryStream& DeviceCreateParams::read(baseLib::BinaryStream& _s) + { + _s.read(params.preferredSamplerate); + _s.read(params.hostSamplerate); + params.romName = _s.readString(); + _s.read(params.romData); + _s.read(params.romHash); + _s.read(params.customData); + return _s; + } + + baseLib::BinaryStream& DeviceCreateParams::write(baseLib::BinaryStream& _s) const + { + _s.write(params.preferredSamplerate); + _s.write(params.hostSamplerate); + _s.write(params.romName); + _s.write(params.romData); + _s.write(params.romHash); + _s.write(params.customData); + return _s; + } + + baseLib::BinaryStream& DeviceDesc::write(baseLib::BinaryStream& _s) const + { + _s.write(samplerate); + _s.write(outChannels); + _s.write(inChannels); + _s.write(dspClockPercent); + _s.write(dspClockHz); + _s.write(latencyInToOut); + _s.write(latencyMidiToOut); + _s.write(preferredSamplerates); + _s.write(supportedSamplerates); + return _s; + } + + baseLib::BinaryStream& DeviceDesc::read(baseLib::BinaryStream& _s) + { + _s.read(samplerate); + _s.read(outChannels); + _s.read(inChannels); + _s.read(dspClockPercent); + _s.read(dspClockHz); + _s.read(latencyInToOut); + _s.read(latencyMidiToOut); + _s.read(preferredSamplerates); + _s.read(supportedSamplerates); + return _s; + } + + baseLib::BinaryStream& RequestDeviceState::write(baseLib::BinaryStream& _s) const + { + _s.write(static_cast<uint32_t>(type)); + return _s; + } + + baseLib::BinaryStream& RequestDeviceState::read(baseLib::BinaryStream& _s) + { + type = static_cast<synthLib::StateType>(_s.read<uint32_t>()); + return _s; + } + + baseLib::BinaryStream& DeviceState::write(baseLib::BinaryStream& _s) const + { + _s.write(static_cast<uint32_t>(type)); + _s.write(state); + return _s; + } + + baseLib::BinaryStream& DeviceState::read(baseLib::BinaryStream& _s) + { + type = static_cast<synthLib::StateType>(_s.read<uint32_t>()); + _s.read(state); + return _s; + } + + baseLib::BinaryStream& SetSamplerate::write(baseLib::BinaryStream& _s) const + { + _s.write(samplerate); + return _s; + } + + baseLib::BinaryStream& SetSamplerate::read(baseLib::BinaryStream& _s) + { + _s.read(samplerate); + return _s; + } + + baseLib::BinaryStream& SetUnknownCustomData::write(baseLib::BinaryStream& _s) const + { + _s.write(data); + return _s; + } + + baseLib::BinaryStream& SetUnknownCustomData::read(baseLib::BinaryStream& _s) + { + _s.read(data); + return _s; + } + + baseLib::BinaryStream& SetDspClockPercent::write(baseLib::BinaryStream& _s) const + { + _s.write(percent); + return _s; + } + + baseLib::BinaryStream& SetDspClockPercent::read(baseLib::BinaryStream& _s) + { + _s.read(percent); + return _s; + } +} diff --git a/source/bridge/bridgeLib/commands.h b/source/bridge/bridgeLib/commands.h @@ -0,0 +1,174 @@ +#pragma once + +#include <cstdint> + +#include "command.h" +#include "commandStruct.h" +#include "error.h" +#include "types.h" +#include "baseLib/md5.h" +#include "synthLib/device.h" +#include "synthLib/deviceTypes.h" + +namespace bridgeLib +{ + enum class ErrorCode : uint32_t; + + enum class Command : uint32_t + { + Invalid = 0, + + Ping = cmd("ping"), + Pong = cmd("pong"), + + PluginInfo = cmd("PInf"), + ServerInfo = cmd("SInf"), + + Error = cmd("Erro"), + + DeviceInfo = cmd("DevI"), + + DeviceCreateParams = cmd("DCrP"), + RequestRom = cmd("ROMr"), + + Midi = cmd("MIDI"), + Audio = cmd("Wave"), + + DeviceState = cmd("DvSt"), + RequestDeviceState = cmd("RqDS"), + + SetSamplerate = cmd("SmpR"), + + SetUnknownCustomData = cmd("UnkD"), + + SetDspClockPercent = cmd("DspC") + }; + + std::string commandToString(Command _command); + void commandToBuffer(std::array<char,5>& _buffer, Command _command); + + struct Error : CommandStruct + { + ErrorCode code = ErrorCode::Ok; + std::string msg; + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + }; + + struct ServerInfo : CommandStruct + { + uint32_t protocolVersion; + uint32_t portUdp; + uint32_t portTcp; + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + }; + + struct PluginDesc : CommandStruct + { + uint32_t protocolVersion = g_protocolVersion; + std::string pluginName; + uint32_t pluginVersion = 0; + std::string plugin4CC; + SessionId sessionId = 0; + + PluginDesc() + { + pluginName.reserve(32); + plugin4CC.reserve(32); + } + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + + bool operator == (const PluginDesc& _desc) const + { + return + pluginVersion == _desc.pluginVersion && + plugin4CC == _desc.plugin4CC && + pluginName == _desc.pluginName && + protocolVersion == _desc.protocolVersion; + } + + bool operator < (const PluginDesc& _desc) const + { + if(pluginVersion < _desc.pluginVersion) return true; + if(pluginVersion > _desc.pluginVersion) return false; + if(plugin4CC < _desc.plugin4CC) return true; + if(plugin4CC > _desc.plugin4CC) return false; + if(pluginName < _desc.pluginName) return true; + if(pluginName > _desc.pluginName) return false; + if(protocolVersion < _desc.protocolVersion) return true; + return false; + } + }; + + struct DeviceCreateParams : CommandStruct + { + synthLib::DeviceCreateParams params; + + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + }; + + struct DeviceDesc : CommandStruct + { + float samplerate = 0.0f; + uint32_t outChannels = 0; + uint32_t inChannels = 0; + uint32_t dspClockPercent = 0; + uint64_t dspClockHz = 0; + uint32_t latencyInToOut = 0; + uint32_t latencyMidiToOut = 0; + std::vector<float> preferredSamplerates; + std::vector<float> supportedSamplerates; + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + }; + + struct RequestDeviceState : CommandStruct + { + synthLib::StateType type; + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + }; + + struct DeviceState : CommandStruct + { + synthLib::StateType type; + std::vector<uint8_t> state; + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + + bool isValid() const { return !state.empty(); } + }; + + struct SetSamplerate : CommandStruct + { + float samplerate; + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + }; + + struct SetUnknownCustomData : CommandStruct + { + std::vector<uint8_t> data; + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + }; + + struct SetDspClockPercent : CommandStruct + { + uint32_t percent; + + baseLib::BinaryStream& write(baseLib::BinaryStream& _s) const override; + baseLib::BinaryStream& read(baseLib::BinaryStream& _s) override; + }; +} diff --git a/source/bridge/bridgeLib/error.cpp b/source/bridge/bridgeLib/error.cpp diff --git a/source/bridge/bridgeLib/error.h b/source/bridge/bridgeLib/error.h @@ -0,0 +1,21 @@ +#pragma once + +#include <cstdint> + +namespace bridgeLib +{ + enum class ErrorCode : uint32_t + { + Ok = 0, + NoError = Ok, + Unknown, + + WrongProtocolVersion, + WrongPluginVersion, + InvalidPluginDesc, + + UnexpectedCommand, + + FailedToCreateDevice, + }; +} diff --git a/source/bridge/bridgeLib/pluginDesc.cpp b/source/bridge/bridgeLib/pluginDesc.cpp @@ -0,0 +1,9 @@ +#include "pluginDesc.h" + +baseLib::BinaryStream& bridgeLib::PluginDesc::write(baseLib::BinaryStream& _s) +{ +} + +baseLib::BinaryStream& bridgeLib::PluginDesc::read(baseLib::BinaryStream& _s) +{ +} diff --git a/source/bridge/bridgeLib/pluginDesc.h b/source/bridge/bridgeLib/pluginDesc.h @@ -0,0 +1,10 @@ +#pragma once + +#include <cstdint> +#include <string> + +#include "commandStruct.h" + +namespace bridgeLib +{ +} diff --git a/source/bridge/bridgeLib/tcpConnection.cpp b/source/bridge/bridgeLib/tcpConnection.cpp @@ -0,0 +1,218 @@ +#include "tcpConnection.h" + +#include "audioBuffers.h" +#include "networkLib/exception.h" +#include "networkLib/logging.h" + +#include "synthLib/midiTypes.h" + +namespace bridgeLib +{ + TcpConnection::TcpConnection(std::unique_ptr<networkLib::TcpStream>&& _stream) : CommandReader(nullptr), m_stream(std::move(_stream)) + { + m_audioTransferBuffer.reserve(16384); + + start(); + } + + TcpConnection::~TcpConnection() + { + shutdown(); + } + + void TcpConnection::handleCommand(const Command _command, baseLib::BinaryStream& _in) + { + switch (_command) + { + case Command::Invalid: + case Command::Ping: + case Command::Pong: + break; + case Command::PluginInfo: handleStruct<PluginDesc>(_in); break; + case Command::ServerInfo: handleStruct<ServerInfo>(_in); break; + case Command::Error: handleStruct<Error>(_in); break; + case Command::DeviceInfo: handleDeviceInfo(_in); break; + case Command::Midi: handleMidi(_in); break; + case Command::Audio: handleAudio(_in); break; + case Command::DeviceState: handleDeviceState(_in); break; + case Command::RequestDeviceState: handleRequestDeviceState(_in); break; + case Command::DeviceCreateParams: handleStruct<DeviceCreateParams>(_in); break; + case Command::SetSamplerate: handleStruct<SetSamplerate>(_in); break; + case Command::SetDspClockPercent: handleStruct<SetDspClockPercent>(_in); break; + case Command::SetUnknownCustomData: handleStruct<SetUnknownCustomData>(_in); break; + } + } + + void TcpConnection::threadFunc() + { + try + { + NetworkThread::threadFunc(); + } + catch (const networkLib::NetException& e) + { + m_stream->close(); + LOGNET(networkLib::LogLevel::Warning, "Network Exception, code " << e.type() << ": " << e.what()); + handleException(e); + } + } + + void TcpConnection::threadLoopFunc() + { + read(*m_stream); + } + + void TcpConnection::send(const Command _command, const CommandStruct& _data) + { + m_writer.build(_command, _data); + m_writer.write(*m_stream); + } + + void TcpConnection::send(Command _command) + { + m_writer.build(_command); + m_writer.write(*m_stream); + } + + void TcpConnection::handleMidi(baseLib::BinaryStream& _in) + { + synthLib::SMidiEvent& ev = m_midiEvent; + _in.read(ev.a); + _in.read(ev.b); + _in.read(ev.c); + _in.read(ev.sysex); + _in.read(ev.offset); + ev.source = static_cast<synthLib::MidiEventSource>(_in.read<uint8_t>()); + handleMidi(ev); + } + + void TcpConnection::sendAudio(const float* const* _data, const uint32_t _numChannels, const uint32_t _numSamplesPerChannel) + { + auto& s = m_writer.build(Command::Audio); + s.write(static_cast<uint8_t>(_numChannels)); + s.write(_numSamplesPerChannel); + + for(uint32_t i=0; i<_numChannels; ++i) + { + // if a channel has data, write it. If not, write 0 for the length + if(const float* channelData = _data[i]) + { + s.write(_numSamplesPerChannel); + s.write(channelData, _numSamplesPerChannel); + } + else + { + s.write(0); + } + } + send(); + } + + void TcpConnection::sendAudio(AudioBuffers& _buffers, const uint32_t _numChannels, uint32_t _numSamplesPerChannel) + { + auto& s = m_writer.build(Command::Audio); + s.write(static_cast<uint8_t>(_numChannels)); + s.write(_numSamplesPerChannel); + + if(m_audioTransferBuffer.size() < _numSamplesPerChannel) + m_audioTransferBuffer.resize(_numSamplesPerChannel); + + for(uint32_t i=0; i<_numChannels; ++i) + { + _buffers.readInput(i, m_audioTransferBuffer, _numSamplesPerChannel); + s.write(_numSamplesPerChannel); + s.write(m_audioTransferBuffer.data(), _numSamplesPerChannel); + } + + _buffers.onInputRead(_numSamplesPerChannel); + + send(); + } + + uint32_t TcpConnection::handleAudio(float* const* _output, baseLib::BinaryStream& _in) + { + const uint32_t numChannels = _in.read<uint8_t>(); + const uint32_t numSamplesMax = _in.read<uint32_t>(); + + for(uint32_t i=0; i<numChannels; ++i) + { + const auto numSamples = _in.read<uint32_t>(); + if(numSamples) + { + assert(_output[i]); + _in.read(_output[i], numSamples); + } + } + return numSamplesMax; + } + + void TcpConnection::handleAudio(AudioBuffers& _buffers, baseLib::BinaryStream& _in) + { + const uint32_t numChannels = _in.read<uint8_t>(); + const uint32_t numSamplesMax = _in.read<uint32_t>(); + + for(uint32_t i=0; i<numChannels; ++i) + { + const auto numSamples = _in.read<uint32_t>(); + + if(numSamples) + { + if(m_audioTransferBuffer.size() < numSamples) + m_audioTransferBuffer.resize(numSamples); + _in.read(m_audioTransferBuffer.data(), numSamples); + _buffers.writeOutput(i, m_audioTransferBuffer, numSamples); + } + } + _buffers.onOutputWritten(numSamplesMax); + } + + void TcpConnection::handleAudio(baseLib::BinaryStream& _in) + { + } + + void TcpConnection::handleRequestDeviceState(baseLib::BinaryStream& _in) + { + RequestDeviceState requestDeviceState; + requestDeviceState.read(_in); + handleRequestDeviceState(requestDeviceState); + } + + void TcpConnection::handleDeviceState(baseLib::BinaryStream& _in) + { + m_deviceState.read(_in); + handleDeviceState(m_deviceState); + } + + void TcpConnection::handleDeviceInfo(baseLib::BinaryStream& _in) + { + handleStruct<DeviceDesc>(_in); + } + + bool TcpConnection::send(const synthLib::SMidiEvent& _ev) + { + if(!isValid()) + return false; + auto& bs = m_writer.build(Command::Midi); + bs.write(_ev.a); + bs.write(_ev.b); + bs.write(_ev.c); + bs.write(_ev.sysex); + bs.write(_ev.offset); + bs.write<uint8_t>(static_cast<uint8_t>(_ev.source)); + send(); + return true; + } + + void TcpConnection::close() const + { + if(m_stream) + m_stream->close(); + } + + void TcpConnection::shutdown() + { + close(); + stop(); + m_stream.reset(); + } +} diff --git a/source/bridge/bridgeLib/tcpConnection.h b/source/bridge/bridgeLib/tcpConnection.h @@ -0,0 +1,107 @@ +#pragma once + +#include "commandReader.h" +#include "commandWriter.h" + +#include "networkLib/networkThread.h" +#include "networkLib/tcpStream.h" +#include "synthLib/deviceTypes.h" + +#include "synthLib/midiTypes.h" + +namespace synthLib +{ + struct SMidiEvent; +} + +namespace networkLib +{ + class NetException; +} + +namespace bridgeLib +{ + class AudioBuffers; + + class TcpConnection : CommandReader, protected networkLib::NetworkThread + { + public: + TcpConnection(std::unique_ptr<networkLib::TcpStream>&& _stream); + ~TcpConnection() override; + + bool isValid() const { return m_stream && m_stream->isValid(); } + + void handleCommand(bridgeLib::Command _command, baseLib::BinaryStream& _in) override; + void threadFunc() override; + void threadLoopFunc() override; + + auto& writer() { return m_writer; } + void send() + { + m_writer.write(*m_stream); + } + void send(Command _command, const CommandStruct& _data); + void send(Command _command); + + // STRUCTS + virtual void handleData(const PluginDesc& _desc) {} + virtual void handleData(const ServerInfo& _desc) {} + virtual void handleData(const DeviceDesc& _desc) {} + virtual void handleData(const DeviceCreateParams& _params) {} + virtual void handleData(const SetSamplerate& _params) {} + virtual void handleData(const SetDspClockPercent& _params) {} + virtual void handleData(const SetUnknownCustomData& _params) {} + virtual void handleData(const Error& _error) {} + + virtual void handleDeviceInfo(baseLib::BinaryStream& _in); + + // MIDI + bool send(const synthLib::SMidiEvent& _ev); + void handleMidi(baseLib::BinaryStream& _in); + virtual void handleMidi(const synthLib::SMidiEvent& _e) {} + + // AUDIO + void sendAudio(const float* const* _data, uint32_t _numChannels, uint32_t _numSamplesPerChannel); + void sendAudio(AudioBuffers& _buffers, uint32_t _numChannels, uint32_t _numSamplesPerChannel); + static uint32_t handleAudio(float* const* _output, baseLib::BinaryStream& _in); + void handleAudio(AudioBuffers& _buffers, baseLib::BinaryStream& _in); + virtual void handleAudio(baseLib::BinaryStream& _in); + + // DEVICE STATE + virtual void handleRequestDeviceState(baseLib::BinaryStream& _in); + virtual void handleRequestDeviceState(bridgeLib::RequestDeviceState& _requestDeviceState) {} + virtual void handleDeviceState(baseLib::BinaryStream& _in); + virtual void handleDeviceState(DeviceState& _state) {} + const auto& getDeviceState() const { return m_deviceState; } + auto& getDeviceState() { return m_deviceState; } + + protected: + template<typename T> + void handleStruct(baseLib::BinaryStream& _in) + { + T data; + handleStruct(data, _in); + } + + template<typename T> + void handleStruct(T& _dst, baseLib::BinaryStream& _in) + { + _dst.read(_in); + handleData(_dst); + } + + virtual void handleException(const networkLib::NetException& _e) = 0; + void close() const; + void shutdown(); + + private: + std::unique_ptr<networkLib::TcpStream> m_stream; + CommandWriter m_writer; + + synthLib::SMidiEvent m_midiEvent; // preallocated for receiver + + std::vector<float> m_audioTransferBuffer; + + DeviceState m_deviceState; + }; +} diff --git a/source/bridge/bridgeLib/types.h b/source/bridge/bridgeLib/types.h @@ -0,0 +1,47 @@ +#pragma once + +#include <cstdint> + +#include "dsp56kEmu/buildconfig.h" + +namespace bridgeLib +{ + static constexpr uint32_t g_udpServerPort = 56303; + static constexpr uint32_t g_tcpServerPort = 56362; + + static constexpr uint32_t g_protocolVersion = 1'00'03; + + using SessionId = uint64_t; + + enum class Platform + { + Windows, + MacOS, + Unix + }; + + enum class Arch + { + X86, + X64, + Aarch64 + }; + +#ifdef _WIN32 + static constexpr Platform g_platform = Platform::Windows; +#elif defined(__APPLE__) + static constexpr Platform g_platform = Platform::MacOS; +#elif defined(__linux__) || defined(__unix__) + static constexpr Platform g_platform = Platform::Unix; +#endif + +#ifdef HAVE_ARM64 + static constexpr Arch g_arch = Arch::Aarch64; +#elif defined(HAVE_X86_64) + static constexpr Arch g_arch = Arch::X64; +#elif defined(_M_IX86) || defined(__i386__) + static constexpr Arch g_arch = Arch::X86; +#else + static_assert(false, "Unknown architecture"); +#endif +} diff --git a/source/bridge/client/CMakeLists.txt b/source/bridge/client/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.10) + +project(bridgeClient) + +add_library(bridgeClient STATIC) + +set(SOURCES + deviceConnection.cpp deviceConnection.h + export.cpp export.h + types.h + plugin.h + remoteDevice.cpp remoteDevice.h + serverList.cpp serverList.h + udpClient.cpp udpClient.h +) + +target_sources(bridgeClient PRIVATE ${SOURCES}) + +source_group("source" FILES ${SOURCES}) + +target_link_libraries(bridgeClient PUBLIC bridgeLib) + +target_include_directories(bridgeClient PUBLIC ../) +set_property(TARGET bridgeClient PROPERTY FOLDER "Bridge") diff --git a/source/bridge/client/deviceConnection.cpp b/source/bridge/client/deviceConnection.cpp @@ -0,0 +1,239 @@ +#include "deviceConnection.h" + +#include "remoteDevice.h" +#include "dsp56kEmu/logging.h" +#include "networkLib/logging.h" + +namespace bridgeClient +{ + static constexpr uint32_t g_replyTimeoutSecs = 10; + + DeviceConnection::DeviceConnection(RemoteDevice& _device, std::unique_ptr<networkLib::TcpStream>&& _stream) : TcpConnection(std::move(_stream)), m_device(_device) + { + m_handleReplyFunc = [](bridgeLib::Command, baseLib::BinaryStream&){}; + + // send plugin description and device creation parameters, this will cause the server to either boot the device or ask for the rom if it doesn't have it yet + send(bridgeLib::Command::PluginInfo, m_device.getPluginDesc()); + + // do not send rom data now but only if the server asks for it + sendDeviceCreateParams(false); + } + + DeviceConnection::~DeviceConnection() + { + shutdown(); + } + + void DeviceConnection::handleCommand(const bridgeLib::Command _command, baseLib::BinaryStream& _in) + { + switch (_command) + { + case bridgeLib::Command::RequestRom: + sendDeviceCreateParams(true); + break; + default: + TcpConnection::handleCommand(_command, _in); + break; + } + } + + void DeviceConnection::handleData(const bridgeLib::DeviceDesc& _desc) + { + m_deviceDesc = _desc; + m_device.onBootFinished(_desc); + } + + void DeviceConnection::handleDeviceInfo(baseLib::BinaryStream& _in) + { + TcpConnection::handleDeviceInfo(_in); + m_handleReplyFunc(bridgeLib::Command::DeviceInfo, _in); + } + + void DeviceConnection::handleException(const networkLib::NetException& _e) + { + m_device.onDisconnect(); + } + + void DeviceConnection::sendDeviceCreateParams(const bool _sendRom) + { + bridgeLib::DeviceCreateParams p; + p.params = m_device.getDeviceCreateParams(); + if(!_sendRom) + p.params.romData.clear(); + send(bridgeLib::Command::DeviceCreateParams, p); + } + + bool DeviceConnection::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, const uint32_t _size, const uint32_t _latency) + { + m_audioBuffers.writeInput(_inputs, _size); + + std::unique_lock lock(m_cvWaitMutex); + + const auto haveEnoughOutput = m_audioBuffers.getOutputSize() >= _size; + + m_audioBuffers.setLatency(_latency, haveEnoughOutput ? 0 : _size); + + const auto sendSize = m_audioBuffers.getInputSize(); + + if(haveEnoughOutput) + { + lock.unlock(); + + if(sendSize > 0) + sendAudio(m_audioBuffers, m_device.getChannelCountIn(), sendSize); + m_audioBuffers.readOutput(_outputs, _size); + } + else + { + assert(sendSize > 0); + sendAudio(m_audioBuffers, m_device.getChannelCountIn(), sendSize); + + m_cvWait.wait_for(lock, std::chrono::seconds(g_replyTimeoutSecs), [this, _size] + { + return m_audioBuffers.getOutputSize() >= _size; + }); + + if(m_audioBuffers.getOutputSize() < _size) + { + LOG("Receive timeout, closing connection"); + close(); + return false; + } + + m_audioBuffers.readOutput(_outputs, _size); + } + return true; + } + + void DeviceConnection::handleAudio(baseLib::BinaryStream& _in) + { + { + std::unique_lock lock(m_cvWaitMutex); + TcpConnection::handleAudio(m_audioBuffers, _in); + } + + m_cvWait.notify_one(); + } + + void DeviceConnection::handleMidi(const synthLib::SMidiEvent& _e) + { + m_midiOut.push_back(_e); + } + + void DeviceConnection::readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) + { + _midiOut.insert(_midiOut.end(), m_midiOut.begin(), m_midiOut.end()); + m_midiOut.clear(); + } + + bool DeviceConnection::getDeviceState(std::vector<uint8_t>& _state, synthLib::StateType _type) + { + return sendAwaitReply([this, _type] + { + bridgeLib::RequestDeviceState s; + s.type = _type; + send(bridgeLib::Command::RequestDeviceState, s); + }, [&](baseLib::BinaryStream& _in) + { + const auto& s = TcpConnection::getDeviceState().state; + _state.insert(_state.end(), s.begin(), s.end()); + }, bridgeLib::Command::DeviceState); + } + + void DeviceConnection::handleDeviceState(baseLib::BinaryStream& _in) + { + TcpConnection::handleDeviceState(_in); + m_handleReplyFunc(bridgeLib::Command::DeviceState, _in); + } + + bool DeviceConnection::setDeviceState(const std::vector<uint8_t>& _state, const synthLib::StateType _type) + { + if(_state.empty()) + return false; + + auto& s = TcpConnection::getDeviceState(); + s.state = _state; + s.type = _type; + + sendAwaitReply([&] + { + send(bridgeLib::Command::DeviceState, s); + }, [](baseLib::BinaryStream&) + { + }, bridgeLib::Command::DeviceInfo); + return true; + } + + void DeviceConnection::setSamplerate(const float _samplerate) + { + sendAwaitReply([&] + { + bridgeLib::SetSamplerate sr; + sr.samplerate = _samplerate; + send(bridgeLib::Command::SetSamplerate, sr); + }, [&](baseLib::BinaryStream&) + { + }, bridgeLib::Command::DeviceInfo); + } + + void DeviceConnection::setStateFromUnknownCustomData(const std::vector<uint8_t>& _state) + { + sendAwaitReply([&] + { + bridgeLib::SetUnknownCustomData d; + d.data = _state; + send(bridgeLib::Command::SetUnknownCustomData, d); + }, [&](baseLib::BinaryStream&) + { + }, bridgeLib::Command::DeviceInfo); + } + + void DeviceConnection::setDspClockPercent(const uint32_t _percent) + { + sendAwaitReply([&] + { + bridgeLib::SetDspClockPercent p; + p.percent = _percent; + send(bridgeLib::Command::SetDspClockPercent, p); + }, [&](baseLib::BinaryStream&) + { + }, bridgeLib::Command::DeviceInfo); + } + + bool DeviceConnection::sendAwaitReply(const std::function<void()>& _send, const std::function<void(baseLib::BinaryStream&)>& _reply, const bridgeLib::Command _replyCommand) + { + bool receiveDone = false; + + m_handleReplyFunc = [&](const bridgeLib::Command _command, baseLib::BinaryStream& _in) + { + if(_command != _replyCommand) + return; + + _reply(_in); + + { + std::unique_lock lockCv(m_cvWaitMutex); + receiveDone = true; + } + m_cvWait.notify_one(); + }; + + _send(); + + std::unique_lock lockCv(m_cvWaitMutex); + m_cvWait.wait_for(lockCv, std::chrono::seconds(g_replyTimeoutSecs), [&] + { + return receiveDone; + }); + + m_handleReplyFunc = [](bridgeLib::Command, baseLib::BinaryStream&) + { + }; + + if(receiveDone) + return true; + LOG("Receive timeout, closing connection"); + close(); + return false; + } +} diff --git a/source/bridge/client/deviceConnection.h b/source/bridge/client/deviceConnection.h @@ -0,0 +1,64 @@ +#pragma once + +#include <mutex> +#include <condition_variable> + +#include "bridgeLib/audioBuffers.h" +#include "bridgeLib/tcpConnection.h" + +#include "synthLib/audioTypes.h" +#include "synthLib/deviceTypes.h" + +namespace bridgeClient +{ + class RemoteDevice; + + class DeviceConnection : public bridgeLib::TcpConnection + { + public: + DeviceConnection(RemoteDevice& _device, std::unique_ptr<networkLib::TcpStream>&& _stream); + ~DeviceConnection() override; + + void handleCommand(bridgeLib::Command _command, baseLib::BinaryStream& _in) override; + + void handleData(const bridgeLib::DeviceDesc& _desc) override; + void handleDeviceInfo(baseLib::BinaryStream& _in) override; + + void handleException(const networkLib::NetException& _e) override; + + // INIT + void sendDeviceCreateParams(bool _sendRom); + + // AUDIO + bool processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, uint32_t _size, uint32_t _latency); + void handleAudio(baseLib::BinaryStream& _in) override; + + // MIDI + void handleMidi(const synthLib::SMidiEvent& _e) override; + void readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut); + + // DEVICE STATE + bool getDeviceState(std::vector<uint8_t>& _state, synthLib::StateType _type); + void handleDeviceState(baseLib::BinaryStream& _in) override; + bool setDeviceState(const std::vector<uint8_t>& _state, synthLib::StateType _type); + + void setSamplerate(float _samplerate); + void setStateFromUnknownCustomData(const std::vector<uint8_t>& _state); + void setDspClockPercent(uint32_t _percent); + + private: + bool sendAwaitReply(const std::function<void()>& _send, const std::function<void(baseLib::BinaryStream&)>& _reply, bridgeLib::Command _replyCommand); + + RemoteDevice& m_device; + bridgeLib::DeviceDesc m_deviceDesc; + + std::function<void(bridgeLib::Command, baseLib::BinaryStream&)> m_handleReplyFunc; + + std::mutex m_cvWaitMutex; + std::condition_variable m_cvWait; + + std::vector<synthLib::SMidiEvent> m_midiOut; + + bridgeLib::AudioBuffers m_audioBuffers; + }; +} diff --git a/source/bridge/client/export.cpp b/source/bridge/client/export.cpp @@ -0,0 +1 @@ +#include "export.h" diff --git a/source/bridge/client/export.h b/source/bridge/client/export.h @@ -0,0 +1,24 @@ +#pragma once + +#include "bridgeLib/commands.h" + +#if defined(_WIN32) && (defined(_MSC_VER) || defined(__MINGW32__)) + #define BRIDGE_CLIENT_API __declspec(dllexport) +#elif defined(_WIN32) && defined(__GNUC__) + #define BRIDGE_CLIENT_API __attribute__((__dllexport__)) +#elif defined(__GNUC__) + #define BRIDGE_CLIENT_API __attribute__((__visibility__("default"))) +#endif + +namespace synthLib +{ + struct DeviceCreateParams; + class Device; +} + +extern "C" +{ + BRIDGE_CLIENT_API synthLib::Device* bridgeDeviceCreate(const synthLib::DeviceCreateParams&); + BRIDGE_CLIENT_API void bridgeDeviceDestroy(const synthLib::Device* _device); + BRIDGE_CLIENT_API void bridgeDeviceGetDesc(bridgeLib::PluginDesc& _desc); +} diff --git a/source/bridge/client/plugin.h b/source/bridge/client/plugin.h @@ -0,0 +1,46 @@ +// ReSharper disable CppNonInlineFunctionDefinitionInHeaderFile +#pragma once + +#include "export.h" + +#include "bridgeLib/commands.h" + +namespace synthLib +{ + class Device; +} + +namespace bridgeClient +{ + void initPluginDesc(bridgeLib::PluginDesc& _desc) + { + _desc.pluginName = PluginName; + _desc.pluginVersion = PluginVersionMajor * 10000 + PluginVersionMinor * 100 + PluginVersionPatch; + _desc.plugin4CC = Plugin4CC; + } + + void getBridgeDeviceDesc(bridgeLib::PluginDesc& _desc) + { + initPluginDesc(_desc); + } +} + +synthLib::Device* createBridgeDevice(const synthLib::DeviceCreateParams& _params); + +extern "C" +{ + BRIDGE_CLIENT_API synthLib::Device* bridgeDeviceCreate(const synthLib::DeviceCreateParams& _params) + { + return createBridgeDevice(_params); + } + + BRIDGE_CLIENT_API void bridgeDeviceDestroy(const synthLib::Device* _device) + { + delete _device; + } + + BRIDGE_CLIENT_API void bridgeDeviceGetDesc(bridgeLib::PluginDesc& _desc) + { + bridgeClient::getBridgeDeviceDesc(_desc); + } +} diff --git a/source/bridge/client/remoteDevice.cpp b/source/bridge/client/remoteDevice.cpp @@ -0,0 +1,288 @@ +#include "remoteDevice.h" + +#include "udpClient.h" + +#include "bridgeLib/types.h" + +#include "dsp56kEmu/logging.h" + +#include "networkLib/tcpClient.h" +#include "networkLib/tcpStream.h" + +#include "synthLib/deviceException.h" + +#include "deviceConnection.h" + +#include <condition_variable> + +#include "networkLib/exception.h" +#include "networkLib/logging.h" + +#include "synthLib/os.h" + +namespace bridgeClient +{ + static constexpr uint32_t g_udpTimeout = 5; // seconds + static constexpr uint32_t g_tcpTimeout = 5; // seconds + + RemoteDevice::RemoteDevice(const synthLib::DeviceCreateParams& _params, bridgeLib::PluginDesc&& _desc, const std::string& _host/* = {}*/, uint32_t _port/* = 0*/) : Device(_params), m_pluginDesc(std::move(_desc)) + { + getDeviceCreateParams().romHash = baseLib::MD5(getDeviceCreateParams().romData); + getDeviceCreateParams().romName = synthLib::getFilenameWithoutPath(getDeviceCreateParams().romName); + + m_pluginDesc.protocolVersion = bridgeLib::g_protocolVersion; + createConnection(_host, _port); + } + + RemoteDevice::~RemoteDevice() + { + m_connection.reset(); + } + + float RemoteDevice::getSamplerate() const + { + return m_deviceDesc.samplerate; + } + + bool RemoteDevice::isValid() const + { + return m_valid; + } + + bool RemoteDevice::getState(std::vector<uint8_t>& _state, const synthLib::StateType _type) + { + return safeCall([&] + { + return m_connection->getDeviceState(_state, _type); + }, [&] + { + // if there is no valid connection anymore attempt to grab the latest state that was sent + if(!m_connection) + return; + auto& state = static_cast<bridgeLib::TcpConnection*>(m_connection.get())->getDeviceState(); + _state.insert(_state.end(), state.state.begin(), state.state.end()); + }); + } + + bool RemoteDevice::setState(const std::vector<uint8_t>& _state, const synthLib::StateType _type) + { + if(!isValid()) + return false; + return m_connection->setDeviceState(_state, _type); + } + + uint32_t RemoteDevice::getChannelCountIn() + { + return m_deviceDesc.inChannels; + } + + uint32_t RemoteDevice::getChannelCountOut() + { + return m_deviceDesc.outChannels; + } + + bool RemoteDevice::setDspClockPercent(const uint32_t _percent) + { + return safeCall([&] + { + m_connection->setDspClockPercent(_percent); + return true; + }); + } + + uint32_t RemoteDevice::getDspClockPercent() const + { + return m_deviceDesc.dspClockPercent; + } + + uint64_t RemoteDevice::getDspClockHz() const + { + return m_deviceDesc.dspClockHz; + } + + uint32_t RemoteDevice::getInternalLatencyInputToOutput() const + { + return m_deviceDesc.latencyInToOut; + } + + uint32_t RemoteDevice::getInternalLatencyMidiToOutput() const + { + return m_deviceDesc.latencyMidiToOut; + } + + void RemoteDevice::getPreferredSamplerates(std::vector<float>& _dst) const + { + _dst = m_deviceDesc.preferredSamplerates; + } + + void RemoteDevice::getSupportedSamplerates(std::vector<float>& _dst) const + { + _dst = m_deviceDesc.supportedSamplerates; + } + + bool RemoteDevice::setSamplerate(const float _samplerate) + { + return safeCall([&] + { + m_connection->setSamplerate(_samplerate); + return true; + }); + } + + bool RemoteDevice::setStateFromUnknownCustomData(const std::vector<uint8_t>& _state) + { + return safeCall([&] + { + m_connection->setStateFromUnknownCustomData(_state); + return true; + }); + } + + void RemoteDevice::onBootFinished(const bridgeLib::DeviceDesc& _desc) + { + { + std::unique_lock lock(m_cvWaitMutex); + m_deviceDesc = _desc; + } + m_cvWait.notify_one(); + } + + void RemoteDevice::onDisconnect() + { + { + std::unique_lock lock(m_cvWaitMutex); + m_valid = false; + } + m_cvWait.notify_one(); + } + + void RemoteDevice::readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) + { + safeCall([&] + { + m_connection->readMidiOut(_midiOut); + return true; + }); + } + + void RemoteDevice::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, const size_t _samples) + { + safeCall([&] + { + return m_connection->processAudio(_inputs, _outputs, static_cast<uint32_t>(_samples), getExtraLatencySamples()); + }); + } + + bool RemoteDevice::sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) + { + return safeCall([&] + { + return m_connection->send(_ev); + }); + } + + bool RemoteDevice::safeCall(const std::function<bool()>& _func, const std::function<void()>& _onFail/* = [] {}*/) + { + if(!isValid()) + { + _onFail(); + return false; + } + + try + { + if(!_func()) + { + onDisconnect(); + _onFail(); + } + } + catch(networkLib::NetException& e) + { + LOG(e.what()); + onDisconnect(); + _onFail(); + } + return true; + } + + void RemoteDevice::createConnection(const std::string& _host, uint32_t _port) + { + // wait for UDP connection up to 5 seconds + bridgeLib::ServerInfo si; + std::string host = _host; + + if(_host.empty() || !_port) + { + UdpClient udpClient(m_pluginDesc, [&](const std::string& _hostname, const bridgeLib::ServerInfo& _si, const bridgeLib::Error& _err) + { + if(_err.code != bridgeLib::ErrorCode::Ok) + return; + { + std::unique_lock lock(m_cvWaitMutex); + si = _si; + host = _hostname; + LOG("Found server "<< _hostname << ':' << si.portTcp); + } + m_cvWait.notify_one(); + }); + + std::unique_lock lockCv(m_cvWaitMutex); + if(!m_cvWait.wait_for(lockCv, std::chrono::seconds(g_udpTimeout), [&si] + { + return si.protocolVersion == bridgeLib::g_protocolVersion; + })) + { + throw synthLib::DeviceException(synthLib::DeviceError::RemoteUdpConnectFailed, "No server found"); + } + } + else + { + si.portTcp = _port; + } + + // wait for TCP connection for another 5 seconds + std::unique_ptr<networkLib::TcpStream> stream; + + LOG("Connecting to "<< host << ':' << si.portTcp); + + networkLib::TcpClient client(host, si.portTcp,[&](std::unique_ptr<networkLib::TcpStream> _tcpStream) + { + { + std::unique_lock lock(m_cvWaitMutex); + stream = std::move(_tcpStream); + } + m_cvWait.notify_one(); + }); + + { + std::unique_lock lockCv(m_cvWaitMutex); + if(!m_cvWait.wait_for(lockCv, std::chrono::seconds(g_tcpTimeout), [&stream] + { + return stream.get() && stream->isValid(); + })) + { + throw synthLib::DeviceException(synthLib::DeviceError::RemoteTcpConnectFailed, "Failed to connect to " + host + ':' + std::to_string(si.portTcp)); + } + } + + // we are connected. Wait for device info as long as the connection is alive. The server will either + // close it if the requirements are not fulfilled (plugin not existing on server) or will eventually + // send device info after the device has bene opened on the server + m_connection.reset(new DeviceConnection(*this, std::move(stream))); + + std::unique_lock lockCv(m_cvWaitMutex); + m_cvWait.wait(lockCv, [this]() + { + // continue waiting if the connection is still active but we didn't receive a device desc yet + return !m_connection->isValid() || m_deviceDesc.outChannels > 0; + }); + + if(!m_connection->isValid() || m_deviceDesc.outChannels == 0) + throw synthLib::DeviceException(synthLib::DeviceError::RemoteTcpConnectFailed); + + m_valid = true; + + LOG("Connection established successfully"); + } +} diff --git a/source/bridge/client/remoteDevice.h b/source/bridge/client/remoteDevice.h @@ -0,0 +1,71 @@ +#pragma once + +#include <condition_variable> +#include <functional> +#include <memory> + +#include "bridgeLib/commands.h" + +#include "synthLib/device.h" + +namespace bridgeLib +{ + struct PluginDesc; +} + +namespace bridgeClient +{ + class DeviceConnection; + + class RemoteDevice : public synthLib::Device + { + public: + RemoteDevice(const synthLib::DeviceCreateParams& _params, bridgeLib::PluginDesc&& _desc, const std::string& _host = {}, uint32_t _port = 0); + ~RemoteDevice() override; + RemoteDevice(RemoteDevice&&) = delete; + RemoteDevice(const RemoteDevice&) = delete; + RemoteDevice& operator = (RemoteDevice&&) = delete; + RemoteDevice& operator = (const RemoteDevice&) = delete; + + float getSamplerate() const override; + bool isValid() const override; + bool getState(std::vector<uint8_t>& _state, synthLib::StateType _type) override; + bool setState(const std::vector<uint8_t>& _state, synthLib::StateType _type) override; + uint32_t getChannelCountIn() override; + uint32_t getChannelCountOut() override; + bool setDspClockPercent(uint32_t _percent) override; + uint32_t getDspClockPercent() const override; + uint64_t getDspClockHz() const override; + uint32_t getInternalLatencyInputToOutput() const override; + uint32_t getInternalLatencyMidiToOutput() const override; + void getPreferredSamplerates(std::vector<float>& _dst) const override; + void getSupportedSamplerates(std::vector<float>& _dst) const override; + + const auto& getPluginDesc() const { return m_pluginDesc; } + auto& getPluginDesc() { return m_pluginDesc; } + + bool setSamplerate(float _samplerate) override; + + bool setStateFromUnknownCustomData(const std::vector<uint8_t>& _state) override; + + void onBootFinished(const bridgeLib::DeviceDesc& _desc); + void onDisconnect(); + + protected: + void readMidiOut(std::vector<synthLib::SMidiEvent>& _midiOut) override; + void processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) override; + bool sendMidi(const synthLib::SMidiEvent& _ev, std::vector<synthLib::SMidiEvent>& _response) override; + + bool safeCall(const std::function<bool()>& _func, const std::function<void()>& _onFail = [] {}); + + void createConnection(const std::string& _host, uint32_t _port); + + bridgeLib::PluginDesc m_pluginDesc; + bridgeLib::DeviceDesc m_deviceDesc; + std::unique_ptr<DeviceConnection> m_connection; + + std::mutex m_cvWaitMutex; + std::condition_variable m_cvWait; + bool m_valid = false; + }; +} diff --git a/source/bridge/client/serverList.cpp b/source/bridge/client/serverList.cpp @@ -0,0 +1,53 @@ +#include "serverList.h" + +namespace bridgeClient +{ + constexpr std::chrono::seconds g_expireTime(10); + + ServerList::ServerList(const bridgeLib::PluginDesc& _desc) : m_udpClient(_desc, [this](const std::string& _host, const bridgeLib::ServerInfo& _serverInfo, const bridgeLib::Error& _error) + { + onServerFound(_host, _serverInfo, _error); + }) + { + } + + std::set<ServerList::Entry> ServerList::getEntries() const + { + std::set<Entry> entries; + { + std::scoped_lock lock(m_entriesMutex); + entries = m_entries; + } + removeExpiredEntries(entries); + return entries; + } + + void ServerList::removeExpiredEntries(std::set<Entry>& _entries) + { + for(auto it = _entries.begin(); it != _entries.end();) + { + auto& e = *it; + + const auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(Clock::now() - e.lastSeen); + + if(elapsed >= g_expireTime) + it = _entries.erase(it); + else + ++it; + } + } + + void ServerList::onServerFound(const std::string& _host, const bridgeLib::ServerInfo& _serverInfo, const bridgeLib::Error& _error) + { + Entry e; + e.serverInfo = _serverInfo; + e.err = _error; + e.host = _host; + e.lastSeen = std::chrono::system_clock::now(); + + std::scoped_lock lock(m_entriesMutex); + m_entries.erase(e); + removeExpiredEntries(m_entries); + m_entries.insert(e); + } +} diff --git a/source/bridge/client/serverList.h b/source/bridge/client/serverList.h @@ -0,0 +1,52 @@ +#pragma once + +#include <mutex> +#include <set> +#include <string> +#include <chrono> + +#include "udpClient.h" + +#include "bridgeLib/commands.h" + +namespace bridgeClient +{ + class ServerList + { + public: + using Clock = std::chrono::system_clock; + using Timestamp = Clock::time_point; + + struct Entry + { + std::string host; + bridgeLib::ServerInfo serverInfo; + bridgeLib::Error err; + Timestamp lastSeen; + + bool operator < (const Entry& _e) const + { + return host < _e.host; + } + + bool operator == (const Entry& _e) const + { + return serverInfo.portTcp == _e.serverInfo.portTcp && host == _e.host; + } + }; + + ServerList(const bridgeLib::PluginDesc& _desc); + + std::set<Entry> getEntries() const; + + static void removeExpiredEntries(std::set<Entry>& _entries); + + private: + void onServerFound(const std::string& _host, const bridgeLib::ServerInfo& _serverInfo, const bridgeLib::Error& _error); + + UdpClient m_udpClient; + + mutable std::mutex m_entriesMutex; + std::set<Entry> m_entries; + }; +} diff --git a/source/bridge/client/types.h b/source/bridge/client/types.h @@ -0,0 +1 @@ +#pragma once diff --git a/source/bridge/client/udpClient.cpp b/source/bridge/client/udpClient.cpp @@ -0,0 +1,65 @@ +#include "udpClient.h" + +#include <utility> + +#include "bridgeLib/commandWriter.h" +#include "bridgeLib/error.h" +#include "bridgeLib/types.h" + +namespace bridgeClient +{ + UdpClient::UdpClient(const bridgeLib::PluginDesc& _desc, ServerFoundCallback&& _callback) + : networkLib::UdpClient(bridgeLib::g_udpServerPort) + , m_callback(std::move(_callback)) + { + bridgeLib::PluginDesc desc = _desc; + + desc.protocolVersion = bridgeLib::g_protocolVersion; + + bridgeLib::CommandWriter w; + desc.write(w.build(bridgeLib::Command::PluginInfo)); + + baseLib::BinaryStream bs; + w.write(bs); + bs.toVector(m_requestPacket); + + start(); + } + + bool UdpClient::validateResponse(const std::string& _host, const std::vector<uint8_t>& _message) + { + bool ok = false; + + bridgeLib::CommandReader reader([&](const bridgeLib::Command _command, baseLib::BinaryStream& _binaryStream) + { + if(_command == bridgeLib::Command::ServerInfo) + { + bridgeLib::ServerInfo si; + si.read(_binaryStream); + + if(si.protocolVersion == bridgeLib::g_protocolVersion && si.portTcp > 0) + { + ok = true; + m_callback(_host, si, {}); + } + else + { + bridgeLib::Error e; + e.code = bridgeLib::ErrorCode::WrongProtocolVersion; + e.msg = "Wrong protocol version"; + m_callback(_host, si, e); + } + } + else if(_command == bridgeLib::Command::Error) + { + bridgeLib::Error e; + e.read(_binaryStream); + m_callback(_host, {}, e); + } + }); + + baseLib::BinaryStream bs(_message); + reader.read(bs); + return false; // continue + } +} diff --git a/source/bridge/client/udpClient.h b/source/bridge/client/udpClient.h @@ -0,0 +1,26 @@ +#pragma once + +#include "bridgeLib/commandReader.h" + +#include "networkLib/udpClient.h" + +namespace bridgeClient +{ + class UdpClient : networkLib::UdpClient + { + public: + using ServerFoundCallback = std::function<void(const std::string&, const bridgeLib::ServerInfo&, const bridgeLib::Error&)>; + + UdpClient(const bridgeLib::PluginDesc& _desc, ServerFoundCallback&& _callback); + + private: + bool validateResponse(const std::string& _host, const std::vector<uint8_t>& _message) override; + const std::vector<uint8_t>& getRequestPacket() override + { + return m_requestPacket; + } + + std::vector<uint8_t> m_requestPacket; + ServerFoundCallback m_callback; + }; +} diff --git a/source/bridge/server/CMakeLists.txt b/source/bridge/server/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) + +project(bridgeServer) + +add_executable(bridgeServer) + +set(SOURCES + bridgeServer.cpp + clientConnection.cpp clientConnection.h + config.cpp config.h + server.cpp server.h + import.cpp import.h + romPool.cpp romPool.h + udpServer.cpp udpServer.h +) + +target_sources(bridgeServer PRIVATE ${SOURCES}) + +source_group("source" FILES ${SOURCES}) + +target_link_libraries(bridgeServer PUBLIC bridgeLib) + +set_property(TARGET bridgeServer PROPERTY FOLDER "Bridge") +set_property(TARGET bridgeServer PROPERTY OUTPUT_NAME "dsp56300EmuServer") + +install(TARGETS bridgeServer DESTINATION . COMPONENT DSPBridgeServer) diff --git a/source/bridge/server/bridgeServer.cpp b/source/bridge/server/bridgeServer.cpp @@ -0,0 +1,43 @@ +#include <iostream> + +#include "server.h" + +#ifndef _WIN32 +#include <cstdio> +#include <execinfo.h> +#include <signal.h> +#include <cstdlib> + +void segFaultHandler(int sig) +{ + void *array[10]; + size_t size; + + size = backtrace(array, 10); + + fprintf(stderr, "Error: signal %d:\n", sig); + backtrace_symbols_fd(array, size, 2); + exit(1); +} +#endif + +int main(int _argc, char** _argv) +{ +#ifndef _WIN32 + signal(SIGSEGV, segFaultHandler); +#endif + + while(true) + { + try + { + bridgeServer::Server server(_argc, _argv); + server.run(); + } + catch(...) + { + std::cout << "Server exception, attempting to restart\n"; + } + } + return 0; +} diff --git a/source/bridge/server/clientConnection.cpp b/source/bridge/server/clientConnection.cpp @@ -0,0 +1,290 @@ +#include "clientConnection.h" + +#include "server.h" +#include "bridgeLib/error.h" +#include "networkLib/logging.h" + +namespace bridgeServer +{ + static constexpr uint32_t g_audioBufferSize = 16384; + + ClientConnection::ClientConnection(Server& _server, std::unique_ptr<networkLib::TcpStream>&& _stream, std::string _name) + : TcpConnection(std::move(_stream)) + , m_server(_server) + , m_name(std::move(_name)) + { + for(size_t i=0; i<m_audioInputBuffers.size(); ++i) + { + auto& buf = m_audioInputBuffers[i]; + buf.resize(g_audioBufferSize); + m_audioInputs[i] = buf.data(); + } + + for(size_t i=0; i<m_audioOutputBuffers.size(); ++i) + { + auto& buf = m_audioOutputBuffers[i]; + buf.resize(g_audioBufferSize); + m_audioOutputs[i] = buf.data(); + } + + m_midiIn.reserve(1024); + m_midiOut.reserve(4096); + + getDeviceState().state.reserve(8 * 1024 * 1024); + } + + ClientConnection::~ClientConnection() + { + shutdown(); + destroyDevice(); + } + + void ClientConnection::handleMidi(const synthLib::SMidiEvent& _e) + { + m_midiIn.push_back(_e); + } + + void ClientConnection::handleData(const bridgeLib::PluginDesc& _desc) + { + m_pluginDesc = _desc; + LOGNET(networkLib::LogLevel::Info, "Client " << m_name << " identified as plugin " << _desc.pluginName << ", version " << _desc.pluginVersion); + m_name = m_pluginDesc.pluginName + '-' + m_name; + createDevice(); + } + + void ClientConnection::handleData(const bridgeLib::DeviceCreateParams& _params) + { + const auto& p = _params.params; + + if(p.romData.empty()) + { + // if no rom data has been transmitted, try to load from cache + const auto& romData = m_server.getRomPool().getRom(p.romHash); + + if(!romData.empty()) + { + // if we have the ROM, we are good to go + LOGNET(networkLib::LogLevel::Info, "ROM " << p.romName << " with hash " << p.romHash.toString() << " found, transfer skipped"); + + m_deviceCreateParams = p; + m_deviceCreateParams.romData = romData; + + createDevice(); + } + else + { + if(m_romRequested) + { + LOGNET(networkLib::LogLevel::Warning, "Client " << m_name << " failed to provide rom that we asked for, disconnecting"); + close(); + return; + } + + // If not, ask client to send it + send(bridgeLib::Command::RequestRom); + LOGNET(networkLib::LogLevel::Info, "ROM " << p.romName << " with hash " << p.romHash.toString() << " not found, requesting client to send it"); + m_romRequested = true; + } + } + else + { + // client sent the rom. Validate transmission by comparing hashes + const baseLib::MD5 calculatedHash(p.romData); + if(calculatedHash != p.romHash) + { + LOGNET(networkLib::LogLevel::Error, "Calculated hash " << calculatedHash.toString() << " of ROM " << p.romName << " does not match sent hash " << p.romHash.toString() << ", transfer error"); + close(); + } + + LOGNET(networkLib::LogLevel::Info, "Adding ROM " << p.romName << " with hash " << p.romHash.toString() << " to pool"); + m_server.getRomPool().addRom(p.romName, p.romData); + + m_deviceCreateParams = p; + + createDevice(); + } + } + + void ClientConnection::handleAudio(baseLib::BinaryStream& _in) + { + if(!m_device) + { + errorClose(bridgeLib::ErrorCode::UnexpectedCommand, "Audio data without valid device"); + return; + } + + const auto numSamples = TcpConnection::handleAudio(const_cast<float* const*>(m_audioInputs.data()), _in); + + m_device->process(m_audioInputs, m_audioOutputs, numSamples, m_midiIn, m_midiOut); + + for (const auto& midiOut : m_midiOut) + send(midiOut); + + sendAudio(m_audioOutputs.data(), std::min(static_cast<uint32_t>(m_audioOutputs.size()), m_device->getChannelCountOut()), numSamples); + + m_midiIn.clear(); + + m_device->release(m_midiOut); + } + + void ClientConnection::sendDeviceState(const synthLib::StateType _type) + { + if(!m_device) + return; + + std::scoped_lock lock(m_mutexDeviceState); + auto& state = getDeviceState(); + state.type = _type; + + state.state.clear(); + m_device->getState(state.state, _type); + + send(bridgeLib::Command::DeviceState, state); + } + + void ClientConnection::handleRequestDeviceState(bridgeLib::RequestDeviceState& _requestDeviceState) + { + if(!m_device) + { + errorClose(bridgeLib::ErrorCode::UnexpectedCommand, "Device state request without valid device"); + return; + } + + sendDeviceState(_requestDeviceState.type); + } + + void ClientConnection::handleDeviceState(bridgeLib::DeviceState& _in) + { + if(!m_device) + { + errorClose(bridgeLib::ErrorCode::UnexpectedCommand, "Device state without valid device"); + return; + } + + m_device->setState(_in.state, _in.type); + sendDeviceInfo(); + } + + void ClientConnection::handleDeviceState(baseLib::BinaryStream& _in) + { + std::scoped_lock lock(m_mutexDeviceState); + TcpConnection::handleDeviceState(_in); + } + + void ClientConnection::handleException(const networkLib::NetException& _e) + { + exit(true); + m_server.onClientException(*this, _e); + } + + void ClientConnection::handleData(const bridgeLib::SetSamplerate& _params) + { + if(!m_device) + { + errorClose(bridgeLib::ErrorCode::UnexpectedCommand, "Set samplerate request without valid device"); + return; + } + + m_device->setSamplerate(_params.samplerate); + sendDeviceInfo(); + } + + void ClientConnection::handleData(const bridgeLib::SetDspClockPercent& _params) + { + if(!m_device) + { + errorClose(bridgeLib::ErrorCode::UnexpectedCommand, "Set DSP clock request without valid device"); + return; + } + + m_device->setDspClockPercent(_params.percent); + sendDeviceInfo(); + } + + void ClientConnection::handleData(const bridgeLib::SetUnknownCustomData& _params) + { + if(!m_device) + { + errorClose(bridgeLib::ErrorCode::UnexpectedCommand, "Set custom data request without valid device"); + return; + } + + m_device->setStateFromUnknownCustomData(_params.data); + sendDeviceInfo(); + } + + void ClientConnection::sendDeviceInfo() + { + bridgeLib::DeviceDesc deviceDesc; + + deviceDesc.samplerate = m_device->getSamplerate(); + deviceDesc.outChannels = m_device->getChannelCountOut(); + deviceDesc.inChannels = m_device->getChannelCountIn(); + deviceDesc.dspClockPercent = m_device->getDspClockPercent(); + deviceDesc.dspClockHz = m_device->getDspClockHz(); + deviceDesc.latencyInToOut = m_device->getInternalLatencyInputToOutput(); + deviceDesc.latencyMidiToOut = m_device->getInternalLatencyMidiToOutput(); + + deviceDesc.preferredSamplerates.reserve(64); + deviceDesc.supportedSamplerates.reserve(64); + + m_device->getPreferredSamplerates(deviceDesc.preferredSamplerates); + m_device->getSupportedSamplerates(deviceDesc.supportedSamplerates); + + send(bridgeLib::Command::DeviceInfo, deviceDesc); + } + + void ClientConnection::createDevice() + { + if(m_pluginDesc.pluginVersion == 0 || m_deviceCreateParams.romData.empty()) + return; + + m_device = m_server.getPlugins().createDevice(m_deviceCreateParams, m_pluginDesc); + + if(!m_device) + { + errorClose(bridgeLib::ErrorCode::FailedToCreateDevice,"Failed to create device"); + return; + } + + const auto& d = m_pluginDesc; + LOGNET(networkLib::LogLevel::Info, "Created new device for plugin '" << d.pluginName << "', version " << d.pluginVersion << ", id " << d.plugin4CC); + + // recover a previously lost connection if possible + const auto cachedDeviceState = m_server.getCachedDeviceState(d.sessionId); + + if(cachedDeviceState.isValid()) + { + LOGNET(networkLib::LogLevel::Info, m_name << ": Recovering previous device state for session id " << d.sessionId); + send(bridgeLib::Command::DeviceState, cachedDeviceState); + m_device->setState(cachedDeviceState.state, cachedDeviceState.type); + } + + sendDeviceInfo(); + } + + void ClientConnection::destroyDevice() + { + if(!m_device) + return; + + if(isValid()) + sendDeviceState(synthLib::StateTypeGlobal); + + m_server.getPlugins().destroyDevice(m_pluginDesc, m_device); + m_device = nullptr; + } + + void ClientConnection::errorClose(const bridgeLib::ErrorCode _code, const std::string& _err) + { + LOGNET(networkLib::LogLevel::Error, m_name + ": " + _err); + + bridgeLib::Error err; + err.code = _code; + err.msg = _err; + + send(bridgeLib::Command::Error, err); + std::this_thread::sleep_for(std::chrono::seconds(1)); + close(); + } +} diff --git a/source/bridge/server/clientConnection.h b/source/bridge/server/clientConnection.h @@ -0,0 +1,63 @@ +#pragma once + +#include <mutex> + +#include "bridgeLib/tcpConnection.h" +#include "networkLib/networkThread.h" +#include "networkLib/tcpStream.h" +#include "synthLib/device.h" + +namespace bridgeServer +{ + class Server; + + class ClientConnection : public bridgeLib::TcpConnection + { + public: + ClientConnection(Server& _server, std::unique_ptr<networkLib::TcpStream>&& _stream, std::string _name); + ~ClientConnection() override; + + void handleMidi(const synthLib::SMidiEvent& _e) override; + void handleData(const bridgeLib::PluginDesc& _desc) override; + void handleData(const bridgeLib::DeviceCreateParams& _params) override; + void handleData(const bridgeLib::SetSamplerate& _params) override; + void handleData(const bridgeLib::SetDspClockPercent& _params) override; + void handleData(const bridgeLib::SetUnknownCustomData& _params) override; + + void handleAudio(baseLib::BinaryStream& _in) override; + void sendDeviceState(synthLib::StateType _type); + void handleRequestDeviceState(bridgeLib::RequestDeviceState& _requestDeviceState) override; + void handleDeviceState(bridgeLib::DeviceState& _in) override; + void handleDeviceState(baseLib::BinaryStream& _in) override; + void handleException(const networkLib::NetException& _e) override; + + const auto& getPluginDesc() const { return m_pluginDesc; } + + private: + void sendDeviceInfo(); + void createDevice(); + void destroyDevice(); + + void errorClose(bridgeLib::ErrorCode _code, const std::string& _err); + + Server& m_server; + std::string m_name; + + bridgeLib::PluginDesc m_pluginDesc; + synthLib::DeviceCreateParams m_deviceCreateParams; + + synthLib::Device* m_device = nullptr; + + synthLib::TAudioInputs m_audioInputs; + synthLib::TAudioOutputs m_audioOutputs; + + std::array<std::vector<float>, std::tuple_size_v<synthLib::TAudioInputs>> m_audioInputBuffers; + std::array<std::vector<float>, std::tuple_size_v<synthLib::TAudioOutputs>> m_audioOutputBuffers; + std::vector<synthLib::SMidiEvent> m_midiIn; + std::vector<synthLib::SMidiEvent> m_midiOut; + + bool m_romRequested = false; + + std::mutex m_mutexDeviceState; + }; +} diff --git a/source/bridge/server/config.cpp b/source/bridge/server/config.cpp @@ -0,0 +1,43 @@ +#include "config.h" + +#include "server.h" +#include "baseLib/commandline.h" +#include "baseLib/configFile.h" + +#include "synthLib/os.h" + +namespace bridgeServer +{ + Config::Config(int _argc, char** _argv) + : portTcp(bridgeLib::g_tcpServerPort) + , portUdp(bridgeLib::g_udpServerPort) + , deviceStateRefreshMinutes(3) + , pluginsPath(getDefaultDataPath() + "plugins/") + , romsPath(getDefaultDataPath() + "roms/") + { + const baseLib::CommandLine commandLine(_argc, _argv); + + auto configFilename = commandLine.get("config"); + + if (configFilename.empty()) + configFilename = getDefaultDataPath() + "config/dspBridgeServer.cfg"; + + baseLib::ConfigFile config(configFilename); + + config.add(commandLine, true); + + portTcp = config.getInt("tcpPort", static_cast<int>(portTcp)); + portUdp = config.getInt("tcpPort", static_cast<int>(portUdp)); + deviceStateRefreshMinutes = config.getInt("deviceStateRefreshMinutes", static_cast<int>(deviceStateRefreshMinutes)); + pluginsPath = config.get("pluginsPath", pluginsPath); + romsPath = config.get("romsPath", romsPath); + + synthLib::createDirectory(pluginsPath); + synthLib::createDirectory(romsPath); + } + + std::string Config::getDefaultDataPath() + { + return synthLib::validatePath(synthLib::getSpecialFolderPath(synthLib::SpecialFolderType::UserDocuments)) + "The Usual Suspects/dspBridgeServer/"; + } +} diff --git a/source/bridge/server/config.h b/source/bridge/server/config.h @@ -0,0 +1,20 @@ +#pragma once + +#include <cstdint> +#include <string> + +namespace bridgeServer +{ + struct Config + { + Config(int _argc, char** _argv); + + uint32_t portTcp; + uint32_t portUdp; + uint32_t deviceStateRefreshMinutes; + std::string pluginsPath; + std::string romsPath; + + static std::string getDefaultDataPath(); + }; +} diff --git a/source/bridge/server/import.cpp b/source/bridge/server/import.cpp @@ -0,0 +1,156 @@ +#include "import.h" + +#include "config.h" +#include "networkLib/logging.h" +#include "synthLib/deviceException.h" +#include "synthLib/os.h" + +#ifdef _WIN32 +#define NOMINMAX +#include <Windows.h> +#define RTLD_LAZY 0 +void* dlopen (const char* _filename, int) +{ + return LoadLibraryA(_filename); +} +FARPROC dlsym (void* _handle, const char* _name) +{ + return GetProcAddress (static_cast<HMODULE>(_handle), _name); +} +int dlclose(void* _handle) +{ + return FreeLibrary(static_cast<HMODULE>(_handle)); +} +#else +#include <dlfcn.h> +#endif + +namespace bridgeServer +{ + Import::Import(const Config& _config) : m_config(_config) + { + findPlugins(); + } + + Import::~Import() + { + for (const auto& it : m_loadedPlugins) + dlclose(it.second.handle); + m_loadedPlugins.clear(); + } + + synthLib::Device* Import::createDevice(const synthLib::DeviceCreateParams& _params, const bridgeLib::PluginDesc& _desc) + { + std::scoped_lock lock(m_mutex); + + auto it = m_loadedPlugins.find(_desc); + + if(it == m_loadedPlugins.end()) + findPlugins(); // try to load additional plugins if not found + + it = m_loadedPlugins.find(_desc); + if(it == m_loadedPlugins.end()) + { + LOGNET(networkLib::LogLevel::Warning, "Failed to create device for plugin '" << _desc.pluginName << "', version " << _desc.pluginVersion << ", id " << _desc.plugin4CC << ", no matching plugin available"); + return nullptr; // still not found + } + + try + { + return it->second.funcCreate(_params); + } + catch(synthLib::DeviceException& e) + { + LOGNET(networkLib::LogLevel::Error, "Failed to create device for plugin '" << _desc.pluginName << "', version " << _desc.pluginVersion << ", id " << _desc.plugin4CC << + ", device creation caused exception: code " << static_cast<uint32_t>(e.errorCode()) << ", message: " << e.what()); + return nullptr; + } + } + + bool Import::destroyDevice(const bridgeLib::PluginDesc& _desc, synthLib::Device* _device) + { + if(!_device) + return true; + + std::scoped_lock lock(m_mutex); + + const auto it = m_loadedPlugins.find(_desc); + if(it == m_loadedPlugins.end()) + { + assert(false && "plugin unloaded before device destroyed"); + return false; + } + it->second.funcDestroy(_device); + return true; + } + + void Import::findPlugins() + { + findPlugins(m_config.pluginsPath); + findPlugins(synthLib::getModulePath() + "plugins/"); + } + + void Import::findPlugins(const std::string& _rootPath) + { + findPlugins(_rootPath, ".dll"); + findPlugins(_rootPath, ".so"); + findPlugins(_rootPath, ".dylib"); + + findPlugins(_rootPath, ".vst3"); + findPlugins(_rootPath, ".clap"); + findPlugins(_rootPath, ".lv2"); + } + + void Import::findPlugins(const std::string& _rootPath, const std::string& _extension) + { + const auto path = synthLib::getModulePath() + "plugins/"; + std::vector<std::string> files; + synthLib::findFiles(files, path, _extension, 0, std::numeric_limits<uint32_t>::max()); + + for (const auto& file : files) + loadPlugin(file); + } + + void Import::loadPlugin(const std::string& _file) + { + // load each plugin lib only once + if(m_loadedFiles.find(_file) != m_loadedFiles.end()) + return; + + Plugin plugin; + + plugin.handle = dlopen(_file.c_str(), RTLD_LAZY); + if(!plugin.handle) + return; + + plugin.funcCreate = reinterpret_cast<FuncBridgeDeviceCreate>(dlsym(plugin.handle, "bridgeDeviceCreate")); // NOLINT(clang-diagnostic-cast-function-type-strict) + plugin.funcDestroy = reinterpret_cast<FuncBridgeDeviceDestroy>(dlsym(plugin.handle, "bridgeDeviceDestroy")); // NOLINT(clang-diagnostic-cast-function-type-strict) + plugin.funcGetDesc = reinterpret_cast<FuncBridgeDeviceGetDesc>(dlsym(plugin.handle, "bridgeDeviceGetDesc")); // NOLINT(clang-diagnostic-cast-function-type-strict) + + if(!plugin.funcCreate || !plugin.funcDestroy || !plugin.funcGetDesc) + { + dlclose(plugin.handle); + return; + } + + bridgeLib::PluginDesc desc; + plugin.funcGetDesc(desc); + + if(desc.plugin4CC.empty() || desc.pluginName.empty() || desc.pluginVersion == 0) + { + dlclose(plugin.handle); + return; + } + + if(m_loadedPlugins.find(desc) != m_loadedPlugins.end()) + { + dlclose(plugin.handle); + return; + } + + LOGNET(networkLib::LogLevel::Info, "Found plugin '" << desc.pluginName << "', version " << desc.pluginVersion << ", id " << desc.plugin4CC); + + m_loadedPlugins.insert({desc, plugin}); + m_loadedFiles.insert(_file); + } +} diff --git a/source/bridge/server/import.h b/source/bridge/server/import.h @@ -0,0 +1,54 @@ +#pragma once + +#include <map> +#include <mutex> +#include <set> + +#include "bridgeLib/commands.h" + +namespace synthLib +{ + struct DeviceCreateParams; + class Device; +} + +namespace bridgeServer +{ + struct Config; + + class Import + { + public: + typedef synthLib::Device* (*FuncBridgeDeviceCreate)(const synthLib::DeviceCreateParams& _params); + typedef void (*FuncBridgeDeviceDestroy)(synthLib::Device*); + typedef void (*FuncBridgeDeviceGetDesc)(bridgeLib::PluginDesc&); + + struct Plugin final + { + std::string filename; + void* handle = nullptr; + FuncBridgeDeviceCreate funcCreate = nullptr; + FuncBridgeDeviceDestroy funcDestroy = nullptr; + FuncBridgeDeviceGetDesc funcGetDesc = nullptr; + }; + + Import(const Config& _config); + ~Import(); + + synthLib::Device* createDevice(const synthLib::DeviceCreateParams& _params, const bridgeLib::PluginDesc& _desc); + bool destroyDevice(const bridgeLib::PluginDesc& _desc, synthLib::Device* _device); + + private: + void findPlugins(); + void findPlugins(const std::string& _rootPath); + void findPlugins(const std::string& _rootPath, const std::string& _extension); + void loadPlugin(const std::string& _file); + + const Config& m_config; + + std::map<bridgeLib::PluginDesc, Plugin> m_loadedPlugins; + std::set<std::string> m_loadedFiles; + + std::mutex m_mutex; + }; +} diff --git a/source/bridge/server/romPool.cpp b/source/bridge/server/romPool.cpp @@ -0,0 +1,70 @@ +#include "romPool.h" + +#include "config.h" +#include "baseLib/md5.h" +#include "networkLib/logging.h" +#include "synthLib/os.h" + +namespace bridgeServer +{ + RomPool::RomPool(Config& _config) : m_config(_config) + { + findRoms(); + } + + const RomData& RomPool::getRom(const baseLib::MD5& _hash) + { + std::scoped_lock lock(m_mutex); + + auto it = m_roms.find(_hash); + if(it == m_roms.end()) + findRoms(); + it = m_roms.find(_hash); + if(it != m_roms.end()) + return it->second; + static RomData empty; + return empty; + } + + void RomPool::addRom(const std::string& _name, const RomData& _data) + { + std::scoped_lock lock(m_mutex); + + const auto hash = baseLib::MD5(_data); + if(m_roms.find(hash) != m_roms.end()) + return; + + if(synthLib::writeFile(getRootPath() + _name + '_' + hash.toString() + ".bin", _data)) + m_roms.insert({hash, _data}); + } + + std::string RomPool::getRootPath() const + { + return m_config.romsPath; + } + + void RomPool::findRoms() + { + std::vector<std::string> files; + synthLib::findFiles(files, getRootPath(), {}, 0, 16 * 1024 * 1024); + + for (const auto& file : files) + { + std::vector<uint8_t> romData; + + if(!synthLib::readFile(romData, file)) + { + LOGNET(networkLib::LogLevel::Error, "Failed to load file " << file); + continue; + } + + const auto hash = baseLib::MD5(romData); + + if(m_roms.find(hash) != m_roms.end()) + continue; + + m_roms.insert({hash, std::move(romData)}); + LOGNET(networkLib::LogLevel::Info, "Loaded ROM " << synthLib::getFilenameWithoutPath(file)); + } + } +} diff --git a/source/bridge/server/romPool.h b/source/bridge/server/romPool.h @@ -0,0 +1,35 @@ +#pragma once + +#include <cstdint> +#include <map> +#include <mutex> +#include <vector> + +namespace baseLib +{ + class MD5; +} + +namespace bridgeServer +{ + struct Config; + using RomData = std::vector<uint8_t>; + + class RomPool + { + public: + RomPool(Config& _config); + + const RomData& getRom(const baseLib::MD5& _hash); + void addRom(const std::string& _name, const RomData& _data); + + private: + std::string getRootPath() const; + void findRoms(); + + const Config& m_config; + + std::mutex m_mutex; + std::map<baseLib::MD5, RomData> m_roms; + }; +} diff --git a/source/bridge/server/server.cpp b/source/bridge/server/server.cpp @@ -0,0 +1,113 @@ +#include "server.h" + +#include <ptypes/pinet.h> + +#include "bridgeLib/types.h" +#include "networkLib/logging.h" + +namespace bridgeServer +{ + Server::Server(int _argc, char** _argv) + : m_config(_argc, _argv) + , m_plugins(m_config) + , m_romPool(m_config) + , m_tcpServer([this](std::unique_ptr<networkLib::TcpStream> _stream){onClientConnected(std::move(_stream));} + , bridgeLib::g_tcpServerPort) + , m_lastDeviceStateUpdate(std::chrono::system_clock::now()) + { + } + + Server::~Server() + { + exit(true); + m_cvWait.notify_one(); + m_clients.clear(); + } + + void Server::run() + { + while(!m_exit) + { + std::unique_lock lock(m_cvWaitMutex); + m_cvWait.wait_for(lock, std::chrono::seconds(10)); + + cleanupClients(); + doPeriodicDeviceStateUpdate(); + } + } + + void Server::onClientConnected(std::unique_ptr<networkLib::TcpStream> _stream) + { + const auto s = _stream->getPtypesStream(); + const std::string name = std::string(ptypes::iptostring(s->get_ip())) + ":" + std::to_string(s->get_port()); + + std::scoped_lock lock(m_mutexClients); + m_clients.emplace_back(std::make_unique<ClientConnection>(*this, std::move(_stream), name)); + } + + void Server::onClientException(const ClientConnection&, const networkLib::NetException& _e) + { + m_cvWait.notify_one(); + } + + void Server::exit(const bool _exit) + { + m_exit = _exit; + } + + bridgeLib::DeviceState Server::getCachedDeviceState(const bridgeLib::SessionId& _id) + { + const auto it = m_cachedDeviceStates.find(_id); + + if(it == m_cachedDeviceStates.end()) + { + static bridgeLib::DeviceState s; + return s; + } + + auto res = std::move(it->second); + m_cachedDeviceStates.erase(it); + return res; + } + + void Server::cleanupClients() + { + std::scoped_lock lock(m_mutexClients); + + for(auto it = m_clients.begin(); it != m_clients.end();) + { + const auto& c = *it; + if(!c->isValid()) + { + const auto& deviceState = c->getDeviceState(); + + if(deviceState.isValid()) + m_cachedDeviceStates[c->getPluginDesc().sessionId] = deviceState; + + it = m_clients.erase(it); + LOGNET(networkLib::LogLevel::Info, "Client removed, now " << m_clients.size() << " clients left"); + } + else + { + ++it; + } + } + } + + void Server::doPeriodicDeviceStateUpdate() + { + std::scoped_lock lock(m_mutexClients); + + const auto now = std::chrono::system_clock::now(); + + const auto diff = std::chrono::duration_cast<std::chrono::minutes>(now - m_lastDeviceStateUpdate); + + if(diff.count() < static_cast<int>(m_config.deviceStateRefreshMinutes)) + return; + + m_lastDeviceStateUpdate = now; + + for (const auto& c : m_clients) + c->sendDeviceState(synthLib::StateTypeGlobal); + } +} diff --git a/source/bridge/server/server.h b/source/bridge/server/server.h @@ -0,0 +1,56 @@ +#pragma once + +#include <mutex> +#include <list> +#include <condition_variable> + +#include "clientConnection.h" +#include "config.h" +#include "import.h" +#include "romPool.h" +#include "udpServer.h" +#include "networkLib/tcpServer.h" + +namespace bridgeServer +{ + class Server + { + public: + Server(int _argc, char** _argv); + ~Server(); + + void run(); + + void onClientConnected(std::unique_ptr<networkLib::TcpStream> _stream); + void onClientException(const ClientConnection& _clientConnection, const networkLib::NetException& _e); + + void exit(bool _exit); + + auto& getPlugins() { return m_plugins; } + auto& getRomPool() { return m_romPool; } + + bridgeLib::DeviceState getCachedDeviceState(const bridgeLib::SessionId& _id); + + private: + void cleanupClients(); + void doPeriodicDeviceStateUpdate(); + + Config m_config; + + Import m_plugins; + RomPool m_romPool; + + UdpServer m_udpServer; + networkLib::TcpServer m_tcpServer; + + std::mutex m_mutexClients; + std::list<std::unique_ptr<ClientConnection>> m_clients; + std::map<bridgeLib::SessionId, bridgeLib::DeviceState> m_cachedDeviceStates; + + bool m_exit = false; + + std::mutex m_cvWaitMutex; + std::condition_variable m_cvWait; + std::chrono::system_clock::time_point m_lastDeviceStateUpdate; + }; +} diff --git a/source/bridge/server/udpServer.cpp b/source/bridge/server/udpServer.cpp @@ -0,0 +1,76 @@ +#include "udpServer.h" + +#include "bridgeLib/commandReader.h" +#include "bridgeLib/commandWriter.h" +#include "bridgeLib/error.h" +#include "bridgeLib/types.h" + +namespace bridgeServer +{ + UdpServer::UdpServer() : networkLib::UdpServer(bridgeLib::g_udpServerPort) + { + } + + std::vector<uint8_t> UdpServer::validateRequest(const std::vector<uint8_t>& _request) + { + // respond with server info if client talks the same protocol version + bridgeLib::ErrorCode errorCode = bridgeLib::ErrorCode::UnexpectedCommand; + std::string errorMsg; + + { + bridgeLib::CommandReader reader([&](const bridgeLib::Command _command, baseLib::BinaryStream& _in) + { + if(_command != bridgeLib::Command::PluginInfo) + return; + + bridgeLib::PluginDesc desc; + desc.read(_in); + if(desc.protocolVersion != bridgeLib::g_protocolVersion) + { + errorCode = bridgeLib::ErrorCode::WrongProtocolVersion; + errorMsg = "Protocol version doesn't match"; + } + else if(desc.pluginVersion == 0) + { + errorCode = bridgeLib::ErrorCode::WrongPluginVersion; + errorMsg = "Invalid plugin version"; + } + else if(desc.pluginName.empty() && desc.plugin4CC.empty()) + { + errorCode = bridgeLib::ErrorCode::InvalidPluginDesc; + errorMsg = "invalid plugin description"; + } + else + { + errorCode = bridgeLib::ErrorCode::Ok; + } + }); + baseLib::BinaryStream bs(_request); + reader.read(bs); + } + + bridgeLib::CommandWriter w; + + if(errorCode == bridgeLib::ErrorCode::Ok) + { + bridgeLib::ServerInfo si; + si.protocolVersion = bridgeLib::g_protocolVersion; + si.portTcp = bridgeLib::g_tcpServerPort; + si.portUdp = bridgeLib::g_udpServerPort; + si.write(w.build(bridgeLib::Command::ServerInfo)); + } + else + { + bridgeLib::Error err; + err.code = errorCode; + err.msg = errorMsg; + err.write(w.build(bridgeLib::Command::Error)); + } + + baseLib::BinaryStream bs; + w.write(bs); + std::vector<uint8_t> buf; + bs.toVector(buf); + return buf; + } +} diff --git a/source/bridge/server/udpServer.h b/source/bridge/server/udpServer.h @@ -0,0 +1,14 @@ +#pragma once + +#include "networkLib/udpServer.h" + +namespace bridgeServer +{ + class UdpServer : public networkLib::UdpServer + { + public: + UdpServer(); + + std::vector<uint8_t> validateRequest(const std::vector<uint8_t>& _request) override; + }; +} diff --git a/source/juce.cmake b/source/juce.cmake @@ -33,6 +33,9 @@ if(USE_LV2) list(APPEND juce_formats LV2) endif() +add_custom_target(ServerPlugins) +set_property(TARGET ServerPlugins PROPERTY FOLDER CustomTargets) + macro(createJucePlugin targetName productName isSynth plugin4CC binaryDataProject synthLibProject) juce_add_plugin(${targetName} # VERSION ... # Set this if the plugin version is different to the project version @@ -58,7 +61,7 @@ macro(createJucePlugin targetName productName isSynth plugin4CC binaryDataProjec LV2URI "http://theusualsuspects.lv2.${productName}" ) - target_sources(${targetName} PRIVATE ${SOURCES}) + target_sources(${targetName} PRIVATE ${SOURCES} serverPlugin.cpp) source_group("source" FILES ${SOURCES}) @@ -74,6 +77,12 @@ macro(createJucePlugin targetName productName isSynth plugin4CC binaryDataProjec JUCE_USE_MP3AUDIOFORMAT=0 JUCE_USE_FLAC=0 JUCE_USE_WINDOWS_MEDIA_FORMAT=0 + + PluginName="${productName}" + PluginVersionMajor=${CMAKE_PROJECT_VERSION_MAJOR} + PluginVersionMinor=${CMAKE_PROJECT_VERSION_MINOR} + PluginVersionPatch=${CMAKE_PROJECT_VERSION_PATCH} + Plugin4CC="${plugin4CC}" ) target_link_libraries(${targetName} @@ -196,6 +205,50 @@ macro(createJucePlugin targetName productName isSynth plugin4CC binaryDataProjec -P ${JUCE_CMAKE_DIR}/runAuValidation.cmake) set_tests_properties(${targetName}_AU_Validate PROPERTIES LABELS "PluginTest") endif() + + # --------- Server Plugin --------- + + set(serverTarget ${productName}ServerPlugin) + + add_library(${serverTarget} SHARED) + + target_compile_definitions(${serverTarget} PUBLIC + PluginName="${productName}" + PluginVersionMajor=${CMAKE_PROJECT_VERSION_MAJOR} + PluginVersionMinor=${CMAKE_PROJECT_VERSION_MINOR} + PluginVersionPatch=${CMAKE_PROJECT_VERSION_PATCH} + Plugin4CC="${plugin4CC}" + ) + target_sources(${serverTarget} PRIVATE serverPlugin.cpp) + target_link_libraries(${serverTarget} ${synthLibProject} bridgeClient) + set_property(TARGET ${serverTarget} PROPERTY FOLDER ${targetName}) + + # build plugins to the "plugins" dir of the server binary output dir + get_target_property(serverOutputDir bridgeServer BINARY_DIR) + + if(NOT serverOutputDir) + get_target_property(serverOutputDir bridgeServer RUNTIME_OUTPUT_DIRECTORY) + endif() + + if(serverOutputDir) + set_property(TARGET ${serverTarget} PROPERTY RUNTIME_OUTPUT_DIRECTORY "${serverOutputDir}/plugins") + set_property(TARGET ${serverTarget} PROPERTY LIBRARY_OUTPUT_DIRECTORY "${serverOutputDir}/plugins") + + get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + + if(isMultiConfig) + set_property(TARGET ${serverTarget} PROPERTY RUNTIME_OUTPUT_DIRECTORY_DEBUG "${serverOutputDir}/Debug/plugins") + set_property(TARGET ${serverTarget} PROPERTY RUNTIME_OUTPUT_DIRECTORY_RELEASE "${serverOutputDir}/Release/plugins") + set_property(TARGET ${serverTarget} PROPERTY LIBRARY_OUTPUT_DIRECTORY_DEBUG "${serverOutputDir}/Debug/plugins") + set_property(TARGET ${serverTarget} PROPERTY LIBRARY_OUTPUT_DIRECTORY_RELEASE "${serverOutputDir}/Release/plugins") + endif() + endif() + + install(TARGETS ${serverTarget} + RUNTIME DESTINATION plugins/ COMPONENT DSPBridgeServer + LIBRARY DESTINATION plugins/ COMPONENT DSPBridgeServer) + + add_dependencies(ServerPlugins ${serverTarget}) endmacro() macro(createJucePluginWithFX targetName productName plugin4CCSynth plugin4CCFX binaryDataProject synthLibProject) diff --git a/source/jucePluginEditorLib/pluginEditorState.cpp b/source/jucePluginEditorLib/pluginEditorState.cpp @@ -11,9 +11,18 @@ #include "dsp56kEmu/logging.h" namespace jucePluginEditorLib + +{ +bridgeLib::PluginDesc getPluginDesc(const pluginLib::Processor& _p) { + bridgeLib::PluginDesc pd; + _p.getPluginDesc(pd); + return pd; +} + PluginEditorState::PluginEditorState(Processor& _processor, pluginLib::Controller& _controller, std::vector<Skin> _includedSkins) : m_processor(_processor), m_parameterBinding(_controller), m_includedSkins(std::move(_includedSkins)) + , m_remoteServerList(getPluginDesc(_processor)) { juce::File(getSkinFolder()).createDirectory(); @@ -302,10 +311,47 @@ void PluginEditorState::openMenu(const juce::MouseEvent* _event) latencyMenu.addItem("4", true, latency == 4, [this, adjustLatency] { adjustLatency(4); }); latencyMenu.addItem("8", true, latency == 8, [this, adjustLatency] { adjustLatency(8); }); + auto servers = m_remoteServerList.getEntries(); + + juce::PopupMenu deviceTypeMenu; + deviceTypeMenu.addItem("Local (default)", true, m_processor.getDeviceType() == pluginLib::DeviceType::Local, [this] { m_processor.setDeviceType(pluginLib::DeviceType::Local); }); + + if(servers.empty()) + { + deviceTypeMenu.addItem("- no servers found -", false, false, [this] {}); + } + else + { + for (const auto & server : servers) + { + if(server.err.code == bridgeLib::ErrorCode::Ok) + { + std::string name = server.host + ':' + std::to_string(server.serverInfo.portTcp); + + const auto isSelected = m_processor.getDeviceType() == pluginLib::DeviceType::Remote && + m_processor.getRemoteDeviceHost() == server.host && + m_processor.getRemoteDevicePort() == server.serverInfo.portTcp; + + deviceTypeMenu.addItem(name, true, isSelected, [this, server] + { + m_processor.setRemoteDevice(server.host, server.serverInfo.portTcp); + }); + } + else + { + std::string name = server.host + " (error " + std::to_string(static_cast<uint32_t>(server.err.code)) + ", " + server.err.msg + ')'; + deviceTypeMenu.addItem(name, false, false, [this] {}); + } + } + } + menu.addSubMenu("GUI Skin", skinMenu); menu.addSubMenu("GUI Scale", scaleMenu); menu.addSubMenu("Latency (blocks)", latencyMenu); + if (m_processor.getConfig().getBoolValue("supportDspBridge", false)) + menu.addSubMenu("Device Type", deviceTypeMenu); + menu.addSeparator(); auto& regions = m_processor.getController().getParameterDescriptions().getRegions(); diff --git a/source/jucePluginEditorLib/pluginEditorState.h b/source/jucePluginEditorLib/pluginEditorState.h @@ -5,6 +5,8 @@ #include <string> #include <vector> +#include "client/serverList.h" + #include "skin.h" #include "jucePluginLib/parameterbinding.h" @@ -84,5 +86,7 @@ namespace jucePluginEditorLib float m_rootScale = 1.0f; std::vector<Skin> m_includedSkins; std::vector<uint8_t> m_instanceConfig; + std::string m_skinFolderName; + bridgeClient::ServerList m_remoteServerList; }; } diff --git a/source/jucePluginLib/CMakeLists.txt b/source/jucePluginLib/CMakeLists.txt @@ -55,7 +55,7 @@ target_sources(jucePluginLib PRIVATE ${SOURCES} ${SOURCES_PATCHDB}) source_group("source" FILES ${SOURCES}) source_group("source\\patchdb" FILES ${SOURCES_PATCHDB}) -target_link_libraries(jucePluginLib PUBLIC juceUiLib synthLib) +target_link_libraries(jucePluginLib PUBLIC juceUiLib synthLib bridgeClient) target_include_directories(jucePluginLib PUBLIC ../JUCE/modules) target_compile_definitions(jucePluginLib PRIVATE JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1) set_property(TARGET jucePluginLib PROPERTY FOLDER "Gearmulator") diff --git a/source/jucePluginLib/dummydevice.cpp b/source/jucePluginLib/dummydevice.cpp @@ -2,6 +2,10 @@ namespace pluginLib { + DummyDevice::DummyDevice(const synthLib::DeviceCreateParams& _params) : Device(_params) + { + } + void DummyDevice::processAudio(const synthLib::TAudioInputs& _inputs, const synthLib::TAudioOutputs& _outputs, size_t _samples) { } diff --git a/source/jucePluginLib/dummydevice.h b/source/jucePluginLib/dummydevice.h @@ -7,6 +7,8 @@ namespace pluginLib class DummyDevice : public synthLib::Device { public: + explicit DummyDevice(const synthLib::DeviceCreateParams& _params); + float getSamplerate() const override { return 44100.0f; } bool isValid() const override { return false; } #if !SYNTHLIB_DEMO_MODE diff --git a/source/jucePluginLib/processor.cpp b/source/jucePluginLib/processor.cpp @@ -1,16 +1,20 @@ #include "processor.h" #include "dummydevice.h" +#include "pluginVersion.h" #include "tools.h" #include "types.h" #include "baseLib/binarystream.h" +#include "bridgeLib/commands.h" + +#include "client/remoteDevice.h" + #include "synthLib/deviceException.h" #include "synthLib/os.h" #include "synthLib/midiBufferParser.h" #include "dsp56kEmu/fastmath.h" - #include "dsp56kEmu/logging.h" #include "synthLib/romLoader.h" @@ -24,7 +28,16 @@ namespace pluginLib constexpr char g_saveMagic[] = "DSP56300"; constexpr uint32_t g_saveVersion = 2; - Processor::Processor(const BusesProperties& _busesProperties, Properties _properties) : juce::AudioProcessor(_busesProperties), m_properties(std::move(_properties)), m_midiPorts(*this) + bridgeLib::SessionId generateRemoteSessionId() + { + return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); + } + + Processor::Processor(const BusesProperties& _busesProperties, Properties _properties) + : juce::AudioProcessor(_busesProperties) + , m_properties(std::move(_properties)) + , m_midiPorts(*this) + , m_remoteSessionId(generateRemoteSessionId()) { juce::File(getPublicRomFolder()).createDirectory(); @@ -147,16 +160,50 @@ namespace pluginLib if(!m_device) { - m_device.reset(new DummyDevice()); + m_device.reset(new DummyDevice({})); } m_device->setDspClockPercent(m_dspClockPercent); - m_plugin.reset(new synthLib::Plugin(m_device.get())); + m_plugin.reset(new synthLib::Plugin(m_device.get(), [this](synthLib::Device* _device) + { + return onDeviceInvalid(_device); + })); return *m_plugin; } + bridgeClient::RemoteDevice* Processor::createRemoteDevice(const synthLib::DeviceCreateParams& _params) + { + bridgeLib::PluginDesc desc; + getPluginDesc(desc); + return new bridgeClient::RemoteDevice(_params, std::move(desc), m_remoteHost, m_remotePort); + } + + void Processor::getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const + { + _params.preferredSamplerate = getPreferredDeviceSamplerate(); + _params.hostSamplerate = getHostSamplerate(); + } + + bridgeClient::RemoteDevice* Processor::createRemoteDevice() + { + synthLib::DeviceCreateParams params; + getRemoteDeviceParams(params); + return createRemoteDevice(params); + } + + synthLib::Device* Processor::createDevice(const DeviceType _type) + { + switch (_type) + { + case DeviceType::Local: return createDevice(); + case DeviceType::Remote: return createRemoteDevice(); + case DeviceType::Dummy: return new DummyDevice({}); + } + return nullptr; + } + bool Processor::setLatencyBlocks(uint32_t _blocks) { if (!getPlugin().setLatencyBlocks(_blocks)) @@ -314,14 +361,18 @@ namespace pluginLib { if(!m_device) return {}; - return m_device->getSupportedSamplerates(); + std::vector<float> result; + m_device->getSupportedSamplerates(result); + return result; } std::vector<float> Processor::getDevicePreferredSamplerates() const { if(!m_device) return {}; - return m_device->getPreferredSamplerates(); + std::vector<float> result; + m_device->getPreferredSamplerates(result); + return result; } std::optional<std::pair<const char*, uint32_t>> Processor::findResource(const std::string& _filename) const @@ -374,6 +425,51 @@ namespace pluginLib return name; } + void Processor::getPluginDesc(bridgeLib::PluginDesc& _desc) const + { + _desc.plugin4CC = getProperties().plugin4CC; + _desc.pluginName = getProperties().name; + _desc.pluginVersion = Version::getVersionNumber(); + _desc.sessionId = m_remoteSessionId; + } + + void Processor::setDeviceType(const DeviceType _type, const bool _forceChange/* = false*/) + { + if(m_deviceType == _type && !_forceChange) + return; + + try + { + if(auto* dev = createDevice(_type)) + { + getPlugin().setDevice(dev); + (void)m_device.release(); + m_device.reset(dev); + m_deviceType = _type; + } + } + catch(synthLib::DeviceException& e) + { + juce::NativeMessageBox::showMessageBox(juce::MessageBoxIconType::WarningIcon, + getName() + " - Failed to switch device type", + std::string("Failed to create device:\n\n") + + e.what() + "\n\n"); + } + + if(_type != DeviceType::Remote) + m_remoteSessionId = generateRemoteSessionId(); + } + + void Processor::setRemoteDevice(const std::string& _host, const uint32_t _port) + { + if(m_remotePort == _port && m_remoteHost == _host && m_deviceType == DeviceType::Remote) + return; + + m_remoteHost = _host; + m_remotePort = _port; + setDeviceType(DeviceType::Remote, true); + } + void Processor::destroyController() { m_controller.reset(); @@ -752,6 +848,42 @@ namespace pluginLib return 0.0f; } + synthLib::Device* Processor::onDeviceInvalid(synthLib::Device* _device) + { + if(dynamic_cast<bridgeClient::RemoteDevice*>(_device)) + { + try + { + // attempt one reconnect + auto* newDevice = createRemoteDevice(); + if(newDevice && newDevice->isValid()) + { + m_device.reset(newDevice); + return newDevice; + } + } + catch (synthLib::DeviceException& e) + { + juce::MessageManager::callAsync([e] + { + juce::NativeMessageBox::showMessageBox(juce::MessageBoxIconType::WarningIcon, + "Device creation failed:", + std::string("The connection to the remote server has been lost and a reconnect failed. Processing mode has been switched to local processing\n\n") + + e.what() + "\n\n"); + }); + } + } + + setDeviceType(DeviceType::Local); + + juce::MessageManager::callAsync([this] + { + getController().onStateLoaded(); + }); + + return m_device.get(); + } + bool Processor::rebootDevice() { try diff --git a/source/jucePluginLib/processor.h b/source/jucePluginLib/processor.h @@ -7,8 +7,20 @@ #include "controller.h" #include "midiports.h" +#include "bridgeLib/types.h" + #include "synthLib/plugin.h" +namespace bridgeClient +{ + class RemoteDevice; +} + +namespace bridgeLib +{ + struct PluginDesc; +} + namespace baseLib { class BinaryStream; @@ -17,6 +29,7 @@ namespace baseLib namespace synthLib { + struct DeviceCreateParams; class Plugin; struct SMidiEvent; } @@ -42,6 +55,7 @@ namespace pluginLib const bool wantsMidiInput; const bool producesMidiOut; const bool isMidiEffect; + const std::string plugin4CC; const std::string lv2Uri; BinaryDataRef binaryData; }; @@ -59,6 +73,10 @@ namespace pluginLib synthLib::Plugin& getPlugin(); virtual synthLib::Device* createDevice() = 0; + virtual bridgeClient::RemoteDevice* createRemoteDevice(const synthLib::DeviceCreateParams& _params); + virtual void getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const; + virtual bridgeClient::RemoteDevice* createRemoteDevice(); + synthLib::Device* createDevice(DeviceType _type); bool hasController() const { @@ -129,6 +147,15 @@ namespace pluginLib std::string getConfigFile(bool _useFxFolder = false) const; std::string getProductName(bool _useFxName = false) const; + void getPluginDesc(bridgeLib::PluginDesc& _desc) const; + + void setDeviceType(DeviceType _type, bool _forceChange = false); + void setRemoteDevice(const std::string& _host, uint32_t _port); + const auto& getRemoteDeviceHost() const { return m_remoteHost; } + const auto& getRemoteDevicePort() const { return m_remotePort; } + + auto getDeviceType() const { return m_deviceType; } + protected: void destroyController(); @@ -169,6 +196,8 @@ namespace pluginLib synthLib::DeviceError getDeviceError() const { return m_deviceError; } + synthLib::Device* onDeviceInvalid(synthLib::Device* _device); + protected: synthLib::DeviceError m_deviceError = synthLib::DeviceError::None; std::unique_ptr<synthLib::Device> m_device; @@ -184,5 +213,9 @@ namespace pluginLib float m_hostSamplerate = 0.0f; MidiPorts m_midiPorts; BypassBuffer m_bypassBuffer; + DeviceType m_deviceType = DeviceType::Local; + std::string m_remoteHost; + uint32_t m_remotePort = 0; + bridgeLib::SessionId m_remoteSessionId; }; } diff --git a/source/jucePluginLib/processorPropertiesInit.h b/source/jucePluginLib/processorPropertiesInit.h @@ -14,6 +14,7 @@ namespace pluginLib JucePlugin_WantsMidiInput, JucePlugin_ProducesMidiOutput, JucePlugin_IsMidiEffect, + Plugin4CC, JucePlugin_Lv2Uri, { BinaryData::namedResourceListSize, diff --git a/source/jucePluginLib/types.h b/source/jucePluginLib/types.h @@ -14,4 +14,11 @@ namespace pluginLib }; using ParamValue = int32_t; + + enum class DeviceType + { + Local, + Remote, + Dummy + }; } diff --git a/source/mqJucePlugin/PluginProcessor.cpp b/source/mqJucePlugin/PluginProcessor.cpp @@ -8,6 +8,7 @@ #include "jucePluginLib/processorPropertiesInit.h" #include "mqLib/device.h" +#include "mqLib/romloader.h" namespace { @@ -52,7 +53,20 @@ namespace mqJucePlugin } synthLib::Device* AudioPluginAudioProcessor::createDevice() { - return new mqLib::Device(); + return new mqLib::Device({}); + } + + void AudioPluginAudioProcessor::getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const + { + Processor::getRemoteDeviceParams(_params); + + const auto rom = mqLib::RomLoader::findROM(); + + if(rom.isValid()) + { + _params.romData = rom.getData(); + _params.romName = rom.getFilename(); + } } pluginLib::Controller* AudioPluginAudioProcessor::createController() diff --git a/source/mqJucePlugin/PluginProcessor.h b/source/mqJucePlugin/PluginProcessor.h @@ -13,6 +13,8 @@ namespace mqJucePlugin jucePluginEditorLib::PluginEditorState* createEditorState() override; synthLib::Device* createDevice() override; + void getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const override; + pluginLib::Controller* createController() override; private: diff --git a/source/mqJucePlugin/serverPlugin.cpp b/source/mqJucePlugin/serverPlugin.cpp @@ -0,0 +1,9 @@ +// ReSharper disable once CppUnusedIncludeDirective +#include "client/plugin.h" + +#include "mqLib/device.h" + +synthLib::Device* createBridgeDevice(const synthLib::DeviceCreateParams& _params) +{ + return new mqLib::Device(_params); +} diff --git a/source/mqLib/device.cpp b/source/mqLib/device.cpp @@ -7,7 +7,11 @@ namespace mqLib { - Device::Device() : m_mq(BootMode::Default), m_state(m_mq), m_sysexRemote(m_mq) + Device::Device(const synthLib::DeviceCreateParams& _params) + : wLib::Device(_params) + , m_mq(BootMode::Default, _params.romData, _params.romName) + , m_state(m_mq) + , m_sysexRemote(m_mq) { // we need to hit the play button to resume boot if the used rom is an OS update. mQ will complain about an uninitialized ROM area in this case m_mq.setButton(Buttons::ButtonType::Play, true); diff --git a/source/mqLib/device.h b/source/mqLib/device.h @@ -16,7 +16,7 @@ namespace mqLib class Device : public wLib::Device { public: - Device(); + Device(const synthLib::DeviceCreateParams& _params); ~Device() override; uint32_t getInternalLatencyMidiToOutput() const override; uint32_t getInternalLatencyInputToOutput() const override; diff --git a/source/mqLib/microq.cpp b/source/mqLib/microq.cpp @@ -13,9 +13,20 @@ namespace mqLib { - MicroQ::MicroQ(BootMode _bootMode/* = BootMode::Default*/) + ROM initRom(const std::vector<uint8_t>& _romData, const std::string& _romName) { - const auto romFile = RomLoader::findROM(); + if(_romData.empty()) + return RomLoader::findROM(); + ROM rom(_romData, _romName); + if(rom.isValid()) + return rom; + return RomLoader::findROM(); + } + + MicroQ::MicroQ(BootMode _bootMode/* = BootMode::Default*/, const std::vector<uint8_t>& _romData, const std::string& _romName) + { + const ROM romFile = initRom(_romData, _romName); + if(!romFile.isValid()) throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "Failed to find device operating system file mq_2_23.mid."); diff --git a/source/mqLib/microq.h b/source/mqLib/microq.h @@ -23,7 +23,7 @@ namespace mqLib class MicroQ { public: - MicroQ(BootMode _bootMode = BootMode::Default); + MicroQ(BootMode _bootMode = BootMode::Default, const std::vector<uint8_t>& _romData = {}, const std::string& _romName = {}); ~MicroQ(); // returns true if the instance is valid, false if the initialization failed diff --git a/source/mqLib/mqmc.cpp b/source/mqLib/mqmc.cpp @@ -31,7 +31,7 @@ namespace mqLib if(!_rom.isValid()) return; m_romRuntimeData.resize(ROM::size()); - memcpy(m_romRuntimeData.data(), m_rom.getData(), ROM::size()); + memcpy(m_romRuntimeData.data(), m_rom.getData().data(), ROM::size()); m_flash.reset(new hwLib::Am29f(m_romRuntimeData.data(), m_romRuntimeData.size(), false, true)); diff --git a/source/mqLib/rom.cpp b/source/mqLib/rom.cpp @@ -6,10 +6,20 @@ namespace mqLib { ROM::ROM(const std::string& _filename) : wLib::ROM(_filename, g_romSize) { - if(getSize() < 5) + verifyRom(); + } + + ROM::ROM(const std::vector<uint8_t>& _data, const std::string& _name) : wLib::ROM(_name, g_romSize, _data) + { + verifyRom(); + } + + void ROM::verifyRom() + { + if(getData().size() < 5) return; - auto* d = reinterpret_cast<const char*>(getData()); + auto* d = reinterpret_cast<const char*>(getData().data()); // OS version is right at the start of the ROM, as zero-terminated ASCII if(strstr(d, "2.23") != d) diff --git a/source/mqLib/rom.h b/source/mqLib/rom.h @@ -11,9 +11,13 @@ namespace mqLib ROM() = default; explicit ROM(const std::string& _filename); + explicit ROM(const std::vector<uint8_t>& _data, const std::string& _name); static constexpr uint32_t size() { return g_romSize; } uint32_t getSize() const override { return g_romSize; } + + private: + void verifyRom(); }; } diff --git a/source/mqTestConsole/mqTestConsole.cpp b/source/mqTestConsole/mqTestConsole.cpp @@ -76,22 +76,16 @@ int main(int _argc, char* _argv[]) std::string devNameMidiOut; std::string devNameAudioOut; - try - { - baseLib::ConfigFile cfg((synthLib::getModulePath() + "config.cfg").c_str()); - for (const auto& v : cfg.getValues()) - { - if(v.first == "MidiIn") - devNameMidiIn = v.second; - else if(v.first == "MidiOut") - devNameMidiOut = v.second; - else if(v.first == "AudioOut") - devNameAudioOut = v.second; - } - } - catch(const std::runtime_error&) + baseLib::ConfigFile cfg((synthLib::getModulePath() + "config.cfg").c_str()); + + for (const auto& v : cfg.getArgsWithValues()) { - // no config file available + if(v.first == "MidiIn") + devNameMidiIn = v.second; + else if(v.first == "MidiOut") + devNameMidiOut = v.second; + else if(v.first == "AudioOut") + devNameAudioOut = v.second; } bool settingsChanged = false; diff --git a/source/networkLib/CMakeLists.txt b/source/networkLib/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.10) + +project(networkLib) + +add_library(networkLib STATIC) + +set(SOURCES + exception.cpp exception.h + logging.cpp logging.h + networkThread.cpp + networkThread.h + stream.cpp + stream.h + tcpClient.cpp + tcpClient.h + tcpConnection.cpp + tcpConnection.h + tcpServer.cpp + tcpServer.h + tcpStream.cpp + tcpStream.h + udpClient.cpp + udpClient.h + udpServer.cpp + udpServer.h +) + +target_sources(networkLib PRIVATE ${SOURCES}) + +source_group("source" FILES ${SOURCES}) + +target_link_libraries(networkLib PUBLIC ptypes) +if(MSVC) + target_link_libraries(networkLib PUBLIC ws2_32) +endif() + +target_include_directories(networkLib PUBLIC ../) +set_property(TARGET networkLib PROPERTY FOLDER "Networking") diff --git a/source/networkLib/exception.cpp b/source/networkLib/exception.cpp @@ -0,0 +1,5 @@ +#include "exception.h" + +namespace networkLib +{ +} diff --git a/source/networkLib/exception.h b/source/networkLib/exception.h @@ -0,0 +1,26 @@ +#pragma once + +#include <stdexcept> +#include <string> + +namespace networkLib +{ + enum ExceptionType + { + ConnectionClosed, + ConnectionLost + }; + + class NetException : public std::runtime_error + { + public: + NetException(const ExceptionType _type, const std::string& _err) : std::runtime_error(_err), m_type(_type) + { + } + + auto type() const { return m_type; } + + private: + ExceptionType m_type; + }; +} diff --git a/source/networkLib/logging.cpp b/source/networkLib/logging.cpp @@ -0,0 +1,39 @@ +#include "logging.h" + +#include <iostream> + +namespace networkLib +{ + constexpr const char* g_logLevelNames[] = + { + "Debug", + "INFO", + "WARNING", + "ERROR" + }; + + static_assert(std::size(g_logLevelNames) == static_cast<uint32_t>(LogLevel::Count)); + + void logStdOut(LogLevel _level, const char* _func, int _line, const std::string& _message) + { + std::cout << g_logLevelNames[static_cast<uint32_t>(_level)] << ": " << _func << '@' << _line << ": " << _message << '\n'; + } + + namespace + { + LogFunc g_logFunc = logStdOut; + } + + void setLogFunc(const LogFunc& _func) + { + if(!_func) + g_logFunc = logStdOut; + else + g_logFunc = _func; + } + + void log(LogLevel _level, const char* _func, int _line, const std::string& _message) + { + g_logFunc(_level, _func, _line, _message); + } +} diff --git a/source/networkLib/logging.h b/source/networkLib/logging.h @@ -0,0 +1,37 @@ +#pragma once + +#include <functional> +#include <string> +#include <sstream> +#include <iomanip> + +namespace networkLib +{ + enum class LogLevel + { + Debug, + Info, + Warning, + Error, + + Count + }; + + using LogFunc = std::function<void(LogLevel, const char*, int, const std::string&)>; + + void setLogFunc(const LogFunc& _func); + + void log(LogLevel _level, const char* _func, int _line, const std::string& _message); +} + +#define LOGNET(L, S) \ +do \ +{ \ + std::stringstream __ss___bridgeLib_logging_h; \ + __ss___bridgeLib_logging_h << S; \ + \ + networkLib::log(L, __func__, __LINE__, __ss___bridgeLib_logging_h.str()); \ +} \ +while(false) + +#define HEXN(S, n) std::hex << std::setfill('0') << std::setw(n) << (uint32_t)S diff --git a/source/networkLib/networkThread.cpp b/source/networkLib/networkThread.cpp @@ -0,0 +1,40 @@ +#include "networkThread.h" + +namespace networkLib +{ + NetworkThread::~NetworkThread() + { + stop(); + } + + void NetworkThread::threadFunc() + { + while (!m_exit) + threadLoopFunc(); + } + + void NetworkThread::exit(bool _exit) + { + m_exit = _exit; + } + + void NetworkThread::start() + { + if(m_thread) + return; + m_exit = false; + m_thread.reset(new std::thread([this]() + { + threadFunc(); + })); + } + + void NetworkThread::stop() + { + if(!m_thread) + return; + m_exit = true; + m_thread->join(); + m_thread.reset(); + } +} diff --git a/source/networkLib/networkThread.h b/source/networkLib/networkThread.h @@ -0,0 +1,27 @@ +#pragma once + +#include <memory> +#include <thread> + +namespace networkLib +{ + class NetworkThread + { + protected: + NetworkThread() = default; + ~NetworkThread(); + + virtual void threadLoopFunc() {} + virtual void threadFunc(); + + bool exit() const { return m_exit; } + void exit(bool _exit); + + void start(); + void stop(); + + private: + std::unique_ptr<std::thread> m_thread; + bool m_exit = false; + }; +} diff --git a/source/networkLib/stream.cpp b/source/networkLib/stream.cpp diff --git a/source/networkLib/stream.h b/source/networkLib/stream.h @@ -0,0 +1,15 @@ +#pragma once + +namespace networkLib +{ + class Stream + { + public: + virtual void close() = 0; + virtual bool isValid() const = 0; + virtual bool flush() = 0; + + virtual bool read(void* _buf, uint32_t _byteSize) = 0; + virtual bool write(const void* _buf, uint32_t _byteSize) = 0; + }; +} diff --git a/source/networkLib/tcpClient.cpp b/source/networkLib/tcpClient.cpp @@ -0,0 +1,41 @@ +#include "tcpClient.h" + +#include <utility> + +#include "logging.h" +#include "../ptypes/pinet.h" + +namespace networkLib +{ + TcpClient::TcpClient(std::string _host, const uint32_t _port, OnConnectedFunc _onConnected) + : TcpConnection(std::move(_onConnected)) + , m_host(std::move(_host)) + , m_port(_port) + { + start(); + } + + void TcpClient::threadFunc() + { + auto* stream = new ptypes::ipstream(m_host.c_str(), static_cast<int>(m_port)); + + while(!exit()) + { + try + { + stream->open(); + + if (stream->get_active()) + { + onConnected(stream); + break; + } + } + catch (ptypes::exception* e) + { + LOGNET(LogLevel::Warning, "Network Error: " << static_cast<const char*>(e->get_message())); + delete e; + } + } + } +} diff --git a/source/networkLib/tcpClient.h b/source/networkLib/tcpClient.h @@ -0,0 +1,19 @@ +#pragma once + +#include "tcpConnection.h" + +#include <string> + +namespace networkLib +{ + class TcpClient : TcpConnection + { + public: + TcpClient(std::string _host, uint32_t _port, OnConnectedFunc _onConnected); + protected: + void threadFunc() override; + private: + const std::string m_host; + const uint32_t m_port; + }; +} diff --git a/source/networkLib/tcpConnection.cpp b/source/networkLib/tcpConnection.cpp @@ -0,0 +1,32 @@ +#include "tcpConnection.h" + +#include <utility> // std::move + +#include "logging.h" +#include "tcpStream.h" + +#include "../ptypes/pinet.h" + +#ifndef _WIN32 +# include <netinet/tcp.h> // TCP_NODELAY +#endif + +namespace networkLib +{ + TcpConnection::TcpConnection(OnConnectedFunc _onConnected) : m_onConnected(std::move(_onConnected)) + { + } + + void TcpConnection::onConnected(ptypes::ipstream* _stream) const + { + constexpr int opt = 1; + const int res = ::setsockopt(_stream->get_handle(), IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char*>(&opt), sizeof(opt)); + if (res < 0) + { + const int err = errno; + LOGNET(LogLevel::Error, static_cast<const char*>(ptypes::iptostring(_stream->get_myip())) << ": Failed to set socket option TCP_NODELAY, err " << err << ": " << strerror(err)); + } + + m_onConnected(std::make_unique<TcpStream>(_stream)); + } +} diff --git a/source/networkLib/tcpConnection.h b/source/networkLib/tcpConnection.h @@ -0,0 +1,30 @@ +#pragma once + +#include "networkThread.h" + +#include <functional> +#include <memory> + +namespace ptypes +{ + class ipstream; +} + +namespace networkLib +{ + class TcpStream; + + class TcpConnection : protected NetworkThread + { + public: + using OnConnectedFunc = std::function<void(std::unique_ptr<TcpStream>)>; + + protected: + explicit TcpConnection(OnConnectedFunc _onConnected); + virtual ~TcpConnection() = default; + void onConnected(ptypes::ipstream* _stream) const; + + private: + OnConnectedFunc m_onConnected; + }; +} diff --git a/source/networkLib/tcpServer.cpp b/source/networkLib/tcpServer.cpp @@ -0,0 +1,51 @@ +#include "tcpServer.h" + +#include "logging.h" + +#include "../ptypes/pinet.h" + +namespace ptypes +{ + class exception; +} + +namespace networkLib +{ + TcpServer::TcpServer(OnConnectedFunc _onConnected, const int _tcpPort) : TcpConnection(std::move(_onConnected)), m_port(_tcpPort) + { + start(); + } + + void TcpServer::threadFunc() + { + ptypes::ipstmserver tcpListener; + + LOGNET(LogLevel::Info, "Waiting for incoming connections on TCP port " << m_port); + + tcpListener.bindall(m_port); + + ptypes::ipstream* stream = new ptypes::ipstream(); + + while (!exit()) + { + try + { + if (tcpListener.serve(*stream, -1, 1000)) + { + LOGNET(LogLevel::Info, "Client " << static_cast<const char*>(ptypes::iptostring(stream->get_ip())) << ":" << stream->get_port() << " connected"); + onConnected(stream); + stream = new ptypes::ipstream(); + } + } + catch (ptypes::exception* e) + { + LOGNET(LogLevel::Warning, "Network Error: " << static_cast<const char*>(e->get_message())); + delete e; + } + } + + LOGNET(LogLevel::Info, "TCP server shutdown"); + delete stream; + stream = nullptr; + } +} diff --git a/source/networkLib/tcpServer.h b/source/networkLib/tcpServer.h @@ -0,0 +1,17 @@ +#pragma once + +#include "tcpConnection.h" + +namespace networkLib +{ + class TcpServer : TcpConnection + { + public: + TcpServer(OnConnectedFunc _onConnected, int _tcpPort); + protected: + void threadFunc() override; + + private: + const int m_port; + }; +} diff --git a/source/networkLib/tcpStream.cpp b/source/networkLib/tcpStream.cpp @@ -0,0 +1,81 @@ +#include "tcpStream.h" + +#include <cstdint> + +#include "exception.h" +#include "ptypes/pinet.h" + +namespace networkLib +{ + TcpStream::TcpStream(ptypes::ipstream* _stream) : m_stream(_stream) + { + } + + TcpStream::~TcpStream() + { + TcpStream::close(); + delete m_stream; + m_stream = nullptr; + } + + void TcpStream::close() + { + if(!m_stream) + return; + m_stream->close(); + } + + bool TcpStream::isValid() const + { + return m_stream && m_stream->get_active(); + } + + bool TcpStream::flush() + { + if (!isValid()) + return false; + + m_stream->flush(); + return true; + } + + bool TcpStream::read(void* _buf, const uint32_t _byteSize) + { + if (!isValid()) + throw NetException(ConnectionClosed, "Couldn't read"); + + try + { + const auto numRead = static_cast<uint32_t>(m_stream->read(_buf, static_cast<int>(_byteSize))); + if(numRead == _byteSize) + return true; + throw NetException(ConnectionClosed, "Couldn't read"); + } + catch(ptypes::exception* e) // NOLINT(misc-throw-by-value-catch-by-reference) + { + const std::string msg(e->get_message()); + delete e; + throw NetException(ConnectionLost, msg); + } + } + + bool TcpStream::write(const void* _buf, const uint32_t _byteSize) + { + if(!isValid()) + throw NetException(ConnectionClosed, "Couldn't write"); + + try + { + const auto numWritten = static_cast<uint32_t>(m_stream->write(_buf, static_cast<int>(_byteSize))); + if(_byteSize == numWritten) + return true; + throw NetException(ConnectionClosed, "Couldn't write"); + } + catch(ptypes::exception* e) // NOLINT(misc-throw-by-value-catch-by-reference) + { + const std::string msg(e->get_message()); + delete e; + throw NetException(ConnectionLost, msg); + } + } +} diff --git a/source/networkLib/tcpStream.h b/source/networkLib/tcpStream.h @@ -0,0 +1,32 @@ +#pragma once + +#include <cstdint> + +#include "stream.h" + +namespace ptypes +{ + class ipstream; +} + +namespace networkLib +{ + class TcpStream : public Stream + { + public: + explicit TcpStream(ptypes::ipstream* _stream); + virtual ~TcpStream(); + + void close() override; + bool isValid() const override; + bool flush() override; + + auto* getPtypesStream() const { return m_stream; } + + private: + bool read(void* _buf, uint32_t _byteSize) override; + bool write(const void* _buf, uint32_t _byteSize) override; + + ptypes::ipstream* m_stream; + }; +} diff --git a/source/networkLib/udpClient.cpp b/source/networkLib/udpClient.cpp @@ -0,0 +1,129 @@ +#include "udpClient.h" + +#include "logging.h" + +#include "../ptypes/pinet.h" + +#ifdef _WIN32 +#define NOMINMAX +#include <Windows.h> +#include <iphlpapi.h> +#include <ws2tcpip.h> +#pragma comment(lib, "Iphlpapi.lib") +#else +#include <ifaddrs.h> +#endif + +namespace networkLib +{ + UdpClient::UdpClient(const int _udpPort) : m_port(_udpPort) + { + getBroadcastAddresses(m_broadcastAddresses); + if(m_broadcastAddresses.empty()) + m_broadcastAddresses.push_back(0xff'ff'ff'ff); + } + + void UdpClient::getBroadcastAddresses(std::vector<uint32_t>& _addresses) + { +#ifdef _WIN32 + ULONG bufferSize = 0; + if (GetAdaptersInfo(nullptr, &bufferSize) != ERROR_BUFFER_OVERFLOW) + return; + + std::vector<BYTE> buffer; + buffer.resize(bufferSize, 0); + + if (GetAdaptersInfo(reinterpret_cast<IP_ADAPTER_INFO*>(buffer.data()), &bufferSize) != ERROR_SUCCESS) + return; + + const IP_ADAPTER_INFO* adapterInfo = reinterpret_cast<IP_ADAPTER_INFO*>(buffer.data()); + + for(; adapterInfo != nullptr; adapterInfo = adapterInfo->Next) + { + IN_ADDR addrIp; + IN_ADDR addrMask; + + inet_pton(AF_INET, adapterInfo->IpAddressList.IpAddress.String, &addrIp); + inet_pton(AF_INET, adapterInfo->IpAddressList.IpMask.String, &addrMask); + + const auto broadcastAddr = addrIp.S_un.S_addr | ~addrMask.S_un.S_addr; + + if(!broadcastAddr) + continue; + + _addresses.push_back(broadcastAddr); + } +#else + ifaddrs* ifap = nullptr; + if (getifaddrs(&ifap) == 0 && ifap) + { + struct ifaddrs * p = ifap; + while(p) + { + auto toUint32 = [](sockaddr* a) -> uint32_t + { + if(!a || a->sa_family != AF_INET) + return 0; + return ((struct sockaddr_in *)a)->sin_addr.s_addr; + }; + + const auto ifaAddr = toUint32(p->ifa_addr); + const auto maskAddr = toUint32(p->ifa_netmask); + + if (ifaAddr > 0) + { + const auto mask = ifaAddr | ~maskAddr; + if(mask) + _addresses.push_back(mask); + } + p = p->ifa_next; + } + freeifaddrs(ifap); + } +#endif + } + + void UdpClient::threadLoopFunc() + { + try + { + const auto& req = getRequestPacket(); + + for (const auto broadcastAddress : m_broadcastAddresses) + { + ptypes::ipmessage msg(ptypes::ipaddress((ptypes::ulong)broadcastAddress), m_port); + msg.send(reinterpret_cast<const char*>(req.data()), static_cast<int>(req.size())); + std::vector<uint8_t> buffer; + buffer.resize(getMaxUdpResponseSize()); + + // wait for answer + while (msg.waitfor(250)) + { + ptypes::ipaddress addr; + + const auto count = msg.receive(reinterpret_cast<char*>(buffer.data()), static_cast<int>(buffer.size()), addr); + + if(!count) + continue; + + buffer.resize(count); + + const auto host = std::string(ptypes::iptostring(addr)); + + if(validateResponse(host, buffer)) + { + exit(true); + break; + } + } + + if(exit()) + break; + } + } + catch(ptypes::exception* e) // NOLINT(misc-throw-by-value-catch-by-reference) + { + LOGNET(LogLevel::Warning, "Network error: " << static_cast<const char*>(e->get_message())); + } + } +} diff --git a/source/networkLib/udpClient.h b/source/networkLib/udpClient.h @@ -0,0 +1,30 @@ +#pragma once + +#include "networkThread.h" + +#include <string> +#include <cstdint> +#include <vector> + +namespace networkLib +{ + class UdpClient : protected NetworkThread + { + public: + UdpClient(int _udpPort); + virtual ~UdpClient() = default; + + static void getBroadcastAddresses(std::vector<uint32_t>& _addresses); + + protected: + void threadLoopFunc() override; + + virtual const std::vector<uint8_t>& getRequestPacket() = 0; + virtual uint32_t getMaxUdpResponseSize() { return 60000; } + virtual bool validateResponse(const std::string& _host, const std::vector<uint8_t>& _message) = 0; + + private: + const int m_port; + std::vector<uint32_t> m_broadcastAddresses; + }; +} diff --git a/source/networkLib/udpServer.cpp b/source/networkLib/udpServer.cpp @@ -0,0 +1,54 @@ +#include "udpServer.h" + +#include <iosfwd> +#include <thread> +#include <vector> + +#include "logging.h" + +#include "ptypes/ptypes.h" +#include "ptypes/pinet.h" + +namespace ptypes +{ + class exception; +} + +namespace networkLib +{ + void UdpServer::threadFunc() + { + ptypes::ipmsgserver udpListener; + udpListener.bindall(m_port); + + LOGNET(LogLevel::Info, "UDP server started on port " << m_port); + + while (!exit()) + { + std::vector<uint8_t> buffer; + buffer.resize(getMaxRequestSize()); + + try + { + // check if any broadcast is there and answer + if (udpListener.poll(-1, 1000)) + { + const auto count = udpListener.receive(reinterpret_cast<char*>(buffer.data()), static_cast<int>(buffer.size())); + if(!count) + continue; + buffer.resize(count); + const auto response = validateRequest(buffer); + if (!response.empty()) + udpListener.send(reinterpret_cast<const char*>(response.data()), static_cast<int>(response.size())); + } + } + catch (ptypes::exception* e) + { + LOGNET(LogLevel::Warning, "Network exception: " << static_cast<const char*>(e->get_message())); + delete e; + } + } + + LOGNET(LogLevel::Info, "UDP server terminated"); + } +} diff --git a/source/networkLib/udpServer.h b/source/networkLib/udpServer.h @@ -0,0 +1,25 @@ +#pragma once + +#include <vector> + +#include "networkThread.h" + +namespace networkLib +{ + class UdpServer : NetworkThread + { + public: + UdpServer(const int _udpPort) : m_port(_udpPort) + { + start(); + } + protected: + void threadFunc() override; + + virtual uint32_t getMaxRequestSize() const { return 60000; } + virtual std::vector<uint8_t> validateRequest(const std::vector<uint8_t>& _request) = 0; + + private: + const int m_port; + }; +} diff --git a/source/nord/n2x/n2xJucePlugin/n2xPluginProcessor.cpp b/source/nord/n2x/n2xJucePlugin/n2xPluginProcessor.cpp @@ -8,6 +8,7 @@ #include "jucePluginLib/processorPropertiesInit.h" #include "n2xLib/n2xdevice.h" +#include "n2xLib/n2xromloader.h" #include "synthLib/deviceException.h" @@ -51,12 +52,25 @@ namespace n2xJucePlugin synthLib::Device* AudioPluginAudioProcessor::createDevice() { - auto* d = new n2x::Device(); + auto* d = new n2x::Device({}); if(!d->isValid()) throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "A firmware rom (512k .bin) is required, but was not found."); return d; } + void AudioPluginAudioProcessor::getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const + { + Processor::getRemoteDeviceParams(_params); + + auto rom = n2x::RomLoader::findROM(); + + if(rom.isValid()) + { + _params.romData.assign(rom.data().begin(), rom.data().end()); + _params.romName = rom.getFilename(); + } + } + pluginLib::Controller* AudioPluginAudioProcessor::createController() { return new n2xJucePlugin::Controller(*this); diff --git a/source/nord/n2x/n2xJucePlugin/n2xPluginProcessor.h b/source/nord/n2x/n2xJucePlugin/n2xPluginProcessor.h @@ -12,6 +12,8 @@ namespace n2xJucePlugin jucePluginEditorLib::PluginEditorState* createEditorState() override; synthLib::Device* createDevice() override; + void getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const override; + pluginLib::Controller* createController() override; private: diff --git a/source/nord/n2x/n2xJucePlugin/serverPlugin.cpp b/source/nord/n2x/n2xJucePlugin/serverPlugin.cpp @@ -0,0 +1,9 @@ +// ReSharper disable once CppUnusedIncludeDirective +#include "client/plugin.h" + +#include "n2xLib/n2xdevice.h" + +synthLib::Device* createBridgeDevice(const synthLib::DeviceCreateParams& _params) +{ + return new n2x::Device(_params); +} diff --git a/source/nord/n2x/n2xLib/n2xdevice.cpp b/source/nord/n2x/n2xLib/n2xdevice.cpp @@ -5,15 +5,13 @@ namespace n2x { - Device::Device() : m_state(&m_hardware, &getMidiTranslator()) + Device::Device(const synthLib::DeviceCreateParams& _params) + : synthLib::Device(_params) + , m_hardware(_params.romData, _params.romName) + , m_state(&m_hardware, &getMidiTranslator()) { } - const std::string& Device::getRomFilename() const - { - return m_hardware.getRomFilename(); - } - float Device::getSamplerate() const { return g_samplerate; diff --git a/source/nord/n2x/n2xLib/n2xdevice.h b/source/nord/n2x/n2xLib/n2xdevice.h @@ -12,9 +12,7 @@ namespace n2x class Device : public synthLib::Device { public: - Device(); - - const std::string& getRomFilename() const; + Device(const synthLib::DeviceCreateParams& _params); float getSamplerate() const override; bool isValid() const override; diff --git a/source/nord/n2x/n2xLib/n2xhardware.cpp b/source/nord/n2x/n2xLib/n2xhardware.cpp @@ -12,8 +12,18 @@ namespace n2x static_assert((g_syncEsaiFrameRate & (g_syncEsaiFrameRate - 1)) == 0, "esai frame sync rate must be power of two"); static_assert(g_syncHaltDspEsaiThreshold >= g_syncEsaiFrameRate * 2, "esai DSP halt threshold must be greater than two times the sync rate"); - Hardware::Hardware() - : m_rom(RomLoader::findROM()) + Rom initRom(const std::vector<uint8_t>& _romData, const std::string& _romName) + { + if(_romData.empty()) + return RomLoader::findROM(); + Rom rom(_romData, _romName); + if(rom.isValid()) + return rom; + return RomLoader::findROM(); + } + + Hardware::Hardware(const std::vector<uint8_t>& _romData, const std::string& _romName) + : m_rom(initRom(_romData, _romName)) , m_uc(*this, m_rom) , m_dspA(*this, m_uc.getHdi08A(), 0) , m_dspB(*this, m_uc.getHdi08B(), 1) diff --git a/source/nord/n2x/n2xLib/n2xhardware.h b/source/nord/n2x/n2xLib/n2xhardware.h @@ -13,7 +13,7 @@ namespace n2x { public: using AudioOutputs = std::array<std::vector<dsp56k::TWord>, 4>; - Hardware(); + Hardware(const std::vector<uint8_t>& _romData = {}, const std::string& _romName = {}); ~Hardware(); bool isValid() const; diff --git a/source/nord/n2x/n2xLib/n2xrom.cpp b/source/nord/n2x/n2xLib/n2xrom.cpp @@ -6,7 +6,7 @@ namespace n2x { - Rom::Rom() : RomData() + Rom::Rom() { if(!isValidRom(data())) invalidate(); @@ -18,6 +18,12 @@ namespace n2x invalidate(); } + Rom::Rom(const std::vector<uint8_t>& _data, const std::string& _filename) : RomData(_data, _filename) + { + if(!isValidRom(data())) + invalidate(); + } + bool Rom::isValidRom(const std::vector<uint8_t>& _data) { constexpr uint8_t key[] = {'n', 'r', '2', 0, 'n', 'L', '2', 0}; diff --git a/source/nord/n2x/n2xLib/n2xrom.h b/source/nord/n2x/n2xLib/n2xrom.h @@ -10,6 +10,7 @@ namespace n2x public: Rom(); Rom(const std::string& _filename); + Rom(const std::vector<uint8_t>& _data, const std::string& _filename); static bool isValidRom(const std::vector<uint8_t>& _data); }; diff --git a/source/nord/n2x/n2xLib/n2xromdata.cpp b/source/nord/n2x/n2xLib/n2xromdata.cpp @@ -21,6 +21,14 @@ namespace n2x m_filename = _filename; } + template <uint32_t Size> RomData<Size>::RomData(const std::vector<uint8_t>& _data, const std::string& _filename) + { + if(_data.size() != MySize) + return; + m_data = _data; + m_filename = _filename; + } + template <uint32_t Size> void RomData<Size>::saveAs(const std::string& _filename) const { synthLib::writeFile(_filename, m_data); diff --git a/source/nord/n2x/n2xLib/n2xromdata.h b/source/nord/n2x/n2xLib/n2xromdata.h @@ -13,6 +13,7 @@ namespace n2x static constexpr uint32_t MySize = Size; RomData(); RomData(const std::string& _filename); + RomData(const std::vector<uint8_t>& _data, const std::string& _filename); bool isValid() const { return !m_filename.empty(); } const auto& data() const { return m_data; } diff --git a/source/osTIrusJucePlugin/serverPlugin.cpp b/source/osTIrusJucePlugin/serverPlugin.cpp @@ -0,0 +1,9 @@ +// ReSharper disable once CppUnusedIncludeDirective +#include "client/plugin.h" + +#include "virusLib/device.h" + +synthLib::Device* createBridgeDevice(const synthLib::DeviceCreateParams& _params) +{ + return new virusLib::Device(_params); +} diff --git a/source/osirusJucePlugin/serverPlugin.cpp b/source/osirusJucePlugin/serverPlugin.cpp @@ -0,0 +1,9 @@ +// ReSharper disable once CppUnusedIncludeDirective +#include "client/plugin.h" + +#include "virusLib/device.h" + +synthLib::Device* createBridgeDevice(const synthLib::DeviceCreateParams& _params) +{ + return new virusLib::Device(_params); +} diff --git a/source/ptypes/CMakeLists.txt b/source/ptypes/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.10) + +project(ptypes) + +set(SOURCES + pmd5.cxx + pvariant.cxx + pipbase.cxx + piobase.cxx + ptime.cxx + patomic.cxx + pputf.cxx + pinstm.cxx + pnpipe.cxx + pstrmanip.cxx + pstring.cxx + pipstm.cxx + pmsgq.cxx + ppodlist.cxx + pipmsg.cxx + pnpserver.cxx + pstrlist.cxx + pstrconv.cxx + poutstm.cxx + pipsvbase.cxx + prwlock.cxx + pthread.cxx + pipmsgsv.cxx + pstrtoi.cxx + pinfilter.cxx + pcset.cxx + pobjlist.cxx + punit.cxx + pstdio.cxx + pfdxstm.cxx + ptimedsem.cxx + poutfile.cxx + pipstmsv.cxx + pcomponent.cxx + pinfile.cxx + pstrcase.cxx + poutmem.cxx + ptrigger.cxx + pcsetdbg.cxx + pinmem.cxx + ptextmap.cxx + pmem.cxx + poutfilter.cxx + pstrutils.cxx + pfatal.cxx + psemaphore.cxx + pintee.cxx + pasync.cxx + ppipe.cxx + pmtxtable.cxx + pexcept.cxx + punknown.cxx + pversion.cxx +) + +add_library(ptypes STATIC) + +target_sources(ptypes PRIVATE ${SOURCES}) + +target_include_directories(ptypes PUBLIC ../) +set_property(TARGET ptypes PROPERTY FOLDER "Networking") diff --git a/source/ptypes/pasync.cxx b/source/ptypes/pasync.cxx @@ -0,0 +1,61 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <stdlib.h> +# include <unistd.h> +# include <pthread.h> +# ifdef __sun__ +# include <poll.h> +# endif +#endif + +#include "pasync.h" + + +namespace ptypes { + + +void ptdecl psleep(uint milliseconds) +{ +#if defined(WIN32) + Sleep(milliseconds); +#elif defined(__sun__) + poll(0, 0, milliseconds); +#else + usleep(milliseconds * 1000); +#endif +} + + +pthread_id_t ptdecl pthrself() +{ +#ifdef WIN32 + return (int)GetCurrentThreadId(); +#else + return pthread_self(); +#endif +} + + +bool ptdecl pthrequal(pthread_id_t id) +{ +#ifdef WIN32 + return GetCurrentThreadId() == (uint)id; +#else + return pthread_equal(pthread_self(), id); +#endif +} + + +} diff --git a/source/ptypes/pasync.h b/source/ptypes/pasync.h @@ -0,0 +1,542 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifndef __PASYNC_H__ +#define __PASYNC_H__ + +#ifdef WIN32 +# define _WINSOCKAPI_ // prevent inclusion of winsock.h, since we need winsock2.h +# include <windows.h> +#else +# include <pthread.h> +# ifndef __bsdi__ +# include <semaphore.h> +# endif +#endif + +#ifndef __PPORT_H__ +#include "pport.h" +#endif + +#ifndef __PTYPES_H__ +#include "ptypes.h" +#endif + + +namespace ptypes { + +// +// Summary of implementation: +// +// atomic increment/decrement/exchange +// MSVC/BCC/i386: internal, asm +// GCC/i386: internal, asm +// GCC/PowerPC: internal, asm +// GCC/SPARC: internal, asm +// Other: internal, mutex hash table +// +// mutex +// Win32: Critical section +// Other: POSIX mutex +// +// trigger +// Win32: Event +// Other: internal, POSIX condvar/mutex +// +// rwlock: +// Win32: internal, Event/mutex +// MacOS: internal, POSIX condvar/mutex +// Other: POSIX rwlock +// +// semaphore: +// Win32: = timedsem +// MacOS: = timedsem +// Other: POSIX semaphore +// +// timedsem (with timed waiting): +// Win32: Semaphore +// Other: internal, POSIX mutex/condvar +// + + +#ifdef _MSC_VER +#pragma pack(push, 4) +#endif + + +#ifdef WIN32 + typedef int pthread_id_t; + typedef HANDLE pthread_t; +#else + typedef pthread_t pthread_id_t; +#endif + + +ptpublic void ptdecl psleep(uint milliseconds); +ptpublic bool ptdecl pthrequal(pthread_id_t id); // note: this is NOT the thread handle, use thread::get_id() +ptpublic pthread_id_t ptdecl pthrself(); // ... same + + +// -------------------------------------------------------------------- // +// --- mutex ---------------------------------------------------------- // +// -------------------------------------------------------------------- // + + +#ifdef WIN32 + +struct ptpublic mutex: public noncopyable +{ +protected: + CRITICAL_SECTION critsec; +public: + mutex() { InitializeCriticalSection(&critsec); } + ~mutex() { DeleteCriticalSection(&critsec); } + void enter() { EnterCriticalSection(&critsec); } + void leave() { LeaveCriticalSection(&critsec); } + void lock() { enter(); } + void unlock() { leave(); } +}; + + +#else + + +struct ptpublic mutex: public noncopyable +{ +protected: + pthread_mutex_t mtx; +public: + mutex() { pthread_mutex_init(&mtx, 0); } + ~mutex() { pthread_mutex_destroy(&mtx); } + void enter() { pthread_mutex_lock(&mtx); } + void leave() { pthread_mutex_unlock(&mtx); } + void lock() { enter(); } + void unlock() { leave(); } +}; + +#endif + + +// +// scopelock +// + +class scopelock: public noncopyable +{ +protected: + mutex* mtx; +public: + scopelock(mutex& imtx): mtx(&imtx) { mtx->lock(); } + ~scopelock() { mtx->unlock(); } +}; + + +// +// mutex table for hashed memory locking (undocumented) +// + +#define _MUTEX_HASH_SIZE 29 // a prime number for hashing + +#ifdef WIN32 +# define pmemlock mutex +# define pmementer(m) (m)->lock() +# define pmemleave(m) (m)->unlock() +#else +# define _MTX_INIT PTHREAD_MUTEX_INITIALIZER +# define pmemlock pthread_mutex_t +# define pmementer pthread_mutex_lock +# define pmemleave pthread_mutex_unlock +#endif + + +ptpublic extern pmemlock _mtxtable[_MUTEX_HASH_SIZE]; + +#define pgetmemlock(addr) (_mtxtable + pintptr(addr) % _MUTEX_HASH_SIZE) + + +// -------------------------------------------------------------------- // +// --- trigger -------------------------------------------------------- // +// -------------------------------------------------------------------- // + + +#ifdef WIN32 + +class ptpublic trigger: public noncopyable +{ +protected: + HANDLE handle; // Event object +public: + trigger(bool autoreset, bool state); + ~trigger() { CloseHandle(handle); } + void wait() { WaitForSingleObject(handle, INFINITE); } + void post() { SetEvent(handle); } + void signal() { post(); } + void reset() { ResetEvent(handle); } +}; + + +#else + + +class ptpublic trigger: public noncopyable +{ +protected: + pthread_mutex_t mtx; + pthread_cond_t cond; + int state; + bool autoreset; +public: + trigger(bool autoreset, bool state); + ~trigger(); + void wait(); + void post(); + void signal() { post(); } + void reset(); +}; + +#endif + + +// -------------------------------------------------------------------- // +// --- rwlock --------------------------------------------------------- // +// -------------------------------------------------------------------- // + + +#if defined(WIN32) || defined(__DARWIN__) || defined(__bsdi__) +# define __PTYPES_RWLOCK__ +#elif defined(linux) + // on Linux rwlocks are included only with -D_GNU_SOURCE. + // programs that don't use rwlocks, do not need to define + // _GNU_SOURCE either. +# if defined(_GNU_SOURCE) || defined(__USE_UNIX98) +# define __POSIX_RWLOCK__ +# endif +#else +# define __POSIX_RWLOCK__ +#endif + + +#ifdef __PTYPES_RWLOCK__ + +struct ptpublic rwlock: protected mutex +{ +protected: +#ifdef WIN32 + HANDLE reading; // Event object + HANDLE finished; // Event object + int readcnt; + int writecnt; +#else + pthread_mutex_t mtx; + pthread_cond_t readcond; + pthread_cond_t writecond; + int locks; + int writers; + int readers; +#endif +public: + rwlock(); + ~rwlock(); + void rdlock(); + void wrlock(); + void unlock(); + void lock() { wrlock(); } +}; + + +#elif defined(__POSIX_RWLOCK__) + + +struct ptpublic rwlock: public noncopyable +{ +protected: + pthread_rwlock_t rw; +public: + rwlock(); + ~rwlock() { pthread_rwlock_destroy(&rw); } + void rdlock() { pthread_rwlock_rdlock(&rw); } + void wrlock() { pthread_rwlock_wrlock(&rw); } + void unlock() { pthread_rwlock_unlock(&rw); } + void lock() { wrlock(); } +}; + +#endif + + +#if defined(__PTYPES_RWLOCK__) || defined(__POSIX_RWLOCK__) + +// +// scoperead & scopewrite +// + +class scoperead: public noncopyable +{ +protected: + rwlock* rw; +public: + scoperead(rwlock& irw): rw(&irw) { rw->rdlock(); } + ~scoperead() { rw->unlock(); } +}; + + +class scopewrite: public noncopyable +{ +protected: + rwlock* rw; +public: + scopewrite(rwlock& irw): rw(&irw) { rw->wrlock(); } + ~scopewrite() { rw->unlock(); } +}; + + +#endif + + +// -------------------------------------------------------------------- // +// --- semaphore ------------------------------------------------------ // +// -------------------------------------------------------------------- // + + +#if defined(WIN32) || defined(__DARWIN__) || defined(__bsdi__) +# define __SEM_TO_TIMEDSEM__ +#endif + + +#ifdef __SEM_TO_TIMEDSEM__ + +// map ordinary semaphore to timed semaphore + +class timedsem; +typedef timedsem semaphore; + + +#else + + +class ptpublic semaphore: public unknown +{ +protected: + sem_t handle; +public: + semaphore(int initvalue); + virtual ~semaphore(); + + void wait(); + void post(); + void signal() { post(); } + + void decrement() { return wait(); } + void increment() { post(); } +}; + +#endif + + +class ptpublic timedsem: public unknown +{ +protected: +#ifdef WIN32 + HANDLE handle; +#else + int count; + pthread_mutex_t mtx; + pthread_cond_t cond; +#endif +public: + timedsem(int initvalue); + virtual ~timedsem(); + bool wait(int msecs = -1); + void post(); + void signal() { post(); } + + bool decrement(int msec = -1) { return wait(msec); } + void increment() { post(); } +}; + + +// -------------------------------------------------------------------- // +// --- thread --------------------------------------------------------- // +// -------------------------------------------------------------------- // + + +class ptpublic thread: public unknown +{ +protected: +#ifdef WIN32 + unsigned id; +#endif + pthread_t handle; + int autofree; + int running; + int signaled; + int finished; + int freed; + int reserved; // for priorities + timedsem relaxsem; + + virtual void execute() = 0; + virtual void cleanup(); + + bool relax(int msecs) { return relaxsem.wait(msecs); } + + friend void _threadepilog(thread* thr); + +#ifdef WIN32 + friend unsigned __stdcall _threadproc(void* arg); +#else + friend void* _threadproc(void* arg); +#endif + +public: + thread(bool iautofree); + virtual ~thread(); + +#ifdef WIN32 + pthread_id_t get_id() { return int(id); } +#else + pthread_id_t get_id() { return handle; } +#endif + + bool get_running() { return running != 0; } + bool get_finished() { return finished != 0; } + bool get_signaled() { return signaled != 0; } + + void start(); + void signal(); + void waitfor(); +}; + + + +// -------------------------------------------------------------------- // +// --- jobqueue & msgqueue -------------------------------------------- // +// -------------------------------------------------------------------- // + + +const int MSG_USER = 0; +const int MSG_QUIT = -1; + +const int DEF_QUEUE_LIMIT = 5000; + +class ptpublic message: public unknown +{ +protected: + message* next; // next in the message chain, used internally + semaphore* sync; // used internally by msgqueue::send(), when called from a different thread + friend class jobqueue; // my friends, job queue and message queue... + friend class msgqueue; +public: + int id; + pintptr param; + pintptr result; + message(int iid, pintptr iparam = 0); + virtual ~message(); +}; + + +class ptpublic jobqueue: public noncopyable +{ +private: + int limit; // queue limit + message* head; // queue head + message* tail; // queue tail + int qcount; // number of items in the queue + timedsem sem; // queue semaphore + timedsem ovrsem; // overflow semaphore + mutex qlock; // critical sections in enqueue and dequeue + +protected: + bool enqueue(message* msg, int timeout = -1); + bool push(message* msg, int timeout = -1); + message* dequeue(bool safe = true, int timeout = -1); + void purgequeue(); + +public: + jobqueue(int ilimit = DEF_QUEUE_LIMIT); + virtual ~jobqueue(); + + int get_count() const { return qcount; } + int get_limit() const { return limit; } + + bool post(message* msg, int timeout = -1); + bool post(int id, pintptr param = 0, int timeout = -1); + bool posturgent(message* msg, int timeout = -1); + bool posturgent(int id, pintptr param = 0, int timeout = -1); + message* getmessage(int timeout = -1); + +#ifdef PTYPES19_COMPAT + int msgsavail() const { return get_count(); } +#endif +}; + + +template <class T> class tjobqueue: protected jobqueue +{ +public: + tjobqueue(int ilimit = DEF_QUEUE_LIMIT); + + int get_count() const { return jobqueue::get_count(); } + int get_limit() const { return jobqueue::get_limit(); } + bool post(T* msg, int timeout = -1) { return jobqueue::post(msg, timeout); } + bool posturgent(T* msg, int timeout = -1) { return jobqueue::posturgent(msg, timeout); } + T* getmessage(int timeout = -1) { return (T*)jobqueue::getmessage(timeout); } +}; + + +class ptpublic msgqueue: protected jobqueue +{ +private: + mutex thrlock; // lock for the queue processing + pthread_id_t owner; // thread ID of the queue processing thread + + pintptr finishmsg(message* msg); + void handlemsg(message* msg); + void takeownership(); + +protected: + bool quit; + + void defhandler(message& msg); + virtual void msghandler(message& msg) = 0; + +public: + msgqueue(int ilimit = DEF_QUEUE_LIMIT); + virtual ~msgqueue(); + + // functions calling from the owner thread: + void processone(); // process one message, may hang if no msgs in the queue + void processmsgs(); // process all available messages and return + void run(); // process messages until MSG_QUIT + + // functions calling from any thread: + int get_count() const { return jobqueue::get_count(); } + int get_limit() const { return jobqueue::get_limit(); } + bool post(message* msg, int timeout = -1) { return jobqueue::post(msg, timeout); } + bool post(int id, pintptr param = 0, int timeout = -1) { return jobqueue::post(id, param, timeout); } + bool posturgent(message* msg, int timeout = -1) { return jobqueue::posturgent(msg, timeout); } + bool posturgent(int id, pintptr param = 0, int timeout = -1) { return jobqueue::posturgent(id, param, timeout); } + pintptr send(message* msg); + pintptr send(int id, pintptr param = 0); + +#ifdef PTYPES19_COMPAT + int msgsavail() const { return get_count(); } +#endif +}; + + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + + +} + +#endif // __PASYNC_H__ diff --git a/source/ptypes/patomic.cxx b/source/ptypes/patomic.cxx @@ -0,0 +1,377 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#endif + +#include "ptypes.h" +#include "pasync.h" // for pmemlock* + +#include <assert.h> + +namespace ptypes { + + +#ifdef PTYPES_ST +// single-threaded version + + +int __PFASTCALL pexchange(int* target, int value) +{ + int r = *target; + *target = value; + return r; +} + + +void* __PFASTCALL pexchange(void** target, void* value) +{ + void* r = *target; + *target = value; + return r; +} + + +int __PFASTCALL pincrement(int* target) +{ + return ++(*target); +} + + +int __PFASTCALL pdecrement(int* target) +{ + return --(*target); +} + + +#else +// multi-threaded version + +#if defined(__GNUC__) && (defined(__i386__) || defined(__I386__)) +# define GCC_i386 +#elif defined(__GNUC__) && defined(__ppc__) +# define GCC_PPC +#elif defined(_MSC_VER) && defined(_M_IX86) +# define MSC_i386 +#elif defined(_MSC_VER) && (defined(_M_IA64) || defined(_WIN64)) +# define MSC_x64 +#elif defined(__BORLANDC__) && defined(_M_IX86) +# define BCC_i386 +#elif defined(__GNUC__) && defined(__sparc__) && !defined(__arch64__) +# define GCC_sparc +#endif + + +#if defined(MSC_i386) || defined(BCC_i386) + +// +// atomic operations for Microsoft C or Borland C on Windows +// + +#if defined(_MSC_VER) +# pragma warning (disable: 4035) +#elif defined(__BORLANDC__) +# pragma warn -rvl +#endif + +// !!! NOTE +// the following functions implement atomic exchange/inc/dec on +// windows. they are dangerous in that they rely on the calling +// conventions of MSVC and BCC. the first one passes the first +// two arguments in ECX and EDX, and the second one - in EAX and +// EDX. + +int __PFASTCALL pincrement(int* v) +{ + return InterlockedIncrement((LONG*)v); +} + + +int __PFASTCALL pdecrement(int* v) +{ + return InterlockedDecrement((LONG*)v); +} + + +int __PFASTCALL pexchange(int* a, int b) +{ + return InterlockedExchange((LONG*)a,b); +} + + +void* __PFASTCALL pexchange(void** a, void* b) +{ +#ifdef _WIN64 + return InterlockedExchange64( a,b); +#else + return (void*)InterlockedExchange((LONG*)a,(LONG)b); +#endif +} + + +#elif defined(GCC_i386) + +// +// GNU C compiler on any i386 platform (actually 486+ for xadd) +// + +int pexchange(int* target, int value) +{ + __asm__ __volatile ("lock ; xchgl (%1),%0" : "+r" (value) : "r" (target)); + return value; +} + + +void* pexchange(void** target, void* value) +{ + __asm__ __volatile ("lock ; xchgl (%1),%0" : "+r" (value) : "r" (target)); + return value; +} + + +int pincrement(int* target) +{ + int temp = 1; + __asm__ __volatile ("lock ; xaddl %0,(%1)" : "+r" (temp) : "r" (target)); + return temp + 1; +} + + +int pdecrement(int* target) +{ + int temp = -1; + __asm__ __volatile ("lock ; xaddl %0,(%1)" : "+r" (temp) : "r" (target)); + return temp - 1; +} + + +#elif defined(GCC_PPC) + +// +// GNU C compiler on any PPC platform +// + +int pexchange(int* target, int value) +{ + int temp; + __asm__ __volatile ( +"1: lwarx %0,0,%1\n\ + stwcx. %2,0,%1\n\ + bne- 1b\n\ + isync" + : "=&r" (temp) + : "r" (target), "r" (value) + : "cc", "memory" + ); + return temp; +} + + +void* pexchange(void** target, void* value) +{ + void* temp; + __asm__ __volatile ( +"1: lwarx %0,0,%1\n\ + stwcx. %2,0,%1\n\ + bne- 1b\n\ + isync" + : "=&r" (temp) + : "r" (target), "r" (value) + : "cc", "memory" + ); + return temp; +} + + +int pincrement(int* target) +{ + int temp; + __asm__ __volatile ( +"1: lwarx %0,0,%1\n\ + addic %0,%0,1\n\ + stwcx. %0,0,%1\n\ + bne- 1b\n\ + isync" + : "=&r" (temp) + : "r" (target) + : "cc", "memory" + ); + return temp; +} + + +int pdecrement(int* target) +{ + int temp; + __asm__ __volatile ( +"1: lwarx %0,0,%1\n\ + addic %0,%0,-1\n\ + stwcx. %0,0,%1\n\ + bne- 1b\n\ + isync" + : "=&r" (temp) + : "r" (target) + : "cc", "memory" + ); + return temp; +} + + +#elif defined GCC_sparc + +// +// GNU C compiler on SPARC in 32-bit mode (pointers are 32-bit) +// + +// assembly routines defined in patomic.sparc.s +// we currently don't use CAS in the library, but let it be there +extern "C" { + int __patomic_add(volatile int* __mem, int __val); + int __patomic_swap(volatile int* __mem, int __val); + int __patomic_cas(volatile int* __mem, int __expected, int __newval); +} + +#define __patomic_swap_p(mem,val) \ + (void*)(__patomic_swap((int*)(mem), (int)(val))) + + +int pexchange(int* target, int value) +{ + return __patomic_swap(target, value); +} + + +void* pexchange(void** target, void* value) +{ + return __patomic_swap_p(target, value); +} + + +int pincrement(int* target) +{ + return __patomic_add(target, 1); +} + + +int pdecrement(int* target) +{ + return __patomic_add(target, -1); +} + +#elif defined __EMSCRIPTEN__ +#include <emscripten/threading.h> +int pexchange(int* target, int value) +{ + return emscripten_atomic_exchange_u32(target, value); +} + + +void* pexchange(void** target, void* value) +{ + return (void*)emscripten_atomic_exchange_u32(*target, (uint32_t)value); +} + + +int pincrement(int* target) +{ + return emscripten_atomic_add_u32(target, 1); +} + + +int pdecrement(int* target) +{ + return emscripten_atomic_add_u32(target, -1); +} + +#elif defined MSC_x64 + +int pexchange(int* target, int value) +{ + assert( sizeof(target) == sizeof(volatile LONG*) ); + + return InterlockedExchange((volatile LONG*)target,value); +} + + +void* pexchange(void** target, void* value) +{ + return InterlockedExchangePointer(target,value); +} + +int pincrement(int* target) +{ + assert( sizeof(target) == sizeof(volatile LONG*) ); + return InterlockedIncrement((volatile LONG*)target); +} + + +int pdecrement(int* target) +{ + assert( sizeof(target) == sizeof(volatile LONG*) ); + return InterlockedDecrement((volatile LONG*)target); +} + + +#else + +// +// other platforms: mutex locking +// + +int pexchange(int* target, int value) +{ + pmemlock* m = pgetmemlock(target); + pmementer(m); + int r = *target; + *target = value; + pmemleave(m); + return r; +} + + +void* pexchange(void** target, void* value) +{ + pmemlock* m = pgetmemlock(target); + pmementer(m); + void* r = *target; + *target = value; + pmemleave(m); + return r; +} + +int pincrement(int* target) +{ + assert( NULL != _mtxtable ); + pmemlock* m = pgetmemlock(target); + pmementer(m); + int r = ++(*target); + pmemleave(m); + return r; +} + + +int pdecrement(int* target) +{ + pmemlock* m = pgetmemlock(target); + pmementer(m); + int r = --(*target); + pmemleave(m); + return r; +} + +#endif + + +#endif + + +} diff --git a/source/ptypes/pcomponent.cxx b/source/ptypes/pcomponent.cxx @@ -0,0 +1,102 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +component::component() + : unknown(), refcount(0), freelist(nil), typeinfo(nil) {} + + +component::~component() +{ + if (freelist != nil) + { + for (int i = 0; i < freelist->get_count(); i++) + (*freelist)[i]->freenotify(this); + delete freelist; + freelist = nil; + } +} + + +void component::freenotify(component*) +{ +} + + +void component::addnotification(component* obj) +{ + if (freelist == nil) + freelist = new tobjlist<component>(false); + freelist->add(obj); +} + + +void component::delnotification(component* obj) +{ + int i = -1; + if (freelist != nil) + { + i = freelist->indexof(obj); + if (i >= 0) { + freelist->del(i); + if (freelist->get_count() == 0) + { + delete freelist; + freelist = nil; + } + } + } + if (i == -1) + fatal(CRIT_FIRST + 1, "delnotification() failed: no such object"); +} + + +int component::classid() +{ + return CLASS_UNDEFINED; +} + + +component* ptdecl addref(component* c) +{ + if (c != nil) +#ifdef PTYPES_ST + c->refcount++; +#else + pincrement(&c->refcount); +#endif + return c; +} + + +bool ptdecl release(component* c) +{ + if (c != nil) + { +#ifdef PTYPES_ST + if (--c->refcount == 0) +#else + if (pdecrement(&c->refcount) == 0) +#endif + delete c; + else + return false; + } + return true; +} + + +} diff --git a/source/ptypes/pcset.cxx b/source/ptypes/pcset.cxx @@ -0,0 +1,139 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +typedef int* pint; + + +static uchar lbitmask[8] = {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80}; +static uchar rbitmask[8] = {0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; + + +void cset::include(char min, char max) +{ + if (uchar(min) > uchar(max)) + return; + int lidx = uchar(min) / 8; + int ridx = uchar(max) / 8; + uchar lbits = lbitmask[uchar(min) % 8]; + uchar rbits = rbitmask[uchar(max) % 8]; + + if (lidx == ridx) + { + data[lidx] |= lbits & rbits; + } + else + { + data[lidx] |= lbits; + for (int i = lidx + 1; i < ridx; i++) + data[i] = -1; + data[ridx] |= rbits; + } + +} + + +char hex4(char c) +{ + if (c >= 'a') + return uchar(c - 'a' + 10); + else if (c >= 'A') + return uchar(c - 'A' + 10); + else + return char(c - '0'); +} + + +static uchar parsechar(const char*& p) +{ + uchar ret = *p; + if (ret == _csetesc) { + p++; + ret = *p; + if ((ret >= '0' && ret <= '9') || (ret >= 'a' && ret <= 'f') || (ret >= 'A' && ret <= 'F')) { + ret = hex4(ret); + p++; + if (*p != 0) + ret = uchar((ret << 4) | hex4(*p)); + } + } + return ret; +} + + +void cset::assign(const char* p) +{ + if (*p == '*' && *(p + 1) == 0) + fill(); + else + { + clear(); + for (; *p != 0; p++) { + uchar left = parsechar(p); + if (*(p + 1) == '-') { + p += 2; + uchar right = parsechar(p); + include(left, right); + } + else + include(left); + } + } +} + + +void cset::unite(const cset& s) +{ + for(int i = 0; i < _csetwords; i++) + *(pint(data) + i) |= *(pint(s.data) + i); +} + + +void cset::subtract(const cset& s) +{ + for(int i = 0; i < _csetwords; i++) + *(pint(data) + i) &= ~(*(pint(s.data) + i)); +} + + +void cset::intersect(const cset& s) +{ + for(int i = 0; i < _csetwords; i++) + *(pint(data) + i) &= *(pint(s.data) + i); +} + + +void cset::invert() +{ + for(int i = 0; i < _csetwords; i++) + *(pint(data) + i) = ~(*(pint(data) + i)); +} + + +bool cset::le(const cset& s) const +{ + for (int i = 0; i < _csetwords; i++) + { + int w1 = *(pint(data) + i); + int w2 = *(pint(s.data) + i); + if ((w2 | w1) != w2) + return false; + } + return true; +} + + +} diff --git a/source/ptypes/pcsetdbg.cxx b/source/ptypes/pcsetdbg.cxx @@ -0,0 +1,78 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +static char hexchar(uchar c) +{ + if (c < 10) + return char(c + '0'); + else + return char(c - 10 + 'a'); +} + + +inline bool isprintable(uchar c) +{ + return ((c >= ' ') && (c < 127)); +} + + +static string showmember(uchar c) +{ + if ((c == '-') || (c == '~')) + return string('~') + string(c); + else if (isprintable(c)) + return c; + else + { + string ret = "~ "; + ret[1] = hexchar(uchar(c >> 4)); + ret[2] = hexchar(uchar(c & 0x0f)); + return ret; + } +} + + +string ptdecl asstring(const cset& s) +{ + string ret; + int l = -1, r = -1; + for(int i = 0; i <= _csetbits; i++) + { + if (i < _csetbits && uchar(i) & s) + { + if (l == -1) + l = i; + else + r = i; + } + else if (l != -1) + { + concat(ret, showmember(uchar(l))); + if (r != -1) { + if (r > l + 1) + concat(ret, '-'); + concat(ret, showmember(uchar(r))); + } + l = -1; + r = -1; + } + } + return ret; +} + + +} diff --git a/source/ptypes/pexcept.cxx b/source/ptypes/pexcept.cxx @@ -0,0 +1,35 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +exception::exception(const char* imsg) + : message(imsg) +{ +} + + +exception::exception(const string& imsg) + : message(imsg) +{ +} + + +exception::~exception() +{ +} + + +} diff --git a/source/ptypes/pfatal.cxx b/source/ptypes/pfatal.cxx @@ -0,0 +1,63 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "pport.h" + +#if defined(WIN32) && !defined(NO_CRIT_MSGBOX) +# include <windows.h> +# define CRIT_MSGBOX +#endif + + +namespace ptypes { + + +static void ptdecl defhandler(int code, const char* msg) +{ +#ifdef CRIT_MSGBOX + char buf[2048]; + _snprintf(buf, sizeof(buf) - 1, "Fatal [%05x]: %s", code, msg); + MessageBoxA(0, buf, "Internal error", MB_OK | MB_ICONSTOP); +#else + fprintf(stderr, "\nInternal [%04x]: %s\n", code, msg); +#endif +} + +static _pcrithandler crith = defhandler; + + +_pcrithandler ptdecl getcrithandler() +{ + return crith; +} + + +_pcrithandler ptdecl setcrithandler(_pcrithandler newh) +{ + _pcrithandler ret = crith; + crith = newh; + return ret; +} + + +void ptdecl fatal(int code, const char* msg) +{ + if (crith != nil) + (*crith)(code, msg); + exit(code); +} + + +} diff --git a/source/ptypes/pfdxstm.cxx b/source/ptypes/pfdxstm.cxx @@ -0,0 +1,162 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <unistd.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +#ifdef _MSC_VER +// disable "'this' : used in base member initializer list" warning +# pragma warning (disable: 4355) +#endif + + +fdxoutstm::fdxoutstm(int ibufsize, fdxstm* iin) + : outstm(false, ibufsize), in(iin) {} + + +fdxoutstm::~fdxoutstm() {} + + +void fdxoutstm::chstat(int newstat) +{ + outstm::chstat(newstat); + if (newstat == IO_WRITING) + in->chstat(newstat); +} + + +int fdxoutstm::uerrno() +{ + return in->uerrno(); +} + + +const char* fdxoutstm::uerrmsg(int code) +{ + return in->uerrmsg(code); +} + + +string fdxoutstm::get_streamname() +{ + return in->get_streamname(); +} + + +void fdxoutstm::doopen() +{ +} + + +void fdxoutstm::doclose() +{ + if (in->active) + in->close(); +} + + +int fdxoutstm::dorawwrite(const char* buf, int count) +{ + return in->dorawwrite(buf, count); +} + + +fdxstm::fdxstm(int ibufsize) + : instm(ibufsize), out(ibufsize, this) +{ + out.in = this; + addref(&out); +} + + +fdxstm::~fdxstm() {} + + +int fdxstm::classid() +{ + return CLASS2_FDX; +} + + +void fdxstm::flush() +{ + if (out.active) + out.flush(); +} + + +int fdxstm::dorawwrite(const char* buf, int count) +{ + if (handle == invhandle) + return -1; +#ifdef WIN32 + unsigned long ret; + if (!WriteFile(HANDLE(handle), buf, count, &ret, nil)) + { + error(uerrno(), "Couldn't write"); + ret = uint(-1); + } +#else + int ret; + if ((ret = ::write(handle, buf, count)) < 0) + error(uerrno(), "Couldn't write"); +#endif + return ret; +} + + +void fdxstm::set_bufsize(int newval) +{ + instm::set_bufsize(newval); + out.set_bufsize(newval); +} + + +void fdxstm::open() +{ + instm::open(); + out.open(); +} + + +void fdxstm::close() +{ + instm::close(); + out.close(); +} + + +void fdxstm::cancel() +{ + instm::cancel(); + out.cancel(); +} + + +large fdxstm::tellx(bool forin) +{ + if (forin) + return instm::tellx(); + else + return out.tellx(); +} + + +} diff --git a/source/ptypes/pinet.h b/source/ptypes/pinet.h @@ -0,0 +1,400 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifndef __PINET_H__ +#define __PINET_H__ + +#ifndef __PPORT_H__ +#include "pport.h" +#endif + +#ifndef __PTYPES_H__ +#include "ptypes.h" +#endif + +#ifndef __PSTREAMS_H__ +#include "pstreams.h" +#endif + + +#ifdef WIN32 +# include <winsock2.h> +#else +# include <netdb.h> // for socklen_t +# include <sys/types.h> +# include <sys/socket.h> +#endif + + +namespace ptypes { + + +#ifdef _MSC_VER +#pragma pack(push, 4) +#endif + + +// +// BSD-compatible socket error codes for Win32 +// + +#if defined(WSAENOTSOCK) && !defined(ENOTSOCK) + +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EINPROGRESS WSAEINPROGRESS +#define EALREADY WSAEALREADY +#define ENOTSOCK WSAENOTSOCK +#define EDESTADDRREQ WSAEDESTADDRREQ +#define EMSGSIZE WSAEMSGSIZE +#define EPROTOTYPE WSAEPROTOTYPE +#define ENOPROTOOPT WSAENOPROTOOPT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#define EOPNOTSUPP WSAEOPNOTSUPP +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define EADDRINUSE WSAEADDRINUSE +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#define ENETDOWN WSAENETDOWN +#define ENETUNREACH WSAENETUNREACH +#define ENETRESET WSAENETRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNRESET WSAECONNRESET +#define ENOBUFS WSAENOBUFS +#define EISCONN WSAEISCONN +#define ENOTCONN WSAENOTCONN +#define ESHUTDOWN WSAESHUTDOWN +#define ETOOMANYREFS WSAETOOMANYREFS +#define ETIMEDOUT WSAETIMEDOUT +#define ECONNREFUSED WSAECONNREFUSED +#define ELOOP WSAELOOP +// #define ENAMETOOLONG WSAENAMETOOLONG +#define EHOSTDOWN WSAEHOSTDOWN +#define EHOSTUNREACH WSAEHOSTUNREACH +// #define ENOTEMPTY WSAENOTEMPTY +#define EPROCLIM WSAEPROCLIM +#define EUSERS WSAEUSERS +#define EDQUOT WSAEDQUOT +#define ESTALE WSAESTALE +#define EREMOTE WSAEREMOTE + +// NOTE: these are not errno constants in UNIX! +#define HOST_NOT_FOUND WSAHOST_NOT_FOUND +#define TRY_AGAIN WSATRY_AGAIN +#define NO_RECOVERY WSANO_RECOVERY +#define NO_DATA WSANO_DATA + +#endif + + +// shutdown() constants + +#if defined(SD_RECEIVE) && !defined(SHUT_RD) +# define SHUT_RD SD_RECEIVE +# define SHUT_WR SD_SEND +# define SHUT_RDWR SD_BOTH +#endif + + +// max backlog value for listen() + +#ifndef SOMAXCONN +# define SOMAXCONN -1 +#endif + +typedef char* sockval_t; + +#ifndef WIN32 +# define closesocket close +#endif + + +#if (defined(__DARWIN__) && !defined(_SOCKLEN_T)) || defined(WIN32) || defined(__hpux) + typedef int psocklen; +#else + typedef socklen_t psocklen; +#endif + + +// -------------------------------------------------------------------- // +// --- IP address class and DNS utilities ---------------------------- // +// -------------------------------------------------------------------- // + +// +// IP address +// + +struct ptpublic ipaddress +{ +public: + union + { + uchar data[4]; + ulong ldata; + }; + ipaddress() {} + ipaddress(ulong a) { ldata = a; } + ipaddress(const ipaddress& a) { ldata = a.ldata; } + ipaddress(int a, int b, int c, int d); + ipaddress& operator= (ulong a) { ldata = a; return *this; } + ipaddress& operator= (const ipaddress& a) { ldata = a.ldata; return *this; } + uchar& operator [] (int i) { return data[i]; } + operator ulong() const { return ldata; } +}; + + +ptpublic extern ipaddress ipnone; +ptpublic extern ipaddress ipany; +ptpublic extern ipaddress ipbcast; + + +// +// IP peer info: host name, IP and the port name +// used internally in ipstream and ipmessage +// + + +class ptpublic ippeerinfo: public noncopyable +{ +protected: + ipaddress ip; // target IP + string host; // target host name; either IP or hostname must be specified + int port; // target port number + + void notfound(); // throws a (estream*) exception + + ptpublic friend bool ptdecl psockname(int, ippeerinfo&); + +public: + ippeerinfo(); + ippeerinfo(ipaddress iip, const string& ihost, int iport); + + ipaddress get_ip(); // resolves the host name if necessary (only once) + string get_host(); // performs reverse-lookup if necessary (only once) + int get_port() { return port; } + void clear(); + string asstring(bool showport) const; +}; + + +ptpublic string ptdecl iptostring(ipaddress ip); +ptpublic ipaddress ptdecl phostbyname(const char* name); +ptpublic string ptdecl phostbyaddr(ipaddress ip); +ptpublic string ptdecl phostcname(const char* name); + +// internal utilities +ptpublic int ptdecl usockerrno(); +ptpublic const char* ptdecl usockerrmsg(int code); +ptpublic bool ptdecl psockwait(int handle, int timeout); +ptpublic bool ptdecl psockname(int handle, ippeerinfo&); + + +// -------------------------------------------------------------------- // +// --- TCP socket classes -------------------------------------------- // +// -------------------------------------------------------------------- // + + +// additional IO status codes + +const int IO_RESOLVING = 10; +const int IO_RESOLVED = 11; +const int IO_CONNECTING = 20; +const int IO_CONNECTED = 21; + + +// +// ipstream +// + +class ptpublic ipstream: public fdxstm, public ippeerinfo +{ + friend class ipstmserver; + +protected: + int svsocket; // server socket descriptor, used internally by ipstmserver + +#ifdef WIN32 + // sockets are not compatible with file handles on Windows + virtual int dorawread(char* buf, int count); + virtual int dorawwrite(const char* buf, int count); +#endif + + virtual int uerrno(); + virtual const char* uerrmsg(int code); + virtual void doopen(); + virtual large doseek(large newpos, ioseekmode mode); + virtual void doclose(); + virtual void sockopt(int socket); + void closehandle(); + +public: + ipstream(); + ipstream(ipaddress ip, int port); + ipstream(const char* host, int port); + ipstream(const string& host, int port); + virtual ~ipstream(); + virtual int classid(); + + virtual string get_streamname(); + + bool waitfor(int timeout); + ipaddress get_myip(); + int get_myport(); + void set_ip(ipaddress); + void set_host(const string&); + void set_host(const char*); + void set_port(int); +}; + + +// +// common internal interfaces for ipstmserver and ipmsgserver +// + +class ipbindinfo: public unknown, public ippeerinfo +{ +public: + int handle; + + ipbindinfo(ipaddress iip, const string& ihost, int iport); + virtual ~ipbindinfo(); +}; + + +class ptpublic ipsvbase: public unknown +{ +protected: + int socktype; + bool active; + tobjlist<ipbindinfo> addrlist; // list of local socket addresses to bind to + + void error(ippeerinfo& peer, int code, const char* defmsg); + bool dopoll(int* i, int timeout); + void setupfds(void* set, int i); + virtual void open(); + virtual void close(); + virtual void dobind(ipbindinfo*) = 0; + virtual void sockopt(int socket); + +public: + ipsvbase(int isocktype); + virtual ~ipsvbase(); + + int bind(ipaddress ip, int port); + int bindall(int port); + + int get_addrcount() { return addrlist.get_count(); } + const ipbindinfo& get_addr(int i) { return *addrlist[i]; } + void clear(); +}; + + +// +// ipstmserver +// + +class ptpublic ipstmserver: public ipsvbase +{ +protected: + virtual void dobind(ipbindinfo*); + +public: + ipstmserver(); + virtual ~ipstmserver(); + + bool poll(int i = -1, int timeout = 0); + bool serve(ipstream& client, int i = -1, int timeout = -1); +}; + + +// -------------------------------------------------------------------- // +// --- UDP socket classes -------------------------------------------- // +// -------------------------------------------------------------------- // + + +// +// ipmessage +// + +class ptpublic ipmessage: public unknown, public ippeerinfo +{ +protected: + int handle; + + void error(int code, const char* msg); + void open(); + void close(); + virtual void sockopt(int socket); + +public: + ipmessage(); + ipmessage(ipaddress ip, int port); + ipmessage(const char* host, int port); + ipmessage(const string& host, int port); + virtual ~ipmessage(); + + void set_ip(ipaddress iip); + void set_host(const string&); + void set_host(const char*); + void set_port(int); + ipaddress get_myip(); + int get_myport(); + int get_handle() { return handle; } + + bool waitfor(int timeout); + int receive(char* buf, int count, ipaddress& src); + int receive(char* buf, int count); + string receive(int max, ipaddress& src); + string receive(int max); + void send(const char* buf, int count); + void send(const string& s) { send(s, length(s)); } +}; + + +// +// ipmsgserver +// + +class ptpublic ipmsgserver: public ipsvbase, public ippeerinfo +{ +protected: + int handle; + + virtual void close(); + virtual void dobind(ipbindinfo*); + +public: + ipmsgserver(); + virtual ~ipmsgserver(); + + int get_handle() { return handle; } + + bool poll(int i = -1, int timeout = 0); + int receive(char* buf, int count); + string receive(int max); + void send(const char* buf, int count); + void send(const string& s) { send(s, length(s)); } + void sendto(const char* buf, int count, ipaddress ip, int port); + void sendto(const string& s, ipaddress ip, int port) + { sendto(s, length(s), ip, port); } +}; + + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + + +} + + +#endif // __PINET_H__ + diff --git a/source/ptypes/pinfile.cxx b/source/ptypes/pinfile.cxx @@ -0,0 +1,93 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <fcntl.h> +# include <unistd.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +// *BSD hack +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif + + +infile::infile() + : instm(), filename(), syshandle(invhandle), peerhandle(invhandle) {} + + +infile::infile(const char* ifn) + : instm(), filename(ifn), syshandle(invhandle), peerhandle(invhandle) {} + + +infile::infile(const string& ifn) + : instm(), filename(ifn), syshandle(invhandle), peerhandle(invhandle) {} + + +infile::~infile() +{ + close(); +} + + +int infile::classid() +{ + return CLASS2_INFILE; +} + + +string infile::get_streamname() +{ + return filename; +} + + +void infile::doopen() +{ + if (syshandle != invhandle) + handle = syshandle; + else + { +#ifdef WIN32 + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + handle = int(CreateFileA(filename, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, 0)); +#else + handle = ::open(filename, O_RDONLY | O_LARGEFILE); +#endif + if (handle == invhandle) + error(uerrno(), "Couldn't open"); + } +} + + +void infile::doclose() +{ + instm::doclose(); + syshandle = invhandle; + peerhandle = invhandle; +} + + +} diff --git a/source/ptypes/pinfilter.cxx b/source/ptypes/pinfilter.cxx @@ -0,0 +1,170 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <string.h> + +#include "pstreams.h" + + +namespace ptypes { + + +string infilter::get_errstmname() +{ + if (stm == nil) + return get_streamname(); + else + return get_streamname() + ": " + stm->get_errstmname(); +} + + +void infilter::copytobuf(string& s) +{ + int n = imin(savecount, length(s)); + if (n > 0) + { + memcpy(savebuf, pconst(s), n); + savebuf += n; + savecount -= n; + if (n == savecount) + clear(s); + else + del(s, 0, n); + } +} + + +void infilter::copytobuf(pconst& buf, int& count) +{ + int n = imin(savecount, count); + if (n > 0) + { + memcpy(savebuf, buf, n); + savebuf += n; + savecount -= n; + buf += n; + count -= n; + } +} + + +bool infilter::copytobuf(char c) +{ + if (savecount > 0) { + *savebuf = c; + savebuf++; + savecount--; + return true; + } + else + return false; +} + + +infilter::infilter(instm* istm, int ibufsize) + : instm(ibufsize), stm(istm), savebuf(nil), savecount(0) +{ + if (stm != nil) + stm->addnotification(this); +} + + +infilter::~infilter() +{ + if (stm != nil) + stm->delnotification(this); +} + + +void infilter::freenotify(component* sender) +{ + if (sender == stm) + { + stm = nil; + close(); + } +} + + +void infilter::doopen() +{ + if (stm != nil && !stm->get_active()) + stm->open(); +} + + +void infilter::doclose() +{ + savebuf = nil; + savecount = 0; + clear(postponed); +} + + +void infilter::set_stm(instm* istm) +{ + close(); + if (stm != nil) + stm->delnotification(this); + stm = istm; + if (stm != nil) + stm->addnotification(this); +} + + +int infilter::dorawread(char* buf, int count) +{ + savebuf = buf; + savecount = count; + if (!isempty(postponed)) + copytobuf(postponed); + if (savecount > 0 && stm != nil) + dofilter(); + return count - savecount; +} + + +void infilter::post(const char* buf, int count) +{ + if (count > 0) + { + copytobuf(buf, count); + if (count > 0) + concat(postponed, buf, count); + } +} + + +void infilter::post(string s) +{ + if (!isempty(s)) + { + copytobuf(s); + if (!isempty(s)) + concat(postponed, s); + } +} + + +void infilter::post(const char* s) +{ + post(s, strlen(s)); +} + + +void infilter::post(char c) +{ + if (!copytobuf(c)) + concat(postponed, c); +} + + +} diff --git a/source/ptypes/pinmem.cxx b/source/ptypes/pinmem.cxx @@ -0,0 +1,104 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "pstreams.h" + + +namespace ptypes { + + +inmemory::inmemory(const string& imem) + : instm(length(imem)), mem(imem) +{ +} + + +inmemory::~inmemory() +{ + close(); +} + + +int inmemory::classid() +{ + return CLASS2_INMEMORY; +} + + +void inmemory::bufalloc() +{ + bufdata = pchar(pconst(mem)); + abspos = bufsize = bufend = length(mem); +} + + +void inmemory::buffree() +{ + bufclear(); + bufdata = nil; +} + + +void inmemory::bufvalidate() +{ + eof = bufpos >= bufend; +} + + +void inmemory::doopen() +{ +} + + +void inmemory::doclose() +{ +} + + +large inmemory::doseek(large, ioseekmode) +{ + // normally shouldn't reach this point, because seek is + // supposed to happen within the I/O buffer + return -1; +} + + +int inmemory::dorawread(char*, int) +{ + return 0; +} + + +string inmemory::get_streamname() +{ + return "mem"; +} + + +large inmemory::seekx(large newpos, ioseekmode mode) +{ + if (mode == IO_END) + { + newpos += bufsize; + mode = IO_BEGIN; + } + return instm::seekx(newpos, mode); +} + + +void inmemory::set_strdata(const string& data) +{ + close(); + mem = data; +} + + +} diff --git a/source/ptypes/pinstm.cxx b/source/ptypes/pinstm.cxx @@ -0,0 +1,350 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <errno.h> +#include <string.h> +#include <limits.h> + +#ifdef WIN32 +# include <windows.h> +#else +# include <unistd.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +instm::instm(int ibufsize): iobase(ibufsize) +{ +} + + +instm::~instm() +{ +} + + +int instm::classid() +{ + return CLASS_INSTM; +} + + +int instm::dorawread(char* buf, int count) +{ + if (handle == invhandle) + return -1; +#ifdef WIN32 + unsigned long ret; + if (!ReadFile(HANDLE(handle), buf, count, &ret, nil)) +#else + int ret; + if ((ret = ::read(handle, buf, count)) < 0) +#endif + { + int e = uerrno(); + if (e == EPIPE) + ret = 0; + else + error(e, "Couldn't read"); + } + return ret; +} + + +int instm::rawread(char* buf, int count) +{ + requireactive(); + try + { + int ret = dorawread(buf, count); + if (ret <= 0) { + ret = 0; + eof = true; + chstat(IO_EOF); + } + else + { + abspos += ret; + chstat(IO_READING); + } + return ret; + } + catch (estream*) + { + eof = true; + chstat(IO_EOF); + throw; + } +} + + +large instm::tellx() +{ + return abspos - bufend + bufpos; +} + + +void instm::bufvalidate() +{ + requirebuf(); + bufclear(); + bufend = rawread(bufdata, bufsize); +} + + +large instm::seekx(large newpos, ioseekmode mode) +{ + if (bufdata != 0 && mode != IO_END) + { + if (mode == IO_CURRENT) + { + newpos += tellx(); + mode = IO_BEGIN; + } + + // see if it is possible to seek within the buffer + large newbufpos = newpos - (abspos - bufend); + if (newbufpos >= 0 && newbufpos <= bufend) + { + bufpos = (int)newbufpos; + eof = false; + return tellx(); + } + } + + // if IO_END or if not possible to seek within the buffer + return iobase::seekx(newpos, mode); +} + + +bool instm::get_eof() +{ + if (!eof && bufdata != 0 && bufpos >= bufend) + bufvalidate(); + return eof; +} + + +int instm::get_dataavail() +{ + get_eof(); + return bufend - bufpos; +} + + +char instm::preview() +{ + if (!eof && bufpos >= bufend) + bufvalidate(); + if (eof) + return eofchar; + return bufdata[bufpos]; +} + + +void instm::putback() +{ + requireactive(); + if (bufpos == 0) + fatal(CRIT_FIRST + 14, "putback() failed"); + bufpos--; + eof = false; +} + + +bool instm::get_eol() +{ + char c = preview(); + return (eof || c == 10 || c == 13); +} + + +void instm::skipeol() +{ + switch (preview()) + { + case 10: + get(); + break; + case 13: + get(); + if (preview() == 10) + get(); + break; + } +} + + +char instm::get() +{ + char ret = preview(); + if (!eof) + bufpos++; + return ret; +} + + +string instm::token(const cset& chars, int limit) +{ + requirebuf(); + string ret; + while (!get_eof()) + { + char* b = bufdata + bufpos; + char* e = bufdata + bufend; + char* p = b; + while (p < e && (*p & chars)) + p++; + int n = p - b; + limit -= n; + if (limit < 0) + { + bufpos += n + limit; + error(ERANGE, "Token too long"); + } + concat(ret, b, n); + bufpos += n; + if (p < e) + break; + } + return ret; +} + + +string instm::token(const cset& chars) +{ + return token(chars, INT_MAX); +} + + +static cset linechars = cset("*") - cset("~0a~0d"); + + +string instm::line(int limit) +{ + string ret = token(linechars, limit); + skipeol(); + return ret; +} + + +string instm::line() +{ + string ret = token(linechars, INT_MAX); + skipeol(); + return ret; +} + + +int instm::token(const cset& chars, char* buf, int count) +{ + requirebuf(); + int ret = 0; + while (count > 0 && !get_eof()) + { + char* b = bufdata + bufpos; + char* e = b + imin(count, bufend - bufpos); + char* p = b; + while (p < e && (*p & chars)) + p++; + int n = p - b; + memcpy(buf, b, n); + buf += n; + ret += n; + count -= n; + bufpos += n; + if (p < e) + break; + } + return ret; +} + + +int instm::line(char* buf, int size, bool eateol) +{ + int ret = token(linechars, buf, size); + if (eateol) + skipeol(); + return ret; +} + + +int instm::read(void* buf, int count) +{ + int ret = 0; + if (bufdata == 0) + ret = rawread(pchar(buf), count); + else + { + while (count > 0 && !get_eof()) + { + int n = imin(count, bufend - bufpos); + memcpy(buf, bufdata + bufpos, n); + buf = pchar(buf) + n; + ret += n; + count -= n; + bufpos += n; + } + } + return ret; +} + + +int instm::skip(int count) +{ + int ret = 0; + requirebuf(); + while (count > 0 && !get_eof()) + { + int n = imin(count, bufend - bufpos); + ret += n; + count -= n; + bufpos += n; + } + return ret; +} + + +int instm::skiptoken(const cset& chars) +{ + int ret = 0; + requirebuf(); + while (!get_eof()) + { + char* b = bufdata + bufpos; + char* e = bufdata + bufend; + char* p = b; + while (p < e && (*p & chars)) + p++; + int n = p - b; + bufpos += n; + ret += n; + if (p < e) + break; + } + return ret; +} + + +void instm::skipline(bool eateol) +{ + if (!get_eol()) + skiptoken(linechars); + if (eateol) + skipeol(); +} + + +} diff --git a/source/ptypes/pintee.cxx b/source/ptypes/pintee.cxx @@ -0,0 +1,68 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "pstreams.h" + + +namespace ptypes { + + +intee::intee(instm* istm, const char* ifn, bool iappend) + : infilter(istm, -1), file(ifn, iappend) +{ +} + + +intee::intee(instm* istm, const string& ifn, bool iappend) + : infilter(istm, -1), file(ifn, iappend) +{ +} + + +intee::~intee() +{ + close(); +} + + +void intee::doopen() +{ + infilter::doopen(); + file.open(); +} + + +void intee::doclose() +{ + file.close(); + infilter::doclose(); +} + + +void intee::dofilter() +{ + int count = stm->read(savebuf, savecount); + if (count > 0) + { + file.write(savebuf, count); + savebuf += count; + savecount -= count; + } +} + + +string intee::get_streamname() +{ + return "tee: " + file.get_filename(); +} + + +} diff --git a/source/ptypes/piobase.cxx b/source/ptypes/piobase.cxx @@ -0,0 +1,388 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <errno.h> +#include <limits.h> + +#ifdef WIN32 +# include <windows.h> +#else +# include <signal.h> +# include <unistd.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +/* + +Known UNIX error codes: + +EPERM 1 Not owner +ENOENT 2 No such file or directory +ESRCH 3 No such process +EINTR 4 Interrupted system call +EIO 5 I/O error +ENXIO 6 No such device or address +E2BIG 7 Argument list too long +ENOEXEC 8 Exec format error +EBADF 9 Bad file number +ECHILD 10 No spawned processes +EAGAIN 11 No more processes; not enough memory; maximum nesting level reached +ENOMEM 12 Not enough memory +EACCES 13 Permission denied +EFAULT 14 Bad address +ENOTBLK 15 Block device required +EBUSY 16 Mount device busy +EEXIST 17 File exists +EXDEV 18 Cross-device link +ENODEV 19 No such device +ENOTDIR 20 Not a directory +EISDIR 21 Is a directory +EINVAL 22 Invalid argument +ENFILE 23 File table overflow +EMFILE 24 Too many open files +ENOTTY 25 Not a teletype +ETXTBSY 26 Text file busy +EFBIG 27 File too large +ENOSPC 28 No space left on device +ESPIPE 29 Illegal seek +EROFS 30 Read-only file system +EMLINK 31 Too many links +EPIPE 32 Broken pipe +EDOM 33 Math argument +ERANGE 34 Result too large +EUCLEAN 35 File system needs cleaning +EDEADLK 36 Resource deadlock would occur +EDEADLOCK 36 Resource deadlock would occur + +*/ + + +#ifndef WIN32 + +static class _io_init +{ +public: + _io_init(); +} _io_init_inst; + + +_io_init::_io_init() +{ + // We don't like broken pipes. PTypes will throw an exception instead. + signal(SIGPIPE, SIG_IGN); +} + +#endif + + + +int ptdecl unixerrno() +{ +#ifdef WIN32 + switch(GetLastError()) + { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: return EACCES; + case ERROR_INVALID_HANDLE: return EBADF; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: return ENOMEM; + case ERROR_INVALID_DRIVE: return ENODEV; + case ERROR_WRITE_PROTECT: return EROFS; + case ERROR_FILE_EXISTS: return EEXIST; + case ERROR_BROKEN_PIPE: return EPIPE; + case ERROR_DISK_FULL: return ENOSPC; + case ERROR_SEEK_ON_DEVICE: return ESPIPE; + default: return EIO; + } +#else + return errno; +#endif +} + + +// +// This function gives error messages for most frequently occurring +// IO errors. If the function returns NULL a generic message +// can be given, e.g. "I/O error". See also iobase::get_errormsg() +// + +const char* ptdecl unixerrmsg(int code) +{ + switch(code) + { + case EBADF: return "Invalid file descriptor"; + case ESPIPE: return "Can not seek on this device"; + case ENOENT: return "No such file or directory"; + case EMFILE: return "Too many open files"; + case EACCES: return "Access denied"; + case ENOMEM: return "Not enough memory"; + case ENODEV: return "No such device"; + case EROFS: return "Read-only file system"; + case EEXIST: return "File already exists"; + case ENOSPC: return "Disk full"; + case EPIPE: return "Broken pipe"; + case EFBIG: return "File too large"; + default: return nil; + } +} + + +estream::estream(iobase* ierrstm, int icode, const char* imsg) + : exception(imsg), code(icode), errstm(ierrstm) {} + + +estream::estream(iobase* ierrstm, int icode, const string& imsg) + : exception(imsg), code(icode), errstm(ierrstm) {} + + +estream::~estream() {} + + +int defbufsize = 8192; +int stmbalance = 0; + +iobase::iobase(int ibufsize) + : component(), active(false), cancelled(false), eof(true), + handle(invhandle), abspos(0), bufsize(0), bufdata(nil), bufpos(0), bufend(0), + stmerrno(0), deferrormsg(), status(IO_CREATED), onstatus(nil) +{ + if (ibufsize < 0) + bufsize = defbufsize; + else + bufsize = ibufsize; +} + + +iobase::~iobase() +{ +} + + +void iobase::bufalloc() +{ + if (bufdata != nil) + fatal(CRIT_FIRST + 13, "(ptypes internal) invalid buffer allocation"); + bufdata = (char*)memalloc(bufsize); +} + + +void iobase::buffree() +{ + bufclear(); + memfree(bufdata); + bufdata = 0; +} + + +void iobase::chstat(int newstat) +{ + status = newstat; + if (onstatus != nil) + (*onstatus)(this, newstat); +} + + +void iobase::errstminactive() +{ + error(EIO, "Stream inactive"); +} + + +void iobase::errbufrequired() +{ + fatal(CRIT_FIRST + 11, "Internal: buffer required"); +} + + +int iobase::convertoffset(large offs) +{ + if (offs < 0 || offs > INT_MAX) + error(EFBIG, "File offset value too large"); + return (int)offs; +} + + +void iobase::open() +{ + cancel(); + chstat(IO_OPENING); + abspos = 0; + cancelled = false; + eof = false; + stmerrno = 0; + clear(deferrormsg); + active = true; + stmbalance++; + bufalloc(); + doopen(); + chstat(IO_OPENED); +} + + +void iobase::close() +{ + if (!active) + return; + stmbalance--; + try + { + if (bufdata != 0 && !cancelled) + flush(); + doclose(); + } + catch(estream* e) + { + delete e; + } + buffree(); + active = false; + eof = true; + chstat(IO_CLOSED); +} + + +void iobase::cancel() +{ + cancelled = true; + close(); +} + + +large iobase::seekx(large newpos, ioseekmode mode) +{ + if (!active) + errstminactive(); + flush(); + large ret = doseek(newpos, mode); + if (ret < 0) + error(ESPIPE, "Seek failed"); + bufclear(); + eof = false; + abspos = ret; + return ret; +} + + +void iobase::flush() +{ +} + + +large iobase::doseek(large newpos, ioseekmode mode) +{ + if (handle == invhandle) + { + error(ESPIPE, "Can't seek on this device"); + return -1; + } +#ifdef WIN32 + static int wmode[3] = {FILE_BEGIN, FILE_CURRENT, FILE_END}; + LARGE_INTEGER li; + li.QuadPart = newpos; + li.LowPart = SetFilePointer(HANDLE(handle), li.LowPart, &li.HighPart, wmode[mode]); + if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) + return -1; + return li.QuadPart; +#else + static int umode[3] = {SEEK_SET, SEEK_CUR, SEEK_END}; + return lseek(handle, newpos, umode[mode]); +#endif +} + + +void iobase::doclose() +{ +#ifdef WIN32 + CloseHandle(HANDLE(pexchange(&handle, invhandle))); +#else + ::close(pexchange(&handle, invhandle)); +#endif +} + + +void iobase::set_active(bool newval) +{ + if (newval != active) + { + if (newval) + open(); + else + close(); + } +} + + +void iobase::set_bufsize(int newval) +{ + if (active) + fatal(CRIT_FIRST + 12, "Cannot change buffer size while stream is active"); + if (newval < 0) + bufsize = defbufsize; + else + bufsize = newval; +} + + +string iobase::get_errstmname() +{ + return get_streamname(); +} + + +const char* iobase::uerrmsg(int code) +{ + return unixerrmsg(code); +} + + +int iobase::uerrno() +{ + return unixerrno(); +} + + +string iobase::get_errormsg() +{ + string s = uerrmsg(stmerrno); + if (isempty(s)) + s = deferrormsg; + if (pos('[', s) >= 0 && *(pconst(s) + length(s) - 1) == ']') + return s; + string e = get_errstmname(); + if (isempty(e)) + return s; + return s + " [" + e + ']'; +} + + +#ifdef _MSC_VER +// disable "unreachable code" warning for throw (known compiler bug) +# pragma warning (disable: 4702) +#endif + +void iobase::error(int code, const char* defmsg) +{ + eof = true; + stmerrno = code; + deferrormsg = defmsg; + throw new estream(this, code, get_errormsg()); +} + + +} diff --git a/source/ptypes/pipbase.cxx b/source/ptypes/pipbase.cxx @@ -0,0 +1,421 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <winsock2.h> +#else +# include <sys/time.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <unistd.h> +# include <signal.h> +# include <time.h> +#endif + +#include <stdio.h> // for snprintf + +#include "pinet.h" +#ifndef PTYPES_ST +# include "pasync.h" // for mutex +#endif + + +namespace ptypes { + +// +// reentrant gehostby*() mess +// + +#if defined(PTYPES_ST) +# define USE_GETHOSTBY +#else +# if defined(WIN32) || defined(__hpux) || defined(__ANDROID__) +# define USE_GETHOSTBY +# elif defined(__FreeBSD__) || defined(__DARWIN__) +# define USE_GETIPNODEBY +# elif defined(linux) || defined(__EMSCRIPTEN__) +# define USE_GETHOSTBY_R6 +# elif defined(__NetBSD__) || defined(__OpenBSD__) || defined(__CYGWIN__) +# define USE_LOCKED_GETHOSTBY +# else // hopefully the Sun-style call will work on all other systems as well +# define USE_GETHOSTBY_R5 +# endif +#endif + +#define GETHOSTBY_BUF_SIZE 4096 + + +// +// sockets init/startup +// + +// usockerrno() is used in all socket classes anyway, so this module +// along with the initialization code below will always be linked to +// a networking program + +#ifdef WIN32 + +static class _sock_init +{ +public: + _sock_init(); + ~_sock_init(); +} _init; + + +_sock_init::_sock_init() +{ + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD(2, 0); + + err = WSAStartup(wVersionRequested, &wsaData); + if ( err != 0 ) + fatal(CRIT_FIRST + 50, "WinSock initialization failure"); + + if ( LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0 ) + fatal(CRIT_FIRST + 51, "WinSock version mismatch (2.0 or compatible required)"); +} + + +_sock_init::~_sock_init() +{ + WSACleanup(); +} + +#endif + + + +// +// internet address +// + +ipaddress ipnone = uint(0xffffffff); +ipaddress ipany = INADDR_ANY; +ipaddress ipbcast = INADDR_BROADCAST; + + + +ipaddress::ipaddress(int a, int b, int c, int d) +{ + data[0] = uchar(a); + data[1] = uchar(b); + data[2] = uchar(c); + data[3] = uchar(d); +} + + +// +// peer info +// + + +ippeerinfo::ippeerinfo() + : ip(ipnone), host(), port(0) +{ +} + + +ippeerinfo::ippeerinfo(ipaddress iip, const string& ihost, int iport) + : ip(iip), host(ihost), port(iport) +{ +} + + +ipaddress ippeerinfo::get_ip() +{ + if (ip == ipnone && !isempty(host)) + { + ip = ulong(phostbyname(host)); + if (ip == ipnone) + notfound(); + } + return ip; +} + + +string ippeerinfo::get_host() +{ + if (!isempty(host)) + return host; + + if (ip == ipnone || ip == ipany || ip == ipbcast) + return nullstring; + + host = phostbyaddr(ip); + if (isempty(host)) + notfound(); + + return host; +} + + +void ippeerinfo::clear() +{ + ip = ipnone; + ptypes::clear(host); + port = 0; +} + + +string ippeerinfo::asstring(bool showport) const +{ + string t; + if (!isempty(host)) + t = host; + else if (ip == ipany) + t = '*'; + else if (ip == ipnone) + t = '-'; + else + t = iptostring(ip); + if (showport && port != 0) + t += ':' + itostring(port); + return t; +} + + +// +// internet utilities +// + +int ptdecl usockerrno() +{ +#ifdef WIN32 + return WSAGetLastError(); +#else + return errno; +#endif +} + +//for VisualStudio2010 +#if (_MSC_VER>=1600) +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#define EHOSTDOWN WSAEHOSTDOWN +#endif + +const char* ptdecl usockerrmsg(int code) +{ + switch(code) + { + // only minimal set of most frequent/expressive errors; others go as "I/O error" + case ENOTSOCK: return "Invalid socket descriptor"; + case EMSGSIZE: return "Message too long"; + case ENOPROTOOPT: + case EPROTONOSUPPORT: + case EPFNOSUPPORT: + case EAFNOSUPPORT: return "Protocol or address family not supported"; + case EADDRINUSE: return "Address already in use"; + case EADDRNOTAVAIL: return "Address not available"; + case ENETDOWN: return "Network is down"; + case ENETUNREACH: return "Network is unreachable"; + case ECONNRESET: return "Connection reset by peer"; + case ETIMEDOUT: return "Operation timed out"; + case ECONNREFUSED: return "Connection refused"; + case EHOSTDOWN: return "Host is down"; + case EHOSTUNREACH: return "No route to host"; + + // we always translate h_errno to ENOENT and simply show "host not found" + case ENOENT: return "Host not found"; + default: return unixerrmsg(code); + } +} + + +string ptdecl iptostring(ipaddress ip) +{ + char buf[16]; + snprintf(buf, sizeof(buf), "%d.%d.%d.%d", + uint(ip[0]), uint(ip[1]), uint(ip[2]), uint(ip[3])); + return string(buf); +} + + +#if defined(USE_LOCKED_GETHOSTBY) +static mutex hplock; +#endif + + +ipaddress ptdecl phostbyname(const char* name) +{ + ipaddress ip; + hostent* hp; + + if ((ip = ::inet_addr(name)) != ipnone) + { + if (ip[3] == 0) // network address? + return ipnone; + } + else + { +#if defined(USE_GETHOSTBY) + if ((hp = ::gethostbyname(name)) != nil) +#elif defined(USE_LOCKED_GETHOSTBY) + hplock.enter(); + if ((hp = ::gethostbyname(name)) != nil) +#elif defined(USE_GETIPNODEBY) + int herrno; + if ((hp = ::getipnodebyname(name, AF_INET, 0, &herrno)) != nil) +#elif defined(USE_GETHOSTBY_R6) + int herrno; + hostent result; + char buf[GETHOSTBY_BUF_SIZE]; + if ((::gethostbyname_r(name, &result, buf, sizeof(buf), &hp, &herrno) == 0) && hp) +#elif defined(USE_GETHOSTBY_R5) + int herrno; + hostent result; + char buf[GETHOSTBY_BUF_SIZE]; + if ((hp = ::gethostbyname_r(name, &result, buf, sizeof(buf), &herrno)) != nil) +#endif + { + if (hp->h_addrtype == AF_INET) + memcpy(ip.data, hp->h_addr, sizeof(ip.data)); +#ifdef USE_GETIPNODEBY + freehostent(hp); +#endif + } +#if defined(USE_LOCKED_GETHOSTBY) + hplock.leave(); +#endif + } + + return ip; +} + + +string ptdecl phostbyaddr(ipaddress ip) +{ + hostent* hp; + string r; + +#if defined(USE_GETHOSTBY) + if ((hp = ::gethostbyaddr(pconst(ip.data), sizeof(ip.data), AF_INET)) != nil) +#elif defined(USE_LOCKED_GETHOSTBY) + hplock.enter(); + if ((hp = ::gethostbyaddr(pconst(ip.data), sizeof(ip.data), AF_INET)) != nil) +#elif defined(USE_GETIPNODEBY) + int herrno; + if ((hp = ::getipnodebyaddr(pconst(ip.data), sizeof(ip.data), AF_INET, &herrno)) != nil) +#elif defined(USE_GETHOSTBY_R6) + int herrno; + hostent result; + char buf[GETHOSTBY_BUF_SIZE]; + if ((::gethostbyaddr_r(pconst(ip.data), sizeof(ip.data), AF_INET, &result, buf, sizeof(buf), &hp, &herrno) == 0) && hp) +#elif defined(USE_GETHOSTBY_R5) + int herrno; + hostent result; + char buf[GETHOSTBY_BUF_SIZE]; + if ((hp = ::gethostbyaddr_r(pconst(ip.data), sizeof(ip.data), AF_INET, &result, buf, sizeof(buf), &herrno)) != nil) +#endif + { + r = hp->h_name; +#ifdef USE_GETIPNODEBY + freehostent(hp); +#endif + } +#if defined(USE_LOCKED_GETHOSTBY) + hplock.leave(); +#endif + + return r; +} + + +string ptdecl phostcname(const char* name) +{ + hostent* hp; + string r; + +#if defined(USE_GETHOSTBY) + if ((hp = ::gethostbyname(name)) != nil) +#elif defined(USE_LOCKED_GETHOSTBY) + hplock.enter(); + if ((hp = ::gethostbyname(name)) != nil) +#elif defined(USE_GETIPNODEBY) + int herrno; + if ((hp = ::getipnodebyname(name, AF_INET, 0, &herrno)) != nil) +#elif defined(USE_GETHOSTBY_R6) + int herrno; + hostent result; + char buf[GETHOSTBY_BUF_SIZE]; + if ((::gethostbyname_r(name, &result, buf, sizeof(buf), &hp, &herrno) == 0) && hp) +#elif defined(USE_GETHOSTBY_R5) + int herrno; + hostent result; + char buf[GETHOSTBY_BUF_SIZE]; + if ((hp = ::gethostbyname_r(name, &result, buf, sizeof(buf), &herrno)) != nil) +#endif + { + r = hp->h_name; +#ifdef USE_GETIPNODEBY + freehostent(hp); +#endif + } +#if defined(USE_LOCKED_GETHOSTBY) + hplock.leave(); +#endif + + return r; +} + + +bool ptdecl psockwait(int handle, int timeout) +{ +#ifdef _MSC_VER +// disable "condition always true" warning caused by Microsoft's FD_SET macro +# pragma warning (disable: 4127) +#endif + if (handle < 0) + return false; + fd_set readfds; + FD_ZERO(&readfds); + FD_SET((uint)handle, &readfds); + timeval t; + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + return ::select(FD_SETSIZE, &readfds, nil, nil, (timeout < 0) ? nil : &t) > 0; +} + + +bool ptdecl psockname(int handle, ippeerinfo& p) +{ + sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + psocklen addrlen = sizeof(sa); + if (getsockname(handle, (sockaddr*)&sa, &addrlen) != 0) + return false; + if (sa.sin_family != AF_INET) + return false; + p.ip = sa.sin_addr.s_addr; + p.port = ntohs(sa.sin_port); + return true; +} + + +#ifdef _MSC_VER +// disable "unreachable code" warning for throw (known compiler bug) +# pragma warning (disable: 4702) +#endif + +void ippeerinfo::notfound() +{ + string t = usockerrmsg(ENOENT); + throw new estream(nil, ENOENT, t + " [" + asstring(false) + ']'); +} + + +} diff --git a/source/ptypes/pipmsg.cxx b/source/ptypes/pipmsg.cxx @@ -0,0 +1,203 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <winsock2.h> +#else +# include <sys/time.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <unistd.h> +# include <time.h> +#endif + + +#include "pinet.h" + + +namespace ptypes { + + +// +// ipmessage: IPv4 UDP message class +// + + +ipmessage::ipmessage() + : unknown(), ippeerinfo(ipnone, nullstring, 0), handle(invhandle) {} + + +ipmessage::ipmessage(ipaddress iip, int iport) + : unknown(), ippeerinfo(iip, nullstring, iport), handle(invhandle) {} + + +ipmessage::ipmessage(const char* ihost, int iport) + : unknown(), ippeerinfo(ipnone, ihost, iport), handle(invhandle) {} + + +ipmessage::ipmessage(const string& ihost, int iport) + : unknown(), ippeerinfo(ipnone, ihost, iport), handle(invhandle) {} + + +ipmessage::~ipmessage() +{ + close(); +} + + +void ipmessage::set_ip(ipaddress iip) +{ + ip = iip; + ptypes::clear(host); +} + + +void ipmessage::set_host(const string& ihost) +{ + host = ihost; + ip = 0; +} + + +void ipmessage::set_host(const char* ihost) +{ + host = ihost; + ip = 0; +} + + +void ipmessage::set_port(int iport) +{ + port = iport; +} + + +ipaddress ipmessage::get_myip() +{ + ippeerinfo p; + if (!psockname(handle, p)) + error(usockerrno(), "Couldn't get my IP"); + return p.get_ip(); +} + + +int ipmessage::get_myport() +{ + ippeerinfo p; + if (!psockname(handle, p)) + error(usockerrno(), "Couldn't get my port number"); + return p.get_port(); +} + + +void ipmessage::close() +{ + if (handle != invhandle) + ::closesocket(pexchange(&handle, invhandle)); +} + + +void ipmessage::open() +{ + close(); + if ((handle = ::socket(AF_INET, SOCK_DGRAM, 0)) < 0) + error(usockerrno(), "Couldn't create socket"); + // allow broadcasts + int one = 1; + if (::setsockopt(handle, SOL_SOCKET, SO_BROADCAST, (sockval_t)&one, sizeof(one)) != 0) + error(usockerrno(), "Couldn't enable broadcasts"); + sockopt(handle); +} + + +void ipmessage::sockopt(int) +{ +} + + +bool ipmessage::waitfor(int timeout) +{ + return psockwait(handle, timeout); +} + + +void ipmessage::send(const char* buf, int count) +{ + if (handle == invhandle) + open(); + + sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(ushort(get_port())); + sa.sin_addr.s_addr = get_ip(); + if (sendto(handle, buf, count, 0, (sockaddr*)&sa, sizeof(sa)) < 0) + error(usockerrno(), "Couldn't write"); +} + + +int ipmessage::receive(char* buf, int count, ipaddress& src) +{ + if (handle == invhandle) + error(EINVAL, "Couldn't read"); // must send() first + + sockaddr_in sa; + psocklen fromlen = sizeof(sa); + int result = ::recvfrom(handle, buf, count, 0, (sockaddr*)&sa, &fromlen); + if (result < 0) + error(usockerrno(), "Couldn't read"); + src = sa.sin_addr.s_addr; + return result; +} + + +int ipmessage::receive(char* buf, int count) +{ + ipaddress src; + return receive(buf, count, src); +} + + +string ipmessage::receive(int max, ipaddress& src) +{ + string result; + setlength(result, max); + int numread = receive(pchar(pconst(result)), max, src); + setlength(result, numread); + return result; +} + + +string ipmessage::receive(int max) +{ + ipaddress src; + return receive(max, src); +} + + +#ifdef _MSC_VER +// disable "unreachable code" warning for throw (known compiler bug) +# pragma warning (disable: 4702) +#endif + +void ipmessage::error(int code, const char* msg) +{ + string s = usockerrmsg(code); + if (isempty(s)) + s = msg; + throw new estream(nil, code, s + " [" + ippeerinfo::asstring(true) + ']'); +} + + +} diff --git a/source/ptypes/pipmsgsv.cxx b/source/ptypes/pipmsgsv.cxx @@ -0,0 +1,140 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <winsock2.h> +#else +# include <sys/time.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <unistd.h> +# include <time.h> +#endif + + +#include "pinet.h" + + +namespace ptypes { + + +// +// ipmsgserver: IPv4 UDP socket server +// + + +ipmsgserver::ipmsgserver() + : ipsvbase(SOCK_DGRAM), ippeerinfo(), handle(invhandle) +{ +} + + +ipmsgserver::~ipmsgserver() +{ + close(); +} + + +void ipmsgserver::dobind(ipbindinfo* b) +{ + sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(ushort(b->get_port())); + sa.sin_addr.s_addr = b->get_ip(); + if (::bind(b->handle, (sockaddr*)&sa, sizeof(sa)) != 0) + error(*b, usockerrno(), "Couldn't bind address"); +} + + +void ipmsgserver::close() +{ + if (!active) + return; + ipsvbase::close(); + handle = invhandle; + ippeerinfo::clear(); +} + + +bool ipmsgserver::poll(int i, int timeout) +{ + if (!active) + open(); + return dopoll(&i, timeout); +} + + +int ipmsgserver::receive(char* buf, int count) +{ + if (!active) + open(); + ippeerinfo::clear(); + + // determine which socket has pending data + int i = -1; + if (!dopoll(&i, -1)) + error(*this, EINVAL, "Couldn't read"); + ipbindinfo* b = (ipbindinfo*)addrlist[i]; + handle = b->handle; + + // read data + sockaddr_in sa; + psocklen len = sizeof(sa); + int result = ::recvfrom(handle, buf, count, 0, (sockaddr*)&sa, &len); + if (result < 0) + error(*b, usockerrno(), "Couldn't read"); + + // set up peer ip and port + ip = sa.sin_addr.s_addr; + port = ntohs(sa.sin_port); + return result; +} + + +string ipmsgserver::receive(int max) +{ + string result; + setlength(result, max); + int numread = receive(pchar(pconst(result)), max); + setlength(result, numread); + return result; +} + + +void ipmsgserver::send(const char* buf, int count) +{ + if (!active || handle == invhandle || ip == ipnone) + error(*this, EINVAL, "Couldn't write"); // must receive() first + + sendto(buf, count, get_ip(), get_port()); +} + + +void ipmsgserver::sendto(const char* buf, int count, ipaddress ip, int port) +{ + if (!active || handle == invhandle || ip == ipnone) + error(*this, EINVAL, "Couldn't write"); // must receive() first + + sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(ushort(port)); + sa.sin_addr.s_addr = ip; + if (::sendto(handle, buf, count, 0, (sockaddr*)&sa, sizeof(sa)) < 0) + error(*this, usockerrno(), "Couldn't write"); +} + + +} diff --git a/source/ptypes/pipstm.cxx b/source/ptypes/pipstm.cxx @@ -0,0 +1,248 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <winsock2.h> +#else +# include <sys/time.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <unistd.h> +# include <time.h> +#endif + + +#include "pinet.h" + + +namespace ptypes { + + +// +// internet (ipv4) socket +// + + +ipstream::ipstream() + : fdxstm(), ippeerinfo(0, nullstring, 0), svsocket(invhandle) {} + + +ipstream::ipstream(ipaddress iip, int iport) + : fdxstm(), ippeerinfo(iip, nullstring, iport), svsocket(invhandle) {} + + +ipstream::ipstream(const char* ihost, int iport) + : fdxstm(), ippeerinfo(ipnone, ihost, iport), svsocket(invhandle) {} + + +ipstream::ipstream(const string& ihost, int iport) + : fdxstm(), ippeerinfo(ipnone, ihost, iport), svsocket(invhandle) {} + + +ipstream::~ipstream() +{ + cancel(); +} + + +int ipstream::classid() +{ + return CLASS3_IPSTM; +} + + +int ipstream::uerrno() +{ + return usockerrno(); +} + + +const char* ipstream::uerrmsg(int code) +{ + return usockerrmsg(code); +} + + +string ipstream::get_streamname() +{ + return ippeerinfo::asstring(true); +} + + +void ipstream::set_ip(ipaddress iip) +{ + close(); + ip = iip; + ptypes::clear(host); +} + + +void ipstream::set_host(const string& ihost) +{ + close(); + host = ihost; + ip = ipnone; +} + + +void ipstream::set_host(const char* ihost) +{ + close(); + host = ihost; + ip = ipnone; +} + + +void ipstream::set_port(int iport) +{ + close(); + port = iport; +} + + +void ipstream::doopen() +{ + sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + + if (svsocket != invhandle) + { + psocklen addrlen = sizeof(sa); + + // open an active server socket and assign ip and host fields + chstat(IO_CONNECTING); + + // the last parameter of accept() can be either int* or uint* + // depending on the target platform :( + if ((handle = ::accept(svsocket, (sockaddr*)&sa, &addrlen)) < 0) + error(uerrno(), "Couldn't create socket"); + chstat(IO_CONNECTED); + + if (sa.sin_family != AF_INET) + error(EAFNOSUPPORT, "Address family not supported"); + + ptypes::clear(host); + ip = sa.sin_addr.s_addr; + port = ntohs(sa.sin_port); + } + + else + { + sa.sin_family = AF_INET; + sa.sin_port = htons(ushort(get_port())); + + chstat(IO_RESOLVING); + sa.sin_addr.s_addr = get_ip(); // force to resolve the address if needed + chstat(IO_RESOLVED); + + // open a client socket + if ((handle = ::socket(sa.sin_family, SOCK_STREAM, 0)) < 0) + error(uerrno(), "Couldn't create socket"); + + // a chance to set up extra socket options + sockopt(handle); + + chstat(IO_CONNECTING); + if (::connect(handle, (sockaddr*)&sa, sizeof(sa)) < 0) + { + int e = uerrno(); + closehandle(); + error(e, "Couldn't connect to remote host"); + } + chstat(IO_CONNECTED); + } +} + + +void ipstream::sockopt(int) +{ +} + + +void ipstream::closehandle() +{ + ::closesocket(pexchange(&handle, invhandle)); +} + + +large ipstream::doseek(large, ioseekmode) +{ + return -1; +} + + +void ipstream::doclose() +{ + svsocket = invhandle; + if (!cancelled) + ::shutdown(handle, SHUT_RDWR); + closehandle(); +} + + +#ifdef WIN32 + +int ipstream::dorawread(char* buf, int count) +{ + int ret; + if ((ret = ::recv(handle, buf, count, 0)) == -1) + error(uerrno(), "Couldn't read"); + return ret; +} + + +int ipstream::dorawwrite(const char* buf, int count) +{ + int ret; + if ((ret = ::send(handle, buf, count, 0)) == -1) + error(uerrno(), "Couldn't write"); + return ret; +} + +#endif + + +bool ipstream::waitfor(int timeout) +{ + if (!active) + errstminactive(); + if (bufsize > 0 && bufend > bufpos) + return true; + return psockwait(handle, timeout); +} + + +ipaddress ipstream::get_myip() +{ + if (!active) + errstminactive(); + ippeerinfo p; + if (!psockname(handle, p)) + error(uerrno(), "Couldn't get my IP"); + return p.get_ip(); +} + + +int ipstream::get_myport() +{ + if (!active) + errstminactive(); + ippeerinfo p; + if (!psockname(handle, p)) + error(uerrno(), "Couldn't get my port number"); + return p.get_port(); +} + + +} diff --git a/source/ptypes/pipstmsv.cxx b/source/ptypes/pipstmsv.cxx @@ -0,0 +1,101 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <winsock2.h> +#else +# include <sys/time.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <unistd.h> +# include <time.h> +#endif + + +#include "pinet.h" + + +namespace ptypes { + + +// +// ipstmserver +// + + +ipstmserver::ipstmserver() + : ipsvbase(SOCK_STREAM) +{ +} + + +ipstmserver::~ipstmserver() +{ + close(); +} + + +void ipstmserver::dobind(ipbindinfo* b) +{ +#ifndef WIN32 + // set SO_REAUSEADDR to true, unix only. on windows this option causes + // the previous owner of the socket to give up, which is not desirable + // in most cases, neither compatible with unix. + int one = 1; + if (::setsockopt(b->handle, SOL_SOCKET, SO_REUSEADDR, + (sockval_t)&one, sizeof(one)) != 0) + error(*b, usockerrno(), "Can't reuse local address"); +#endif + + // set up sockaddr_in and try to bind it to the socket + sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(ushort(b->get_port())); + sa.sin_addr.s_addr = b->get_ip(); + + if (::bind(b->handle, (sockaddr*)&sa, sizeof(sa)) != 0) + error(*b, usockerrno(), "Couldn't bind address"); + + if (::listen(b->handle, SOMAXCONN) != 0) + error(*b, usockerrno(), "Couldn't listen on socket"); +} + + +bool ipstmserver::poll(int i, int timeout) +{ + if (!active) + open(); + return dopoll(&i, timeout); +} + + +bool ipstmserver::serve(ipstream& client, int i, int timeout) +{ + if (!active) + open(); + + client.cancel(); + if (dopoll(&i, timeout)) + { + // connect the ipstream object to the client requesting the connection + client.svsocket = get_addr(i).handle; + client.open(); + return true; + } + return false; +} + + +} diff --git a/source/ptypes/pipsvbase.cxx b/source/ptypes/pipsvbase.cxx @@ -0,0 +1,177 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <winsock2.h> +#else +# include <sys/time.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +# include <unistd.h> +# include <time.h> +#endif + +#include "pinet.h" + + +namespace ptypes { + + +// +// ipbindinfo +// + + +ipbindinfo::ipbindinfo(ipaddress iip, const string& ihost, int iport) + : unknown(), ippeerinfo(iip, ihost, iport), handle(invhandle) +{ +} + + +ipbindinfo::~ipbindinfo() +{ +} + + +// +// ipsvbase +// + + +ipsvbase::ipsvbase(int isocktype) + : socktype(isocktype), active(false), addrlist(true) {} + + +ipsvbase::~ipsvbase() +{ + close(); +} + + +void ipsvbase::error(ippeerinfo& p, int code, const char* defmsg) +{ + string msg = usockerrmsg(code); + if (isempty(msg)) + msg = defmsg; + msg += " [" + p.asstring(true) + ']'; + throw new estream(nil, code, msg); +} + + +int ipsvbase::bind(ipaddress ip, int port) +{ + close(); + addrlist.add(new ipbindinfo(ip, nullstring, port)); + return addrlist.get_count() - 1; +} + + +int ipsvbase::bindall(int port) +{ + close(); + return bind(ipany, port); +} + + +void ipsvbase::clear() +{ + close(); + addrlist.clear(); +} + + +void ipsvbase::open() +{ + close(); + if (addrlist.get_count() == 0) + fatal(CRIT_FIRST + 52, "No addresses specified to bind to"); + active = true; + for (int i = 0; i < addrlist.get_count(); i++) + { + ipbindinfo* b = addrlist[i]; + b->handle = ::socket(AF_INET, socktype, 0); + if (b->handle < 0) + error(*b, usockerrno(), "Couldn't create socket"); + sockopt(b->handle); + dobind(b); + } +} + + +void ipsvbase::close() +{ + if (!active) + return; + for (int i = 0; i < addrlist.get_count(); i++) + { + ipbindinfo* b = addrlist[i]; + ::closesocket(pexchange(&b->handle, invhandle)); + } + active = false; +} + + +bool ipsvbase::dopoll(int* i, int timeout) +{ + fd_set set; + setupfds(&set, *i); + timeval t; + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + if (::select(FD_SETSIZE, &set, nil, nil, (timeout < 0) ? nil : &t) > 0) + { + if (*i >= 0) + return true; + // if the user selected -1 (all), find the socket which has a pending connection + // and assign it to i + for (int j = 0; j < addrlist.get_count(); j++) + if (FD_ISSET(uint(addrlist[j]->handle), &set)) + { + *i = j; + return true; + } + } + return false; +} + + +void ipsvbase::setupfds(void* set, int i) +{ +#ifdef _MSC_VER +// disable "condition always true" warning caused by Microsoft's FD_SET macro +# pragma warning (disable: 4127) +#endif + FD_ZERO((fd_set*)set); + if (i >= 0) + { + int h = get_addr(i).handle; + if (h >= 0) + FD_SET((uint)h, (fd_set*)set); + } + else + for (i = 0; i < addrlist.get_count(); i++) + { + int h = addrlist[i]->handle; + if (h >= 0) + FD_SET((uint)h, (fd_set*)set); + } +} + + +void ipsvbase::sockopt(int) +{ +} + + +} diff --git a/source/ptypes/pmd5.cxx b/source/ptypes/pmd5.cxx @@ -0,0 +1,517 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +/* + * Derived from L. Peter Deutsch's independent implementation + * of MD5 (RFC1321). The original copyright notice follows. + * This file is a concatenation of the original md5.h and + * md5.c and contains PTypes' MD5 wrapper class at the bottom. + */ + +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ + +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + <ghost@aladdin.com>. Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke <purschke@bnl.gov>. + 1999-05-03 lpd Original version. + */ + + +#include <string.h> + +#include "pstreams.h" + + +namespace ptypes { + + +// +// --- md5.h --------------------------------------------------------------- +// + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + + +// +// typedef unsigned char md5_byte_t; /* 8-bit byte */ +// typedef unsigned int md5_word_t; /* 32-bit word */ +// +// /* Define the state of the MD5 Algorithm. */ +// typedef struct md5_state_s { +// md5_word_t count[2]; /* message length in bits, lsw first */ +// md5_word_t abcd[4]; /* digest buffer */ +// md5_byte_t buf[64]; /* accumulate block */ +// } md5_state_t; +// + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + + +// +// --- md5.c --------------------------------------------------------------- +// + + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + + + +// +// --- PTypes' wrapper class ----------------------------------------------- +// + + +outmd5::outmd5(outstm* istm): outfilter(istm, 0) +{ + memset(&ctx, 0, sizeof ctx); + memset(digest, 0, sizeof digest); +} + + +outmd5::~outmd5() +{ + close(); +} + + +void outmd5::doopen() +{ + outfilter::doopen(); + memset(digest, 0, sizeof digest); + md5_init(&ctx); +} + + +void outmd5::doclose() +{ + md5_finish(&ctx, (unsigned char*)digest); + outfilter::doclose(); +} + + +int outmd5::dorawwrite(const char* buf, int count) +{ + if (count > 0) + { + md5_append(&ctx, (const unsigned char*)buf, (unsigned)count); + if (stm != nil) + stm->write(buf, count); + return count; + } + else + return 0; +} + + +string outmd5::get_streamname() +{ + return "MD5"; +} + + +string outmd5::get_digest() +{ + close(); + string result; + // the first 120 bits are divided into 24-bit portions; + // each portion is represented with 4 characters from the base64 set + for (int i = 0; i <= 12; i += 3) + { + long v = (digest[i] << 16) | (digest[i + 1] << 8) | digest[i + 2]; + result += itostring(large(v), 64, 4); + } + // the last byte is complemented with 4 zero bits to form + // the last two base64 characters + return result + itostring(large(digest[15] << 4), 64, 2); +} + + +} diff --git a/source/ptypes/pmem.cxx b/source/ptypes/pmem.cxx @@ -0,0 +1,86 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <stdlib.h> + +#include "pport.h" + + +namespace ptypes { + + +const int quant = 64; +const int qmask = ~63; +const int quant2 = 4096; +const int qmask2 = ~4095; + +// dynamic reallocation policy for strings and lists + +int ptdecl memquantize(int a) +{ + if (a <= 16) + return 16; + if (a <= 32) + return 32; + else if (a <= 2048) + return (a + quant - 1) & qmask; + else + return (a + quant2 - 1) & qmask2; +} + + +void ptdecl memerror() +{ + fatal(CRIT_FIRST + 5, "Not enough memory"); +} + + +void* ptdecl memalloc(uint a) +{ + if (a == 0) + return nil; + else + { + void* p = malloc(a); + if (p == nil) + memerror(); + return p; + } +} + + +void* ptdecl memrealloc(void* p, uint a) +{ + if (a == 0) + { + memfree(p); + return nil; + } + else if (p == nil) + return memalloc(a); + else + { + p = realloc(p, a); + if (p == nil) + memerror(); + return p; + } +} + + +void ptdecl memfree(void* p) +{ + if (p != nil) + free(p); +} + + +} diff --git a/source/ptypes/pmsgq.cxx b/source/ptypes/pmsgq.cxx @@ -0,0 +1,282 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "pasync.h" + + +namespace ptypes { + + +static void msgerror() +{ + fatal(CRIT_FIRST + 42, "Invalid message object"); +} + + +message::message(int iid, pintptr iparam) + : next(nil), sync(nil), id(iid), param(iparam), result(0) +{ +} + + +message::~message() +{ +} + + +jobqueue::jobqueue(int ilimit) + : limit(ilimit), head(nil), tail(nil), qcount(0), sem(0), ovrsem(ilimit), qlock() +{ +} + + +jobqueue::~jobqueue() +{ + purgequeue(); +} + + +bool jobqueue::enqueue(message* msg, int timeout) +{ + if (msg == nil) + msgerror(); + + if (!ovrsem.wait(timeout)) + return false; + qlock.enter(); + msg->next = nil; + if (head != nil) + head->next = msg; + head = msg; + if (tail == nil) + tail = msg; + qcount++; + qlock.leave(); + sem.post(); + return true; +} + + +bool jobqueue::push(message* msg, int timeout) +{ + if (msg == nil) + msgerror(); + + if (!ovrsem.wait(timeout)) + return false; + qlock.enter(); + msg->next = tail; + tail = msg; + if (head == nil) + head = msg; + qcount++; + qlock.leave(); + sem.post(); + return true; +} + + +message* jobqueue::dequeue(bool safe, int timeout) +{ + if (!sem.wait(timeout)) + return nil; + if (safe) + qlock.enter(); + message* msg = tail; + tail = msg->next; + qcount--; + if (tail == nil) + head = nil; + if (safe) + qlock.leave(); + ovrsem.post(); + return msg; +} + + +void jobqueue::purgequeue() +{ + qlock.enter(); + while (get_count() > 0) + delete dequeue(false); + qlock.leave(); +} + + +bool jobqueue::post(message* msg, int timeout) +{ + return enqueue(msg, timeout); +} + + +bool jobqueue::post(int id, pintptr param, int timeout) +{ + return post(new message(id, param), timeout); +} + + +bool jobqueue::posturgent(message* msg, int timeout) +{ + return push(msg, timeout); +} + + +bool jobqueue::posturgent(int id, pintptr param, int timeout) +{ + return posturgent(new message(id, param), timeout); +} + + +message* jobqueue::getmessage(int timeout) +{ + return dequeue(true, timeout); +} + + +msgqueue::msgqueue(int ilimit) + : jobqueue(ilimit), thrlock(), owner(0), quit(false) +{ +} + + +msgqueue::~msgqueue() +{ +} + + +void msgqueue::takeownership() +{ + if (owner != pthrself()) + { + thrlock.enter(); // lock forever +// if (owner != 0) +// fatal(CRIT_FIRST + 45, "Ownership of the message queue already taken"); + owner = pthrself(); + } +} + + +pintptr msgqueue::finishmsg(message* msg) +{ + if (msg != nil) + { + pintptr result = msg->result; + + // if the message was sent by send(), + // just signale the semaphore + if (msg->sync != nil) + msg->sync->post(); + + // otherwise finish it + else + delete msg; + + return result; + } + else + return 0; +} + + +pintptr msgqueue::send(message* msg) +{ + if (msg == nil) + msgerror(); + + try + { + // if we are in the main thread, + // immediately handle the msg + if (pthrequal(owner)) + handlemsg(msg); + + // if this is called from a concurrent thread, + // sync through a semaphore + else + { + if (msg->sync != nil) + msgerror(); + semaphore sync(0); + msg->sync = &sync; + push(msg); + msg->sync->wait(); + msg->sync = 0; + } + } + catch (...) + { + finishmsg(msg); + throw; + } + + return finishmsg(msg); +} + + +pintptr msgqueue::send(int id, pintptr param) +{ + return send(new message(id, param)); +} + + +void msgqueue::processone() +{ + takeownership(); + message* msg = dequeue(); + try + { + handlemsg(msg); + } + catch(...) + { + finishmsg(msg); + throw; + } + finishmsg(msg); +} + + +void msgqueue::processmsgs() +{ + while (!quit && get_count() > 0) + processone(); +} + + +void msgqueue::run() +{ + quit = false; + do + { + processone(); + } + while (!quit); +} + + +void msgqueue::handlemsg(message* msg) +{ + msghandler(*msg); +} + + +void msgqueue::defhandler(message& msg) +{ + switch(msg.id) + { + case MSG_QUIT: + quit = true; + break; + } +} + + +} diff --git a/source/ptypes/pmtxtable.cxx b/source/ptypes/pmtxtable.cxx @@ -0,0 +1,42 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + + +#include "pasync.h" + + +namespace ptypes { + + +pmemlock _mtxtable[_MUTEX_HASH_SIZE] // currently 29 +#ifndef WIN32 + = { + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT, _MTX_INIT, + _MTX_INIT +} +#endif +; + + +} diff --git a/source/ptypes/pnpipe.cxx b/source/ptypes/pnpipe.cxx @@ -0,0 +1,290 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/un.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +#ifdef WIN32 + +string ptdecl namedpipe::realpipename(const string& pipename, const string& svrname) +{ + if (isempty(pipename)) + return nullstring; + string realname = pipename; + if (*pconst(pipename) == '/') + { + int i = rpos('/', realname); + del(realname, 0, i + 1); + } + string s; + if (isempty(svrname)) + s = '.'; + else + s = svrname; + return "\\\\" + s + "\\pipe\\" + realname; +} + + +#else + +string ptdecl namedpipe::realpipename(const string& pipename) +{ + if (isempty(pipename)) + return nullstring; + if (*pconst(pipename) == '/') + return pipename; + else + return DEF_NAMED_PIPES_DIR + pipename; +} + + +bool namedpipe::setupsockaddr(const string& pipename, void* isa) +{ + sockaddr_un* sa = (sockaddr_un*)isa; + memset(sa, 0, sizeof(sockaddr_un)); + sa->sun_family = AF_UNIX; +#ifdef __FreeBSD__ + sa->sun_len = length(pipename); +#endif + + // copy the path name into the sockaddr structure, 108 chars max (?) + if (length(pipename) + 1 > (int)sizeof(sa->sun_path)) + return false; + strcpy(sa->sun_path, pipename); + return true; +} + +#endif + + +namedpipe::namedpipe() + : fdxstm(), pipename(), svhandle(invhandle) +{ + initovr(); +} + + +namedpipe::namedpipe(const string& ipipename) + : fdxstm(), pipename(), svhandle(invhandle) +{ + pipename = realpipename(ipipename); + initovr(); +} + + +#ifdef WIN32 + +namedpipe::namedpipe(const string& ipipename, const string& servername) + : fdxstm(), pipename(), svhandle(invhandle) +{ + pipename = realpipename(ipipename, servername); + initovr(); +} + + +void namedpipe::initovr() +{ + ovr.hEvent = CreateEvent(0, false, false, 0); +} + + +#endif + + +namedpipe::~namedpipe() +{ + cancel(); +#ifdef WIN32 + CloseHandle(ovr.hEvent); +#endif +} + + +int namedpipe::classid() +{ + return CLASS3_NPIPE; +} + + +string namedpipe::get_streamname() +{ + return pipename; +} + + +void namedpipe::set_pipename(const string& newvalue) +{ + close(); + pipename = realpipename(newvalue); +} + + +void namedpipe::set_pipename(const char* newvalue) +{ + close(); + pipename = realpipename(newvalue); +} + + +large namedpipe::doseek(large, ioseekmode) +{ + return -1; +} + + +void namedpipe::doopen() +{ + +#ifdef WIN32 + + if (svhandle != invhandle) + handle = svhandle; + else + { + int tries = DEF_PIPE_OPEN_RETRY; + int delay = DEF_PIPE_OPEN_TIMEOUT / 2; +retry: + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + handle = int(CreateFileA(pipename, GENERIC_READ | GENERIC_WRITE, + 0, &sa, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0)); + if (handle == invhandle) + { + if (GetLastError() == ERROR_PIPE_BUSY) + { + if (--tries > 0) + { + delay *= 2; + Sleep(delay); + goto retry; + } + else + error(EIO, "Pipe busy"); + } + else + error(uerrno(), "Couldn't open named pipe"); + } + } + +#else + if (svhandle != invhandle) + { + if ((handle = ::accept(svhandle, 0, 0)) < 0) + error(uerrno(), "Couldn't create local socket"); + } + + else + { + sockaddr_un sa; + if (!setupsockaddr(pipename, &sa)) + error(ERANGE, "Socket name too long"); + + // cteate a client socket + if ((handle = ::socket(sa.sun_family, SOCK_STREAM, 0)) < 0) + error(uerrno(), "Couldn't create local socket"); + + // ... and connect to the local socket + if (::connect(handle, (sockaddr*)&sa, sizeof(sa)) < 0) + { + int e = uerrno(); + doclose(); + error(e, "Couldn't connect to local socket"); + } + } + +#endif +} + + +#ifdef WIN32 + +int namedpipe::dorawread(char* buf, int count) +{ + unsigned long ret = uint(-1); + ovr.Offset = 0; + ovr.OffsetHigh = 0; + if (!ReadFile(HANDLE(handle), buf, count, &ret, &ovr)) + { + if (GetLastError() == ERROR_IO_PENDING) + { + if (WaitForSingleObject(ovr.hEvent, DEF_PIPE_TIMEOUT) == WAIT_TIMEOUT) + error(EIO, "Timed out"); + if (!GetOverlappedResult(HANDLE(handle), &ovr, &ret, false)) + error(uerrno(), "Couldn't read"); + } + else + error(uerrno(), "Couldn't read"); + } + return ret; +} + + +int namedpipe::dorawwrite(const char* buf, int count) +{ + unsigned long ret = uint(-1); + ovr.Offset = 0; + ovr.OffsetHigh = 0; + if (!WriteFile(HANDLE(handle), buf, count, &ret, &ovr)) + { + if (GetLastError() == ERROR_IO_PENDING) + { + if (WaitForSingleObject(ovr.hEvent, DEF_PIPE_TIMEOUT) == WAIT_TIMEOUT) + error(EIO, "Timed out"); + if (!GetOverlappedResult(HANDLE(handle), &ovr, &ret, false)) + error(uerrno(), "Couldn't write"); + } + else + error(uerrno(), "Couldn't write"); + } + return ret; +} + + +#endif + + +void namedpipe::doclose() +{ +#ifdef WIN32 + if (svhandle != invhandle) + DisconnectNamedPipe(HANDLE(handle)); +#endif + svhandle = invhandle; + fdxstm::doclose(); +} + + +void namedpipe::flush() +{ +#ifdef WIN32 + if (!cancelled) + FlushFileBuffers(HANDLE(handle)); +#endif + fdxstm::flush(); +} + + + +} diff --git a/source/ptypes/pnpserver.cxx b/source/ptypes/pnpserver.cxx @@ -0,0 +1,185 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <sys/time.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/un.h> +# include <unistd.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +npserver::npserver(const string& ipipename) + : pipename(), handle(invhandle), active(false) +{ + pipename = namedpipe::realpipename(ipipename); +} + + +npserver::~npserver() +{ + close(); +} + + +void npserver::error(int code, const char* defmsg) +{ + string msg = unixerrmsg(code); + if (isempty(msg)) + msg = defmsg; + msg += " [" + pipename + ']'; + throw new estream(nil, code, msg); +} + + +#ifdef WIN32 + +void npserver::openinst() +{ + // called once at startup and then again, after + // each client connection. strange logic, to say the least... + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + handle = (int)CreateNamedPipeA(pipename, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + DEF_PIPE_SYSTEM_BUF_SIZE, DEF_PIPE_SYSTEM_BUF_SIZE, + DEF_PIPE_TIMEOUT, &sa); + + if (handle == invhandle) + error(unixerrno(), "Couldn't create"); +} + + +void npserver::closeinst() +{ + CloseHandle(HANDLE(pexchange(&handle, invhandle))); +} + +#endif + + +void npserver::open() +{ + close(); + +#ifdef WIN32 + + openinst(); + +#else + + sockaddr_un sa; + if (!namedpipe::setupsockaddr(pipename, &sa)) + error(ERANGE, "Socket name too long"); + + if ((handle = ::socket(sa.sun_family, SOCK_STREAM, 0)) < 0) + error(unixerrno(), "Couldn't create local socket"); + + unlink(pipename); + if (::bind(handle, (sockaddr*)&sa, sizeof(sa)) != 0) + error(unixerrno(), "Couldn't bind local socket"); + + if (::listen(handle, SOMAXCONN) != 0) + error(unixerrno(), "Couldn't listen on local socket"); + +#endif + + active = true; +} + + +void npserver::close() +{ + if (active) + { + active = false; +#ifdef WIN32 + closeinst(); +#else + ::close(pexchange(&handle, invhandle)); + unlink(pipename); +#endif + } +} + + +bool npserver::serve(namedpipe& client, int timeout) +{ + if (!active) + open(); + + client.cancel(); + +#ifdef WIN32 + + client.ovr.Offset = 0; + client.ovr.OffsetHigh = 0; + bool result = ConnectNamedPipe(HANDLE(handle), &client.ovr) ? + true : (GetLastError() == ERROR_PIPE_CONNECTED); + + if (!result && GetLastError() == ERROR_IO_PENDING) + { + if (WaitForSingleObject(client.ovr.hEvent, timeout) == WAIT_TIMEOUT) + return false; + unsigned long ret; + if (!GetOverlappedResult(HANDLE(handle), &client.ovr, &ret, false)) + error(unixerrno(), "Couldn't read"); + result = true; + } + + if (result) + { + client.svhandle = handle; + client.pipename = pipename; + openinst(); + client.open(); + return true; + } + else + error(unixerrno(), "Couldn't connect to client"); + + return false; + +#else + + fd_set set; + FD_ZERO(&set); + FD_SET((uint)handle, &set); + timeval t; + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + if (::select(FD_SETSIZE, &set, nil, nil, (timeout < 0) ? nil : &t) > 0) + { + client.svhandle = handle; + client.pipename = pipename; + client.open(); + return true; + } + return false; + +#endif +} + + +} diff --git a/source/ptypes/pobjlist.cxx b/source/ptypes/pobjlist.cxx @@ -0,0 +1,156 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +_objlist::_objlist() + : tpodlist<void*,true>() +{ + memset(&config, 0, sizeof(config)); +} + + +_objlist::_objlist(bool ownobjects) + : tpodlist<void*,true>() +{ + memset(&config, 0, sizeof(config)); + config.ownobjects = ownobjects; +} + + +_objlist::~_objlist() +{ +} + + +void _objlist::dofree(void*) +{ + fatal(CRIT_FIRST + 38, "ptrlist::dofree() not defined"); +} + + +int _objlist::compare(const void*, const void*) const +{ + fatal(CRIT_FIRST + 38, "ptrlist::compare() not defined"); + return 0; +} + + +void _objlist::dofree(int index, int num) +{ + void** p = (void**)list + index; + while (--num >= 0) + dofree(*p++); +} + + +void _objlist::doput(int index, void* obj) +{ + void** p = (void**)list + index; + if (config.ownobjects) + dofree(*p); + *p = obj; +} + + +void _objlist::dodel(int index) +{ + if (config.ownobjects) + dofree(doget(index)); + tpodlist<void*, true>::dodel(index); +} + + +void _objlist::dodel(int index, int delcount) +{ + if (config.ownobjects) + { + if (index + delcount > count) + delcount = count - index; + dofree(index, delcount); + } + tpodlist<void*, true>::dodel(index, delcount); +} + + +void _objlist::set_count(int newcount) +{ + if (newcount < count && config.ownobjects) + { + if (newcount < 0) + newcount = 0; + dofree(newcount, count - newcount); + } + _podlist::set_count(newcount, true); +} + + +void* _objlist::dopop() +{ + void* t = doget(--count); + if (count == 0) + set_capacity(0); + return t; +} + + +bool _objlist::search(const void* key, int& index) const +{ + int l, h, i, c; + bool ret = false; + l = 0; + h = count - 1; + while (l <= h) + { + i = (l + h) / 2; + c = compare(key, doget(i)); + if (c > 0) + l = i + 1; + else + { + h = i - 1; + if (c == 0) + { + ret = true; + if (!config.duplicates) + l = i; + } + } + } + index = l; + return ret; +} + + +int _objlist::indexof(void* obj) const +{ + for (int i = 0; i < count; i++) + if (doget(i) == obj) + return i; + return -1; +} + + +#ifdef PTYPES19_COMPAT + +objlist::objlist(bool ownobjects): tobjlist<unknown>(ownobjects) {} + +objlist::~objlist() {} + +#endif + + +} + diff --git a/source/ptypes/poutfile.cxx b/source/ptypes/poutfile.cxx @@ -0,0 +1,111 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "errno.h" + +#ifdef WIN32 +# include <windows.h> +#else +# include <fcntl.h> +# include <unistd.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +// *BSD hack +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif + + +outfile::outfile() + : outstm(), filename(), syshandle(invhandle), peerhandle(invhandle), + umode(0644), append(false) {} + + +outfile::outfile(const char* ifn, bool iappend) + : outstm(), filename(ifn), syshandle(invhandle), peerhandle(invhandle), + umode(0644), append(iappend) {} + + +outfile::outfile(string const& ifn, bool iappend) + : outstm(), filename(ifn), syshandle(invhandle), peerhandle(invhandle), + umode(0644), append(iappend) {} + + +outfile::~outfile() +{ + close(); +} + + +int outfile::classid() +{ + return CLASS2_OUTFILE; +} + + +string outfile::get_streamname() +{ + return filename; +} + + +void outfile::doopen() +{ + if (syshandle != invhandle) + handle = syshandle; + else + { +#ifdef WIN32 + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = TRUE; + + handle = int(CreateFileA(filename, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, + (append ? OPEN_ALWAYS : CREATE_ALWAYS), 0, 0)); +#else + handle = ::open(filename, + O_WRONLY | O_CREAT | O_LARGEFILE | (append ? 0 : O_TRUNC), umode); +#endif + if (handle == invhandle) + error(uerrno(), "Couldn't open"); + if (append) + if (doseek(0, IO_END) == -1) + error(uerrno(), "Couldn't seek to end of file"); + } +} + + +void outfile::flush() +{ + outstm::flush(); +#ifdef WIN32 + FlushFileBuffers(HANDLE(handle)); +#endif +} + + +void outfile::doclose() +{ + outstm::doclose(); + syshandle = invhandle; + peerhandle = invhandle; +} + + +} diff --git a/source/ptypes/poutfilter.cxx b/source/ptypes/poutfilter.cxx @@ -0,0 +1,77 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <string.h> + +#include "pstreams.h" + + +namespace ptypes { + + +outfilter::outfilter(outstm* istm, int ibufsize) +: outstm(false, ibufsize), stm(istm) +{ + if (stm != nil) + stm->addnotification(this); +} + + +outfilter::~outfilter() +{ + if (stm != nil) + stm->delnotification(this); +} + + +void outfilter::freenotify(component* sender) +{ + if (sender == stm) + { + stm = nil; + close(); + } +} + + +void outfilter::doopen() +{ + if (stm != nil && !stm->get_active()) + stm->open(); +} + + +void outfilter::doclose() +{ +} + + +string outfilter::get_errstmname() +{ + if (stm == nil) + return get_streamname(); + else + return get_streamname() + ": " + stm->get_errstmname(); +} + + +void outfilter::set_stm(outstm* istm) +{ + close(); + if (stm != nil) + stm->delnotification(this); + stm = istm; + if (stm != nil) + stm->addnotification(this); +} + + +} diff --git a/source/ptypes/poutmem.cxx b/source/ptypes/poutmem.cxx @@ -0,0 +1,104 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "pstreams.h" + + +namespace ptypes { + + +outmemory::outmemory(int ilimit) + : outstm(false, 0), mem(), limit(ilimit) +{ +} + + +outmemory::~outmemory() +{ + close(); +} + + +int outmemory::classid() +{ + return CLASS2_OUTMEMORY; +} + + +void outmemory::doopen() +{ +} + + +void outmemory::doclose() +{ + clear(mem); +} + + +large outmemory::doseek(large newpos, ioseekmode mode) +{ + large pos; + + switch (mode) + { + case IO_BEGIN: + pos = newpos; + break; + case IO_CURRENT: + pos = abspos + newpos; + break; + default: // case IO_END: + pos = length(mem) + newpos; + break; + } + + if (limit >= 0 && pos > limit) + pos = limit; + + return pos; +} + + +int outmemory::dorawwrite(const char* buf, int count) +{ + if (count <= 0) + return 0; + if (limit >= 0 && abspos + count > limit) + { + count = limit - (int)abspos; + if (count <= 0) + return 0; + } + + // the string reallocator takes care of efficiency + if ((int)abspos + count > length(mem)) + setlength(mem, (int)abspos + count); + memcpy(pchar(pconst(mem)) + (int)abspos, buf, count); + return count; +} + + +string outmemory::get_streamname() +{ + return "mem"; +} + + +string outmemory::get_strdata() +{ + if (!active) + errstminactive(); + return mem; +} + + +} diff --git a/source/ptypes/poutstm.cxx b/source/ptypes/poutstm.cxx @@ -0,0 +1,215 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <string.h> + +#ifdef WIN32 +# include <windows.h> +#else +# include <unistd.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +outstm::outstm(bool iflusheol, int ibufsize) + : iobase(ibufsize), flusheol(iflusheol) {} + + +outstm::~outstm() +{ +} + + +int outstm::classid() +{ + return CLASS_OUTSTM; +} + + +int outstm::dorawwrite(const char* buf, int count) +{ + if (handle == invhandle) + return -1; +#ifdef WIN32 + unsigned long ret; + if (!WriteFile(HANDLE(handle), buf, count, &ret, nil)) + { + error(uerrno(), "Couldn't write"); + ret = uint(-1); + } +#else + int ret; + if ((ret = ::write(handle, buf, count)) < 0) + error(uerrno(), "Couldn't write"); +#endif + return ret; +} + + +int outstm::rawwrite(const char* buf, int count) +{ + if (!active) + errstminactive(); + try + { + int ret = dorawwrite(buf, count); + if (ret < 0) + ret = 0; + else + abspos += ret; + chstat(IO_WRITING); + if (ret < count) + { + eof = true; + chstat(IO_EOF); + } + return ret; + } + catch (estream*) + { + eof = true; + chstat(IO_EOF); + throw; + } +} + + +void outstm::bufvalidate() +{ + if (!active) + errstminactive(); + if (bufend > 0) + rawwrite(bufdata, bufend); + bufclear(); +} + + +large outstm::seekx(large newpos, ioseekmode mode) +{ + if (bufdata != 0 && mode != IO_END) + { + large pos; + if (mode == IO_BEGIN) + pos = newpos; + else + pos = tellx() + newpos; + pos -= abspos; + if (pos >= 0 && pos <= bufpos) + { + bufpos = (int)pos; + eof = false; + return tellx(); + } + } + return iobase::seekx(newpos, mode); +} + + +bool outstm::canwrite() +{ + if (bufdata != 0 && bufpos >= bufsize) + { + bufvalidate(); + return bufend < bufsize; + } + else + return true; +} + + +void outstm::flush() +{ + if (bufdata != 0 && stmerrno == 0) + bufvalidate(); +} + + +void outstm::put(char c) +{ + if (!active) + errstminactive(); + if (bufdata == 0) + rawwrite(&c, 1); + else if (canwrite()) + { + bufdata[bufpos] = c; + bufadvance(1); + if (c == 10 && flusheol) + flush(); + } +} + + +int outstm::write(const void* buf, int count) +{ + if (!active) + errstminactive(); + int ret = 0; + if (bufdata == 0) + ret = rawwrite(pconst(buf), count); + else + { + while (count > 0 && canwrite()) + { + int n = imin(count, bufsize - bufpos); + memcpy(bufdata + bufpos, buf, n); + ret += n; + count -= n; + buf = pconst(buf) + n; + bufadvance(n); + } + } + + return ret; +} + + +void outstm::put(const char* str) +{ + if (str != nil) + write(str, hstrlen(str)); +} + + +void outstm::put(const string& str) +{ + write(pconst(str), length(str)); +} + + +void outstm::putline(const char* s) +{ + put(s); + puteol(); +} + + +void outstm::putline(const string& s) +{ + put(s); + puteol(); +} + + +void outstm::puteol() +{ +#ifdef WIN32 + put(13); +#endif + put(10); +} + + +} diff --git a/source/ptypes/ppipe.cxx b/source/ptypes/ppipe.cxx @@ -0,0 +1,51 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <unistd.h> +#endif + +#include "pstreams.h" + + +namespace ptypes { + + +void infile::pipe(outfile& out) +{ +#ifdef WIN32 + + SECURITY_ATTRIBUTES sa; + HANDLE h[2]; + + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&h[0], &h[1], &sa, 0)) +#else + int h[2]; + if (::pipe(h) != 0) +#endif + error(uerrno(), "Couldn't create a local pipe"); + + set_syshandle(int(h[0])); + peerhandle = int(h[1]); + out.set_syshandle(int(h[1])); + out.peerhandle = int(h[0]); + open(); + out.open(); +} + + +} diff --git a/source/ptypes/ppodlist.cxx b/source/ptypes/ppodlist.cxx @@ -0,0 +1,192 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +void _podlist::idxerror() +{ + fatal(CRIT_FIRST + 30, "List index out of bounds"); +} + + +_podlist::_podlist(int iitemsize) + : list(0), count(0), capacity(0), itemsize(iitemsize) +{ + if (itemsize <= 0 || itemsize > 255) + fatal(CRIT_FIRST + 37, "Invalid item size for podlist"); +} + + +_podlist::~_podlist() +{ + set_count(0); +} + + +void _podlist::set_capacity(int newcap) +{ + if (newcap != capacity) + { + if (newcap < count) + fatal(CRIT_FIRST + 36, "List capacity can't be smaller than count"); + list = memrealloc(list, newcap * itemsize); + capacity = newcap; + } +} + + +void _podlist::grow() +{ + if (capacity > count) + return; + set_capacity(capacity == 0 ? 4 : ((capacity + 1) / 2) * 3); +} + + +void _podlist::set_count(int newcount, bool zero) +{ + if (newcount > count) + { + if (newcount > capacity) + set_capacity(newcount); + if (zero) + memset(doget(count), 0, (newcount - count) * itemsize); + count = newcount; + } + else if (newcount < count) + { + if (newcount < 0) + // wrong newcount: we don't want to generate an error here. + // instead, we'll set count to 0 and wait until some other + // operation raises an error + newcount = 0; + count = newcount; + if (count == 0) + set_capacity(0); + } +} + + +// doXXX() methods do not perform bounds checking; used internally +// where index is guaranteed to be valid + +void* _podlist::doins(int index) +{ + grow(); + pchar s = pchar(doget(index)); + if (index < count) + memmove(s + itemsize, s, (count - index) * itemsize); + count++; + return s; +} + + +void _podlist::doins(int index, const _podlist& t) +{ + if (&t == this) + return; + if (index == count) + add(t); + else + { + if (itemsize != t.itemsize) + fatal(CRIT_FIRST + 35, "Incompatible list"); + if (t.count == 0) + return; + int ocnt = count; + set_count(ocnt + t.count); + pchar s = pchar(doget(index)); + memmove(s + t.count * itemsize, s, (ocnt - index) * itemsize); + memcpy(s, t.list, t.count * itemsize); + } +} + + +void* _podlist::add() +{ + grow(); + return doget(count++); +} + + +void _podlist::add(const _podlist& t) +{ + if (count == 0) + operator =(t); + else + { + if (itemsize != t.itemsize) + fatal(CRIT_FIRST + 35, "Incompatible list"); + int ocnt = count; + int tcnt = t.count; + set_count(ocnt + tcnt); + memcpy(doget(ocnt), t.list, tcnt * itemsize); + } +} + + +_podlist& _podlist::operator =(const _podlist& t) +{ + if (&t != this) + { + if (itemsize != t.itemsize) + fatal(CRIT_FIRST + 35, "Incompatible list"); + set_count(t.count); + pack(); + memcpy(list, t.list, count * itemsize); + } + return *this; +} + + +void _podlist::dodel(int index) +{ + count--; + if (index < count) + { + pchar s = pchar(doget(index)); + memmove(s, s + itemsize, (count - index) * itemsize); + } + else if (count == 0) + set_capacity(0); +} + + +void _podlist::dodel(int index, int delcount) +{ + if (delcount <= 0) + return; + if (index + delcount > count) + delcount = count - index; + count -= delcount; + if (index < count) + { + pchar s = pchar(doget(index)); + memmove(s, s + delcount * itemsize, (count - index) * itemsize); + } + else if (count == 0) + set_capacity(0); +} + + +void _podlist::dopop() +{ + if (--count == 0) + set_capacity(0); +} + + +} + diff --git a/source/ptypes/pport.h b/source/ptypes/pport.h @@ -0,0 +1,189 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifndef __PPORT_H__ +#define __PPORT_H__ + + +#if defined(linux) +# include <stdint.h> // for uintptr_t +#endif + +#include <sys/types.h> + + +#ifndef __cplusplus +# error "This is a C++ source" +#endif + +// +// Windows DLL export/import and calling convention macros +// + +#ifdef WIN32 +# if defined(PTYPES_DLL_EXPORTS) +# define ptpublic __declspec(dllexport) +# elif defined(PTYPES_DLL) +# define ptpublic __declspec(dllimport) +# else +# define ptpublic +# endif +# define ptdecl __stdcall +# define __PFASTCALL __fastcall +#else +# define ptpublic +# define ptdecl +# define __PFASTCALL +#endif + + +// +// versioning +// + + +extern "C" ptpublic unsigned long __ptypes_version; + +// this enables old algebraic list interfaces; NO_PTYPES19_COMPAT +// can be defined at command line +#if !defined(NO_PTYPES19_COMPAT) +# define PTYPES19_COMPAT +#endif + + +namespace ptypes { + + +#ifdef _MSC_VER +// we don't want "unreferenced inline function" warning +# pragma warning (disable: 4514) +// ... also "copy constructor/assignment operator could not be generated" +# pragma warning (disable: 4511) +# pragma warning (disable: 4512) +// disable deprecation warnings for snprintf and others +# pragma warning (disable: 4996) +#endif + +#if defined(_DEBUG) && !defined(DEBUG) +# define DEBUG +#endif + +#if defined(__WIN32__) && !defined(WIN32) +# define WIN32 +#endif + +// __APPLE__ is the only predefined macro on MacOS X +#if defined(__APPLE__) +# define __DARWIN__ +#endif + +// CHECK_BOUNDS enables bounds checking for strings and lists +#if defined(DEBUG) && !defined(CHECK_BOUNDS) +# define CHECK_BOUNDS +#endif + +// COUNT_OBJALLOC helps to keep track of the number of +// objects created/destroyed +#if defined(DEBUG) && !defined(COUNT_OBJALLOC) +# define COUNT_OBJALLOC +#endif + + +// +// useful typedefs +// + +typedef unsigned int uint; +typedef unsigned long ulong; +typedef unsigned short ushort; +typedef unsigned char uchar; +typedef signed char schar; +typedef char* pchar; +typedef const char* pconst; +typedef void* ptr; +typedef int* pint; + +#ifdef WIN32 + typedef size_t pintptr; +#else + typedef uintptr_t pintptr; +#endif + + +// +// portable 64-bit integers +// + +#if defined(_MSC_VER) || defined(__BORLANDC__) + typedef __int64 large; + typedef unsigned __int64 ularge; +# define LLCONST(a) (a##i64) +#else + typedef long long large; + typedef unsigned long long ularge; +# define LLCONST(a) (a##ll) +#endif + +#define LARGE_MIN (LLCONST(-9223372036854775807)-1) +#define LARGE_MAX (LLCONST(9223372036854775807)) +#define ULARGE_MAX (LLCONST(18446744073709551615u)) + +#if defined(_MSC_VER) || defined(__BORLANDC__) +# define strcasecmp stricmp +#endif +#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(__BORLANDC__) +# define snprintf _snprintf +#endif + + +// +// misc. +// + +// I like Pascal's nil +#define nil 0 + +inline int imax(int x, int y) { return (x > y) ? x : y; } +inline int imin(int x, int y) { return (x < y) ? x : y; } +inline large lmax(large x, large y) { return (x > y) ? x : y; } +inline large lmin(large x, large y) { return (x < y) ? x : y; } + + +// +// critical error processing +// + +#define CRIT_FIRST 0xC0000 + +typedef void (ptdecl *_pcrithandler)(int code, const char* msg); + +ptpublic _pcrithandler ptdecl getcrithandler(); +ptpublic _pcrithandler ptdecl setcrithandler(_pcrithandler newh); + +ptpublic void ptdecl fatal(int code, const char* msg); + + +// +// memory management (undocumented) +// hides some BSD* incompatibility issues +// + +ptpublic void* ptdecl memalloc(uint a); +ptpublic void* ptdecl memrealloc(void* p, uint a); +ptpublic void ptdecl memfree(void* p); +ptpublic void ptdecl memerror(); +ptpublic int ptdecl memquantize(int); + + +} + + +#endif // __PPORT_H__ diff --git a/source/ptypes/pputf.cxx b/source/ptypes/pputf.cxx @@ -0,0 +1,287 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> + +#include "ptypes.h" +#include "pstreams.h" +#include "pinet.h" // for ipaddress type +#include "ptime.h" // for dttotm() + +#ifndef PTYPES_ST +#include "pasync.h" // for mutex locking in logfile::vputf() +#endif + + +namespace ptypes { + + +// %t and %T formats +const char* const shorttimefmt = "%d-%b-%Y %X"; +const char* const longtimefmt = "%a %b %d %X %Y"; + + +static cset fmtopts = " #+~-0-9."; + + +enum fmt_type_t +{ + FMT_NONE, + FMT_CHAR, + FMT_SHORT, + FMT_INT, + FMT_LONG, + FMT_LARGE, + FMT_STR, + FMT_PTR, + FMT_DOUBLE, + FMT_LONG_DOUBLE, + FMT_IPADDR, + FMT_TIME, + FMT_LONGTIME +}; + + +void outstm::vputf(const char* fmt, va_list va) +{ + const char* p = fmt; + while (*p != 0) + { + // write out raw data between format specifiers + const char* e = strchr(p, '%'); + if (e == 0) + e = p + strlen(p); + if (e > p) + write(p, e - p); + + if (*e != '%') + break; + + e++; + if (*e == '%') + { + // write out a single '%' + put('%'); + p = e + 1; + continue; + } + + // build a temporary buffer for the conversion specification + char fbuf[128]; + fbuf[0] = '%'; + char* f = fbuf + 1; + bool modif = false; + + // formatting flags and width specifiers + while (*e & fmtopts && uint(f - fbuf) < sizeof(fbuf) - 5) + { + *f++ = *e++; + modif = true; + } + + // prefixes + fmt_type_t fmt_type = FMT_NONE; + switch(*e) + { + case 'h': + fmt_type = FMT_SHORT; + *f++ = *e++; + break; + case 'L': + fmt_type = FMT_LONG_DOUBLE; + *f++ = *e++; + break; + case 'l': + e++; + if (*e == 'l') + { +#if defined(_MSC_VER) || defined(__BORLANDC__) + *f++ = 'I'; + *f++ = '6'; + *f++ = '4'; +#else + *f++ = 'l'; + *f++ = 'l'; +#endif + e++; + fmt_type = FMT_LARGE; + } + else + { + *f++ = 'l'; + fmt_type = FMT_LONG; + } + break; + } + + // format specifier + switch(*e) + { + case 'c': + fmt_type = FMT_CHAR; + *f++ = *e++; + break; + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + if (fmt_type < FMT_SHORT || fmt_type > FMT_LARGE) + fmt_type = FMT_INT; + *f++ = *e++; + break; + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + if (fmt_type != FMT_LONG_DOUBLE) + fmt_type = FMT_DOUBLE; + *f++ = *e++; + break; + case 's': + fmt_type = FMT_STR; + *f++ = *e++; + break; + case 'p': + fmt_type = FMT_PTR; + *f++ = *e++; + break; + case 'a': + fmt_type = FMT_IPADDR; + *f++ = *e++; + break; + case 't': + fmt_type = FMT_TIME; + *f++ = *e++; + break; + case 'T': + fmt_type = FMT_LONGTIME; + *f++ = *e++; + break; + } + + if (fmt_type == FMT_NONE) + break; + + *f = 0; + + // some formatters are processed here 'manually', + // while others are passed to snprintf + char buf[4096]; + int s = 0; + switch(fmt_type) + { + case FMT_NONE: + break; // to avoid compiler warning + case FMT_CHAR: + if (modif) + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,int)); + else + put(char(va_arg(va,int))); + break; + case FMT_SHORT: + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,int)); + break; + case FMT_INT: + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,int)); + break; + case FMT_LONG: + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,long)); + break; + case FMT_LARGE: + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,large)); + break; + case FMT_STR: + if (modif) + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,char*)); + else + put(va_arg(va,const char*)); + break; + case FMT_PTR: + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,void*)); + break; + case FMT_DOUBLE: + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,double)); + break; + case FMT_LONG_DOUBLE: + s = snprintf(buf, sizeof(buf), fbuf, va_arg(va,long double)); + break; + + case FMT_IPADDR: + { + ipaddress ip = va_arg(va,long); + s = snprintf(buf, sizeof(buf), "%d.%d.%d.%d", + uint(ip[0]), uint(ip[1]), uint(ip[2]), uint(ip[3])); + } + break; + + case FMT_TIME: + case FMT_LONGTIME: + { + const char* const fmt = (fmt_type == FMT_TIME) ? shorttimefmt : longtimefmt; + struct tm t; + datetime dt = va_arg(va,large); + if (dt < 0) + dt = 0; + s = strftime(buf, sizeof(buf), fmt, dttotm(dt, t)); + } + break; + } + if (s > 0) + write(buf, s); + + p = e; + } +} + + +void outstm::putf(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + vputf(fmt, va); + va_end(va); +} + + +void fdxstm::putf(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + out.vputf(fmt, va); + va_end(va); +} + + +void logfile::vputf(const char* fmt, va_list va) +{ +#ifndef PTYPES_ST + scopelock sl(lock); +#endif + outfile::vputf(fmt, va); +} + + +void logfile::putf(const char* fmt, ...) +{ + va_list va; + va_start(va, fmt); + vputf(fmt, va); + va_end(va); +} + + +} diff --git a/source/ptypes/prwlock.cxx b/source/ptypes/prwlock.cxx @@ -0,0 +1,193 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <pthread.h> +#endif + +#include "pasync.h" + + +namespace ptypes { + + +static void rwlock_fail() +{ + fatal(CRIT_FIRST + 41, "rwlock failed"); +} + + +inline void rwlock_syscheck(int r) +{ + if (r != 0) + rwlock_fail(); +} + + +#ifdef __PTYPES_RWLOCK__ + + +# ifdef WIN32 + +// +// this implementation of the read/write lock is derived +// from Apache Portable Runtime (APR) source, which +// in turn was originally based on an erroneous (or just +// incomplete?) example in one of the MSDN technical articles. +// + +rwlock::rwlock() + : mutex(), readcnt(-1), writecnt(0) +{ + reading = CreateEvent(0, true, false, 0); + finished = CreateEvent(0, false, true, 0); + if (reading == 0 || finished == 0) + rwlock_fail(); +} + + +rwlock::~rwlock() +{ + CloseHandle(reading); + CloseHandle(finished); +} + + +void rwlock::rdlock() +{ + if (pincrement(&readcnt) == 0) + { + WaitForSingleObject(finished, INFINITE); + SetEvent(reading); + } + WaitForSingleObject(reading, INFINITE); +} + + +void rwlock::wrlock() +{ + mutex::enter(); + WaitForSingleObject(finished, INFINITE); + writecnt++; +} + + +void rwlock::unlock() +{ + if (writecnt != 0) + { + writecnt--; + SetEvent(finished); + mutex::leave(); + } + else if (pdecrement(&readcnt) < 0) + { + ResetEvent(reading); + SetEvent(finished); + } +} + + +# else // !defined(WIN32) + +// +// for other platforms that lack POSIX rwlock we implement +// the rwlock object using POSIX condvar. the code below +// is based on Sean Burke's algorithm posted in +// comp.programming.threads. +// + + +rwlock::rwlock() + : locks(0), writers(0), readers(0) +{ + rwlock_syscheck(pthread_mutex_init(&mtx, 0)); + rwlock_syscheck(pthread_cond_init(&readcond, 0)); + rwlock_syscheck(pthread_cond_init(&writecond, 0)); +} + + +rwlock::~rwlock() +{ + pthread_cond_destroy(&writecond); + pthread_cond_destroy(&readcond); + pthread_mutex_destroy(&mtx); +} + + +void rwlock::rdlock() +{ + pthread_mutex_lock(&mtx); + readers++; + while (locks < 0) + pthread_cond_wait(&readcond, &mtx); + readers--; + locks++; + pthread_mutex_unlock(&mtx); +} + + +void rwlock::wrlock() +{ + pthread_mutex_lock(&mtx); + writers++; + while (locks != 0) + pthread_cond_wait(&writecond, &mtx); + locks = -1; + writers--; + pthread_mutex_unlock(&mtx); +} + + +void rwlock::unlock() +{ + pthread_mutex_lock(&mtx); + if (locks > 0) + { + locks--; + if (locks == 0) + pthread_cond_signal(&writecond); + } + else + { + locks = 0; + if (readers != 0) + pthread_cond_broadcast(&readcond); + else + pthread_cond_signal(&writecond); + } + pthread_mutex_unlock(&mtx); +} + + +# endif // !defined(WIN32) + + +#else // !defined(__PTYPES_RWLOCK__) + +// +// for other systems we declare a fully-inlined rwlock +// object in pasync.h +// + +rwlock::rwlock() +{ + rwlock_syscheck(pthread_rwlock_init(&rw, 0)); +} + + +#endif + + + +} diff --git a/source/ptypes/psemaphore.cxx b/source/ptypes/psemaphore.cxx @@ -0,0 +1,76 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <errno.h> + +#ifdef WIN32 +# include <windows.h> +#else +# include <pthread.h> +#endif + +#include "pasync.h" + + +namespace ptypes { + + +#ifndef __SEM_TO_TIMEDSEM__ + + +static void sem_fail() +{ + fatal(CRIT_FIRST + 41, "Semaphore failed"); +} + + +semaphore::semaphore(int initvalue) +{ + if (sem_init(&handle, 0, initvalue) != 0) + sem_fail(); +} + + +semaphore::~semaphore() +{ + sem_destroy(&handle); +} + + +void semaphore::wait() +{ + int err; + do { + err = sem_wait(&handle); + } while (err == -1 && errno == EINTR); + if (err != 0) + sem_fail(); +} + + +void semaphore::post() +{ + if (sem_post(&handle) != 0) + sem_fail(); +} + + +#else + + +int _psemaphore_dummy_symbol; // avoid ranlib's warning message + + +#endif + + + +} diff --git a/source/ptypes/pstdio.cxx b/source/ptypes/pstdio.cxx @@ -0,0 +1,155 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "pport.h" +#include "pstreams.h" + +#ifdef WIN32 +# include <windows.h> +#else +# include <unistd.h> +#endif + + +namespace ptypes { + +infile pin; +logfile pout; +logfile perr; +outnull pnull; + + +static class _stdio_init +{ +public: + _stdio_init(); +} _stdio_init_inst; + + +#ifdef WIN32 + +static HANDLE DuplicateSysHandle(DWORD stdh) +{ + HANDLE hold = GetStdHandle(stdh); + HANDLE hnew = 0; + DuplicateHandle(GetCurrentProcess(), hold, GetCurrentProcess(), + &hnew, 0, true, DUPLICATE_SAME_ACCESS); + return hnew; +} + +#endif + + +_stdio_init::_stdio_init() +{ +#ifdef WIN32 + pin.set_syshandle(int(DuplicateSysHandle(STD_INPUT_HANDLE))); + pout.set_syshandle(int(DuplicateSysHandle(STD_OUTPUT_HANDLE))); + perr.set_syshandle(int(DuplicateSysHandle(STD_ERROR_HANDLE))); +#else + pin.set_syshandle(::dup(STDIN_FILENO)); + pout.set_syshandle(::dup(STDOUT_FILENO)); + perr.set_syshandle(::dup(STDERR_FILENO)); +#endif + + pin.set_bufsize(4096); + pin.open(); + pout.open(); + perr.open(); + + pnull.open(); + + // prevent others from freeing these objects, if assigned to a variant. + // will need to handle reference counting for static objects better. any ideas? + addref(&pin); + addref(&pout); + addref(&perr); + addref(&pnull); + + // this is to show objalloc = 0 at program exit + objalloc -= 4; +} + + +// +// null output stream +// + + +outnull::outnull() + : outstm(0) +{ +} + + +outnull::~outnull() +{ + close(); +} + + +int outnull::dorawwrite(const char*, int) +{ + return 0; +} + + +void outnull::doopen() +{ +} + + +void outnull::doclose() +{ +} + + +string outnull::get_streamname() +{ + return "<null>"; +} + + +// +// logfile - file output with thread-safe putf() +// + +logfile::logfile(): outfile() +{ + set_bufsize(0); +} + + +logfile::logfile(const char* ifn, bool iappend): outfile(ifn, iappend) +{ + set_bufsize(0); +} + + +logfile::logfile(const string& ifn, bool iappend): outfile(ifn, iappend) +{ + set_bufsize(0); +} + + +logfile::~logfile() +{ +} + + +int logfile::classid() +{ + return CLASS3_LOGFILE; +} + + +} + diff --git a/source/ptypes/pstrcase.cxx b/source/ptypes/pstrcase.cxx @@ -0,0 +1,73 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +string ptdecl lowercase(const char* p) +{ + // we rely on the function locase() which converts one single + // character to lower case. all locale specific things can be + // settled down in the future releases. + string r; + if (p != nil) + { + char* d = setlength(r, strlen(p)); + while (*p != 0) + *d++ = locase(*p++); + } + return r; +} + + +string ptdecl lowercase(const string& s) +{ + // this function does practically nothing if the string s + // contains no uppercase characters. once an uppercase character + // is encountered, the string is copied to another buffer and the + // rest is done as usual. + + string r = s; + + // a trick to get a non-const pointer without making + // the string unique + char* p = pchar(pconst(r)); + bool u = false; + int i = 0; + + while (*p != 0) + { + char c = locase(*p); + // if the character went lowercase... + if (c != *p) + { + // if the resulting string r is not unique yet... + if (!u) + { + // ... make it unique and adjust the pointer p accordingly + // this is done only once. + p = unique(r) + i; + u = true; + } + *p = c; + } + p++; + i++; + } + + return r; +} + + +} diff --git a/source/ptypes/pstrconv.cxx b/source/ptypes/pstrconv.cxx @@ -0,0 +1,147 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <string.h> + +#include "ptypes.h" + + +namespace ptypes { + + +static const char* _itobase(large value, char* buf, int base, int& len, bool _signed) +{ + // internal conversion routine: converts the value to a string + // at the end of the buffer and returns a pointer to the first + // character. this is to get rid of copying the string to the + // beginning of the buffer, since finally the string is supposed + // to be copied to a dynamic string in itostring(). the buffer + // must be at least 65 bytes long. + + static char digits[65] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + char* pdigits; + if (base > 36) + pdigits = digits; // start from '.' + else + pdigits = digits + 2; // start from '0' + + int i = 64; + buf[i] = 0; + + bool neg = false; + ularge v = value; + if (_signed && base == 10 && value < 0) + { + v = -value; + // since we can't handle the lowest signed 64-bit value, we just + // return a built-in string. + if (large(v) < 0) // the LLONG_MIN negated results in the same value + { + len = 20; + return "-9223372036854775808"; + } + neg = true; + } + + do + { + buf[--i] = pdigits[uint(v % base)]; + v /= base; + } while (v > 0); + + if (neg) + buf[--i] = '-'; + + len = 64 - i; + return buf + i; +} + + +static void _itobase2(string& result, large value, int base, int width, char padchar, bool _signed) +{ + if (base < 2 || base > 64) + { + clear(result); + return; + } + + char buf[65]; // the longest possible string is when base=2 + int reslen; + const char* p = _itobase(value, buf, base, reslen, _signed); + + if (width > reslen) + { + if (padchar == 0) + { + // default pad char + if (base == 10) + padchar = ' '; + else if (base > 36) + padchar = '.'; + else + padchar = '0'; + } + + setlength(result, width); + bool neg = *p == '-'; + width -= reslen; + memset(pchar(pconst(result)) + neg, padchar, width); + memcpy(pchar(pconst(result)) + width + neg, p + neg, reslen - neg); + if (neg) + *pchar(pconst(result)) = '-'; + } + else + assign(result, p, reslen); +} + + +string ptdecl itostring(large value, int base, int width, char padchar) +{ + string result; + _itobase2(result, value, base, width, padchar, true); + return result; +} + + +string ptdecl itostring(ularge value, int base, int width, char padchar) +{ + string result; + _itobase2(result, value, base, width, padchar, false); + return result; +} + + +string ptdecl itostring(int value, int base, int width, char padchar) +{ + string result; + _itobase2(result, large(value), base, width, padchar, true); + return result; +} + + +string ptdecl itostring(uint value, int base, int width, char padchar) +{ + string result; + _itobase2(result, ularge(value), base, width, padchar, false); + return result; +} + + +string ptdecl itostring(large v) { return itostring(v, 10, 0, ' '); } +string ptdecl itostring(ularge v) { return itostring(v, 10, 0, ' '); } +string ptdecl itostring(int v) { return itostring(large(v), 10, 0, ' '); } +string ptdecl itostring(uint v) { return itostring(ularge(v), 10, 0, ' '); } + + +} + diff --git a/source/ptypes/pstreams.h b/source/ptypes/pstreams.h @@ -0,0 +1,807 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifndef __PSTREAMS_H__ +#define __PSTREAMS_H__ + +#ifndef __PPORT_H__ +#include "pport.h" +#endif + +#ifndef __PTYPES_H__ +#include "ptypes.h" +#endif + +#ifndef PTYPES_ST +# ifndef __PASYNC_H__ +# include "pasync.h" // for logfile.lock +# endif +#endif + +#include <stdarg.h> +#include <errno.h> + + +#ifdef WIN32 +# define _WINSOCKAPI_ // prevent inclusion of winsock.h, because we need winsock2.h +# include "windows.h" // for OVERLAPPED +#endif + + +namespace ptypes { + + +#ifdef _MSC_VER +#pragma pack(push, 4) +#endif + + +// -------------------------------------------------------------------- // +// --- abstract stream i/o classes ----------------------------------- // +// -------------------------------------------------------------------- // + + +// +// stream exception class +// + +class iobase; + +class ptpublic estream: public exception +{ +protected: + int code; + iobase* errstm; +public: + estream(iobase* ierrstm, int icode, const char* imsg); + estream(iobase* ierrstm, int icode, const string& imsg); + virtual ~estream(); + int get_code() { return code; } + iobase* get_errstm() { return errstm; } +}; + + +typedef void (ptdecl *iostatusevent)(iobase* sender, int code); + +ptpublic int ptdecl unixerrno(); +ptpublic const char* ptdecl unixerrmsg(int code); + + +// status codes: compatible with WinInet API +// additional status codes are defined in pinet.h for ipsocket + +const int IO_CREATED = 1; +const int IO_OPENING = 5; +const int IO_OPENED = 35; +const int IO_READING = 37; +const int IO_WRITING = 38; +const int IO_EOF = 45; +const int IO_CLOSING = 250; +const int IO_CLOSED = 253; + + +// +// iobase +// + +enum ioseekmode +{ + IO_BEGIN, + IO_CURRENT, + IO_END +}; + + +const int invhandle = -1; + + +class ptpublic iobase: public component +{ + friend class fdxoutstm; + +protected: + bool active; // active status, changed by open() and close() + bool cancelled; // the stream was cancelled by cancel() + bool eof; // end of file reached, only for input streams + int handle; // used in many derivative classes + large abspos; // physical stream position + int bufsize; // buffer size, can be changed only when not active + char* bufdata; // internal: allocated buffer data + int bufpos; // internal: current position + int bufend; // internal: current data size in the buffer + int stmerrno; // UNIX-compatible error numbers, see comments in piobase.cxx + string deferrormsg; // internal: default error message when an exception is thrown, + int status; // stream status code, see IO_xxx constants above + iostatusevent onstatus; // user-defined status change handler + + virtual void bufalloc(); + virtual void buffree(); + void bufclear() { bufpos = 0; bufend = 0; } + + void errstminactive(); + void errbufrequired(); + void requireactive() { if (!active) errstminactive(); } + void requirebuf() { requireactive(); if (bufdata == 0) errbufrequired(); } + int convertoffset(large); + + virtual void doopen() = 0; + virtual void doclose(); + virtual large doseek(large newpos, ioseekmode mode); + + virtual void chstat(int newstat); + virtual int uerrno(); + virtual const char* uerrmsg(int code); + +public: + iobase(int ibufsize = -1); + virtual ~iobase(); + + void open(); + void close(); + void cancel(); + void reopen() { open(); } + large seekx(large newpos, ioseekmode mode = IO_BEGIN); + int seek(int newpos, ioseekmode mode = IO_BEGIN) { return convertoffset(seekx(newpos, mode)); } + void error(int code, const char* defmsg); + virtual void flush(); + + virtual string get_errormsg(); + virtual string get_errstmname(); + virtual string get_streamname() = 0; + + bool get_active() { return active; } + void set_active(bool newval); + bool get_cancelled() { return cancelled; } + void set_cancelled(bool newval) { cancelled = newval; } + int get_handle() { return handle; } + int get_bufsize() { return bufsize; } + void set_bufsize(int newval); + int get_stmerrno() { return stmerrno; } + int get_status() { return status; } + iostatusevent get_onstatus() { return onstatus; } + void set_onstatus(iostatusevent newval) { onstatus = newval; } +}; +typedef iobase* piobase; + + +ptpublic extern int defbufsize; +ptpublic extern int stmbalance; + + +// +// instm - abstract input stream +// + +const char eofchar = 0; + +class ptpublic instm: public iobase +{ +protected: + virtual int dorawread(char* buf, int count); + int rawread(char* buf, int count); + virtual void bufvalidate(); + void skipeol(); + +public: + instm(int ibufsize = -1); + virtual ~instm(); + virtual int classid(); + + bool get_eof(); + void set_eof(bool ieof) { eof = ieof; } + bool get_eol(); + int get_dataavail(); + char preview(); + char get(); + void putback(); + string token(const cset& chars); + string token(const cset& chars, int limit); + int token(const cset& chars, char* buf, int size); + string line(); + string line(int limit); + int line(char* buf, int size, bool eateol = true); + int read(void* buf, int count); + int skip(int count); + int skiptoken(const cset& chars); + void skipline(bool eateol = true); + large tellx(); + int tell() { return convertoffset(tellx()); } + large seekx(large newpos, ioseekmode mode = IO_BEGIN); + int seek(int newpos, ioseekmode mode = IO_BEGIN) { return convertoffset(seekx(newpos, mode)); } +}; +typedef instm* pinstm; + + +// +// outstm - abstract output stream +// + +class ptpublic outstm: public iobase +{ +protected: + bool flusheol; + + virtual int dorawwrite(const char* buf, int count); + int rawwrite(const char* buf, int count); + virtual void bufvalidate(); + void bufadvance(int delta) + { bufpos += delta; if (bufend < bufpos) bufend = bufpos; } + bool canwrite(); + +public: + outstm(bool iflusheol = false, int ibufsize = -1); + virtual ~outstm(); + virtual int classid(); + + bool get_flusheol() { return flusheol; } + void set_flusheol(bool newval) { flusheol = newval; } + + virtual void flush(); + bool get_eof() { return eof; } + void put(char c); + void put(const char* str); + void put(const string& str); + void vputf(const char* fmt, va_list); + void putf(const char* fmt, ...); + void putline(const char* str); + void putline(const string& str); + void puteol(); + int write(const void* buf, int count); + large tellx() { return abspos + bufpos; } + int tell() { return convertoffset(tellx()); } + large seekx(large newpos, ioseekmode mode = IO_BEGIN); + int seek(int newpos, ioseekmode mode = IO_BEGIN) { return convertoffset(seekx(newpos, mode)); } +}; +typedef outstm* poutstm; + + +// %t and %T formats +ptpublic extern const char* const shorttimefmt; // "%d-%b-%Y %X" +ptpublic extern const char* const longtimefmt; // "%a %b %d %X %Y" + + +// +// internal class used in fdxstm +// + +class ptpublic fdxstm; + + +class ptpublic fdxoutstm: public outstm +{ + friend class fdxstm; + +protected: + fdxstm* in; + virtual void chstat(int newstat); + virtual int uerrno(); + virtual const char* uerrmsg(int code); + virtual void doopen(); + virtual void doclose(); + virtual int dorawwrite(const char* buf, int count); + +public: + fdxoutstm(int ibufsize, fdxstm* iin); + virtual ~fdxoutstm(); + virtual string get_streamname(); +}; +typedef fdxstm* pfdxstm; + + +// +// fdxstm: abstract full-duplex stream (for sockets and pipes) +// + +class ptpublic fdxstm: public instm +{ + friend class fdxoutstm; + +protected: + fdxoutstm out; + + virtual int dorawwrite(const char* buf, int count); + +public: + + fdxstm(int ibufsize = -1); + virtual ~fdxstm(); + virtual int classid(); + + void set_bufsize(int newval); // sets both input and output buffer sizes + + void open(); // rewritten to pass the call to the output stream too + void close(); + void cancel(); + virtual void flush(); + large tellx(bool); // true for input and false for output + int tell(bool forin) { return convertoffset(tellx(forin)); } + + // output interface: pretend this class is derived both + // from instm and outstm. actually we can't use multiple + // inheritance here, since this is a full-duplex stream, + // hence everything must be duplicated for input and output + void putf(const char* fmt, ...); + void put(char c) { out.put(c); } + void put(const char* str) { out.put(str); } + void put(const string& str) { out.put(str); } + void putline(const char* str) { out.putline(str); } + void putline(const string& str) { out.putline(str); } + void puteol() { out.puteol(); } + int write(const void* buf, int count) { return out.write(buf, count); } + bool get_flusheol() { return out.get_flusheol(); } + void set_flusheol(bool newval) { out.set_flusheol(newval); } + + operator outstm&() { return out; } +}; + + +// +// abstract input filter class +// + +class ptpublic infilter: public instm +{ +protected: + instm* stm; + char* savebuf; + int savecount; + string postponed; + + void copytobuf(string& s); + void copytobuf(pconst& buf, int& count); + bool copytobuf(char c); + + virtual void freenotify(component* sender); + virtual void doopen(); + virtual void doclose(); + virtual int dorawread(char* buf, int count); + virtual void dofilter() = 0; + + bool bufavail() { return savecount > 0; } + void post(const char* buf, int count); + void post(const char* s); + void post(char c); + virtual void post(string s); + +public: + infilter(instm* istm, int ibufsize = -1); + virtual ~infilter(); + + virtual string get_errstmname(); + + instm* get_stm() { return stm; } + void set_stm(instm* stm); +}; + + +// +// abstract output filter class +// + +class ptpublic outfilter: public outstm +{ +protected: + outstm* stm; + virtual void freenotify(component* sender); + virtual void doopen(); + virtual void doclose(); + +public: + outfilter(outstm* istm, int ibufsize = -1); + virtual ~outfilter(); + virtual string get_errstmname(); + outstm* get_stm() { return stm; } + void set_stm(outstm* stm); +}; + + +// +// inmemory - memory stream +// + +class ptpublic inmemory: public instm +{ +protected: + string mem; + virtual void bufalloc(); + virtual void buffree(); + virtual void bufvalidate(); + virtual void doopen(); + virtual void doclose(); + virtual large doseek(large newpos, ioseekmode mode); + virtual int dorawread(char* buf, int count); + +public: + inmemory(const string& imem); + virtual ~inmemory(); + virtual int classid(); + virtual string get_streamname(); + large seekx(large newpos, ioseekmode mode = IO_BEGIN); + int seek(int newpos, ioseekmode mode = IO_BEGIN) { return convertoffset(seekx(newpos, mode)); } + string get_strdata() { return mem; } + void set_strdata(const string& data); +}; + + +// +// outmemory - memory stream +// + +class ptpublic outmemory: public outstm +{ +protected: + string mem; + int limit; + + virtual void doopen(); + virtual void doclose(); + virtual large doseek(large newpos, ioseekmode mode); + virtual int dorawwrite(const char* buf, int count); + +public: + outmemory(int limit = -1); + virtual ~outmemory(); + virtual int classid(); + virtual string get_streamname(); + large tellx() { return abspos; } + int tell() { return (int)abspos; } + string get_strdata(); +}; + + +// -------------------------------------------------------------------- // +// --- file input/output --------------------------------------------- // +// -------------------------------------------------------------------- // + + +// +// infile - file input +// + +class outfile; + +class ptpublic infile: public instm +{ +protected: + string filename; + int syshandle; // if not -1, assigned to handle in open() instead of opening a file by a name + int peerhandle; // pipe peer handle, needed for closing the peer after fork() on unix + + virtual void doopen(); + virtual void doclose(); + +public: + infile(); + infile(const char* ifn); + infile(const string& ifn); + virtual ~infile(); + virtual int classid(); + + void pipe(outfile&); + virtual string get_streamname(); + int get_syshandle() { return syshandle; } + void set_syshandle(int ihandle) { close(); syshandle = ihandle; } + int get_peerhandle() { return peerhandle; } + string get_filename() { return filename; } + void set_filename(const string& ifn) { close(); filename = ifn; } + void set_filename(const char* ifn) { close(); filename = ifn; } +}; + + +// +// outfile - file output +// + +class ptpublic outfile: public outstm +{ +protected: + friend class infile; // infile::pipe() needs access to peerhandle + + string filename; + int syshandle; // if not -1, assigned to handle in open() instead of opening a file by a name + int peerhandle; // pipe peer handle, needed for closing the peer after fork() on unix + int umode; // unix file mode (unix only), default = 644 + bool append; // append (create new if needed), default = false + + virtual void doopen(); + virtual void doclose(); + +public: + outfile(); + outfile(const char* ifn, bool iappend = false); + outfile(const string& ifn, bool iappend = false); + virtual ~outfile(); + virtual int classid(); + + virtual void flush(); + virtual string get_streamname(); + + int get_syshandle() { return syshandle; } + void set_syshandle(int ihandle) { close(); syshandle = ihandle; } + int get_peerhandle() { return peerhandle; } + string get_filename() { return filename; } + void set_filename(const string& ifn) { close(); filename = ifn; } + void set_filename(const char* ifn) { close(); filename = ifn; } + bool get_append() { return append; } + void set_append(bool iappend) { close(); append = iappend; } + int get_umode() { return umode; } + void set_umode(int iumode) { close(); umode = iumode; } +}; + + +// +// logfile - file output with thread-safe putf() +// + +class ptpublic logfile: public outfile +{ +protected: +#ifndef PTYPES_ST + mutex lock; +#endif +public: + logfile(); + logfile(const char* ifn, bool iappend = true); + logfile(const string& ifn, bool iappend = true); + virtual ~logfile(); + virtual int classid(); + + void vputf(const char* fmt, va_list); + void putf(const char* fmt, ...); +}; + + +// +// intee - UNIX tee-style utility class +// + +class ptpublic intee: public infilter { +protected: + outfile file; + virtual void doopen(); + virtual void doclose(); + virtual void dofilter(); +public: + intee(instm* istm, const char* ifn, bool iappend = false); + intee(instm* istm, const string& ifn, bool iappend = false); + virtual ~intee(); + + outfile* get_file() { return &file; } + virtual string get_streamname(); +}; + + +// -------------------------------------------------------------------- // +// --- named pipes --------------------------------------------------- // +// -------------------------------------------------------------------- // + + +// on Unix this directory can be overridden by providing the +// full path, e.g. '/var/run/mypipe'. the path is ignored on +// Windows and is always replaced with '\\<server>\pipe\' + +#ifndef WIN32 +# define DEF_NAMED_PIPES_DIR "/tmp/" +#endif + + +#ifdef WIN32 + +const int DEF_PIPE_TIMEOUT = 20000; // in milliseconds, for reading and writing +const int DEF_PIPE_OPEN_TIMEOUT = 1000; // for connecting to the remote pipe: +const int DEF_PIPE_OPEN_RETRY = 5; // will double the timeout value for each retry, + // i.e. 1 second, then 2, then 4 etc. +const int DEF_PIPE_SYSTEM_BUF_SIZE = 4096; + +#endif + + +class ptpublic namedpipe: public fdxstm +{ + friend class npserver; + +protected: + string pipename; + int svhandle; + +#ifdef WIN32 + // we use overlapped IO in order to have timed waiting in serve() + // and also to implement timeout error on the client side + OVERLAPPED ovr; + virtual int dorawread(char* buf, int count); + virtual int dorawwrite(const char* buf, int count); + static string ptdecl realpipename(const string& pipename, const string& svrname = nullstring); + void initovr(); +#else + static string realpipename(const string& pipename); + static bool setupsockaddr(const string& pipename, void* sa); + void initovr() {} +#endif + + virtual void doopen(); + virtual void doclose(); + virtual large doseek(large, ioseekmode); + +public: + namedpipe(); + namedpipe(const string& ipipename); +#ifdef WIN32 + namedpipe(const string& ipipename, const string& servername); +#endif + virtual ~namedpipe(); + virtual int classid(); + + virtual void flush(); + virtual string get_streamname(); + + string get_pipename() { return pipename; } + void set_pipename(const string&); + void set_pipename(const char*); +}; + + +class ptpublic npserver: public unknown +{ + string pipename; + int handle; + bool active; + + void error(int code, const char* defmsg); + void open(); + void close(); +#ifdef WIN32 + void openinst(); + void closeinst(); +#endif + +public: + npserver(const string& ipipename); + ~npserver(); + + bool serve(namedpipe& client, int timeout = -1); +}; + + +// -------------------------------------------------------------------- // +// --- utility streams ----------------------------------------------- // +// -------------------------------------------------------------------- // + +// +// MD5 -- message digest algorithm +// Derived from L. Peter Deutsch's work, please see src/pmd5.cxx +// + + +const int md5_digsize = 16; +typedef uchar md5_digest[md5_digsize]; + +// from md5.h + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + + +typedef struct md5_state_s +{ + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + + +class ptpublic outmd5: public outfilter +{ +protected: + md5_state_s ctx; + md5_digest digest; + + virtual void doopen(); + virtual void doclose(); + virtual int dorawwrite(const char* buf, int count); + +public: + outmd5(outstm* istm = nil); + virtual ~outmd5(); + + virtual string get_streamname(); + + const unsigned char* get_bindigest() { close(); return digest; } + string get_digest(); +}; + + +// +// null output stream +// + + +class ptpublic outnull: public outstm +{ +protected: + virtual int dorawwrite(const char*, int); + virtual void doopen(); + virtual void doclose(); +public: + outnull(); + virtual ~outnull(); + virtual string get_streamname(); +}; + + +// -------------------------------------------------------------------- // +// --- unit ---------------------------------------------------------- // +// -------------------------------------------------------------------- // + + +#ifdef _MSC_VER +// disable "type name first seen using 'struct' now seen using 'class'" warning +# pragma warning (disable: 4099) +// disable "class '...' needs to have dll-interface to be used by clients of class +// '...'" warning, since the compiler may sometimes give this warning incorrectly. +# pragma warning (disable: 4251) +#endif + +class unit_thread; + +class ptpublic unit: public component +{ +protected: + friend class unit_thread; + + unit* pipe_next; // next unit in the pipe chain, assigned by connect() + unit_thread* main_thread; // async execution thread, started by run() if necessary + int running; // running status, to protect from recursive calls to run() and waitfor() + + void do_main(); + +public: + compref<instm> uin; + compref<outstm> uout; + + unit(); + virtual ~unit(); + virtual int classid(); + + // things that may be overridden in descendant classes + virtual void main(); // main code, called from run() + virtual void cleanup(); // main code cleanup, called from run() + + // service methods + void connect(unit* next); + void run(bool async = false); + void waitfor(); +}; +typedef unit* punit; + + +typedef unit CUnit; // send me a $10 check if you use this alias (not obligatory though, + // because the library is free, after all) + + +// +// standard input, output and error devices +// + +ptpublic extern infile pin; +ptpublic extern logfile pout; +ptpublic extern logfile perr; +ptpublic extern outnull pnull; + + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + + +} + +#endif // __PSTREAMS_H__ + diff --git a/source/ptypes/pstring.cxx b/source/ptypes/pstring.cxx @@ -0,0 +1,289 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "ptypes.h" +#include "ptime.h" // nowstring() is defined in this module + + +namespace ptypes { + + +const int strrecsize = sizeof(_strrec); + + +static void stringoverflow() +{ + fatal(CRIT_FIRST + 21, "String overflow"); +} + + +void string::idxerror() +{ + fatal(CRIT_FIRST + 20, "String index overflow"); +} + + +int stralloc; + +char emptystrbuf[strrecsize + 4]; +char* emptystr = emptystrbuf + strrecsize; + + +string nullstring; + + +inline int quantize(int numchars) +{ + return memquantize(numchars + 1 + strrecsize); +} + + +void string::_alloc(int numchars) +{ + if (numchars <= 0) + stringoverflow(); + size_t a = quantize(numchars); +#ifdef DEBUG + stralloc += a; +#endif + data = (char*)(memalloc(a)) + strrecsize; + STR_LENGTH(data) = numchars; + STR_REFCOUNT(data) = 1; + data[numchars] = 0; +} + + +void string::_realloc(int numchars) +{ + if (numchars <= 0 || STR_LENGTH(data) <= 0) + stringoverflow(); + int a = quantize(numchars); + int b = quantize(STR_LENGTH(data)); + if (a != b) + { +#ifdef DEBUG + stralloc += a - b; +#endif + data = (char*)(memrealloc(data - strrecsize, a)) + strrecsize; + } + STR_LENGTH(data) = numchars; + data[numchars] = 0; +} + + +inline void _freestrbuf(char* data) +{ +#ifdef DEBUG + stralloc -= quantize(STR_LENGTH(data)); +#endif + memfree((char*)(STR_BASE(data))); +} + + +void string::_free() +{ + _freestrbuf(data); + data = emptystr; +} + + +void string::initialize(const char* sc, int initlen) +{ + if (initlen <= 0 || sc == nil) + data = emptystr; + else + { + _alloc(initlen); + memmove(data, sc, initlen); + } +} + + +void string::initialize(const char* sc) +{ + initialize(sc, hstrlen(sc)); +} + + +void string::initialize(char c) +{ + _alloc(1); + data[0] = c; +} + + +void string::initialize(const string& s) +{ + data = s.data; +#ifdef PTYPES_ST + STR_REFCOUNT(data)++; +#else + pincrement(&STR_REFCOUNT(data)); +#endif +} + + +void string::finalize() +{ + if (STR_LENGTH(data) != 0) + { + +#ifdef PTYPES_ST + if (--STR_REFCOUNT(data) == 0) +#else + if (pdecrement(&STR_REFCOUNT(data)) == 0) +#endif + _freestrbuf(data); + + data = emptystr; + } +} + + +char* ptdecl unique(string& s) +{ + if (STR_LENGTH(s.data) > 0 && STR_REFCOUNT(s.data) > 1) + { + char* odata = s.data; + s._alloc(STR_LENGTH(s.data)); + memcpy(s.data, odata, STR_LENGTH(s.data)); +#ifdef PTYPES_ST + STR_REFCOUNT(odata)--; +#else + if (pdecrement(&STR_REFCOUNT(odata)) == 0) + _freestrbuf(odata); +#endif + } + return s.data; +} + + +char* ptdecl setlength(string& s, int newlen) +{ + if (newlen < 0) + return nil; + + int curlen = STR_LENGTH(s.data); + + // if becoming empty + if (newlen == 0) + s.finalize(); + + // if otherwise s was empty before + else if (curlen == 0) + s._alloc(newlen); + + // if length is not changing, return a unique string + else if (newlen == curlen) + unique(s); + + // non-unique reallocation + else if (STR_REFCOUNT(s.data) > 1) + { + char* odata = s.data; + s._alloc(newlen); + memcpy(s.data, odata, imin(curlen, newlen)); +#ifdef PTYPES_ST + STR_REFCOUNT(odata)--; +#else + if (pdecrement(&STR_REFCOUNT(odata)) == 0) + _freestrbuf(odata); +#endif + } + + // unique reallocation + else + s._realloc(newlen); + + return s.data; +} + + +void string::assign(const char* sc, int initlen) +{ + if (STR_LENGTH(data) > 0 && initlen > 0 && STR_REFCOUNT(data) == 1) + { + // reuse data buffer if unique + _realloc(initlen); + memmove(data, sc, initlen); + } + else + { + finalize(); + if (initlen == 1) + initialize(sc[0]); + else if (initlen > 1) + initialize(sc, initlen); + } +} + + +void string::assign(const char* sc) +{ + assign(sc, hstrlen(sc)); +} + + +void string::assign(char c) +{ + assign(&c, 1); +} + + +void string::assign(const string& s) +{ + if (data != s.data) + { + finalize(); + initialize(s); + } +} + + +string ptdecl dup(const string& s) +{ + // dup() only reads the data pointer so it is thread-safe + return string(s.data); +} + + +string ptdecl nowstring(const char* fmt, bool utc) +{ + char buf[128]; + time_t longtime; + time(&longtime); + +#if defined(PTYPES_ST) || defined(WIN32) + tm* t; + if (utc) + t = gmtime(&longtime); + else + t = localtime(&longtime); + int r = strftime(buf, sizeof(buf), fmt, t); +#else + tm t; + if (utc) + gmtime_r(&longtime, &t); + else + localtime_r(&longtime, &t); + int r = strftime(buf, sizeof(buf), fmt, &t); +#endif + + buf[r] = 0; + return string(buf); +} + + +} diff --git a/source/ptypes/pstrlist.cxx b/source/ptypes/pstrlist.cxx @@ -0,0 +1,204 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +typedef _stritem* pstritem; + + +void _strlist::sortederror() +{ + fatal(CRIT_FIRST + 32, "Operation not allowed on sorted string lists"); +} + + +void _strlist::notsortederror() +{ + fatal(CRIT_FIRST + 33, "Search only allowed on sorted string lists"); +} + + +void _strlist::duperror() +{ + fatal(CRIT_FIRST + 34, "Duplicate items not allowed in this string list"); +} + + +_strlist::_strlist(int flags) + : tobjlist<_stritem>(true) +{ + if ((flags & SL_SORTED) != 0) + config.sorted = 1; + if ((flags & SL_DUPLICATES) != 0) + config.duplicates = 1; + if ((flags & SL_CASESENS) != 0) + config.casesens = 1; + if ((flags & SL_OWNOBJECTS) != 0) + config.ownslobjects = 1; +} + + +_strlist::~_strlist() +{ +} + + +void _strlist::dofree(void* item) +{ + if (config.ownslobjects) + dofreeobj(pstritem(item)->obj); + delete pstritem(item); +} + + +void _strlist::dofreeobj(void*) +{ + fatal(CRIT_FIRST + 38, "strlist::dofree() not defined"); +} + + +int _strlist::compare(const void* key, const void* item) const +{ + if (config.casesens) + return strcmp(pconst(key), pstritem(item)->key); + else + return strcasecmp(pconst(key), pstritem(item)->key); +} + + +void _strlist::doins(int index, const string& key, void* obj) +{ + tobjlist<_stritem>::ins(index, new _stritem(key, obj)); +} + + +void _strlist::doput(int index, const string& key, void* obj) +{ + if (config.sorted) + sortederror(); + _stritem* p = doget(index); + if (config.ownslobjects) + dofreeobj(p->obj); + p->key = key; + p->obj = obj; +} + + +void _strlist::doput(int index, void* obj) +{ + _stritem* p = doget(index); + if (config.ownslobjects) + dofreeobj(p->obj); + p->obj = obj; +} + + +int _strlist::put(const string& key, void* obj) +{ + if (!config.sorted) + notsortederror(); + if (config.duplicates) + duperror(); + int index; + if (search(key, index)) + { + if (obj == nil) + dodel(index); + else + doput(index, obj); + } + else if (obj != nil) + doins(index, key, obj); + return index; +} + + +int _strlist::add(const string& key, void* obj) +{ + int index; + if (config.sorted) + { + if (search(key, index) && !config.duplicates) + duperror(); + } + else + index = count; + doins(index, key, obj); + return index; +} + + +void* _strlist::operator [](const char* key) const +{ + if (!config.sorted) + notsortederror(); + int index; + if (search(key, index)) + return dogetobj(index); + else + return nil; +} + + +int _strlist::indexof(const char* key) const +{ + if (config.sorted) + { + int index; + if (search(key, index)) + return index; + } + else + { + for (int i = 0; i < count; i++) + if (compare(key, doget(i)) == 0) + return i; + } + return -1; +} + + +int _strlist::indexof(void* obj) const +{ + for (int i = 0; i < count; i++) + if (pstritem(doget(i))->obj == obj) + return i; + return -1; +} + + +// +// strmap +// + +#ifdef PTYPES19_COMPAT + +strlist::strlist(int flags): tstrlist<unknown>(flags) {} + +strlist::~strlist() {} + +strmap::strmap(int flags) + : tstrlist<unknown>((flags | SL_SORTED) & ~SL_DUPLICATES) +{ +} + +strmap::~strmap() +{ +} + +#endif + + +} diff --git a/source/ptypes/pstrmanip.cxx b/source/ptypes/pstrmanip.cxx @@ -0,0 +1,281 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <stdlib.h> +#include <string.h> +#include <limits.h> // for INT_MAX + +#include "ptypes.h" + + +namespace ptypes { + + +void string::initialize(const char* s1, int len1, const char* s2, int len2) +{ + if (len1 <= 0) + initialize(s2, len2); + else if (len2 <= 0) + initialize(s1, len1); + else + { + _alloc(len1 + len2); + memcpy(data, s1, len1); + memcpy(data + len1, s2, len2); + } +} + + +void ptdecl concat(string& s, const char* sc, int catlen) +{ + if (length(s) == 0) + s.assign(sc, catlen); + else if (catlen > 0) + { + int oldlen = length(s); + + // we must check this before calling setlength(), since + // the buffer pointer may be changed during reallocation + if (s.data == sc) + { + setlength(s, oldlen + catlen); + memmove(s.data + oldlen, s.data, catlen); + } + else + { + setlength(s, oldlen + catlen); + memmove(s.data + oldlen, sc, catlen); + } + } +} + + +void ptdecl concat(string& s, const char* sc) +{ + concat(s, sc, hstrlen(sc)); +} + + +void ptdecl concat(string& s, char c) +{ + if (length(s) == 0) + s.assign(c); + else + { + setlength(s, length(s) + 1); + s.data[length(s) - 1] = c; + } +} + + +void ptdecl concat(string& s, const string& s1) +{ + if (length(s) == 0) + s = s1; + else if (length(s1) > 0) + concat(s, s1.data, length(s1)); +} + + +bool ptdecl contains(const char* s1, int s1len, const string& s, int at) +{ + return (s1len >= 0) && (at >= 0) && (at + s1len <= length(s)) + && (s1len == 0 || memcmp(s.data + at, s1, s1len) == 0); +} + + +bool ptdecl contains(const char* s1, const string& s, int at) +{ + return contains(s1, hstrlen(s1), s, at); +} + + +bool ptdecl contains(char s1, const string& s, int at) +{ + return (at >= 0) && (at < length(s)) && (s.data[at] == s1); +} + + +bool ptdecl contains(const string& s1, const string& s, int at) +{ + return contains(s1.data, length(s1), s, at); +} + + +string string::operator+ (const char* sc) const +{ + if (length(*this) == 0) + return string(sc); + else + return string(data, length(*this), sc, hstrlen(sc)); +} + + +string string::operator+ (char c) const +{ + if (length(*this) == 0) + return string(c); + else + return string(data, length(*this), &c, 1); +} + + +string string::operator+ (const string& s) const +{ + if (length(*this) == 0) + return s; + else if (length(s) == 0) + return *this; + else + return string(data, length(*this), s.data, length(s)); +} + + +string ptdecl operator+ (const char* sc, const string& s) +{ + if (length(s) == 0) + return string(sc); + else + return string(sc, hstrlen(sc), s.data, length(s)); +} + + +string ptdecl operator+ (char c, const string& s) +{ + if (length(s) == 0) + return string(c); + else + return string(&c, 1, s.data, length(s)); +} + + +bool string::operator== (const string& s) const +{ + return (length(*this) == length(s)) + && ((length(*this) == 0) || (memcmp(data, s.data, length(*this)) == 0)); +} + + +bool string::operator== (char c) const +{ + return (length(*this) == 1) && (data[0] == c); +} + + +string ptdecl copy(const string& s, int from, int cnt) +{ + string t; + if (length(s) > 0 && from >= 0 && from < length(s)) + { + int l = imin(cnt, length(s) - from); + if (from == 0 && l == length(s)) + t = s; + else if (l > 0) + { + t._alloc(l); + memmove(t.data, s.data + from, l); + t.data[l] = 0; + } + } + return t; +} + + +string ptdecl copy(const string& s, int from) +{ + return copy(s, from, INT_MAX); +} + + +void ptdecl ins(const char* s1, int s1len, string& s, int at) +{ + int curlen = length(s); + if (s1len > 0 && at >= 0 && at <= curlen) + { + if (curlen == 0) + s.assign(s1, s1len); + else + { + setlength(s, curlen + s1len); + int t = length(s) - at - s1len; + char* p = s.data + at; + if (t > 0) + memmove(p + s1len, p, t); + memmove(p, s1, s1len); + } + } +} + + +void ptdecl ins(const char* sc, string& s, int at) +{ + ins(sc, hstrlen(sc), s, at); +} + + +void ptdecl ins(char c, string& s, int at) +{ + ins(&c, 1, s, at); +} + + +void ptdecl ins(const string& s1, string& s, int at) +{ + ins(s1.data, length(s1), s, at); +} + + +void ptdecl del(string& s, int from, int cnt) +{ + int l = length(s); + int d = l - from; + if (from >= 0 && d > 0 && cnt > 0) + { + if (cnt < d) + { + unique(s); + memmove(s.data + from, s.data + from + cnt, d - cnt); + } + else + cnt = d; + setlength(s, l - cnt); + } +} + + +void ptdecl del(string& s, int from) +{ + setlength(s, from); +} + + +int ptdecl pos(const char* sc, const string& s) +{ + const char* t = (char*)strstr(s.data, sc); + return (t == NULL ? (-1) : int(t - s.data)); +} + + +int ptdecl pos(char c, const string& s) +{ + const char* t = (char*)strchr(s.data, c); + return (t == NULL ? (-1) : int(t - s.data)); +} + + +int ptdecl rpos(char c, const string& s) +{ + const char* t = (char*)strrchr(s.data, c); + return (t == NULL ? (-1) : int(t - s.data)); +} + + +} diff --git a/source/ptypes/pstrtoi.cxx b/source/ptypes/pstrtoi.cxx @@ -0,0 +1,136 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <string.h> + +#include "ptypes.h" + + +namespace ptypes { + + +static void throw_conv(const char* p); +static void throw_overflow(const char* p); + + +large ptdecl stringtoi(const char* p) +{ + if (p == 0) + return -1; + if (*p == 0) + return -1; + + large r = 0; + do + { + char c = *p++; + if (c < '0' || c > '9') + return -1; // invalid character + large t = r * 10; + if (t < r) + return -1; // overflow + t += c - '0'; + if (t < r) + return -1; // overflow + r = t; + } while (*p != 0); + + return r; +} + + +econv::~econv() +{ +} + + +ularge ptdecl stringtoue(const char* str, int base) +{ + if (str == 0) + throw_conv(str); + if (*str == 0 || base < 2 || base > 64) + throw_conv(str); + + const char* p = str; + ularge result = 0; + + do + { + int c = *p++; + + if (c >= 'a') + { + // for the numeration bases that use '.', '/', digits and + // uppercase letters the letter case is insignificant. + if (base <= 38) + c -= 'a' - '9' - 1; + else // others use both upper and lower case letters + c -= ('a' - 'Z' - 1) + ('A' - '9' - 1); + } + else if (c > 'Z') + throw_conv(str); + else if (c >= 'A') + c -= 'A' - '9' - 1; + else if (c > '9') + throw_conv(str); + + c -= (base > 36) ? '.' : '0'; + if (c < 0 || c >= base) + throw_conv(str); + + ularge t = result * uint(base); + if (t / base != result) + throw_overflow(str); + result = t; + t = result + uint(c); + if (t < result) + throw_overflow(str); + result = t; + } while (*p != 0); + + return result; +} + + +large ptdecl stringtoie(const char* str) +{ + if (str == 0) + throw_conv(str); + bool neg = *str == '-'; + ularge result = stringtoue(str + int(neg), 10); + if (result > (ularge(LARGE_MAX) + uint(neg))) + throw_overflow(str); + if (neg) + return - large(result); + else + return large(result); +} + + +#ifdef _MSC_VER +// disable "unreachable code" warning for throw (known compiler bug) +# pragma warning (disable: 4702) +#endif + +static void throw_conv(const char* p) +{ + throw new econv("Invalid number: '" + string(p) + '\''); +} + + +static void throw_overflow(const char* p) +{ + throw new econv("Out of range: '" + string(p) + '\''); +} + + +} + diff --git a/source/ptypes/pstrutils.cxx b/source/ptypes/pstrutils.cxx @@ -0,0 +1,57 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include <string.h> + +#include "ptypes.h" + + +namespace ptypes { + + +string ptdecl fill(int width, char pad) +{ + string res; + if (width > 0) { + setlength(res, width); + memset(pchar(pconst(res)), pad, length(res)); + } + return res; +} + + +string ptdecl pad(const string& s, int width, char c, bool left) +{ + int len = length(s); + if (len < width && width > 0) + { + string res; + setlength(res, width); + if (left) + { + if (len > 0) + memcpy(pchar(pconst(res)), pconst(s), len); + memset(pchar(pconst(res)) + len, c, width - len); + } + else + { + memset(pchar(pconst(res)), c, width - len); + if (len > 0) + memcpy(pchar(pconst(res)) + width - len, pconst(s), len); + } + return res; + } + else + return s; +} + + +} diff --git a/source/ptypes/ptextmap.cxx b/source/ptypes/ptextmap.cxx @@ -0,0 +1,79 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +typedef _textitem* ptextitem; + + +textmap::textmap(bool casesens) + : tobjlist<_textitem>(true) +{ + config.sorted = true; + config.casesens = casesens; +} + + +textmap::~textmap() +{ +} + + +int textmap::compare(const void* key, const void* item) const +{ + if (config.casesens) + return strcmp(pconst(key), ptextitem(item)->key); + else + return strcasecmp(pconst(key), ptextitem(item)->key); +} + + +const string& textmap::get(const char* key) const +{ + int index; + if (search(key, index)) + return dogetvalue(index); + else + return nullstring; +} + + +int textmap::put(const string& key, const string& value) +{ + int index; + if (search(pconst(key), index)) + { + if (isempty(value)) + dodel(index); + else + doget(index)->value = value; + } + else if (!isempty(value)) + doins(index, new _textitem(key, value)); + return index; +} + + +int textmap::indexof(const char* key) const +{ + int index; + if (search(key, index)) + return index; + else + return -1; +} + + +} diff --git a/source/ptypes/pthread.cxx b/source/ptypes/pthread.cxx @@ -0,0 +1,154 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <process.h> +#else +# include <pthread.h> +#endif + +#include "pasync.h" + + +namespace ptypes { + + +thread::thread(bool iautofree) + : +#ifdef WIN32 + id(0), +#endif + handle(0), autofree(iautofree), + running(0), signaled(0), finished(0), freed(0), + reserved(0), relaxsem(0) +{ +} + + +thread::~thread() +{ + if (pexchange(&freed, 1) != 0) + return; +#ifdef WIN32 + if (autofree) + // MSDN states this is not necessary, however, without closing + // the handle debuggers show an obvious handle leak here + CloseHandle(handle); +#else + // though we require non-autofree threads to always call waitfor(), + // the statement below is provided to cleanup thread resources even + // if waitfor() was not called. + if (!autofree && running) + pthread_detach(handle); +#endif +} + + +void thread::signal() +{ + if (pexchange(&signaled, 1) == 0) + relaxsem.post(); +} + + +void thread::waitfor() +{ + if (pexchange(&freed, 1) != 0) + return; + if (pthrequal(get_id())) + fatal(CRIT_FIRST + 47, "Can not waitfor() on myself"); + if (autofree) + fatal(CRIT_FIRST + 48, "Can not waitfor() on an autofree thread"); +#ifdef WIN32 + WaitForSingleObject(handle, INFINITE); + CloseHandle(handle); +#else + pthread_join(handle, nil); +// detaching after 'join' is not required (or even do harm on some systems) +// except for HPUX. we don't support HPUX yet. +// pthread_detach(handle); +#endif + handle = 0; +} + + +#ifdef WIN32 +unsigned _stdcall _threadproc(void* arg) +{ +#else +void* _threadproc(void* arg) +{ +#endif + thread* thr = (thread*)arg; +#ifndef WIN32 + if (thr->autofree) + // start() does not assign the handle for autofree threads + thr->handle = pthread_self(); +#endif + try + { + thr->execute(); + } + catch(exception*) + { + _threadepilog(thr); + throw; + } + _threadepilog(thr); + return 0; +} + + +void _threadepilog(thread* thr) +{ + try + { + thr->cleanup(); + } + catch(exception* e) + { + delete e; + } + pexchange(&thr->finished, 1); + if (thr->autofree) + delete thr; +} + + +void thread::start() +{ + if (pexchange(&running, 1) == 0) + { +#ifdef WIN32 + handle = (HANDLE)_beginthreadex(nil, 0, _threadproc, this, 0, &id); + if (handle == 0) + fatal(CRIT_FIRST + 40, "CreateThread() failed"); +#else + pthread_t temp_handle; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, + autofree ? PTHREAD_CREATE_DETACHED : PTHREAD_CREATE_JOINABLE); + if (pthread_create(autofree ? &temp_handle : &handle, + &attr, _threadproc, this) != 0) + fatal(CRIT_FIRST + 40, "pthread_create() failed"); + pthread_attr_destroy(&attr); +#endif + } +} + + +void thread::cleanup() +{ +} + + +} diff --git a/source/ptypes/ptime.cxx b/source/ptypes/ptime.cxx @@ -0,0 +1,325 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +// altzone fix for Sun/GCC 3.2. any better ideas? +#if defined(__sun__) && defined(__GNUC__) && defined(_XOPEN_SOURCE) +# undef _XOPEN_SOURCE +#endif + +#ifdef WIN32 +# include <windows.h> +#else +# include <sys/time.h> +#endif + +#include <time.h> +#include <string.h> + +#include "ptime.h" + + +namespace ptypes { + + +datetime ptdecl mkdt(int days, int msecs) +{ + return large(days) * _msecsmax + msecs; +} + + +bool ptdecl isvalid(datetime d) +{ + return d >= 0 && d < _datetimemax; +} + + +bool ptdecl isleapyear(int year) +{ + return year > 0 && year % 4 == 0 + && (year % 100 != 0 || year % 400 == 0); +} + + +int ptdecl daysinmonth(int year, int month) +{ + if (month < 1 || month > 12) + return 0; + static const int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + int res = mdays[month - 1]; + if (month == 2) + if (isleapyear(year)) + res++; + return res; +} + + +int ptdecl daysinyear(int year, int month) +{ + if (month < 1 || month > 12) + return 0; + static const int ndays[12] = {31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; + int res = ndays[month - 1]; + if (month > 1) + if (isleapyear(year)) + res++; + return res; +} + + +int ptdecl dayofweek(datetime d) +{ + return (days(d) + 1) % 7; +} + + +bool ptdecl isdatevalid(int year, int month, int day) +{ + return year >= 1 && year <= 9999 + && month >= 1 && month <= 12 + && day >= 1 && day <= daysinmonth(year, month); +} + + +datetime ptdecl encodedate(int year, int month, int day) +{ + if (!isdatevalid(year, month, day)) + return invdatetime; + int y = year - 1; + return mkdt(day // days in this month + + daysinyear(year, month - 1) // plus days since the beginning of the year + + y * 365 // plus "pure" days + + y / 4 - y / 100 + y / 400 // plus leap year correction + - 1, 0); // ... minus one (guess why :) +} + + +bool ptdecl decodedate(datetime date, int& year, int& month, int& day) +{ + int d = days(date); + if (d < 0 || d >= _daysmax) // allowed date range is 01/01/0001 - 12/31/9999 + { + year = 0; + month = 0; + day = 0; + return false; + } + + const int d1 = 365; // number of days in 1 year + const int d4 = d1 * 4 + 1; // ... in 4 year period + const int d100 = d4 * 25 - 1; // ... in 100 year period + const int d400 = d100 * 4 + 1; // ... in 400 year period + + year = (d / d400) * 400 + 1; + d %= d400; + + int t = d / d100; + d %= d100; + if (t == 4) + { + t--; + d += d100; + } + year += t * 100; + + year += (d / d4) * 4; + d %= d4; + + t = d / d1; + d %= d1; + if (t == 4) + { + t--; + d += d1; + } + year += t; + + month = d / 29; // approximate month no. (to avoid loops) + if (d < daysinyear(year, month)) // month no. correction + month--; + + day = d - daysinyear(year, month) + 1; + month++; + return true; +} + + +bool ptdecl istimevalid(int hour, int min, int sec, int msec) +{ + return hour >= 0 && hour < 24 + && min >= 0 && min < 60 + && sec >= 0 && sec < 60 + && msec >= 0 && msec < 1000; +} + + +datetime ptdecl encodetime(int hour, int min, int sec, int msec) +{ + large res = large(hour) * 3600000 + large(min) * 60000 + large(sec) * 1000 + msec; + if (!isvalid(res)) + res = invdatetime; + return res; +} + + +bool ptdecl decodetime(datetime t, int& hour, int& min, int& sec, int& msec) +{ + if (!isvalid(t)) + { + hour = 0; + min = 0; + sec = 0; + msec = 0; + return false; + } + int m = msecs(t); + hour = m / 3600000; + m %= 3600000; + min = m / 60000; + m %= 60000; + sec = m / 1000; + msec = m % 1000; + return true; +} + + +bool ptdecl decodetime(datetime t, int& hour, int& min, int& sec) +{ + int msec; + return decodetime(t, hour, min, sec, msec); +} + + +tm* ptdecl dttotm(datetime dt, tm& t) +{ + memset(&t, 0, sizeof(tm)); + if (!decodedate(dt, t.tm_year, t.tm_mon, t.tm_mday) + || !decodetime(dt, t.tm_hour, t.tm_min, t.tm_sec)) + return nil; + t.tm_mon--; + t.tm_yday = daysinyear(t.tm_year, t.tm_mon) + t.tm_mday - 1; + t.tm_wday = dayofweek(dt); + t.tm_year -= 1900; + return &t; +} + + +string ptdecl dttostring(datetime dt, const char* fmt) +{ + char buf[128]; + tm t; + int r = (int)strftime(buf, sizeof(buf), fmt, dttotm(dt, t)); + buf[r] = 0; + return string(buf); +} + + +datetime ptdecl now(bool utc) +{ +#ifdef WIN32 + SYSTEMTIME t; + if (utc) + GetSystemTime(&t); + else + GetLocalTime(&t); + return encodedate(t.wYear, t.wMonth, t.wDay) + + encodetime(t.wHour, t.wMinute, t.wSecond, t.wMilliseconds); + +#else // Unix + // we can't use localtime() and gmtime() here as they don't return + // milliseconds which are needed for our datetime format. instead, + // we call gettimeofday() which have microsecond precision, and then + // adjust the time according to timzone info returned by localtime() + // on BSD and Linux, and global variables altzone/timezone on SunOS. + + // NOTE: at the moment of passing the DST adjustment (twice a year) + // the local time value returned by now() may be incorrect. + // the application should call tzupdate() from time to time if it + // is supposed to be running infinitely, e.g. if it's a daemon. + + // always rely on UTC time inside your application whenever possible. + timeval tv; + gettimeofday(&tv, nil); + int edays = tv.tv_sec / 86400 // days since Unix "Epoch", i.e. 01/01/1970 + + 719162; // plus days between 01/01/0001 and Unix Epoch + int esecs = tv.tv_sec % 86400; // the remainder, i.e. seconds since midnight + datetime res = mkdt(edays, esecs * 1000 + tv.tv_usec / 1000); + + if (!utc) + res += tzoffset() * 60 * 1000; + return res; +#endif +} + + +void ptdecl tzupdate() +{ + tzset(); +} + + +int ptdecl tzoffset() +{ +#if defined(WIN32) + TIME_ZONE_INFORMATION tz; + DWORD res = GetTimeZoneInformation(&tz); + if ((res == TIME_ZONE_ID_DAYLIGHT) && (tz.DaylightDate.wMonth != 0)) + return - (tz.Bias + tz.DaylightBias); + else + return - tz.Bias; + +#else // UNIX + time_t t0 = time(0); + +#if defined(__sun__) +#ifdef PTYPES_ST + // localtime_r() is not available without -D_REENTRANT + tm* t = localtime(&t0); + if(t->tm_isdst != 0 && daylight != 0) +#else + tm t; + localtime_r(&t0, &t); + if(t.tm_isdst != 0 && daylight != 0) +#endif + return - altzone / 60; + else + return - timezone / 60; + +#elif defined(__CYGWIN__) + time_t local_time = time(NULL); + tm gt; + gmtime_r(&local_time, &gt); + time_t gmt_time = mktime(&gt); + return (local_time - gmt_time) / 60; + +#elif defined(__hpux) + tm local; + localtime_r(&t0, &local); + local.tm_isdst = 0; + time_t t1 = mktime(&local); + return - (timezone - (t1 - t0)) / 60; + +#else // other UNIX + tm t; + localtime_r(&t0, &t); + return t.tm_gmtoff / 60; +#endif + +#endif +} + + +datetime ptdecl utodatetime(time_t u) +{ + return _unixepoch + large(u) * 1000; +} + + +} diff --git a/source/ptypes/ptime.h b/source/ptypes/ptime.h @@ -0,0 +1,72 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifndef __PTIME_H__ +#define __PTIME_H__ + +#ifndef __PPORT_H__ +#include "pport.h" +#endif + +#ifndef __PTYPES_H__ +#include "ptypes.h" +#endif + + +#include <time.h> + + +namespace ptypes { + +// datetime type: 64-bit, number of milliseconds since midnight 01/01/0001 +typedef large datetime; + +#define invdatetime LLCONST(-1) + +#define _msecsmax 86400000 // number of milliseconds in one day +#define _daysmax 3652059 // number of days between 01/01/0001 and 12/31/9999 +#define _datetimemax LLCONST(315537897600000) // max. allowed number for datetime type +#define _unixepoch LLCONST(62135596800000) // difference between time_t and datetime in milliseconds + + +// datetime general utilities +inline int days(datetime d) { return int(d / _msecsmax); } +inline int msecs(datetime d) { return int(d % _msecsmax); } + +ptpublic datetime ptdecl mkdt(int days, int msecs); +ptpublic bool ptdecl isvalid(datetime); +ptpublic datetime ptdecl now(bool utc = true); +ptpublic void ptdecl tzupdate(); +ptpublic int ptdecl tzoffset(); +ptpublic string ptdecl dttostring(datetime, const char* fmt); +ptpublic string ptdecl nowstring(const char* fmt, bool utc = true); +ptpublic datetime ptdecl utodatetime(time_t u); +ptpublic struct tm* ptdecl dttotm(datetime dt, struct tm& t); + +// date/calendar manipulation +ptpublic bool ptdecl isleapyear(int year); +ptpublic int ptdecl daysinmonth(int year, int month); +ptpublic int ptdecl daysinyear(int year, int month); +ptpublic int ptdecl dayofweek(datetime); +ptpublic bool ptdecl isdatevalid(int year, int month, int day); +ptpublic datetime ptdecl encodedate(int year, int month, int day); +ptpublic bool ptdecl decodedate(datetime, int& year, int& month, int& day); + +// time manipulation +ptpublic bool ptdecl istimevalid(int hour, int min, int sec, int msec = 0); +ptpublic datetime ptdecl encodetime(int hour, int min, int sec, int msec = 0); +ptpublic bool ptdecl decodetime(datetime, int& hour, int& min, int& sec, int& msec); +ptpublic bool ptdecl decodetime(datetime, int& hour, int& min, int& sec); + + +} + +#endif // __PTIME_H__ diff --git a/source/ptypes/ptimedsem.cxx b/source/ptypes/ptimedsem.cxx @@ -0,0 +1,130 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <sys/time.h> +# include <pthread.h> +# include <errno.h> +#endif + +#include "pasync.h" + + +namespace ptypes { + + +static void tsem_fail() +{ + fatal(CRIT_FIRST + 41, "Timed semaphore failed"); +} + + +#ifdef WIN32 + + +timedsem::timedsem(int initvalue) +{ + handle = CreateSemaphore(nil, initvalue, 65535, nil); + if (handle == 0) + tsem_fail(); +} + + +timedsem::~timedsem() +{ + CloseHandle(handle); +} + + +bool timedsem::wait(int timeout) +{ + uint r = WaitForSingleObject(handle, timeout); + if (r == WAIT_FAILED) + tsem_fail(); + return r != WAIT_TIMEOUT; +} + + +void timedsem::post() +{ + if (ReleaseSemaphore(handle, 1, nil) == 0) + tsem_fail(); +} + + +#else + + +inline void tsem_syscheck(int r) +{ + if (r != 0) + tsem_fail(); +} + + +timedsem::timedsem(int initvalue) + : unknown(), count(initvalue) +{ + tsem_syscheck(pthread_mutex_init(&mtx, 0)); + tsem_syscheck(pthread_cond_init(&cond, 0)); +} + + +timedsem::~timedsem() +{ + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mtx); +} + + +bool timedsem::wait(int timeout) +{ + pthread_mutex_lock(&mtx); + while (count <= 0) + { + if (timeout >= 0) + { + timespec abs_ts; + timeval cur_tv; + gettimeofday(&cur_tv, NULL); + abs_ts.tv_sec = cur_tv.tv_sec + timeout / 1000; + abs_ts.tv_nsec = cur_tv.tv_usec * 1000 + + (timeout % 1000) * 1000000; + int rc = pthread_cond_timedwait(&cond, &mtx, &abs_ts); + if (rc == ETIMEDOUT) { + pthread_mutex_unlock(&mtx); + return false; + } + } + else + pthread_cond_wait(&cond, &mtx); + } + count--; + pthread_mutex_unlock(&mtx); + return true; +} + + +void timedsem::post() +{ + pthread_mutex_lock(&mtx); + count++; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mtx); +} + + +#endif + + +} diff --git a/source/ptypes/ptrigger.cxx b/source/ptypes/ptrigger.cxx @@ -0,0 +1,101 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifdef WIN32 +# include <windows.h> +#else +# include <sys/time.h> +# include <pthread.h> +# include <errno.h> +#endif + +#include "pasync.h" + + +namespace ptypes { + + +static void trig_fail() +{ + fatal(CRIT_FIRST + 41, "Trigger failed"); +} + + + +#ifdef WIN32 + + +trigger::trigger(bool autoreset, bool state) +{ + handle = CreateEvent(0, !autoreset, state, 0); + if (handle == 0) + trig_fail(); +} + + +#else + + +inline void trig_syscheck(int r) +{ + if (r != 0) + trig_fail(); +} + + +trigger::trigger(bool iautoreset, bool istate) + : state(int(istate)), autoreset(iautoreset) +{ + trig_syscheck(pthread_mutex_init(&mtx, 0)); + trig_syscheck(pthread_cond_init(&cond, 0)); +} + + +trigger::~trigger() +{ + pthread_cond_destroy(&cond); + pthread_mutex_destroy(&mtx); +} + + +void trigger::wait() +{ + pthread_mutex_lock(&mtx); + while (state == 0) + pthread_cond_wait(&cond, &mtx); + if (autoreset) + state = 0; + pthread_mutex_unlock(&mtx); +} + + +void trigger::post() +{ + pthread_mutex_lock(&mtx); + state = 1; + if (autoreset) + pthread_cond_signal(&cond); + else + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mtx); +} + + +void trigger::reset() +{ + state = 0; +} + + +#endif + + +} diff --git a/source/ptypes/ptypes.h b/source/ptypes/ptypes.h @@ -0,0 +1,1159 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#ifndef __PTYPES_H__ +#define __PTYPES_H__ + + +#ifndef __PPORT_H__ +#include "pport.h" +#endif + +#include <string.h> + + +namespace ptypes { + + +#ifdef _MSC_VER +#pragma pack(push, 4) +// disable "non dll-interface class '...' used as base for dll-interface class '...'" warning +#pragma warning(disable : 4275) +// disable "conditional expression constant" warning +#pragma warning(push) +#pragma warning(disable : 4127) +#endif + + +ptpublic int __PFASTCALL pincrement(int* target); +ptpublic int __PFASTCALL pdecrement(int* target); +ptpublic int __PFASTCALL pexchange(int* target, int value); +ptpublic void* __PFASTCALL pexchange(void** target, void* value); + +template <class T> inline T* tpexchange(T** target, T* value) + { return (T*)pexchange((void**)target, (void*)value); } + + +#if ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ == 4) || defined(__hpux) +# define VARIANT_TYPECAST_HACK +#endif + + +// -------------------------------------------------------------------- // +// --- string class --------------------------------------------------- // +// -------------------------------------------------------------------- // + +// dynamic string class with thread-safe ref-counted buffer + +struct _strrec +{ + int refcount; + int length; +#ifdef __EMSCRIPTEN__ + int _padding0; + int _padding1; +#endif +}; +typedef _strrec* _pstrrec; + + +#define STR_BASE(x) (_pstrrec(x)-1) +#define STR_REFCOUNT(x) (STR_BASE(x)->refcount) +#define STR_LENGTH(x) (STR_BASE(x)->length) + +#define PTR_TO_PSTRING(p) (pstring(&(p))) +#define PTR_TO_STRING(p) (*PTR_TO_PSTRING(p)) + + +ptpublic extern char* emptystr; + +class ptpublic variant; + + +class ptpublic string +{ + friend class variant; + +protected: + char* data; + + static void idxerror(); + + void _alloc(int); + void _realloc(int); + void _free(); + + void initialize() { data = emptystr; } + void initialize(const char*, int); + void initialize(const char*); + void initialize(char); + void initialize(const string& s); + void initialize(const char*, int, const char*, int); + void initialize(const variant&); + void finalize(); + + void assign(const char*, int); + void assign(const char*); + void assign(const string&); + void assign(char); + +#ifdef CHECK_BOUNDS + void idx(int index) const { if (unsigned(index) >= unsigned(STR_LENGTH(data))) idxerror(); } +#else + void idx(int) const { } +#endif + + string(const char* s1, int len1, const char* s2, int len2) { initialize(s1, len1, s2, len2); } + +public: + friend int length(const string& s); + friend int refcount(const string& s); + friend void assign(string& s, const char* buf, int len); + friend void clear(string& s); + friend bool isempty(const string& s); + ptpublic friend char* ptdecl setlength(string&, int); + ptpublic friend char* ptdecl unique(string&); + ptpublic friend void ptdecl concat(string& s, const char* sc, int catlen); + ptpublic friend void ptdecl concat(string& s, const char* s1); + ptpublic friend void ptdecl concat(string& s, char s1); + ptpublic friend void ptdecl concat(string& s, const string& s1); + ptpublic friend string ptdecl copy(const string& s, int from, int cnt); + ptpublic friend string ptdecl copy(const string& s, int from); + ptpublic friend void ptdecl ins(const char* s1, int s1len, string& s, int at); + ptpublic friend void ptdecl ins(const char* s1, string& s, int at); + ptpublic friend void ptdecl ins(char s1, string& s, int at); + ptpublic friend void ptdecl ins(const string& s1, string& s, int at); + ptpublic friend void ptdecl del(string& s, int at, int cnt); + ptpublic friend void ptdecl del(string& s, int at); + ptpublic friend int ptdecl pos(const char* s1, const string& s); + ptpublic friend int ptdecl pos(char s1, const string& s); + friend int pos(const string& s1, const string& s); + ptpublic friend int ptdecl rpos(char s1, const string& s); + ptpublic friend bool ptdecl contains(const char* s1, int len, const string& s, int at); + ptpublic friend bool ptdecl contains(const char* s1, const string& s, int at); + ptpublic friend bool ptdecl contains(char s1, const string& s, int at); + ptpublic friend bool ptdecl contains(const string& s1, const string& s, int at); + ptpublic friend string ptdecl dup(const string& s); + + string() { initialize(); } + string(const char* sc, int initlen) { initialize(sc, initlen); } + string(const char* sc) { initialize(sc); } + string(char c) { initialize(c); } + string(const string& s) { initialize(s); } + ~string() { finalize(); } + +#ifdef VARIANT_TYPECAST_HACK + string(const variant& v) { initialize(v); } +#endif + + string& operator= (const char* sc) { assign(sc); return *this; } + string& operator= (char c) { assign(c); return *this; } + string& operator= (const string& s) { assign(s); return *this; } + string& operator+= (const char* sc) { concat(*this, sc); return *this; } + string& operator+= (char c) { concat(*this, c); return *this; } + string& operator+= (const string& s) { concat(*this, s); return *this; } + + string operator+ (const char* sc) const; + string operator+ (char c) const; + string operator+ (const string& s) const; + + ptpublic friend string ptdecl operator+ (const char* sc, const string& s); + ptpublic friend string ptdecl operator+ (char c, const string& s); + + bool operator== (const char* sc) const { return strcmp(data, sc) == 0; } + bool operator== (char) const; + bool operator== (const string&) const; + bool operator!= (const char* sc) const { return !(*this == sc); } + bool operator!= (char c) const { return !(*this == c); } + bool operator!= (const string& s) const { return !(*this == s); } + + friend bool operator== (const char*, const string&); + friend bool operator== (char, const string&); + friend bool operator!= (const char*, const string&); + friend bool operator!= (char, const string&); + + operator const char*() const { return data; } + operator const uchar*() const { return (uchar*)data; } + + char& operator[] (int i) { idx(i); return unique(*this)[i]; } + const char& operator[] (int i) const { idx(i); return data[i]; } + + friend void initialize(string& s); + friend void initialize(string& s, const string& s1); + friend void initialize(string& s, const char* s1); + friend void finalize(string& s); +}; + + +typedef string* pstring; + +inline int length(const string& s) { return STR_LENGTH(s.data); } +inline int refcount(const string& s) { return STR_REFCOUNT(s.data); } +inline void assign(string& s, const char* buf, int len) { s.assign(buf, len); } +inline void clear(string& s) { s.finalize(); } +inline bool isempty(const string& s) { return length(s) == 0; } +inline int pos(const string& s1, const string& s) { return pos(s1.data, s); } +inline bool operator== (const char* sc, const string& s){ return s == sc; } +inline bool operator== (char c, const string& s) { return s == c; } +inline bool operator!= (const char* sc, const string& s){ return s != sc; } +inline bool operator!= (char c, const string& s) { return s != c; } +inline void initialize(string& s) { s.initialize(); } +inline void initialize(string& s, const string& s1) { s.initialize(s1); } +inline void initialize(string& s, const char* s1) { s.initialize(s1); } +inline void finalize(string& s) { s.finalize(); } + + +ptpublic extern int stralloc; + +ptpublic extern string nullstring; + + +// -------------------------------------------------------------------- // +// --- string utilities ----------------------------------------------- // +// -------------------------------------------------------------------- // + + +ptpublic string ptdecl fill(int width, char pad); +ptpublic string ptdecl pad(const string& s, int width, char c, bool left = true); + +ptpublic string ptdecl itostring(large value, int base, int width = 0, char pad = 0); +ptpublic string ptdecl itostring(ularge value, int base, int width = 0, char pad = 0); +ptpublic string ptdecl itostring(int value, int base, int width = 0, char pad = 0); +ptpublic string ptdecl itostring(unsigned value, int base, int width = 0, char pad = 0); +ptpublic string ptdecl itostring(large v); +ptpublic string ptdecl itostring(ularge v); +ptpublic string ptdecl itostring(int v); +ptpublic string ptdecl itostring(unsigned v); + +ptpublic large ptdecl stringtoi(const char*); +ptpublic large ptdecl stringtoie(const char*); +ptpublic ularge ptdecl stringtoue(const char*, int base); + +ptpublic string ptdecl lowercase(const char* s); +ptpublic string ptdecl lowercase(const string& s); + +char hex4(char c); + +inline char locase(char c) + { if (c >= 'A' && c <= 'Z') return char(c + 32); return c; } + +inline char upcase(char c) + { if (c >= 'a' && c <= 'z') return char(c - 32); return c; } + +inline int hstrlen(const char* p) // some Unix systems do not accept NULL + { return p == nil ? 0 : (int)strlen(p); } + + + + +// -------------------------------------------------------------------- // +// --- character set -------------------------------------------------- // +// -------------------------------------------------------------------- // + + +const int _csetbits = 256; +const int _csetbytes = _csetbits / 8; +const int _csetwords = _csetbytes / sizeof(int); +const char _csetesc = '~'; + + +class ptpublic cset +{ +protected: + char data[_csetbytes]; + + void assign(const cset& s) { memcpy(data, s.data, _csetbytes); } + void assign(const char* setinit); + void clear() { memset(data, 0, _csetbytes); } + void fill() { memset(data, -1, _csetbytes); } + void include(char b) { data[uchar(b) / 8] |= uchar(1 << (uchar(b) % 8)); } + void include(char min, char max); + void exclude(char b) { data[uchar(b) / 8] &= uchar(~(1 << (uchar(b) % 8))); } + void unite(const cset& s); + void subtract(const cset& s); + void intersect(const cset& s); + void invert(); + bool contains(char b) const { return (data[uchar(b) / 8] & (1 << (uchar(b) % 8))) != 0; } + bool eq(const cset& s) const { return memcmp(data, s.data, _csetbytes) == 0; } + bool le(const cset& s) const; + +public: + cset() { clear(); } + cset(const cset& s) { assign(s); } + cset(const char* setinit) { assign(setinit); } + + cset& operator= (const cset& s) { assign(s); return *this; } + cset& operator+= (const cset& s) { unite(s); return *this; } + cset& operator+= (char b) { include(b); return *this; } + cset operator+ (const cset& s) const { cset t = *this; return t += s; } + cset operator+ (char b) const { cset t = *this; return t += b; } + cset& operator-= (const cset& s) { subtract(s); return *this; } + cset& operator-= (char b) { exclude(b); return *this; } + cset operator- (const cset& s) const { cset t = *this; return t -= s; } + cset operator- (char b) const { cset t = *this; return t -= b; } + cset& operator*= (const cset& s) { intersect(s); return *this; } + cset operator* (const cset& s) const { cset t = *this; return t *= s; } + cset operator! () const { cset t = *this; t.invert(); return t; } + bool operator== (const cset& s) const { return eq(s); } + bool operator!= (const cset& s) const { return !eq(s); } + bool operator<= (const cset& s) const { return le(s); } + bool operator>= (const cset& s) const { return s.le(*this); } + + friend cset operator+ (char b, const cset& s); + friend bool operator& (char b, const cset& s); + friend void assign(cset& s, const char* setinit); + friend void clear(cset& s); + friend void fill(cset& s); + friend void include(cset& s, char b); + friend void include(cset& s, char min, char max); + friend void exclude(cset& s, char b); + + ptpublic friend string ptdecl asstring(const cset& s); +}; + + +inline cset operator+ (char b, const cset& s) { return s + b; } +inline bool operator& (char b, const cset& s) { return s.contains(b); } +inline void assign(cset& s, const char* setinit) { s.assign(setinit); } +inline void clear(cset& s) { s.clear(); } +inline void fill(cset& s) { s.fill(); } +inline void include(cset& s, char b) { s.include(b); } +inline void include(cset& s, char min, char max) { s.include(min, max); } +inline void exclude(cset& s, char b) { s.exclude(b); } + + +// -------------------------------------------------------------------- // +// --- basic abstract classes ----------------------------------------- // +// -------------------------------------------------------------------- // + +// basic class with virtual destructor; historically was used as a base +// for all list items. also helps to count the number of created and +// destroyed objects in a program (objalloc global) in DEBUG mode, to +// detect memory leaks. most classes in ptypes are derived from unknown. + +ptpublic extern int objalloc; + +class ptpublic unknown +{ +private: + // make all classes non-copyable by default + unknown(const unknown&); + const unknown& operator= (const unknown&); +public: +#ifdef COUNT_OBJALLOC + unknown() { pincrement(&objalloc); } + virtual ~unknown() { pdecrement(&objalloc); } +#else + unknown() { } + virtual ~unknown() { } +#endif +}; + +typedef unknown* punknown; + + +// provide non-copyable base for all classes that are +// not derived from 'unknown' + +class ptpublic noncopyable +{ +private: + noncopyable(const noncopyable&); + const noncopyable& operator= (const noncopyable&); +public: + noncopyable() {} + ~noncopyable() {} +}; + + + +// -------------------------------------------------------------------- // +// --- exception ------------------------------------------------------ // +// -------------------------------------------------------------------- // + +// the basic exception class. NOTE: the library always throws dynamically +// allocated exception objects. + +class ptpublic exception: public unknown +{ +protected: + string message; +public: + exception(const char* imsg); + exception(const string& imsg); + virtual ~exception(); + virtual const string& get_message() const { return message; } +}; + + +// conversion exception class for stringtoie() and stringtoue() + +class ptpublic econv: public exception +{ +public: + econv(const char* msg): exception(msg) {} + econv(const string& msg): exception(msg) {} + virtual ~econv(); +}; + + +// -------------------------------------------------------------------- // +// --- tpodlist ------------------------------------------------------- // +// -------------------------------------------------------------------- // + +// _podlist implements dynamic array of small POD structures; it serves +// as a basis for all list types in the library. this class is undocumented. +// tpodlist template must be used instead. + +class ptpublic _podlist: public noncopyable +{ +protected: + void* list; // pointer to the array + int count; // number of items in the list + int capacity; // allocated for the list + int itemsize; // list item size + + static void idxerror(); + + _podlist& operator =(const _podlist& t); + + void grow(); + void* doins(int index); + void doins(int index, const _podlist&); + void* doget(int index) const { return (char*)list + index * itemsize; } + void dodel(int index); + void dodel(int index, int count); + void dopop(); + +#ifdef CHECK_BOUNDS + void idx(int index) const { if (unsigned(index) >= unsigned(count)) idxerror(); } + void idxa(int index) const { if (unsigned(index) > unsigned(count)) idxerror(); } +#else + void idx(int) const { } + void idxa(int) const { } +#endif + +public: + _podlist(int itemsize); + ~_podlist(); + + int get_count() const { return count; } + void set_count(int newcount, bool zero = false); + int get_capacity() const { return capacity; } + void set_capacity(int newcap); + void clear() { set_count(0); } + void pack() { set_capacity(count); } + void* ins(int index) { idxa(index); return doins(index); } + void ins(int index, const _podlist& t) { idxa(index); doins(index, t); } + void* add(); + void add(const _podlist& t); + void* operator [](int index) { idx(index); return doget(index); } + void* top() { return operator [](count - 1); } + void del(int index) { idx(index); dodel(index); } + void del(int index, int count) { idx(index); dodel(index, count); } + void pop() { idx(0); dopop(); } +}; + + +// tpodlist is a fully-inlined template based on _podlist + +template <class X, bool initzero = false> class tpodlist: public _podlist +{ +protected: + X& dozero(X& t) { if (initzero) memset(&t, 0, sizeof(X)); return t; } + X& doget(int index) const { return ((X*)list)[index]; } + X& doins(int index) { X& t = *(X*)_podlist::doins(index); return dozero(t); } + void doins(int index, const X& item) { *(X*)_podlist::doins(index) = item; } + +public: + tpodlist(): _podlist(sizeof(X)) {} + tpodlist<X, initzero>& operator =(const tpodlist<X, initzero>& t) + { _podlist::operator =(t); return *this; } + + void set_count(int newcount) { _podlist::set_count(newcount, initzero); } + X& ins(int index) { idxa(index); return doins(index); } + void ins(int index, const X& item) { idxa(index); doins(index, item); } + void ins(int index, const tpodlist<X, initzero>& t) + { _podlist::ins(index, t); } + X& add() { grow(); return dozero(doget(count++)); } + void add(const X& item) { grow(); doget(count++) = item; } + void add(const tpodlist<X, initzero>& t) + { _podlist::add(t); } + X& operator [](int index) { idx(index); return doget(index); } + const X& operator [](int index) const { idx(index); return doget(index); } + X& top() { idx(0); return doget(count - 1); } +}; + + +// -------------------------------------------------------------------- // +// --- tobjlist ------------------------------------------------------- // +// -------------------------------------------------------------------- // + +// _objlist is a base for the tobjlist template, don't use it directly. +// also, _objlist is a base for _strlist and derivatives. + +class ptpublic _objlist: public unknown, protected tpodlist<void*, true> +{ +protected: + struct + { + unsigned ownobjects :1; // list is responsible for destroying the items; used in _objlist + unsigned ownslobjects :1; // same but for _strlist items (in _stritem structure) + unsigned sorted :1; // sorted list (_objlist+) + unsigned duplicates :1; // sorted: allows duplicate keys (_objlist+) + unsigned casesens :1; // sorted: string comparison is case sensitive (_strlist+) + unsigned _reserved :27; + } config; + + _objlist(bool ownobjects); // we hide this ctor, since _objlist actually can't free objects + + void* doget(int index) const { return ((void**)list)[index]; } + void doput(int index, void* obj); + void dodel(int index); + void dodel(int index, int count); + void* dopop(); + void dofree(int index, int count); + + virtual void dofree(void* obj); // pure method; defined in tobjlist instances + virtual int compare(const void* key, const void* obj) const; // pure method; defined in _strlist + +public: + _objlist(); + virtual ~_objlist(); + + int get_count() const { return count; } + void set_count(int newcount); + int get_capacity() const { return capacity; } + void set_capacity(int newcap) { tpodlist<void*,true>::set_capacity(newcap); } + void clear() { set_count(0); } + void pack() { tpodlist<void*,true>::pack(); } + void ins(int index, void* obj) { tpodlist<void*,true>::ins(index, obj); } + void add(void* obj) { tpodlist<void*,true>::add(obj); } + void put(int index, void* obj) { idx(index); doput(index, obj); } + void* operator [](int index) const { idx(index); return doget(index); } + void* top() const { idx(0); return doget(count - 1); } + void* pop() { idx(0); return dopop(); } + void del(int index) { idx(index); dodel(index); } + void del(int index, int count) { idx(index); dodel(index, count); } + int indexof(void* obj) const; + bool search(const void* key, int& index) const; +}; + + +// the tobjlist template implements a list of pointers to arbitrary +// structures. optionally can automatically free objects (ownobjects) +// when removed from a list. only 2 virtual functions are being +// instantiated by this template, the rest is static code in _objlist. + +template <class X> class tobjlist: public _objlist +{ +protected: + X* doget(int index) const { return (X*)_objlist::doget(index); } + virtual void dofree(void* obj); + +public: + tobjlist(bool ownobjects = false): _objlist(ownobjects) {} + virtual ~tobjlist(); + + bool get_ownobjects() const { return config.ownobjects; } + void set_ownobjects(bool newval) { config.ownobjects = newval; } + void ins(int index, X* obj) { _objlist::ins(index, obj); } + void add(X* obj) { _objlist::add(obj); } + void put(int index, X* obj) { _objlist::put(index, obj); } + X* operator [](int index) const { idx(index); return (X*)doget(index); } + X* top() const { return (X*)_objlist::top(); } + X* pop() { return (X*)_objlist::pop(); } + int indexof(X* obj) const { return _objlist::indexof(obj); } + +#ifdef PTYPES19_COMPAT + friend inline void ins(tobjlist& s, int i, X* obj) { s.ins(i, obj); } + friend inline int add(tobjlist& s, X* obj) { s.add(obj); return s.get_count() - 1; } + friend inline void put(tobjlist& s, int i, X* obj) { s.put(i, obj); } + friend inline int indexof(const tobjlist& s, X* obj) { return s.indexof(obj); } + friend inline int push(tobjlist& s, X* obj) { s.add(obj); return s.get_count() - 1; } + friend inline X* pop(tobjlist& s) { return (X*)s.pop(); } + friend inline X* top(const tobjlist& s) { return (X*)s.top(); } + friend inline X* get(const tobjlist& s, int i) { return (X*)s[i]; } +#endif +}; + + +template <class X> void tobjlist<X>::dofree(void* item) +{ + delete (X*)item; +} + + +template <class X> tobjlist<X>::~tobjlist() +{ + set_count(0); +} + + +// -------------------------------------------------------------------- // +// --- tstrlist ------------------------------------------------------- // +// -------------------------------------------------------------------- // + +// _strlist is a base for the tstrlist template + + +typedef int slflags; // left for compatibility + +#define SL_SORTED 1 +#define SL_DUPLICATES 2 +#define SL_CASESENS 4 +#define SL_OWNOBJECTS 8 + + +struct _stritem +{ + string key; + void* obj; + + _stritem(const string& ikey, void* iobj) + : key(ikey), obj(iobj) {} +}; + + +class ptpublic _strlist: protected tobjlist<_stritem> +{ +protected: + static void sortederror(); + static void notsortederror(); + static void duperror(); + + virtual void dofree(void* item); + virtual int compare(const void* key, const void* item) const; + virtual void dofreeobj(void* obj); // pure; tstrlist overrides it + + const string& dogetkey(int index) const { return doget(index)->key; } + void* dogetobj(int index) const { return doget(index)->obj; } + void doins(int index, const string& key, void* obj); + void doput(int index, const string& key, void* obj); + void doput(int index, void* obj); + +public: + _strlist(int flags = 0); + virtual ~_strlist(); + + int get_count() const { return count; } + void set_count(int newcount) { tobjlist<_stritem>::set_count(newcount); } + int get_capacity() const { return capacity; } + void set_capacity(int newcap) { tobjlist<_stritem>::set_capacity(newcap); } + void clear() { tobjlist<_stritem>::clear(); } + void pack() { tobjlist<_stritem>::pack(); } + bool get_sorted() const { return config.sorted; } + bool get_duplicates() const { return config.duplicates; } + bool get_casesens() const { return config.casesens; } + bool get_ownobjects() const { return config.ownslobjects; } + void set_ownobjects(bool newval) { config.ownslobjects = newval; } + void ins(int index, const string& key, void* obj) { idxa(index); doins(index, key, obj); } + void put(int index, const string& key, void* obj) { idx(index); doput(index, key, obj); } + void put(int index, void* obj) { idx(index); doput(index, obj); } + int put(const string& key, void* obj); + int add(const string& key, void* obj); + void* operator [](int index) const { idx(index); return dogetobj(index); } + void* operator [](const char* key) const; + const string& getkey(int index) const { idx(index); return dogetkey(index); } + bool search(const char* key, int& index) const { return _objlist::search(key, index); } + void del(int index) { idx(index); dodel(index); } + void del(int index, int delcount) { idx(index); dodel(index, delcount); } + void del(const char* key) { put(key, nil); } + int indexof(const char* key) const; + int indexof(void* obj) const; +}; + + +// the tstrlist template implements a list of string/object pairs, +// optionally sorted for fast searching by string key. + +template <class X> class tstrlist: public _strlist +{ +protected: + virtual void dofreeobj(void* obj); + +public: + tstrlist(int flags = 0): _strlist(flags) {} + virtual ~tstrlist(); + + void ins(int index, const string& key, X* obj) { _strlist::ins(index, key, obj); } + void put(int index, const string& key, X* obj) { _strlist::put(index, key, obj); } + void put(int index, X* obj) { _strlist::put(index, obj); } + int put(const string& key, X* obj) { return _strlist::put(key, obj); } + int add(const string& key, X* obj) { return _strlist::add(key, obj); } + X* operator [](int index) const { return (X*)_strlist::operator [](index); } + X* operator [](const char* key) const { return (X*)_strlist::operator [](key); } + int indexof(X* obj) const { return _strlist::indexof(obj); } + int indexof(const char* key) const { return _strlist::indexof(key); } + +#ifdef PTYPES19_COMPAT + // pre-2.0 interface for backwards compatibility + friend inline void ins(tstrlist& s, int i, const string& str, X* obj) { s.ins(i, str, obj); } + friend inline int add(tstrlist& s, const string& str, X* obj) { return s.add(str, obj); } + friend inline void put(tstrlist& s, int i, const string& str, X* obj) { s.put(i, str, obj); } + friend inline void put(tstrlist& s, int i, X* obj) { s.put(i, obj); } + friend inline int indexof(const tstrlist& s, X* obj) { return s.indexof(obj); } + friend inline X* get(const tstrlist& s, int i) { return (X*)s[i]; } +#endif +}; + + +template <class X> void tstrlist<X>::dofreeobj(void* obj) +{ + delete (X*)obj; +} + + +template <class X> tstrlist<X>::~tstrlist() +{ + set_count(0); +} + + +// -------------------------------------------------------------------- // +// --- textmap -------------------------------------------------------- // +// -------------------------------------------------------------------- // + +// textmap is a list of string pairs (key/value) + +struct _textitem +{ + string key; + string value; + + _textitem(const string& ikey, const string& ivalue) + : key(ikey), value(ivalue) {} +}; + + +class ptpublic textmap: protected tobjlist<_textitem> +{ +protected: + virtual int compare(const void* key, const void* item) const; + const string& dogetvalue(int index) const { return doget(index)->value; } + const string& dogetkey(int index) const { return doget(index)->key; } + +public: + textmap(bool casesens = false); + virtual ~textmap(); + + int get_count() const { return tobjlist<_textitem>::get_count(); } + void pack() { tobjlist<_textitem>::pack(); } + void clear() { tobjlist<_textitem>::clear(); } + int put(const string& key, const string& value); + void del(int index) { idx(index); dodel(index); } + void del(const char* key) { put(key, nullstring); } + const string& get(int index) const { idx(index); return dogetvalue(index); } + const string& getkey(int index) const { idx(index); return dogetkey(index); } + const string& get(const char* key) const; + const string& operator [](int index) const { return get(index); } + const string& operator [](const char* key) const { return get(key); } + int indexof(const char* key) const; +}; + + +// -------------------------------------------------------------------- // +// --- component ------------------------------------------------------ // +// -------------------------------------------------------------------- // + +// the component class is an abstract class that provides reference +// counting and delete notification mechanisms. all stream classes +// in ptypes are derived from component. + +// class ID's for all basic types: the first byte (least significant) +// contains the base ID, the next is for the second level of inheritance, +// etc. total of 4 levels allowed for basic types. call classid() for an +// object, mask out first N bytes of interest and compare with a CLASS_XXX +// value. f.ex. to determine whether an object is of type infile or any +// derivative: (o->classid() & 0xffff) == CLASS2_INFILE. this scheme is for +// internal use by PTypes and Objection; partly replaces the costly C++ RTTI +// system. + +// first level of inheritance +const int CLASS_UNDEFINED = 0x00000000; +const int CLASS_INSTM = 0x00000001; +const int CLASS_OUTSTM = 0x00000002; +const int CLASS_UNIT = 0x00000003; + +// second level of inheritance +const int CLASS2_INFILE = 0x00000100 | CLASS_INSTM; +const int CLASS2_INMEMORY = 0x00000200 | CLASS_INSTM; +const int CLASS2_FDX = 0x00000300 | CLASS_INSTM; +const int CLASS2_OUTFILE = 0x00000100 | CLASS_OUTSTM; +const int CLASS2_OUTMEMORY = 0x00000200 | CLASS_OUTSTM; + +// third level of inheritance +const int CLASS3_LOGFILE = 0x00010000 | CLASS2_OUTFILE; +const int CLASS3_IPSTM = 0x00020000 | CLASS2_FDX; +const int CLASS3_NPIPE = 0x00030000 | CLASS2_FDX; + + +class ptpublic component: public unknown +{ +protected: + int refcount; // reference counting, used by addref() and release() + tobjlist<component>* freelist; // list of components to notify about destruction, safer alternative to ref-counting + void* typeinfo; // reserved for future use + + virtual void freenotify(component* sender); + +public: + component(); + virtual ~component(); + void addnotification(component* obj); + void delnotification(component* obj); + + ptpublic friend component* ptdecl addref(component*); + ptpublic friend bool ptdecl release(component*); + friend int refcount(component* c); + + virtual int classid(); + + void set_typeinfo(void* t) { typeinfo = t; } + void* get_typeinfo() { return typeinfo; } +}; + +typedef component* pcomponent; + + +inline int refcount(component* c) { return c->refcount; } + +component* ptdecl addref(component* c ); + +template <class T> inline T* taddref(T* c) + { return (T*)addref((component*)c); } + + +template <class T> class compref +{ +protected: + T* ref; +public: + compref() { ref = 0; } + compref(const compref<T>& r) { ref = taddref<T>(r.ref); } + compref(T* c) { ref = taddref<T>(c); } + ~compref() { release(ref); } + compref<T>& operator =(T* c); + compref<T>& operator =(const compref<T>& r) { return operator =(r.ref); } + T& operator *() const { return *ref; } + T* operator ->() const { return ref; } + bool operator ==(const compref<T>& r) const { return ref == r.ref; } + bool operator ==(T* c) const { return ref == c; } + bool operator !=(const compref<T>& r) const { return ref != r.ref; } + bool operator !=(T* c) const { return ref != c; } + operator T*() const { return ref; } +}; + + +template <class T> compref<T>& compref<T>::operator =(T* c) +{ + release(tpexchange<T>(&ref, taddref<T>(c))); + return *this; +} + + +// -------------------------------------------------------------------- // +// --- variant -------------------------------------------------------- // +// -------------------------------------------------------------------- // + + +enum { + VAR_NULL, + VAR_INT, + VAR_BOOL, + VAR_FLOAT, + VAR_STRING, + VAR_ARRAY, + VAR_OBJECT, + + VAR_COMPOUND = VAR_STRING +}; + + +class ptpublic _varray; + + +class ptpublic variant +{ + friend class string; + friend class _varray; + +protected: + int tag; // VAR_XXX + union { + large i; // 64-bit int value + bool b; // bool value + double f; // double value + char* s; // string object; can't declare as string because of the union + _varray* a; // pointer to a reference-counted _strlist object + component* o; // pointer to a reference-counted component object (or derivative) + } value; // we need this name to be able to copy the entire union in some situations + + void initialize() { tag = VAR_NULL; } + void initialize(large v) { tag = VAR_INT; value.i = v; } + void initialize(bool v) { tag = VAR_BOOL; value.b = v; } + void initialize(double v) { tag = VAR_FLOAT; value.f = v; } + void initialize(const char* v) { tag = VAR_STRING; ptypes::initialize(PTR_TO_STRING(value.s), v); } + void initialize(const string& v) { tag = VAR_STRING; ptypes::initialize(PTR_TO_STRING(value.s), v); } + void initialize(_varray* a); + void initialize(component* o); + void initialize(const variant& v); + void finalize(); + + void assign(large); + void assign(bool); + void assign(double); + void assign(const char*); + void assign(const string&); + void assign(_varray*); + void assign(component*); + void assign(const variant&); + + bool equal(const variant& v) const; + + variant(_varray* a) { initialize(a); } + +public: + // construction + variant() { initialize(); } + variant(int v) { initialize(large(v)); } + variant(unsigned int v) { initialize(large(v)); } + variant(large v) { initialize(v); } + variant(bool v) { initialize(v); } + variant(double v) { initialize(v); } + variant(const char* v) { initialize(v); } + variant(const string& v) { initialize(v); } + variant(component* v) { initialize(v); } + variant(const variant& v) { initialize(v); } + ~variant() { finalize(); } + + // assignment + variant& operator= (int v) { assign(large(v)); return *this; } + variant& operator= (unsigned int v) { assign(large(v)); return *this; } + variant& operator= (large v) { assign(v); return *this; } + variant& operator= (bool v) { assign(v); return *this; } + variant& operator= (double v) { assign(v); return *this; } + variant& operator= (const char* v) { assign(v); return *this; } + variant& operator= (const string& v) { assign(v); return *this; } + variant& operator= (component* v) { assign(v); return *this; } + variant& operator= (const variant& v) { assign(v); return *this; } + + // typecast + operator int() const; + operator unsigned int() const; + operator long() const; + operator unsigned long() const; + operator large() const; + operator bool() const; + operator double() const; + operator string() const; + operator component*() const; + + // comparison + bool operator== (const variant& v) const { return equal(v); } + bool operator!= (const variant& v) const { return !equal(v); } + + // typification + ptpublic friend void ptdecl clear(variant&); + friend int vartype(const variant& v); + friend bool isnull(const variant& v); + friend bool isint(const variant& v); + friend bool isbool(const variant& v); + friend bool isfloat(const variant& v); + friend bool isstring(const variant& v); + friend bool isarray(const variant& v); + friend bool isobject(const variant& v); + friend bool iscompound(const variant& v); + + // array manipulation + ptpublic friend void ptdecl aclear(variant&); + ptpublic friend variant ptdecl aclone(const variant&); + ptpublic friend const variant& ptdecl get(const variant&, const string& key); + ptpublic friend const variant& ptdecl get(const variant&, large key); + ptpublic friend void ptdecl put(variant&, const string& key, const variant& item); + ptpublic friend void ptdecl put(variant&, large key, const variant& item); + ptpublic friend void ptdecl del(variant&, const string& key); + ptpublic friend void ptdecl del(variant&, large key); + + // indexed access to arrays + ptpublic friend int ptdecl alength(const variant&); + ptpublic friend void ptdecl apack(variant&); + ptpublic friend bool ptdecl anext(const variant& a, int&, variant& item); + ptpublic friend bool ptdecl anext(const variant& a, int&, variant& item, string& key); + ptpublic friend int ptdecl aadd(variant&, const variant& item); + ptpublic friend void ptdecl aput(variant&, int index, const variant& item); + ptpublic friend void ptdecl ains(variant&, int index, const variant& item); + ptpublic friend void ptdecl adel(variant&, int index); + ptpublic friend const variant& ptdecl aget(const variant&, int index); + ptpublic friend string ptdecl akey(const variant&, int index); + + const variant& operator[](const char* key) const { return get(*this, string(key)); } + const variant& operator[](const string& key) const { return get(*this, key); } + const variant& operator[](large key) const { return get(*this, key); } + + // 'manual' initialization/finalization, undocumented. use with care! + friend void initialize(variant& v); + friend void initialize(variant& v, large i); + friend void initialize(variant& v, int i); + friend void initialize(variant& v, unsigned int i); + friend void initialize(variant& v, bool i); + friend void initialize(variant& v, double i); + friend void initialize(variant& v, const char* i); + friend void initialize(variant& v, const string& i); + friend void initialize(variant& v, component* i); + friend void initialize(variant& v, const variant& i); + friend void finalize(variant& v); +}; + + +typedef variant* pvariant; + + +inline int vartype(const variant& v) { return v.tag; } +inline bool isnull(const variant& v) { return v.tag == VAR_NULL; } +inline bool isint(const variant& v) { return v.tag == VAR_INT; } +inline bool isbool(const variant& v) { return v.tag == VAR_BOOL; } +inline bool isfloat(const variant& v) { return v.tag == VAR_FLOAT; } +inline bool isstring(const variant& v) { return v.tag == VAR_STRING; } +inline bool isarray(const variant& v) { return v.tag == VAR_ARRAY; } +inline bool isobject(const variant& v) { return v.tag == VAR_OBJECT; } +inline bool iscompound(const variant& v) { return v.tag >= VAR_COMPOUND; } + +inline void initialize(variant& v) { v.initialize(); } +inline void initialize(variant& v, large i) { v.initialize(i); } +inline void initialize(variant& v, int i) { v.initialize(large(i)); } +inline void initialize(variant& v, unsigned int i) { v.initialize(large(i)); } +inline void initialize(variant& v, bool i) { v.initialize(i); } +inline void initialize(variant& v, double i) { v.initialize(i); } +inline void initialize(variant& v, const char* i) { v.initialize(i); } +inline void initialize(variant& v, const string& i) { v.initialize(i); } +inline void initialize(variant& v, component* i) { v.initialize(i); } +inline void initialize(variant& v, const variant& i) { v.initialize(i); } +inline void finalize(variant& v) { if (v.tag >= VAR_COMPOUND) v.finalize(); } + + +ptpublic extern const variant nullvar; + + +// variant exception class; may be thrown when a variant +// is being typecast'ed to 32-bit int and the value is +// out of range + +class ptpublic evariant: public exception +{ +protected: +public: + evariant(const char* msg): exception(msg) {} + evariant(const string& msg): exception(msg) {} + virtual ~evariant(); +}; + + + +// -------------------------------------------------------------------- // +// --- pre-2.0 compatibility declarations ----------------------------- // +// -------------------------------------------------------------------- // + + +#ifdef PTYPES19_COMPAT + +// ptypes-1.9 objlist and strlist: accept only 'unknown' and +// derivatives as a base type + +class ptpublic objlist: public tobjlist<unknown> +{ +public: + objlist(bool ownobjects = false); + virtual ~objlist(); +}; + +inline int length(const _objlist& s) { return s.get_count(); } +inline void setlength(_objlist& s, int newcount) { s.set_count(newcount); } +inline void pack(_objlist& s) { s.pack(); } +inline void clear(_objlist& s) { s.clear(); } +inline int push(_objlist& s, unknown* obj) { s.add(obj); return length(s) - 1; } +inline unknown* top(const _objlist& s) { return (unknown*)s.top(); } +inline void ins(_objlist& s, int i, unknown* obj) { s.ins(i, obj); } +inline int add(_objlist& s, unknown* obj) { s.add(obj); return length(s) - 1; } +inline void put(_objlist& s, int i, unknown* obj) { s.put(i, obj); } +inline unknown* get(const _objlist& s, int i) { return (unknown*)s[i]; } +inline unknown* pop(_objlist& s) { return (unknown*)s.pop(); } +inline void del(_objlist& s, int i) { s.del(i); } +inline int indexof(const _objlist& s, unknown* obj) { return s.indexof(obj); } + + +class ptpublic strlist: public tstrlist<unknown> +{ +public: + strlist(int flags = 0); + virtual ~strlist(); +}; + +inline int length(const _strlist& s) { return s.get_count(); } +inline void clear(_strlist& s) { s.clear(); } +inline void pack(_strlist& s) { s.pack(); } +inline bool search(const _strlist& s, const char* key, int& i) { return s.search(key, i); } +inline void ins(_strlist& s, int i, const string& key, unknown* obj) { s.ins(i, key, obj); } +inline int add(_strlist& s, const string& key, unknown* obj) { return s.add(key, obj); } +inline void put(_strlist& s, int i, const string& key, unknown* obj) { s.put(i, key, obj); } +inline void put(_strlist& s, int i, unknown* obj) { s.put(i, obj); } +inline unknown* get(const _strlist& s, int i) { return (unknown*)s[i]; } +inline const string& getstr(const _strlist& s, int i) { return s.getkey(i); } +inline void del(_strlist& s, int i) { s.del(i); } +inline int find(const _strlist& s, const char* key) { return s.indexof(key); } +inline int indexof(const _strlist& s, unknown* obj) { return s.indexof(obj); } + + +// ptypes-1.9 strmap: now replaced with _strlist(SL_SORTED) + +class ptpublic strmap: public tstrlist<unknown> +{ +public: + strmap(int flags = 0); + virtual ~strmap(); +}; + +inline void put(strmap& m, const string& key, unknown* obj) { m.put(key, obj); } +inline unknown* get(const strmap& m, const char* key) { return m[key]; } +inline void del(strmap& m, const char* key) { m.del(key); } + +template <class X> class tstrmap: public strmap +{ +public: + tstrmap(): strmap() {} + tstrmap(int iflags): strmap(iflags) {} + friend inline X* get(const tstrmap& m, const char* str) { return (X*)ptypes::get((const strmap&)m, str); } + friend inline void put(tstrmap& m, const string& str, X* obj) { unknown* t = obj; ptypes::put(m, str, t); } + X* operator[] (const char* str) const { return (X*)ptypes::get(*this, str); } +}; + + +// ptypes-1.9 textmap interface + +inline int length(const textmap& m) { return m.get_count(); } +inline void clear(textmap& m) { m.clear(); } +inline const string& get(const textmap& m, const string& k) { return m.get(k); } +inline void put(textmap& m, const string& k, const string& v) { m.put(k, v); } +inline void del(textmap& m, const string& k) { m.del(k); } + + +#endif // PTYPES19_COMPAT + + +#ifdef _MSC_VER +#pragma warning(pop) +#pragma pack(pop) +#endif + + +} + +#endif // __PTYPES_H__ diff --git a/source/ptypes/ptypes_test.cxx b/source/ptypes/ptypes_test.cxx @@ -0,0 +1,1622 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ +#include <stdlib.h> +#include <stdio.h> + +#include "pport.h" +#include "ptypes.h" +#include "pstreams.h" +#include "pinet.h" +#include "ptime.h" + + +#ifndef PTYPES_ST +#include "pasync.h" +#endif + + +USING_PTYPES + + +void passert(bool cond) +{ + if (!cond) + { + fatal(0xa3, "*** ASSERTION FAILED ***"); + } +} + + +void showstr(const char* shouldbe, const char* is) +{ + passert(strcmp(shouldbe, is) == 0); + pout.putf("[%s] %s\n", shouldbe, is); +} + + +void showstr(const char* shouldbe, const string& is) +{ + showstr(shouldbe, pconst(is)); +} + + +void showhex(const char* shouldbe, const char* is, int islen) +{ + string s; + int i; + for (i = 0; i < islen; i++) + s += itostring((unsigned char)is[i], 16, 2); + s = lowercase(s); + showstr(shouldbe, s); +} + + +void showint(int shouldbe, int is) +{ + passert(shouldbe == is); + pout.putf("[%d] %d\n", shouldbe, is); +} + + +void showint(large shouldbe, large is) +{ + passert(shouldbe == is); + pout.putf("[%lld] %lld\n", shouldbe, is); +} + + +void showchar(char shouldbe, char is) +{ + passert(shouldbe == is); + pout.putf("[%c] %c\n", shouldbe, is); +} + + +// +// string test +// + + +void const_string_call(const string& s) +{ + showchar('R', s[2]); +} + + +void string_test1() +{ + pout.put("\n--- STRING CLASS\n"); + + static char strbuf[10] = "STRING"; + + char c = 'A'; + + string s1 = "string"; + s1 = s1; + s1 += s1; + del(s1, 6, 6); + string s2 = s1; + string s3; + string s4(strbuf, strlen(strbuf)); + string s5 = 'A'; + string s6 = c; + + showstr("string", s1); + showstr(s1, s2); + showstr("", s3); + showstr("STRING", s4); + const_string_call(s4); + showchar('I', s4[3]); + showint(6, length(s4)); + showint(2, refcount(s1)); + clear(s2); + showint(1, refcount(s1)); + s2 = s1; + unique(s1); + showint(1, refcount(s1)); + setlength(s1, 64); + string s7 = s1; + setlength(s1, 3); + showstr("str", s1); + showstr("strING", s1 += copy(s4, 3, 3)); + del(s1, 3, 3); + showstr("str", s1); + ins("ing", s1, 0); + showstr("ingstr", s1); + s2 = "str" + s1 + "ing"; + showstr("stringstring", s2); + s3 = s2; + s3 = "strungstrung"; + s3 = s2; + s3 = "strung"; + del(s3, 4); + showstr("stru", s3); + + s2 = "str" + s1; + s2 = s1 + "str"; + s2 += "str"; + + s2 = 's' + s1; + s2 = s1 + 's'; + s2 += 's'; + + s2 = c + s1; + s2 = s1 + c; + s2 += c; + + s2 = 'a'; + s2 = c; +} + + +void string_test2() +{ + pout.put("\n--- STRING CLASS\n"); + + string s1 = "ingstr"; + string s2 = "stringstring"; + string s4 = "STRING"; + + showint(1, pos('t', s2)); + showint(2, pos("ri", s2)); + showint(3, pos(s1, s2)); + showint(1, contains("tr", s2, 1)); + showint(0, contains("tr", s2, 2)); + showint(1, s4 == "STRING"); + showint(1, "STRING" == s4); + showchar('R', s4[2]); + + string s5 = 'A'; + showint(1, s5 == 'A'); + showint(1, 'A' == s5); + + showstr("123456789", itostring(123456789)); + showstr("123", itostring(char(123))); + showstr("-000123", itostring(-123, 10, 7, '0')); + showstr("0ABCDE", itostring(0xabcde, 16, 6)); + showstr("-9223372036854775808", itostring(LARGE_MIN)); + showstr("18446744073709551615", itostring(ULARGE_MAX)); + + showint(1234, (int)stringtoi("1234")); + showint(large(0x15AF), stringtoue("15AF", 16)); + showint(LARGE_MAX, stringtoue("5zzzzzzzzzz", 64)); + + try + { + // out of range by 1 + stringtoue("18446744073709551616", 10); + fatal(0xb0, "Conversion overflow not detected"); + } + catch (econv* e) + { + showstr("Out of range: '18446744073709551616'", e->get_message()); + delete e; + } + + showint(large(123), stringtoie("123")); + showint(large(-123), stringtoie("-123")); + showint(LARGE_MIN, stringtoie("-9223372036854775808")); + showint(LARGE_MAX, stringtoie("9223372036854775807")); + + try + { + // out of range by 1 + stringtoie("9223372036854775808"); + fatal(0xb0, "Conversion overflow not detected"); + } + catch (econv* e) + { + showstr("Out of range: '9223372036854775808'", e->get_message()); + delete e; + } + + showstr("abcabc", lowercase(s1 = "aBCAbc")); + showstr("abcabc", lowercase(s1)); +} + + +void string_benchmarks() +{ + pout.put("\n--- STRING BENCHMARKS\n"); + + int i; + string s1 = "string"; + string s2; + + // for the first time we run the test to let the VM settle down and stop swapping + for (i = 0; i < 156000; i++) + s2 += s1; + + // here is the actual test + clear(s2); + datetime start = now(); + for (i = 0; i < 156000; i++) + s2 += s1; + datetime diff = now() - start; + + pout.putf("Performance index compared to WinNT on P3/800MHz (smaller = better):\n%.3f\n", diff / 1000.0); +} + + +// +// cset test +// + +void cset_test() +{ + pout.put("\n--- CSET CLASS\n"); + + cset s1 = "~09~0a~0d~F0 A-Z~~"; + cset s2 = "A-F"; + char c = 'B'; + + showstr("~09~0a~0d A-Z~~~f0", asstring(s1)); + s1 -= char(0xf0); + s1 -= cset("~00-~20"); + showstr("A-Z~~", asstring(s1)); + s1 -= s2; + showstr("G-Z~~", asstring(s1)); + s1 += s2; + s1 += ' '; + showstr(" A-Z~~", asstring(s1)); + showint(1, s2 == cset("A-F")); + showint(1, s2 <= s1); + s1 -= 'A'; + s1 -= c; + showint(0, s2 <= s1); + s1 = s1 + char(0xf1); + showint(1, char(0xf1) & s1); + showint(0, char(0xf2) & s1); +} + + +// +// podlist +// + + +void const_podlist_test(const tpodlist<int, true>& p) +{ +// int& i = p[0]; + showint(7, p[1]); +} + + +void podlist_test() +{ + pout.put("\n--- PODLIST\n"); + + tpodlist<int, true> p; + p.add() = 6; + p.add(8); + p.ins(1, 7); + showint(7, p[1]); + const_podlist_test(p); + showint(3, p.get_count()); + p.set_count(5); + p.ins(5) = 10; + showint(10, p.top()); + p.pop(); + + tpodlist<int, true> p1; + p1.add(p); + p1.pop(); + p1.add(p); +} + + +// +// ptrlist +// + + +struct known: public unknown +{ + int value; + known(int ivalue): value(ivalue) {} +}; + +typedef tobjlist<known> knownlist; + + +string ol_asstring(const knownlist& s) +{ + string ret = "{"; + for (int i = 0; i < s.get_count(); i++) { + if (i > 0) + concat(ret, ", "); + ret += itostring(s[i]->value); + } + concat(ret, '}'); + return ret; +} + + +void ptrlist_test() +{ + pout.put("\n--- PTRLIST\n"); + + knownlist s1(true); + known* obj; + + s1.add(new known(10)); + s1.ins(0, new known(5)); + s1.ins(2, obj = new known(20)); + s1.add(new known(30)); + s1.add(new known(40)); + s1.put(4, new known(45)); + s1.del(4); + s1.del(1); + + s1[0]; + showint(3, s1.get_count()); + showint(1, s1.indexof(obj)); + showstr("{5, 20, 30}", ol_asstring(s1)); + + s1.clear(); + showstr("{}", ol_asstring(s1)); +} + + + +// +// strlist +// + + +typedef tstrlist<known> knownstrlist; + + +string sl_asstring(const knownstrlist& s) +{ + string ret = "{"; + for (int i = 0; i < s.get_count(); i++) + { + if (i > 0) + concat(ret, ", "); + ret += s.getkey(i) + ":" + itostring(s[i]->value); + } + concat(ret, '}'); + return ret; +} + + +void strlist_test() +{ + pout.put("\n--- STRLIST\n"); + + knownstrlist s1(SL_OWNOBJECTS); + known* obj; + + s1.add("ten", new known(10)); + s1.ins(0, "five", new known(5)); + s1.ins(2, "twenty", obj = new known(20)); + s1.add("thirty", new known(30)); + s1.add("forty", new known(40)); + s1.put(4, "forty five", new known(45)); + s1.del(4); + s1.del(1); + + showint(3, s1.get_count()); + showint(1, s1.indexof(obj)); + showint(2, s1.indexof("thirty")); + showint(2, s1.indexof("THIRTY")); + showint(-1, s1.indexof("forty")); + + showstr("{five:5, twenty:20, thirty:30}", sl_asstring(s1)); + + knownstrlist s2(SL_OWNOBJECTS | SL_SORTED | SL_CASESENS); + s2.add("five", new known(5)); + s2.add("ten", new known(10)); + s2.add("twenty", new known(20)); + s2.add("thirty", new known(30)); + s2.add("forty", new known(40)); + + showint(5, s2.get_count()); + showint(3, s2.indexof("thirty")); + showint(-1, s2.indexof("THIRTY")); + showint(-1, s2.indexof("hovik")); + + showstr("{five:5, forty:40, ten:10, thirty:30, twenty:20}", sl_asstring(s2)); + + s2.clear(); + showstr("{}", sl_asstring(s2)); + + tstrlist<known> s3(SL_OWNOBJECTS | SL_SORTED | SL_DUPLICATES); + + s3.add("a", nil); + s3.add("b", nil); + s3.add("b", nil); + s3.add("b", nil); + s3.add("b", nil); + s3.add("b", nil); + s3.add("b", nil); + s3.add("c", nil); + + showint(1, s3.indexof("b")); + s3.del(1, 2); + + tstrlist<known> s(SL_OWNOBJECTS | SL_SORTED); + + s.put("five", new known(5)); + s.put("ten", new known(10)); + s.put("twenty", new known(20)); + s.put("thirty", new known(30)); + s.put("forty", new known(40)); + + showint(20, s["twenty"]->value); + showint(0, pintptr(s["hovik"])); + showint(5, s.get_count()); + s.put("twenty", nil); + showint(4, s.get_count()); +} + + +// +// textmap +// + + +void textmap_test() +{ + pout.put("\n--- TEXTMAP CLASS\n"); + + textmap c; + + c.put("name1", "value1"); + c.put("name2", "value2"); + c.put("name1", "value3"); + showstr("name2", c.getkey(1)); + c.put("name2", ""); + showint(1, c.get_count()); + showstr("value3", c["name1"]); + showstr("", c["name2"]); +} + + + +// +// streams +// + + +char buf1[] = "This is a test."; +char buf2[] = "The file should contain readable text."; + +const char* fname = "stmtest.txt"; + +void outfile_test() +{ + pout.put("\n--- OUTFILE CLASS\n"); + + showint(8, sizeof(off_t)); + + outfile f(fname, false); + f.set_umode(0600); + f.set_bufsize(3); + + f.open(); + f.put(buf1[0]); + f.put(buf1[1]); + f.put("is is a TEST."); + f.seek(-5, IO_END); + f.put("tes*/"); + f.seek(13); + f.put("t."); + f.puteol(); + f.close(); + + f.set_append(true); + f.open(); + f.write(buf2, strlen(buf2)); + f.puteol(); + f.close(); + + pnull.put("This should go to nowhere I"); + pnull.put("This should go to nowhere II"); +} + + +void infile_test() +{ + pout.put("\n--- INFILE CLASS\n"); + + compref<instm> f = &pin; + f = new infile(fname); + f->set_bufsize(3); + + char temp[4]; + + f->open(); + pout.putf("%c", f->get()); + pout.putf("%s\n", pconst(f->line())); + f->read(temp, sizeof temp - 1); + temp[sizeof temp - 1] = 0; + pout.putf("%s", temp); + f->get(); + f->putback(); + pout.putf("%s", pconst(f->token(cset("~20-~FF")))); + f->preview(); + if (f->get_eol()) + { + f->skipline(); + pout.put("\n"); + } + if (f->get_eof()) + pout.put("EOF\n"); +// f.error(1, "Test error message"); + +} + + +void mem_test() +{ + pout.put("\n--- OUT/IN MEMORY CLASS\n"); + + { + outmemory m(12); + m.open(); + m.put("MEMOry"); + m.put(" c"); + m.put("lass is working"); + m.seek(1); + m.put("emo"); + showstr("Memory class", m.get_strdata()); + // try reuse + m.open(); + m.put("memory"); + showstr("memory", m.get_strdata()); + } + { + inmemory m(""); + m.open(); + showstr("", m.token("*")); + m.set_strdata("gArbaGe"); + m.open(); + // try reuse + m.set_strdata("string strong"); + m.set_bufsize(2); // has no effect + m.open(); + showstr("string", m.token("a-z")); + m.seek(-6, IO_END); + showstr("strong", m.token("a-z")); + } +} + + +#ifndef PTYPES_ST + +// +// multithreading +// + +// +// rwlock test +// + +const int rw_max_threads = 30; +const int rw_max_tries = 30; +const int rw_max_delay = 20; +const int rw_rw_ratio = 5; +const bool rw_swap = false; + + +class rwthread: public thread +{ +protected: + virtual void execute(); +public: + rwthread(): thread(false) {} + virtual ~rwthread() { waitfor(); } +}; + + +rwlock rw; + +int reader_cnt = 0; +int writer_cnt = 0; +int total_writers = 0; +int total_readers = 0; +int max_readers = 0; + + +int prand(int max) +{ + return rand() % max; +} + + +void rwthread::execute() +{ + + for(int i = 0; i < rw_max_tries; i++) + { + psleep(prand(rw_max_delay)); + bool writer = prand(rw_rw_ratio) == 0; + if (writer ^ rw_swap) + { + rw.wrlock(); + pout.put('w'); + if (pincrement(&writer_cnt) > 1) + fatal(0xa0, "Writer: Huh?! Writers in here?"); + pincrement(&total_writers); + } + else + { + rw.rdlock(); + pout.put('.'); + int t; + if ((t = pincrement(&reader_cnt)) > max_readers) + max_readers = t; + if (writer_cnt > 0) + fatal(0xa1, "Reader: Huh?! Writers in here?"); + pincrement(&total_readers); + } + psleep(prand(rw_max_delay)); + if (writer ^ rw_swap) + pdecrement(&writer_cnt); + else + pdecrement(&reader_cnt); + rw.unlock(); + } +} + + + +void rwlock_test() +{ +// #ifdef __PTYPES_RWLOCK__ + pout.put("\n--- RWLOCK\n"); + + rwthread* threads[rw_max_threads]; + + srand((unsigned)time(0)); + + int i; + for(i = 0; i < rw_max_threads; i++) + { + threads[i] = new rwthread(); + threads[i]->start(); + } + for(i = 0; i < rw_max_threads; i++) + delete threads[i]; + + pout.putf("\nmax readers: %d\n", max_readers); + pout.putline("do writers 'starve'?"); +// #endif +} + + +// +// jobqueue test ---------------------------------------------------------- +// + + +const int MSG_MYJOB = MSG_USER + 1; +const int NUM_JOB_THREADS = 3; + + +class jobthread: public thread +{ +protected: + int id; + jobqueue* jq; + virtual void execute(); +public: + jobthread(int iid, jobqueue* ijq): thread(false), id(iid), jq(ijq) {} + ~jobthread() { waitfor(); } +}; + + +void jobthread::execute() +{ + bool quit = false; + while (!quit) + { + message* m = jq->getmessage(); + try + { + switch (m->id) + { + case MSG_MYJOB: + // ... do the job ... + psleep(prand(10)); + // report + pout.putf("Thread %d finished the job (param=%d)\n", id, m->param); + break; + case MSG_QUIT: + quit = true; + break; + } + } + catch(...) + { + // the message object must be freed! + delete m; + throw; + } + delete m; + } +} + + +void jobqueue_test() +{ + pout.put("\n--- JOBQUEUE\n"); + + jobqueue jq(3); + tobjlist<jobthread> threads(true); + + srand((unsigned)time(0)); + + // create the thread pool and start all threads + int i; + for(i = 0; i < NUM_JOB_THREADS; i++) + { + jobthread* j = new jobthread(i + 1, &jq); + j->start(); + threads.add(j); + } + + // post jobs for processing + jq.post(MSG_MYJOB, 1); + jq.post(MSG_MYJOB, 2); + jq.post(MSG_MYJOB, 3); + jq.post(MSG_MYJOB, 4); + jq.post(MSG_MYJOB, 5); + jq.post(MSG_MYJOB, 6); + jq.post(MSG_MYJOB, 7); + jq.post(MSG_MYJOB, 8); + + // terminate all threads + for(i = 0; i < NUM_JOB_THREADS; i++) + jq.post(MSG_QUIT); + + // threads are being waitfor()'ed and destroyed + // automatically by the list object +} + + + +// +// msgqueue test ---------------------------------------------------------- +// + + +const int MSG_DIAG = MSG_USER + 1; + + +// +// msgqueue test +// + +// +// class diagmessage +// + +class diagmessage: public message +{ +protected: + string module; + string diagstr; + friend class diagthread; +public: + diagmessage(string imodule, string idiagstr) + : message(MSG_DIAG), module(imodule), + diagstr(idiagstr) {} +}; + + +// +// class diagthread +// + +class diagthread: public thread, protected msgqueue +{ +protected: + virtual void execute(); // override thread::execute() + virtual void cleanup(); // override thread::cleanup() + virtual void msghandler(message& msg); // override msgqueue::msghandler() +public: + diagthread(): thread(false), msgqueue() { } + void postdiag(string module, string diagstr); + void postquit(); +}; + + +void diagthread::postdiag(string module, string diagstr) +{ + msgqueue::post(new diagmessage(module, diagstr)); +} + + +void diagthread::postquit() +{ + msgqueue::post(MSG_QUIT); +} + + +void diagthread::execute() +{ + // starts message queue processing; calls + // msghandler for each message + msgqueue::run(); +} + + +void diagthread::cleanup() +{ +} + + +void diagthread::msghandler(message& msg) +{ + switch (msg.id) + { + case MSG_DIAG: + { + diagmessage& m = (diagmessage&)msg; + pout.putf("%s: %s\n", pconst(m.module), pconst(m.diagstr)); + } + break; + default: + defhandler(msg); + } +} + + +// +// class testthread +// + + +class testthread: public thread +{ +protected: + diagthread* diag; + string myname; + virtual void execute(); + virtual void cleanup(); +public: + semaphore sem; + timedsem tsem; + testthread(diagthread* idiag) + : thread(false), diag(idiag), myname("testthread"), sem(0), tsem(0) {} +}; + + +void testthread::execute() +{ + diag->postdiag(myname, "starts and enters sleep for 1 second"); + psleep(1000); + diag->postdiag(myname, "signals the timed semaphore"); + tsem.post(); + diag->postdiag(myname, "releases the simple semaphore"); + sem.post(); + diag->postdiag(myname, "enters sleep for 1 more second"); + psleep(1000); +} + + +void testthread::cleanup() +{ + diag->postdiag(myname, "terminates"); +} + + +int thread_test() +{ + pout.put("\n--- THREAD AND SEMAPHORE CLASSES\n"); + + int v = 0; + showint(0, pexchange(&v, 5)); + showint(5, pexchange(&v, 10)); + + void* pv = 0; + passert(pexchange(&pv, &v) == 0); + passert(pexchange(&pv, 0) == &v); + + showint(11, pincrement(&v)); + showint(10, pdecrement(&v)); + + diagthread diag; + testthread thr(&diag); + + string myname = "main"; + + diag.start(); + thr.start(); + + diag.postdiag(myname, "waits 5 secs for the timed semaphore (actually wakes up after a second)"); + thr.tsem.wait(5000); // must exit after 1 second instead of 5 + + diag.postdiag(myname, "waits for the semaphore"); + thr.sem.wait(); + diag.postdiag(myname, "now waits for testthread to terminate"); + thr.waitfor(); + + diag.postquit(); + diag.waitfor(); + return 0; +} + + +// +// trigger test +// + + +class trigthread: public thread +{ +protected: + diagthread* diag; + string myname; + virtual void execute(); +public: + trigger trig; + trigthread(diagthread* idiag) + : thread(false), diag(idiag), myname("trigthread"), trig(true, false) {} + virtual ~trigthread() { waitfor(); } +}; + + +void trigthread::execute() +{ + diag->postdiag(myname, "waits on the trigger"); + trig.wait(); + + psleep(2000); + diag->postdiag(myname, "waits on the trigger"); + trig.wait(); + + diag->postdiag(myname, "terminates"); +} + + +int trigger_test() +{ + pout.put("\n--- TRIGGER\n"); + + diagthread diag; + trigthread thr(&diag); + + string myname = "main"; + + diag.start(); + thr.start(); + + psleep(1000); + diag.postdiag(myname, "posts the trigger"); + thr.trig.post(); + + psleep(1000); + diag.postdiag(myname, "posts the trigger again"); + thr.trig.post(); + + thr.waitfor(); + diag.postquit(); + diag.waitfor(); + return 0; +} + + + +#endif // PTYPES_ST + + +// +// md5 test +// + +static md5_digest digest; + +char* md5str(string data) +{ + outmd5 m; + m.open(); + m.put(data); + memcpy(digest, m.get_bindigest(), sizeof(md5_digest)); + return (char*)digest; +} + +string cryptpw(string username, string password) +{ + outmd5 m; + m.open(); + m.put(username); + m.put(password); + m.close(); + return m.get_digest(); +} + +void md5_test() +{ + pout.put("\n--- MD5 OUTPUT STREAM\n"); + // MD5 test suite from RFC1321 + showhex("d41d8cd98f00b204e9800998ecf8427e", md5str(""), md5_digsize); + showhex("0cc175b9c0f1b6a831c399e269772661", md5str("a"), md5_digsize); + showhex("900150983cd24fb0d6963f7d28e17f72", md5str("abc"), md5_digsize); + showhex("f96b697d7cb7938d525a2f31aaf161d0", md5str("message digest"), md5_digsize); + showhex("c3fcd3d76192e4007dfb496cca67e13b", md5str("abcdefghijklmnopqrstuvwxyz"), md5_digsize); + showhex("d174ab98d277d9f5a5611c2c9f419d9f", md5str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"), md5_digsize); + showhex("57edf4a22be3c955ac49da2e2107b67a", md5str("12345678901234567890123456789012345678901234567890123456789012345678901234567890"), md5_digsize); + + showstr("t0htL.C9vunX8SPPsJjDmk", cryptpw("hovik", "myfavoritelonglonglongpassword")); +} + + +// +// outstm::putf() test +// + +void putf_test() +{ + pout.put("\n--- PUTF TEST\n"); + outmemory m; + m.open(); + + m.putf("%s, %c, %d, %llx", "string", 'A', 1234, large(-1)); + showstr("string, A, 1234, ffffffffffffffff", m.get_strdata()); + + m.open(); + + m.putf(" %%, %#o, %+010d", 0765, -3); + showstr(" %, 0765, -000000003", m.get_strdata()); +} + + +// +// pinet/socket tests +// + + +void inet_test1() +{ + try + { + pout.put("\n--- INET SOCKET & UTILITIES\n"); + + ipaddress ip(127, 0, 0, 1); + + // as a test target host we use the one that would never be down! :) + string testname = "www.apache.org"; + + ip = phostbyname(testname); + string ips = iptostring(ip); + pout.putf("IP address of %s: %s\n", pconst(testname), (ip == ipnone) ? "failed" : pconst(ips)); + + if (ip != ipnone) + { + string hs = phostbyaddr(ip); + pout.putf("Name of %s: %s\n", pconst(ips), pconst(hs)); + } + + pout.putf("Canonical name of your local www: %s\n", pconst(phostcname("www"))); + + testname = "www.melikyan.com"; + pout.putf("\nTrying %s:80...\n", pconst(testname)); + ipstream s(testname, 80); + + const char* request = + "GET /ptypes/test.txt HTTP/1.1\r\n" + "Accept: * /*\r\n" + "User-Agent: ptypes_test/2.1\r\n" + "Host: www.melikyan.com\r\n" + "Connection: close\r\n\r\n"; + + s.open(); + s.put(request); + s.flush(); + + while (!s.get_eof()) + { + char buf[16]; + int r = s.read(buf, sizeof(buf)); + pout.write(buf, r); + } + pout.put("\n"); + + s.close(); + } + catch(estream* e) + { + perr.putf("Socket error: %s\n", pconst(e->get_message())); + delete e; + } +} + + +#ifndef PTYPES_ST + + +const int testport = 8085; + + +class svthread: public thread, protected ipstmserver +{ +protected: + virtual void execute(); // override thread::execute() + virtual void cleanup(); // override thread::cleanup() +public: + svthread(): thread(false) {} + virtual ~svthread(); +}; + + +svthread::~svthread() +{ + waitfor(); +} + + +void svthread::execute() +{ + ipstream client; // a socket object to communicate with the client + + try + { + bindall(testport); // listen to all local addresses on port 8081 + serve(client); // wait infinitely for a connection request + + if (client.get_active()) + { + // read one line from the stream; note that theoretically the line can be long, + // so calling client.line(buf, sizeof(buf)) here is a much better idea + string req = lowercase(client.line()); + if (req == "hello") + { + // try to reverse-lookup the client's IP + string host = phostbyaddr(client.get_ip()); + if (isempty(host)) + host = iptostring(client.get_ip()); + + // now send our greeting to the client + client.putf("Hello, %s (%a), nice to see you!\n", + pconst(host), long(client.get_ip())); + client.flush(); + } + + // close() should be called explicitly to shut down the socket + // *gracefully*; otherwise ipstream's destructor may close the + // socket but in a less polite manner + client.close(); + } + } + catch(estream* e) + { + perr.putf("Server error: %s\n", pconst(e->get_message())); + delete e; + } + + // a real server could enter an infinite loop serving requests + // and producing separate threads for each connection +} + + +void svthread::cleanup() +{ +} + + +void inet_test2() +{ + pout.put("\n--- INET CLIENT/SERVER\n"); + + // we run the server in a separate thread in order to be able + // to imitate a client connection from the main thread + svthread server; + + pout.put("\nStarting the server thread...\n"); + server.start(); // it's that easy! :) + + // sleep some time to let the server start its job + psleep(1000); + + try + { + // now create a client socket and send a greeting to our server + ipstream client(ipaddress(127, 0, 0, 1), testport); + client.open(); + + pout.put("Sending a request to the server...\n"); + client.putline("Hello"); + client.flush(); + string rsp = client.line(); + pout.putf("Received: %s\n", pconst(rsp)); + pout.putf("My address and port: %s:%d\n", + pconst(iptostring(client.get_myip())), client.get_myport()); + + client.close(); + } + catch(estream* e) + { + perr.putf("Error: %s\n", pconst(e->get_message())); + delete e; + } + +} + + +// +// UDP test +// + + +class msgsvthread: public thread +{ +protected: + void execute(); +public: + msgsvthread(): thread(false) {} + virtual ~msgsvthread() { waitfor(); } +}; + + +void msgsvthread::execute() +{ + ipmsgserver s; + s.bindall(testport); + try + { + string req = s.receive(1024); + pout.putf("Server received: %s\n", pconst(req)); + string rsp = "gotcha"; + s.send(rsp); + } + catch(estream* e) + { + perr.putf("Server error: %s\n", pconst(e->get_message())); + delete e; + } +} + + +void inet_test3() +{ + pout.put("\n--- INET MESSAGE CLIENT/SERVER\n"); + + msgsvthread sv; + sv.start(); + psleep(1000); + + ipmessage m(ipbcast /* ipaddress(127, 0, 0, 1) */, testport); + try + { + string msg = "hello"; + m.send(msg); + string rsp = m.receive(1024); + pout.putf("Client received: %s\n", pconst(rsp)); + } + catch(estream* e) + { + perr.putf("Client error: %s\n", pconst(e->get_message())); + delete e; + } +} + + +// +// named pipes test +// + + +#define TEST_PIPE "ptypes.test" + + +class npthread: public thread, protected npserver +{ +protected: + virtual void execute(); + virtual void cleanup(); +public: + npthread(): thread(false), npserver(TEST_PIPE) {} + virtual ~npthread(); +}; + + +npthread::~npthread() +{ + waitfor(); +} + + +void npthread::execute() +{ + namedpipe client; + + try + { + serve(client); + + if (client.get_active()) + { + string req = lowercase(client.line()); + if (req == "hello") + { + client.putline("Hello, nice to see you!"); + client.flush(); + } + + client.close(); + } + } + catch(estream* e) + { + perr.putf("Pipe server error: %s\n", pconst(e->get_message())); + delete e; + } +} + + +void npthread::cleanup() +{ +} + + +void pipe_test() +{ + npthread server; + + pout.put("\n--- NAMED PIPES\n"); + pout.put("Starting the pipe server thread...\n"); + server.start(); + + psleep(1000); + + namedpipe client(TEST_PIPE); + + try + { + client.open(); + + pout.put("Sending a request to the server...\n"); + client.putline("Hello"); + client.flush(); + string rsp = client.line(); + pout.putf("Received: %s\n", pconst(rsp)); + + client.close(); + } + catch(estream* e) + { + perr.putf("Error: %s\n", pconst(e->get_message())); + delete e; + } +} + + +#endif // PTYPES_ST + + +// +// date/time/calendar +// + + +void time_test() +{ + pout.put("\n--- DATE/TIME/CALENDAR\n"); + + tzupdate(); + + int year, month, day; + datetime d = encodedate(9999, 12, 31); + decodedate(d, year, month, day); + d = encodedate(1970, 1, 1); + decodedate(d, year, month, day); + + datetime dt; + dt = invdatetime; + int hour, min, sec, msec; + dt = encodetime(23, 59, 59, 998); + decodetime(dt, hour, min, sec, msec); + + dt = encodedate(2001, 8, 27) + encodetime(14, 33, 10); + d = encodedate(2001, 8, 28) + encodetime(14, 33, 10, 500); + dayofweek(dt); + + dt = now(false); + pout.putf("Local time: %s\n", pconst(dttostring(dt, "%x %X %Z"))); + datetime utc = now(); + pout.putf("UTC time: %t GMT\n", utc); + + time_t ut; + time(&ut); + pout.putline(dttostring(utodatetime(ut), "%c")); + + int t = tzoffset(); + bool neg = t < 0; + if (neg) + t = -t; + pout.putf("Time zone offset: %c%02d%02d\n", neg ? '-' : '+', t / 60, t % 60); + { + // PTypes' birthday (birth moment, if you wish) + datetime d = encodedate(2000, 3, 30) + encodetime(13, 24, 58, 995); + pout.putf("PTypes' birth moment: %T GMT\n", d); + + // now see how old is PTypes in days, hours, minutes, etc + datetime diff = now() - d; + int hours, mins, secs, msecs; + decodetime(diff, hours, mins, secs, msecs); + pout.putf("PTypes' life time: %d days %d hours %d minutes %d seconds and %d milliseconds\n", + days(diff), hours, mins, secs, msecs); + +#ifndef PTYPES_ST + // measure the difference in milliseconds between two calls to now() + datetime m = now(); + psleep(17); // sleep for 17 milliseconds + pout.putf("A 17 millisecond dream lasted actually %d milliseconds\n", int(now() - m)); + // this will show the actual precision of the clock on the given platform; + // Windows, f.ex., always shows the difference in 10 msec increments +#endif + } +} + + +// +// streams documentation example #2 +// + + +const cset letters("_A-Za-z"); +const cset digits("0-9"); +const cset identchars = letters + digits; +const cset otherchars = !letters; + + +void doc_example() +{ + tstrlist<void*> dic(SL_SORTED); + + infile f("../src/ptypes_test.cxx"); + + try + { + f.open(); + + while (!f.get_eof()) + { + char c = f.preview(); + + // a C identifier starts with a letter + if (c & letters) + { + // ... and may contain letters and digits + string ident = f.token(identchars); + int i; + if (!dic.search(ident, i)) + dic.add(ident, 0); + } + + else + f.skiptoken(otherchars); + } + + } + + catch (estream* e) + { + pout.putf("Error: %s\n", pconst(e->get_message())); + delete e; + } + + // now print the dictionary + for (int i = 0; i < dic.get_count(); i++) + pout.putline(dic.getkey(i)); +} + + +// +// variant +// + + +void variant_test() +{ + pout.put("\n--- VARIANT\n"); + + variant v0 = 'A'; + variant v1 = short(33); + variant v2 = "456"; + variant v3 = int(v1) + int(v2); + variant v4 = string(v1) + " cows"; + string s = string(v4); +// s = v4; + variant v5 = new component(); + variant v6; + + put(v6, 291, v1); + put(v6, "key", v2); + put(v6, "another-key", "another-value"); + showstr("33 cows", string(v4)); + showint(456, v6["key"]); + showstr("33", string(v1)); + showint(1, bool(v6)); + v2 = aclone(v6); + v5 = v6; + + variant vi; + int i; + for (i = 0; anext(v6, i, vi);) + pout.putf("%d\n", int(vi)); + + variant v7; + aadd(v7, v1); + aadd(v7, v3); + aadd(v7, v4); + adel(v7, 2); + for (i = 0; i < alength(v7); i++) + pout.putf("%s\n", pconst(string(aget(v7, i)))); +} + + +// +// main +// + + +int main() +{ + try + { + string_test1(); + string_test2(); + string_benchmarks(); + cset_test(); + + podlist_test(); + ptrlist_test(); + strlist_test(); + textmap_test(); + + outfile_test(); + infile_test(); + mem_test(); + md5_test(); + putf_test(); + + time_test(); + variant_test(); + + inet_test1(); + +#ifndef PTYPES_ST + pipe_test(); + + jobqueue_test(); + thread_test(); + rwlock_test(); + trigger_test(); + + inet_test2(); + inet_test3(); +#endif + + } + + catch (estream* e) + { + perr.putf("\nError: %s\n", pconst(e->get_message())); + exit(1); + delete e; + } + +#ifdef DEBUG + if (stralloc != 0 || objalloc != 0) + { + perr.putf("DBG stralloc: %d, objalloc: %d\n", stralloc, objalloc); + fatal(255, "Allocation problems"); + } +#endif + + return 0; +} + diff --git a/source/ptypes/punit.cxx b/source/ptypes/punit.cxx @@ -0,0 +1,176 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" +#include "pasync.h" +#include "pstreams.h" + +#ifdef WIN32 +# include <windows.h> +#else +# include <unistd.h> +#endif + + +namespace ptypes { + + +// +// internal thread class for running units asynchronously +// + +class unit_thread: public thread +{ +protected: + unit* target; + virtual void execute(); +public: + unit_thread(unit* itarget); + virtual ~unit_thread(); +}; + + +unit_thread::unit_thread(unit* itarget) + : thread(false), target(itarget) +{ + start(); +} + + + +unit_thread::~unit_thread() +{ + waitfor(); +} + + +void unit_thread::execute() +{ + target->do_main(); +} + + +// +// unit class +// + +unit::unit() + : component(), pipe_next(nil), main_thread(nil), + running(0), uin(&pin), uout(&pout) +{ +} + + +unit::~unit() +{ + delete tpexchange<unit_thread>(&main_thread, nil); +} + + +int unit::classid() +{ + return CLASS_UNIT; +} + + +void unit::main() +{ +} + + +void unit::cleanup() +{ +} + + +void unit::do_main() +{ + try + { + if (!uout->get_active()) + uout->open(); + if (!uin->get_active()) + uin->open(); + main(); + if (uout->get_active()) + uout->flush(); + } + catch(exception* e) + { + perr.putf("Error: %s\n", pconst(e->get_message())); + delete e; + } + + try + { + cleanup(); + } + catch(exception* e) + { + perr.putf("Error: %s\n", pconst(e->get_message())); + delete e; + } + + if (pipe_next != nil) + uout->close(); +} + + +void unit::connect(unit* next) +{ + waitfor(); + pipe_next = next; + infile* in = new infile(); + outfile* out = new outfile(); + next->uin = in; + uout = out; + in->pipe(*out); +} + + +void unit::waitfor() +{ + if (running == 0) + return; + delete tpexchange<unit_thread>(&main_thread, nil); + unit* next = tpexchange<unit>(&pipe_next, nil); + if (next != nil) + { + next->waitfor(); + next->uin = &pin; + } + uout = &pout; + running = 0; +} + + +void unit::run(bool async) +{ + if (pexchange(&running, 1) != 0) + return; + + if (main_thread != nil) + fatal(CRIT_FIRST + 60, "Unit already running"); + + if (pipe_next != nil) + pipe_next->run(true); + + if (async) + main_thread = new unit_thread(this); + else + { + do_main(); + waitfor(); + } +} + + +} diff --git a/source/ptypes/punknown.cxx b/source/ptypes/punknown.cxx @@ -0,0 +1,21 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "ptypes.h" + + +namespace ptypes { + + +int objalloc = 0; + + +} diff --git a/source/ptypes/pvariant.cxx b/source/ptypes/pvariant.cxx @@ -0,0 +1,641 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + + +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> + +#include "ptypes.h" + + +namespace ptypes { + + +const variant nullvar; + + +struct _varitem +{ + string key; + variant var; + + _varitem(const string& ikey, const variant& ivar): key(ikey), var(ivar) {} +}; +typedef _varitem* pvaritem; + + +class ptpublic _varray: protected tobjlist<_varitem> +{ +protected: + int refcount; + + virtual int compare(const void* key, const void* item) const; + + friend class variant; + +public: + _varray(); + _varray(const _varray& a); + virtual ~_varray(); + + int get_count() { return tobjlist<_varitem>::get_count(); } + void clear() { tobjlist<_varitem>::clear(); } + void pack() { tobjlist<_varitem>::pack(); } + _varitem* doget(int index) const { return tobjlist<_varitem>::doget(index); } + const variant& get(int index) const { if (unsigned(index) < unsigned(count)) return doget(index)->var; else return nullvar; } + const string& getkey(int index) const { if (unsigned(index) < unsigned(count)) return doget(index)->key; else return nullstring; } + const variant& get(const char* key) const; + int put(const string& key, const variant& var); + void put(int index, const variant& var) { if (unsigned(index) < unsigned(count)) doget(index)->var = var; } + void ins(int index, const variant& var) { if (unsigned(index) < unsigned(count)) doins(index, new _varitem(nullstring, var)); } + int addvar(const variant& var); + void del(int index) { if (unsigned(index) < unsigned(count)) dodel(index); } + void del(const string& key) { put(key, nullstring); } +}; +typedef _varray* pvarray; + + +_varray::_varray() + : tobjlist<_varitem>(true), refcount(0) +{ + config.sorted = true; + config.casesens = true; +} + + +_varray::_varray(const _varray& a) + : tobjlist<_varitem>(true), refcount(0) +{ + config.sorted = true; + config.casesens = true; + set_capacity(a.count); + for (int i = 0; i < a.count; i++) + { + _varitem* v = a.doget(i); + doins(i, new _varitem(v->key, v->var)); + } +} + + +_varray::~_varray() +{ +} + + +int _varray::compare(const void* key, const void* item) const +{ + if (config.casesens) + return strcmp(pconst(key), pvaritem(item)->key); + else + return strcasecmp(pconst(key), pvaritem(item)->key); +} + + +const variant& _varray::get(const char* key) const +{ + int index; + if (search(key, index)) + return doget(index)->var; + else + return nullvar; +} + + +int _varray::put(const string& key, const variant& var) +{ + int index; + if (search(pconst(key), index)) + { + if (isnull(var)) + dodel(index); + else + doget(index)->var = var; + } + else if (!isnull(var)) + doins(index, new _varitem(key, var)); + return index; +} + + +int _varray::addvar(const variant& v) +{ + int i; + if (count > 0 && isempty(doget(count - 1)->key)) + i = count; + else + i = 0; + doins(i, new _varitem(nullstring, v)); + return i; +} + + +static void vconverr(large v); + + +static void vfatal() +{ + fatal(CRIT_FIRST + 60, "Variant data corrupt"); +} + + +evariant::~evariant() +{ +} + + +void variant::initialize(_varray* a) +{ + tag = VAR_ARRAY; +#ifdef PTYPES_ST + a->refcount++; +#else + pincrement(&a->refcount); +#endif + value.a = a; +} + + +void variant::initialize(component* o) +{ + tag = VAR_OBJECT; + value.o = addref(o); +} + + +void variant::initialize(const variant& v) +{ + switch (v.tag) + { + case VAR_NULL: + tag = VAR_NULL; + break; + case VAR_INT: + case VAR_BOOL: + case VAR_FLOAT: + tag = v.tag; + value = v.value; + break; + case VAR_STRING: + initialize(PTR_TO_STRING(v.value.s)); + break; + case VAR_ARRAY: + initialize(v.value.a); + break; + case VAR_OBJECT: + initialize(v.value.o); + break; + default: + vfatal(); + } +} + + +void variant::finalize() +{ + if (tag >= VAR_COMPOUND) + { + switch (tag) + { + case VAR_STRING: + ptypes::finalize(PTR_TO_STRING(value.s)); + break; + case VAR_ARRAY: +#ifdef PTYPES_ST + if (--value.a->refcount == 0) +#else + if (pdecrement(&value.a->refcount) == 0) +#endif + delete value.a; + break; + case VAR_OBJECT: + release(value.o); + break; + default: + vfatal(); + } + } + tag = VAR_NULL; +} + + +void variant::assign(large v) { finalize(); initialize(v); } +void variant::assign(bool v) { finalize(); initialize(v); } +void variant::assign(double v) { finalize(); initialize(v); } +void variant::assign(const char* v) { finalize(); initialize(v); } + + +void variant::assign(const string& v) +{ + if (tag == VAR_STRING) + PTR_TO_STRING(value.s) = v; + else + { + finalize(); + initialize(v); + } +} + + +void variant::assign(_varray* a) +{ + if (tag == VAR_ARRAY && value.a == a) + return; + finalize(); + initialize(a); +} + + +void variant::assign(component* o) +{ + if (tag == VAR_OBJECT) + { + if (value.o == o) + return; + else + release(value.o); + } + else + finalize(); + initialize(o); +} + + +void variant::assign(const variant& v) +{ + switch (v.tag) + { + case VAR_NULL: + finalize(); + tag = VAR_NULL; + break; + case VAR_INT: + case VAR_BOOL: + case VAR_FLOAT: + finalize(); + tag = v.tag; + value = v.value; + break; + case VAR_STRING: + assign(PTR_TO_STRING(v.value.s)); + break; + case VAR_ARRAY: + assign(v.value.a); + break; + case VAR_OBJECT: + assign(v.value.o); + break; + default: + vfatal(); + } +} + + +void ptdecl clear(variant& v) +{ + v.finalize(); + v.initialize(); +} + + +variant::operator int() const +{ + large t = operator large(); + if (t < INT_MIN || t > INT_MAX) + vconverr(t); + return int(t); +} + + +variant::operator unsigned int() const +{ + large t = operator large(); + if (t < 0 || t > UINT_MAX) + vconverr(t); + return uint(t); +} + + +variant::operator long() const +{ + large t = operator large(); + if (t < LONG_MIN || t > LONG_MAX) + vconverr(t); + return int(t); +} + + +variant::operator unsigned long() const +{ + large t = operator large(); + if (t < 0 || t > large(ULONG_MAX)) + vconverr(t); + return uint(t); +} + + +variant::operator large() const +{ + switch(tag) + { + case VAR_NULL: return 0; + case VAR_INT: return value.i; + case VAR_BOOL: return int(value.b); + case VAR_FLOAT: return int(value.f); + case VAR_STRING: + { + const char* p = PTR_TO_STRING(value.s); + bool neg = *p == '-'; + if (neg) + p++; + large t = stringtoi(p); + if (t < 0) + return 0; + else + return neg ? -t : t; + } + case VAR_ARRAY: return value.a->count != 0; + case VAR_OBJECT: return 0; + default: vfatal(); + } + return 0; +} + + +variant::operator bool() const +{ + switch(tag) + { + case VAR_NULL: return false; + case VAR_INT: return value.i != 0; + case VAR_BOOL: return value.b; + case VAR_FLOAT: return value.f != 0; + case VAR_STRING: return !isempty((PTR_TO_STRING(value.s))); + case VAR_ARRAY: return value.a->count != 0; + case VAR_OBJECT: return value.o != nil; + default: vfatal(); + } + return false; +} + + +variant::operator double() const +{ + switch(tag) + { + case VAR_NULL: return 0; + case VAR_INT: return double(value.i); + case VAR_BOOL: return int(value.b); + case VAR_FLOAT: return value.f; + case VAR_STRING: + { + char* e; + double t = strtod(PTR_TO_STRING(value.s), &e); + if (*e != 0) + return 0; + else + return t; + } + case VAR_ARRAY: return int(value.a->count != 0); + case VAR_OBJECT: return 0; + default: vfatal(); + } + return 0; +} + + +void string::initialize(const variant& v) +{ + switch(v.tag) + { + case VAR_NULL: initialize(); break; + case VAR_INT: initialize(itostring(v.value.i)); break; + case VAR_BOOL: if (v.value.b) initialize('1'); else initialize('0'); break; + case VAR_FLOAT: + { + char buf[256]; + sprintf(buf, "%g", v.value.f); + initialize(buf); + } + break; + case VAR_STRING: initialize(PTR_TO_STRING(v.value.s)); break; + case VAR_ARRAY: initialize(); break; + case VAR_OBJECT: initialize(); break; + default: vfatal(); + } +} + + +variant::operator string() const +{ + // this is a 'dirty' solution to gcc 3.3 typecast problem. most compilers + // handle variant::operator string() pretty well, while gcc 3.3 requires + // to explicitly declare a constructor string::string(const variant&). + // ironically, the presence of both the typecast operator and the constructor + // confuses the MSVC compiler. so the only thing we can do to please all + // those compilers [that "move towards the c++ standard"] is to conditionally + // exclude the constructor string(const variant&). and this is not the whole + // story. i see you are bored with it and i let you go. nobody would ever care + // about this. it just works, though i'm not happy with what i wrote here: + string t; + t.initialize(*this); + return t; +} + + +variant::operator component*() const +{ + if (tag == VAR_OBJECT) + return value.o; + else + return nil; +} + + +bool variant::equal(const variant& v) const +{ + if (tag != v.tag) + return false; + switch (tag) + { + case VAR_NULL: return true; + case VAR_INT: return value.i == v.value.i; + case VAR_BOOL: return value.b == v.value.b; + case VAR_FLOAT: return value.f == v.value.f; + case VAR_STRING: return strcmp(value.s, v.value.s) == 0; + case VAR_ARRAY: return value.a == v.value.a; + case VAR_OBJECT: return value.o == v.value.o; + default: vfatal(); return false; + } +} + + +static string numkey(large key) +{ + return itostring(key, 16, 16, '0'); +} + + +void ptdecl aclear(variant& v) +{ + if (v.tag == VAR_ARRAY) + v.value.a->clear(); + else + { + v.finalize(); + v.initialize(new _varray()); + } +} + + +void ptdecl apack(variant& v) +{ + if (v.tag == VAR_ARRAY) + v.value.a->pack(); +} + + +variant ptdecl aclone(const variant& v) +{ + if (v.tag == VAR_ARRAY) + return variant(new _varray(*(v.value.a))); + else + return variant(new _varray()); +} + + +int ptdecl alength(const variant& v) +{ + if (v.tag == VAR_ARRAY) + return v.value.a->get_count(); + else + return 0; +} + + +const variant& ptdecl get(const variant& v, const string& key) +{ + if (v.tag == VAR_ARRAY) + return v.value.a->get(key); + else + return nullvar; +} + + +const variant& ptdecl get(const variant& v, large key) +{ + return get(v, numkey(key)); +} + + +void ptdecl put(variant& v, const string& key, const variant& item) +{ + if (v.tag != VAR_ARRAY) + aclear(v); + v.value.a->put(key, item); +} + + +void ptdecl put(variant& v, large key, const variant& item) +{ + put(v, numkey(key), item); +} + + +void ptdecl del(variant& v, const string& key) +{ + if (v.tag == VAR_ARRAY) + v.value.a->del(key); +} + + +void ptdecl del(variant& v, large key) +{ + del(v, numkey(key)); +} + + +bool ptdecl anext(const variant& array, int& index, variant& item) +{ + string key; + return anext(array, index, item, key); +} + + +bool ptdecl anext(const variant& array, int& index, variant& item, string& key) +{ + if (array.tag != VAR_ARRAY) + { + clear(item); + return false; + } + if (index < 0 || index >= array.value.a->get_count()) + { + clear(item); + return false; + } + item = array.value.a->doget(index)->var; + key = array.value.a->doget(index)->key; + index++; + return true; +} + + +int ptdecl aadd(variant& array, const variant& item) +{ + if (array.tag != VAR_ARRAY) + aclear(array); + return array.value.a->addvar(item); +} + + +const variant& ptdecl aget(const variant& array, int index) +{ + if (array.tag == VAR_ARRAY) + return array.value.a->get(index); + else + return nullvar; +} + + +void ptdecl adel(variant& array, int index) +{ + if (array.tag == VAR_ARRAY) + array.value.a->del(index); +} + + +void ptdecl aput(variant& array, int index, const variant& item) +{ + if (array.tag == VAR_ARRAY) + array.value.a->put(index, item); +} + + +void ptdecl ains(variant& array, int index, const variant& item) +{ + if (array.tag == VAR_ARRAY) + array.value.a->ins(index, item); +} + + +#ifdef _MSC_VER +// disable "unreachable code" warning for throw (known compiler bug) +# pragma warning (disable: 4702) +#endif + +static void vconverr(large v) +{ + throw new evariant("Value out of range: " + itostring(v)); +} + + +} diff --git a/source/ptypes/pversion.cxx b/source/ptypes/pversion.cxx @@ -0,0 +1,15 @@ +/* + * + * C++ Portable Types Library (PTypes) + * Version 2.1.1 Released 27-Jun-2007 + * + * Copyright (C) 2001-2007 Hovik Melikyan + * + * http://www.melikyan.com/ptypes/ + * + */ + +#include "pport.h" + +unsigned long __ptypes_version = 0x00020101; + diff --git a/source/synthLib/device.cpp b/source/synthLib/device.cpp @@ -10,9 +10,16 @@ using namespace dsp56k; namespace synthLib { - Device::Device() = default; + Device::Device(const DeviceCreateParams& _params) : m_createParams(_params) // NOLINT(modernize-pass-by-value) dll transition, do not mess with the input data + { + } Device::~Device() = default; + ASMJIT_NOINLINE void Device::release(std::vector<SMidiEvent>& _events) + { + _events.clear(); + } + void Device::dummyProcess(const uint32_t _numSamples) { std::vector<float> buf; @@ -29,6 +36,8 @@ namespace synthLib void Device::process(const TAudioInputs& _inputs, const TAudioOutputs& _outputs, const size_t _size, const std::vector<SMidiEvent>& _midiIn, std::vector<SMidiEvent>& _midiOut) { + _midiOut.clear(); + for (const auto& ev : _midiIn) { m_translatorOut.clear(); @@ -60,7 +69,9 @@ namespace synthLib bool Device::isSamplerateSupported(const float& _samplerate) const { - for (const auto& sr : getSupportedSamplerates()) + std::vector<float> srs; + getSupportedSamplerates(srs); + for (const auto& sr : srs) { if(std::fabs(sr - _samplerate) < 1.0f) return true; @@ -82,7 +93,8 @@ namespace synthLib float Device::getDeviceSamplerateForHostSamplerate(const float _hostSamplerate) const { - const auto preferred = getPreferredSamplerates(); + std::vector<float> preferred; + getPreferredSamplerates(preferred); // if there is no choice we need to use the only one that is supported if(preferred.size() == 1) diff --git a/source/synthLib/device.h b/source/synthLib/device.h @@ -2,6 +2,7 @@ #include <cstdint> #include <cstddef> +#include <string> #include "audioTypes.h" #include "deviceTypes.h" @@ -10,12 +11,26 @@ #include "buildconfig.h" #include "midiTranslator.h" +#include "asmjit/core/api-config.h" + +#include "baseLib/md5.h" + namespace synthLib { + struct DeviceCreateParams + { + float preferredSamplerate = 0.0f; + float hostSamplerate = 0.0f; + std::string romName; + std::vector<uint8_t> romData; + baseLib::MD5 romHash; + uint32_t customData = 0; + }; + class Device { public: - Device(); + Device(const DeviceCreateParams& _params); Device(const Device&) = delete; Device(Device&&) = delete; @@ -32,14 +47,14 @@ namespace synthLib virtual uint32_t getInternalLatencyMidiToOutput() const { return 0; } virtual uint32_t getInternalLatencyInputToOutput() const { return 0; } - virtual std::vector<float> getSupportedSamplerates() const + virtual void getSupportedSamplerates(std::vector<float>& _dst) const { - return {getSamplerate()}; + _dst.push_back(getSamplerate()); } virtual float getSamplerate() const = 0; - virtual std::vector<float> getPreferredSamplerates() const + virtual void getPreferredSamplerates(std::vector<float>& _dst) const { - return getSupportedSamplerates(); + return getSupportedSamplerates(_dst); } bool isSamplerateSupported(const float& _samplerate) const; @@ -49,6 +64,9 @@ namespace synthLib float getDeviceSamplerate(float _preferredDeviceSamplerate, float _hostSamplerate) const; float getDeviceSamplerateForHostSamplerate(float _hostSamplerate) const; + auto& getDeviceCreateParams() { return m_createParams; } + const auto& getDeviceCreateParams() const { return m_createParams; } + virtual bool isValid() const = 0; #if SYNTHLIB_DEMO_MODE == 0 @@ -64,6 +82,9 @@ namespace synthLib virtual uint32_t getDspClockPercent() const = 0; virtual uint64_t getDspClockHz() const = 0; + + ASMJIT_NOINLINE virtual void release(std::vector<SMidiEvent>& _events); + auto& getMidiTranslator() { return m_midiTranslator; } protected: @@ -72,8 +93,9 @@ namespace synthLib virtual bool sendMidi(const SMidiEvent& _ev, std::vector<SMidiEvent>& _response) = 0; void dummyProcess(uint32_t _numSamples); - + private: + DeviceCreateParams m_createParams; std::vector<SMidiEvent> m_midiIn; uint32_t m_extraLatency = 0; diff --git a/source/synthLib/deviceTypes.h b/source/synthLib/deviceTypes.h @@ -12,7 +12,9 @@ namespace synthLib { Invalid = -1, None = 0, - Unknown = 1, - FirmwareMissing = 2, + Unknown, + FirmwareMissing, + RemoteUdpConnectFailed, + RemoteTcpConnectFailed, }; } diff --git a/source/synthLib/plugin.cpp b/source/synthLib/plugin.cpp @@ -11,11 +11,12 @@ namespace synthLib { constexpr uint8_t g_stateVersion = 1; - Plugin::Plugin(Device* _device) + Plugin::Plugin(Device* _device, CallbackDeviceInvalid _callbackDeviceInvalid) : m_resampler(_device->getChannelCountIn(), _device->getChannelCountOut()) , m_device(_device) - , m_deviceSamplerate(_device->getSamplerate()) , m_midiClock(*this) + , m_deviceSamplerate(_device->getSamplerate()) + , m_callbackDeviceInvalid(std::move(_callbackDeviceInvalid)) { } @@ -37,7 +38,7 @@ namespace synthLib const auto sr = m_device->getDeviceSamplerate(_samplerate, m_hostSamplerate); - if(sr == m_deviceSamplerate) + if(sr == m_deviceSamplerate) // NOLINT(clang-diagnostic-float-equal) return true; if(!m_device->setSamplerate(sr)) @@ -50,16 +51,16 @@ namespace synthLib return true; } - void Plugin::setHostSamplerate(const float _samplerate, float _preferredDeviceSamplerate) + void Plugin::setHostSamplerate(const float _hostSamplerate, const float _preferredDeviceSamplerate) { std::lock_guard lock(m_lock); - m_deviceSamplerate = m_device->getDeviceSamplerate(_preferredDeviceSamplerate, _samplerate); + m_deviceSamplerate = m_device->getDeviceSamplerate(_preferredDeviceSamplerate, _hostSamplerate); m_device->setSamplerate(m_deviceSamplerate); - m_resampler.setSamplerates(_samplerate, m_deviceSamplerate); + m_resampler.setSamplerates(_hostSamplerate, m_deviceSamplerate); - m_hostSamplerate = _samplerate; - m_hostSamplerateInv = _samplerate > 0 ? 1.0f / _samplerate : 0.0f; + m_hostSamplerate = _hostSamplerate; + m_hostSamplerateInv = _hostSamplerate > 0 ? 1.0f / _hostSamplerate : 0.0f; updateDeviceLatency(); } @@ -80,7 +81,12 @@ namespace synthLib std::lock_guard lock(m_lock); if(!m_device->isValid()) - return; + { + m_device = m_callbackDeviceInvalid(m_device); + + if(!m_device || !m_device->isValid()) + return; + } processMidiInEvents(); processMidiClock(_bpm, _ppqPos, _isPlaying, _count); @@ -120,7 +126,8 @@ namespace synthLib m_device = _device; m_device->setSamplerate(m_deviceSamplerate); - setState(deviceState); + if(!deviceState.empty()) + setState(deviceState); // MIDI clock has to send the start event again, some device find it confusing and do strange things if there isn't any m_midiClock.restart(); @@ -140,7 +147,7 @@ namespace synthLib return m_device->getState(_state, _type); } - bool Plugin::setState(const std::vector<uint8_t>& _state) + bool Plugin::setState(const std::vector<uint8_t>& _state) const { if(!m_device) return false; diff --git a/source/synthLib/plugin.h b/source/synthLib/plugin.h @@ -1,6 +1,7 @@ #pragma once #include <mutex> +#include <functional> #include "midiTypes.h" #include "resamplerInOut.h" @@ -18,7 +19,9 @@ namespace synthLib class Plugin { public: - Plugin(Device* _device); + using CallbackDeviceInvalid = std::function<Device*(Device*)>; + + Plugin(Device* _device, CallbackDeviceInvalid _callbackDeviceInvalid); void addMidiEvent(const SMidiEvent& _ev); @@ -42,7 +45,7 @@ namespace synthLib #if !SYNTHLIB_DEMO_MODE bool getState(std::vector<uint8_t>& _state, StateType _type) const; - bool setState(const std::vector<uint8_t>& _state); + bool setState(const std::vector<uint8_t>& _state) const; #endif void insertMidiEvent(const SMidiEvent& _ev); @@ -63,7 +66,7 @@ namespace synthLib SMidiEvent m_pendingSysexInput; ResamplerInOut m_resampler; - mutable std::mutex m_lock; + mutable std::recursive_mutex m_lock; mutable std::mutex m_lockAddMidiEvent; Device* m_device; @@ -83,5 +86,6 @@ namespace synthLib uint32_t m_extraLatencyBlocks = 1; float m_deviceSamplerate = 0.0f; + CallbackDeviceInvalid m_callbackDeviceInvalid; }; } diff --git a/source/synthLib/resamplerInOut.cpp b/source/synthLib/resamplerInOut.cpp @@ -192,6 +192,10 @@ namespace synthLib } m_scaledInput.fillPointers(inputs); } + else + { + inputs.fill(nullptr); + } _processFunc(inputs, _outs, _numProcessedSamples, m_processedMidiIn, m_midiOut); diff --git a/source/virusJucePlugin/Leds.cpp b/source/virusJucePlugin/Leds.cpp @@ -1,5 +1,6 @@ #include "Leds.h" +#include "VirusController.h" #include "VirusEditor.h" #include "VirusProcessor.h" diff --git a/source/virusJucePlugin/VirusProcessor.cpp b/source/virusJucePlugin/VirusProcessor.cpp @@ -1,8 +1,7 @@ #include "VirusProcessor.h" #include "VirusEditorState.h" #include "ParameterNames.h" - -#include "virusLib/romloader.h" +#include "VirusController.h" #include "baseLib/binarystream.h" @@ -48,7 +47,7 @@ namespace virus try { - synthLib::Device* device = createDevice(); + synthLib::Device* device = pluginLib::Processor::createDevice(getDeviceType()); getPlugin().setDevice(device); (void)m_device.release(); m_device.reset(device); @@ -86,6 +85,15 @@ namespace virus synthLib::Device* VirusProcessor::createDevice() { + synthLib::DeviceCreateParams p; + getRemoteDeviceParams(p); + return new virusLib::Device(p, true); + } + + void VirusProcessor::getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const + { + pluginLib::Processor::getRemoteDeviceParams(_params); + const auto* rom = getSelectedRom(); if(!rom || !rom->isValid()) @@ -94,7 +102,10 @@ namespace virus throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "A Virus TI firmware (.bin) is required, but was not found."); throw synthLib::DeviceException(synthLib::DeviceError::FirmwareMissing, "A Virus A/B/C operating system (.bin or .mid) is required, but was not found."); } - return new virusLib::Device(*rom, getPreferredDeviceSamplerate(), getHostSamplerate(), true); + + _params.romName = rom->getFilename(); + _params.romData = rom->getRomFileData(); + _params.customData = static_cast<uint32_t>(rom->getModel()); } pluginLib::Controller* VirusProcessor::createController() diff --git a/source/virusJucePlugin/VirusProcessor.h b/source/virusJucePlugin/VirusProcessor.h @@ -5,8 +5,6 @@ #include "jucePluginLib/event.h" -#include "VirusController.h" - #include "jucePluginEditorLib/pluginProcessor.h" namespace virus @@ -60,6 +58,7 @@ namespace virus // private: synthLib::Device* createDevice() override; + void getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const override; pluginLib::Controller* createController() override; diff --git a/source/virusLib/device.cpp b/source/virusLib/device.cpp @@ -15,9 +15,10 @@ namespace virusLib { - Device::Device(ROMFile _rom, const float _preferredDeviceSamplerate, const float _hostSamplerate, const bool _createDebugger/* = false*/) - : m_rom(std::move(_rom)) - , m_samplerate(getDeviceSamplerate(_preferredDeviceSamplerate, _hostSamplerate)) + Device::Device(const synthLib::DeviceCreateParams& _params, const bool _createDebugger/* = false*/) + : synthLib::Device(_params) + , m_rom(_params.romData, _params.romName, static_cast<DeviceModel>(_params.customData)) + , m_samplerate(getDeviceSamplerate(_params.preferredSamplerate, _params.hostSamplerate)) { m_frontpanelStateMidiEvent.source = synthLib::MidiEventSource::Internal; @@ -76,7 +77,7 @@ namespace virusLib m_dsp.reset(); } - std::vector<float> Device::getSupportedSamplerates() const + void Device::getSupportedSamplerates(std::vector<float>& _dst) const { switch (m_rom.getModel()) { @@ -84,25 +85,35 @@ namespace virusLib case DeviceModel::A: case DeviceModel::B: case DeviceModel::C: - return {12000000.0f / 256.0f}; + _dst.push_back(12000000.0f / 256.0f); + break; case DeviceModel::Snow: case DeviceModel::TI: case DeviceModel::TI2: - return {32000.0f, 44100.0f, 48000.0f, 64000.0f, 88200.0f, 96000.0f}; + _dst.push_back(32000.0f); + _dst.push_back(44100.0f); + _dst.push_back(48000.0f); + _dst.push_back(64000.0f); + _dst.push_back(88200.0f); + _dst.push_back(96000.0f); + break; } } - std::vector<float> Device::getPreferredSamplerates() const + void Device::getPreferredSamplerates(std::vector<float>& _dst) const { switch (m_rom.getModel()) { default: case DeviceModel::ABC: - return getSupportedSamplerates(); + getSupportedSamplerates(_dst); + break; case DeviceModel::Snow: case DeviceModel::TI: case DeviceModel::TI2: - return {44100.0f, 48000.0f}; + _dst.push_back(44100.0f); + _dst.push_back(48000.0f); + break; } } diff --git a/source/virusLib/device.h b/source/virusLib/device.h @@ -19,11 +19,11 @@ namespace virusLib class Device final : public synthLib::Device { public: - Device(ROMFile _rom, float _preferredDeviceSamplerate, float _hostSamplerate, bool _createDebugger = false); + Device(const synthLib::DeviceCreateParams& _params, bool _createDebugger = false); ~Device() override; - std::vector<float> getSupportedSamplerates() const override; - std::vector<float> getPreferredSamplerates() const override; + void getSupportedSamplerates(std::vector<float>& _dst) const override; + void getPreferredSamplerates(std::vector<float>& _dst) const override; float getSamplerate() const override; bool setSamplerate(float _samplerate) override; diff --git a/source/virusLib/romfile.h b/source/virusLib/romfile.h @@ -119,6 +119,8 @@ public: const auto& getHash() const { return m_romDataHash; } + const auto& getRomFileData() const { return m_romFileData; } + private: std::vector<Chunk> readChunks(std::istream& _file) const; bool loadPresetFiles(); diff --git a/source/wLib/wDevice.cpp b/source/wLib/wDevice.cpp @@ -4,7 +4,11 @@ namespace wLib { - bool Device::setDspClockPercent(uint32_t _percent) + Device::Device(const synthLib::DeviceCreateParams& _params): synthLib::Device(_params) + { + } + + bool Device::setDspClockPercent(const uint32_t _percent) { auto* c = getDspEsxiClock(); if(!c) diff --git a/source/wLib/wDevice.h b/source/wLib/wDevice.h @@ -12,6 +12,8 @@ namespace wLib { class Device : public synthLib::Device { + public: + explicit Device(const synthLib::DeviceCreateParams& _params); bool setDspClockPercent(uint32_t _percent) override; uint32_t getDspClockPercent() const override; uint64_t getDspClockHz() const override; diff --git a/source/wLib/wRom.h b/source/wLib/wRom.h @@ -10,14 +10,14 @@ namespace wLib { public: ROM() = default; - explicit ROM(const std::string& _filename, const uint32_t _expectedSize, std::vector<uint8_t> _data = {}) : m_buffer(std::move(_data)) + explicit ROM(const std::string& _filename, const uint32_t _expectedSize, std::vector<uint8_t> _data = {}) : m_buffer(std::move(_data)), m_filename(_filename) { if (m_buffer.size() != _expectedSize) loadFromFile(_filename, _expectedSize); } virtual ~ROM() = default; - const uint8_t* getData() const { return m_buffer.data(); } + const auto& getData() const { return m_buffer; } bool isValid() const { return !m_buffer.empty(); } virtual uint32_t getSize() const = 0; diff --git a/source/xtJucePlugin/PluginProcessor.cpp b/source/xtJucePlugin/PluginProcessor.cpp @@ -9,6 +9,8 @@ #include "xtLib/xtDevice.h" +#include "xtLib/xtRomLoader.h" + class Controller; namespace @@ -52,7 +54,22 @@ namespace xtJucePlugin synthLib::Device* AudioPluginAudioProcessor::createDevice() { - return new xt::Device(); + synthLib::DeviceCreateParams p; + getRemoteDeviceParams(p); + return new xt::Device(p); + } + + void AudioPluginAudioProcessor::getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const + { + Processor::getRemoteDeviceParams(_params); + + const auto rom = xt::RomLoader::findROM(); + + if(rom.isValid()) + { + _params.romData = rom.getData(); + _params.romName = rom.getFilename(); + } } pluginLib::Controller* AudioPluginAudioProcessor::createController() diff --git a/source/xtJucePlugin/PluginProcessor.h b/source/xtJucePlugin/PluginProcessor.h @@ -13,6 +13,7 @@ namespace xtJucePlugin jucePluginEditorLib::PluginEditorState* createEditorState() override; synthLib::Device* createDevice() override; + void getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const override; pluginLib::Controller* createController() override; diff --git a/source/xtJucePlugin/serverPlugin.cpp b/source/xtJucePlugin/serverPlugin.cpp @@ -0,0 +1,9 @@ +// ReSharper disable once CppUnusedIncludeDirective +#include "client/plugin.h" + +#include "xtLib/xtDevice.h" + +synthLib::Device* createBridgeDevice(const synthLib::DeviceCreateParams& _params) +{ + return new xt::Device(_params); +} diff --git a/source/xtLib/xt.cpp b/source/xtLib/xt.cpp @@ -11,9 +11,9 @@ namespace xt { - Xt::Xt() + Xt::Xt(const std::vector<uint8_t>& _romData, const std::string& _romName) { - m_hw.reset(new Hardware()); + m_hw.reset(new Hardware(_romData, _romName)); if(!isValid()) return; diff --git a/source/xtLib/xt.h b/source/xtLib/xt.h @@ -30,7 +30,7 @@ namespace xt Lcd = 0x02, }; - Xt(); + Xt(const std::vector<uint8_t>& _romData, const std::string& _romName); ~Xt(); bool isValid() const; diff --git a/source/xtLib/xtDevice.cpp b/source/xtLib/xtDevice.cpp @@ -9,7 +9,7 @@ namespace mqLib namespace xt { - Device::Device() : m_wavePreview(m_xt), m_state(m_xt, m_wavePreview), m_sysexRemote(m_xt) + Device::Device(const synthLib::DeviceCreateParams& _params) : wLib::Device(_params), m_xt(_params.romData, _params.romName), m_wavePreview(m_xt), m_state(m_xt, m_wavePreview), m_sysexRemote(m_xt) { while(!m_xt.isBootCompleted()) m_xt.process(8); diff --git a/source/xtLib/xtDevice.h b/source/xtLib/xtDevice.h @@ -16,7 +16,7 @@ namespace xt class Device : public wLib::Device { public: - Device(); + Device(const synthLib::DeviceCreateParams& _params); float getSamplerate() const override; bool isValid() const override; diff --git a/source/xtLib/xtHardware.cpp b/source/xtLib/xtHardware.cpp @@ -9,9 +9,16 @@ namespace xt { - Hardware::Hardware() + Rom initializeRom(const std::vector<uint8_t>& _romData, const std::string& _romName) + { + if(_romData.empty()) + return RomLoader::findROM(); + return Rom{_romName, _romData}; + } + + Hardware::Hardware(const std::vector<uint8_t>& _romData, const std::string& _romName) : wLib::Hardware(40000) - , m_rom(RomLoader::findROM()) + , m_rom(initializeRom(_romData, _romName)) , m_uc(m_rom) , m_dsps{DSP(*this, m_uc.getHdi08A().getHdi08(), 0)} , m_midi(m_uc.getQSM(), 40000) diff --git a/source/xtLib/xtHardware.h b/source/xtLib/xtHardware.h @@ -19,8 +19,8 @@ namespace xt static constexpr uint32_t g_dspCount = 1; public: - explicit Hardware(); - virtual ~Hardware(); + explicit Hardware(const std::vector<uint8_t>& _romData, const std::string& _romName); + ~Hardware() override; void process(); diff --git a/source/xtLib/xtRom.h b/source/xtLib/xtRom.h @@ -13,7 +13,7 @@ namespace xt public: static constexpr uint32_t Size = g_romSize; - Rom(std::vector<uint8_t> _data) : ROM({}, Size, std::move(_data)) + Rom(const std::string& _filename, std::vector<uint8_t> _data) : ROM(_filename, Size, std::move(_data)) { } @@ -24,7 +24,7 @@ namespace xt static Rom invalid() { - return {{}}; + return {{}, {}}; } }; } diff --git a/source/xtLib/xtRomLoader.cpp b/source/xtLib/xtRomLoader.cpp @@ -153,7 +153,7 @@ namespace xt best.name += "_upgraded_" + bestMidi.name; } - return {best.data}; + return {best.name, best.data}; } std::vector<RomLoader::File> RomLoader::findFiles(const std::string& _extension, const size_t _sizeMin, const size_t _sizeMax) diff --git a/source/xtLib/xtUc.cpp b/source/xtLib/xtUc.cpp @@ -17,7 +17,7 @@ namespace xt if(!_rom.isValid()) return; - memcpy(m_romRuntimeData.data(), _rom.getData(), g_romSize); + memcpy(m_romRuntimeData.data(), _rom.getData().data(), g_romSize); m_memory.fill(0); // dumpAssembly("xt_68k.asm", g_romAddr, g_romSize); diff --git a/source/xtTestConsole/xtTestConsole.cpp b/source/xtTestConsole/xtTestConsole.cpp @@ -12,7 +12,7 @@ int main() { // dsp56k::JitUnittests tests; - const std::unique_ptr uc(std::make_unique<xt::Xt>()); + const std::unique_ptr uc(std::make_unique<xt::Xt>(std::vector<uint8_t>(), std::string())); constexpr uint32_t blockSize = 64; std::vector<dsp56k::TWord> stereoOutput;