PiPedal 1.2.33 initial commit
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
cmake_minimum_required(VERSION 3.19.0)
|
||||
|
||||
|
||||
# specify the C++ standard
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)# apt get lbsdbus-c++-dev
|
||||
|
||||
find_package(sdbus-c++ REQUIRED)
|
||||
|
||||
include(FindPkgConfig)
|
||||
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
|
||||
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
message(STATUS "Debug build")
|
||||
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -DDEBUG" )
|
||||
endif()
|
||||
|
||||
# Can't get the pkg_check to work.
|
||||
# pkg_check_modules(LIBNL3 "nl-genl-3")
|
||||
# if(!LIBNL3_FOUND)
|
||||
# message(ERROR "libnl-genl-3-dev package not installed.")
|
||||
# else()
|
||||
# message(STATUS "LIBNL3 libraries: ${LIBNL3_LIBRARIES}")
|
||||
# message(STATUS "LBNL3 includes: ${LIBNL3_INCLUDE_DIRS}")
|
||||
# endif()
|
||||
|
||||
# nlgenl-3 library.
|
||||
execute_process(COMMAND ls /usr/include/libnl3/netlink/netlink.h RESULT_VARIABLE LNL3_MISSING OUTPUT_QUIET ERROR_QUIET)
|
||||
if(LNL3_MISSING)
|
||||
message(ERROR " Need to: sudo apt install libnl-3-dev libnl-genl-3-dev ")
|
||||
endif()
|
||||
set(LIBNL3_INCLUDE_DIRS /usr/include/libnl3)
|
||||
set(LIBNL3_LIBRARIES nl-3 nl-genl-3)
|
||||
|
||||
|
||||
|
||||
message(STATUS "NMPIPEDAL CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
|
||||
|
||||
|
||||
# Use the sdbus-c++ target in SDBusCpp namespace
|
||||
add_library(PiPedalCommon STATIC
|
||||
ConfigSerializer.cpp include/ConfigSerializer.hpp
|
||||
WifiRegs.cpp include/WifiRegs.hpp
|
||||
WifiRegulations.cpp include/WifiRegulations.hpp
|
||||
WifiDirectConfigSettings.cpp include/WifiDirectConfigSettings.hpp
|
||||
WifiConfigSettings.cpp include/WifiConfigSettings.hpp
|
||||
ServiceConfiguration.cpp include/ServiceConfiguration.hpp
|
||||
WriteTemplateFile.cpp include/WriteTemplateFile.hpp
|
||||
#NetLinkChannelInfo.cpp include/NetLinkChannelInfo.hpp
|
||||
ChannelInfo.cpp include/ChannelInfo.hpp
|
||||
HtmlHelper.cpp include/HtmlHelper.hpp
|
||||
RegDb.cpp include/RegDb.hpp
|
||||
ConfigUtil.cpp include/ConfigUtil.hpp
|
||||
SysExec.cpp include/SysExec.hpp
|
||||
util.cpp include/util.hpp
|
||||
json.cpp include/json.hpp
|
||||
|
||||
json_variant.cpp include/json_variant.hpp
|
||||
include/autoptr_vector.h
|
||||
|
||||
include/P2pConfigFiles.hpp
|
||||
|
||||
ss.hpp
|
||||
)
|
||||
target_include_directories(PiPedalCommon PUBLIC "include" ${LIBNL3_INCLUDE_DIRS})
|
||||
target_link_libraries(PiPedalCommon PUBLIC ${LIBNL3_LIBRARIES} )
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
#include "ChannelInfo.hpp"
|
||||
#include "WifiRegulations.hpp"
|
||||
#include "RegDb.hpp"
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include "ss.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
// To do: no support for ac 80GHz channels (which pi supports).
|
||||
// Wifi 6 ax channels are explicitly disabled (pi doesn't support them).
|
||||
|
||||
|
||||
struct OpClassChannels
|
||||
{
|
||||
WifiMode band;
|
||||
int32_t regDomain;
|
||||
int32_t firstChannel;
|
||||
int32_t lastChannel;
|
||||
int32_t increment;
|
||||
WifiBandwidth bandwidth;
|
||||
bool p2p_support;
|
||||
};
|
||||
// from wpa_supplicant.
|
||||
static std::vector<OpClassChannels> opClasses = {
|
||||
{WifiMode::IEEE80211G, 81, 1, 13, 1, WifiBandwidth::BW20, true},
|
||||
{WifiMode::IEEE80211G, 82, 14, 14, 1, WifiBandwidth::BW20, false},
|
||||
|
||||
/* Do not enable HT40 on 2.4 GHz for P2P use for now */
|
||||
{WifiMode::IEEE80211G, 83, 1, 9, 1, WifiBandwidth::BW40PLUS, false},
|
||||
{WifiMode::IEEE80211G, 84, 5, 13, 1, WifiBandwidth::BW40MINUS, false},
|
||||
|
||||
{WifiMode::IEEE80211A, 115, 36, 48, 4, WifiBandwidth::BW20, true},
|
||||
{WifiMode::IEEE80211A, 116, 36, 44, 8, WifiBandwidth::BW40PLUS, true},
|
||||
{WifiMode::IEEE80211A, 117, 40, 48, 8, WifiBandwidth::BW40MINUS, true},
|
||||
{WifiMode::IEEE80211A, 118, 52, 64, 4, WifiBandwidth::BW20, false},
|
||||
{WifiMode::IEEE80211A, 119, 52, 60, 8, WifiBandwidth::BW40PLUS, false},
|
||||
{WifiMode::IEEE80211A, 120, 56, 64, 8, WifiBandwidth::BW40MINUS, false},
|
||||
{WifiMode::IEEE80211A, 121, 100, 140, 4, WifiBandwidth::BW20, false},
|
||||
{WifiMode::IEEE80211A, 122, 100, 132, 8, WifiBandwidth::BW40PLUS, false},
|
||||
{WifiMode::IEEE80211A, 123, 104, 136, 8, WifiBandwidth::BW40MINUS, false},
|
||||
{WifiMode::IEEE80211A, 124, 149, 161, 4, WifiBandwidth::BW20, true},
|
||||
{WifiMode::IEEE80211A, 125, 149, 177, 4, WifiBandwidth::BW20, true},
|
||||
{WifiMode::IEEE80211A, 126, 149, 173, 8, WifiBandwidth::BW40PLUS, true},
|
||||
{WifiMode::IEEE80211A, 127, 153, 177, 8, WifiBandwidth::BW40MINUS, true},
|
||||
|
||||
/*
|
||||
* IEEE P802.11ax/D8.0 (WIFI 6)Table E-4 actually talks about channel center
|
||||
* frequency index 42, 58, 106, 122, 138, 155, 171 with channel spacing
|
||||
* of 80 MHz, but currently use the following definition for simplicity
|
||||
* (these center frequencies are not actual channels, which makes
|
||||
* wpas_p2p_verify_channel() fail). wpas_p2p_verify_80mhz() should take
|
||||
* care of removing invalid channels.
|
||||
*/
|
||||
|
||||
// WiFi 5.
|
||||
// {WifiMode::IEEE80211A, 128, 36, 177, 4, WifiBandwidth::BW80, true},
|
||||
// {WifiMode::IEEE80211A, 129, 36, 177, 4, WifiBandwidth::BW160, true},
|
||||
// {WifiMode::IEEE80211A, 130, 36, 177, 4, WifiBandwidth::BW80P80, true},
|
||||
|
||||
// WiFi 6.
|
||||
// {WifiMode::IEEE80211AX, 131, 1, 233, 4, WifiBandwidth::BW20, true},
|
||||
// {WifiMode::IEEE80211AX, 132, 1, 233, 8, WifiBandwidth::BW40PLUS, true},
|
||||
// {WifiMode::IEEE80211AX, 133, 1, 233, 16, WifiBandwidth::BW80, true},
|
||||
// {WifiMode::IEEE80211AX, 134, 1, 233, 32, WifiBandwidth::BW160, true},
|
||||
// {WifiMode::IEEE80211AX, 135, 1, 233, 16, WifiBandwidth::BW80P80, false},
|
||||
// {WifiMode::IEEE80211AX, 136, 2, 2, 4, WifiBandwidth::BW20, false},
|
||||
|
||||
|
||||
/* IEEE Std 802.11ad-2012 and P802.ay/D5.0 60 GHz operating classes.
|
||||
* Class 180 has the legacy channels 1-6. Classes 181-183 include
|
||||
* channels which implement channel bonding features.
|
||||
*/
|
||||
// {WifiMode::IEEE80211AD, 180, 1, 6, 1, WifiBandwidth::BW2160, true},
|
||||
// {WifiMode::IEEE80211AD, 181, 9, 13, 1, WifiBandwidth::BW4320, true},
|
||||
// {WifiMode::IEEE80211AD, 182, 17, 20, 1, WifiBandwidth::BW6480, true},
|
||||
// {WifiMode::IEEE80211AD, 183, 25, 27, 1, WifiBandwidth::BW8640, true},
|
||||
|
||||
};
|
||||
|
||||
static int32_t ChannelToFrequency(int32_t channel)
|
||||
{
|
||||
if (channel >= 1 && channel <= 13)
|
||||
{
|
||||
return 2412 + (channel - 1) * 5;
|
||||
}
|
||||
if (channel == 14)
|
||||
{
|
||||
return 2484;
|
||||
}
|
||||
if (channel >= 36 && channel <= 165)
|
||||
{
|
||||
return 5180 + (channel - 36) * 5;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
static bool BandwidthPermitted(int32_t frequency,WifiBandwidth bandwidth, const WifiRule &rule)
|
||||
{
|
||||
int32_t channelWidth = -1;
|
||||
switch (bandwidth)
|
||||
{
|
||||
case WifiBandwidth::BW20:
|
||||
channelWidth = 20;
|
||||
break;
|
||||
case WifiBandwidth::BW40PLUS:
|
||||
channelWidth = 40;
|
||||
if (rule.HasFlag(RegRuleFlags::NO_HT40PLUS))
|
||||
return false;
|
||||
break;
|
||||
case WifiBandwidth::BW40MINUS:
|
||||
if (rule.HasFlag(RegRuleFlags::NO_HT40MINUS))
|
||||
return false;
|
||||
channelWidth = 40;
|
||||
break;
|
||||
case WifiBandwidth::BW40: // wifi 6 only. do NOT use for a/g channels.
|
||||
channelWidth = 40;
|
||||
break;
|
||||
case WifiBandwidth::BW80:
|
||||
if (rule.HasFlag(RegRuleFlags::NO_80MHZ))
|
||||
return false;
|
||||
channelWidth = 80;
|
||||
break;
|
||||
case WifiBandwidth::BW160:
|
||||
if (rule.HasFlag(RegRuleFlags::NO_160MHZ))
|
||||
return false;
|
||||
channelWidth = 160;
|
||||
break;
|
||||
case WifiBandwidth::BW80P80:
|
||||
if (rule.HasFlag(RegRuleFlags::NO_160MHZ))
|
||||
return false;
|
||||
channelWidth = 160;
|
||||
break;
|
||||
case WifiBandwidth::BW2160:
|
||||
channelWidth = 2160;
|
||||
break;
|
||||
case WifiBandwidth::BW4320:
|
||||
channelWidth = 4320;
|
||||
break;
|
||||
case WifiBandwidth::BW6480:
|
||||
channelWidth = 6480;
|
||||
break;
|
||||
case WifiBandwidth::BW8640:
|
||||
channelWidth = 8640;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (channelWidth * 1000 > rule.max_bandwidth_khz)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int32_t minFrequency = frequency-channelWidth/2;
|
||||
int32_t maxFrequency = minFrequency+channelWidth;
|
||||
if (bandwidth == WifiBandwidth::BW40PLUS)
|
||||
{
|
||||
minFrequency = frequency-channelWidth/4;
|
||||
maxFrequency = minFrequency+channelWidth;
|
||||
} else if (bandwidth == WifiBandwidth::BW40MINUS)
|
||||
{
|
||||
maxFrequency = frequency+channelWidth/4;
|
||||
minFrequency = maxFrequency-channelWidth;
|
||||
}
|
||||
if (minFrequency*1000 < rule.start_freq_khz || maxFrequency*1000 > rule.end_freq_khz)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
WifiInfo pipedal::getWifiInfo(const std::string&countryIso3661)
|
||||
{
|
||||
const RegDb ®Db = RegDb::GetInstance();
|
||||
const WifiRegulations ®ulations = regDb.getWifiRegulations(countryIso3661);
|
||||
|
||||
WifiInfo info;
|
||||
info.reg_alpha2 = regulations.reg_alpha2;
|
||||
info.dfsRegion = regulations.dfs_region;
|
||||
|
||||
|
||||
for (const auto &classMap : opClasses)
|
||||
{
|
||||
if (!classMap.p2p_support)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (classMap.band != WifiMode::IEEE80211A && classMap.band != WifiMode::IEEE80211G)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (auto channel = classMap.firstChannel; channel <= classMap.lastChannel; channel += classMap.increment)
|
||||
{
|
||||
if (!classMap.p2p_support)
|
||||
continue;
|
||||
|
||||
int32_t frequency = ChannelToFrequency(channel);
|
||||
if (frequency == -1)
|
||||
continue;
|
||||
|
||||
const auto *rule = regulations.GetRule(frequency);
|
||||
if (!rule)
|
||||
continue;
|
||||
|
||||
if (rule->HasFlag(
|
||||
RegRuleFlags::NO_IR | RegRuleFlags::DFS | RegRuleFlags::PTP_ONLY | RegRuleFlags::PTMP_ONLY))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (rule->HasFlag(RegRuleFlags::DFS))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
WifiBandwidth bandwidth = classMap.bandwidth;
|
||||
if (!BandwidthPermitted(frequency,bandwidth, *rule))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
WifiChannelInfo channelInfo;
|
||||
channelInfo.channelNumber = channel;
|
||||
channelInfo.hardwareMode = classMap.band;
|
||||
channelInfo.disabled = false;
|
||||
channelInfo.indoorOnly = rule->HasFlag(RegRuleFlags::NO_OUTDOOR);
|
||||
channelInfo.outdoorsOnly = rule->HasFlag(RegRuleFlags::NO_INDOOR);
|
||||
channelInfo.no10MHz = rule->max_bandwidth_khz < 10 * 1000;
|
||||
channelInfo.no20MHz = rule->max_bandwidth_khz < 20 * 1000;
|
||||
channelInfo.no80MHz = rule->max_bandwidth_khz < 80 * 1000;
|
||||
channelInfo.noHt40Minus = rule->max_bandwidth_khz < 40 * 1000 || rule->HasFlag(RegRuleFlags::NO_HT40MINUS);
|
||||
channelInfo.noHt40Plus = rule->max_bandwidth_khz < 40 * 1000 || rule->HasFlag(RegRuleFlags::NO_HT40PLUS);
|
||||
|
||||
channelInfo.mhz = frequency;
|
||||
channelInfo.no10MHz = rule->HasFlag(RegRuleFlags::NO_IR);
|
||||
channelInfo.regDomain = classMap.regDomain;
|
||||
channelInfo.maxAntennaGain = rule->max_antenna_gain;
|
||||
channelInfo.maxEirp = rule->max_eirp;
|
||||
channelInfo.bandwidth = bandwidth;
|
||||
|
||||
info.channels.push_back(std::move(channelInfo));
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
std::vector<int32_t> pipedal::getValidChannels(const std::string&countryIso3661,int32_t maxChannelWidthMhz)
|
||||
{
|
||||
std::set<int32_t> channels;
|
||||
|
||||
|
||||
WifiBandwidth maxBandwidth;
|
||||
if (maxChannelWidthMhz <= 20)
|
||||
{
|
||||
maxBandwidth = WifiBandwidth::BW20;
|
||||
} else if (maxChannelWidthMhz <= 40)
|
||||
{
|
||||
maxBandwidth = WifiBandwidth::BW40;
|
||||
} else if (maxChannelWidthMhz <= 80)
|
||||
{
|
||||
maxBandwidth = WifiBandwidth::BW80;
|
||||
} else // if (maxChannelWidthMhz <= 160)
|
||||
{
|
||||
maxBandwidth = WifiBandwidth::BW160;
|
||||
}
|
||||
RegDb ®Db = RegDb::GetInstance();
|
||||
auto info = pipedal::getWifiInfo(countryIso3661);
|
||||
|
||||
for (const auto&channelInfo: info.channels)
|
||||
{
|
||||
channels.insert(channelInfo.channelNumber);
|
||||
}
|
||||
|
||||
|
||||
std::vector<int32_t> result;
|
||||
result.reserve(channels.size());
|
||||
for (auto v : channels)
|
||||
{
|
||||
result.push_back(v);
|
||||
}
|
||||
std::sort(result.begin(),result.end());
|
||||
return result;
|
||||
}
|
||||
int32_t pipedal::getWifiRegClass(const std::string &countryIso3661, int32_t channel, int32_t maxChannelWidthMhz)
|
||||
{
|
||||
WifiBandwidth maxBandwidth;
|
||||
if (maxChannelWidthMhz <= 20)
|
||||
{
|
||||
maxBandwidth = WifiBandwidth::BW20;
|
||||
} else if (maxChannelWidthMhz <= 40)
|
||||
{
|
||||
maxBandwidth = WifiBandwidth::BW40;
|
||||
} else if (maxChannelWidthMhz <= 80)
|
||||
{
|
||||
maxBandwidth = WifiBandwidth::BW80;
|
||||
} else // if (maxChannelWidthMhz <= 160)
|
||||
{
|
||||
maxBandwidth = WifiBandwidth::BW160;
|
||||
}
|
||||
RegDb ®Db = RegDb::GetInstance();
|
||||
auto info = pipedal::getWifiInfo(countryIso3661);
|
||||
|
||||
const WifiChannelInfo *bestChannel = nullptr;
|
||||
for (const auto&channelInfo: info.channels)
|
||||
{
|
||||
if (channelInfo.channelNumber == channel && channelInfo.bandwidth <= maxBandwidth)
|
||||
{
|
||||
if (bestChannel == nullptr || bestChannel->bandwidth < channelInfo.bandwidth)
|
||||
{
|
||||
bestChannel = &channelInfo;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (bestChannel)
|
||||
{
|
||||
if (channel >= 1 && channel <= 13) { // alway 20Ghz.
|
||||
return 81;
|
||||
}
|
||||
return bestChannel->regDomain;
|
||||
}
|
||||
std::vector<int32_t> valid_channels;
|
||||
try {
|
||||
valid_channels = getValidChannels(countryIso3661,40);
|
||||
}
|
||||
catch (const std::exception& /**/)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Channel " << channel << " is not permitted in the country " << countryIso3661 << ".";
|
||||
throw std::runtime_error(ss.str());
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Channel " << channel << " is not permitted in the country " << countryIso3661 << ". Permitted channels:\n ";
|
||||
bool first = true;
|
||||
for (auto channel: valid_channels)
|
||||
{
|
||||
if (!first) ss << ", ";
|
||||
first = false;
|
||||
ss << channel;
|
||||
}
|
||||
ss << ".";
|
||||
throw std::runtime_error(ss.str());
|
||||
}
|
||||
|
||||
|
||||
// Hard-coded values lifted from wpa_supplicant sources. I don't really think it respect the regulatory db.
|
||||
|
||||
/* Operating class 81 - 2.4 GHz band channels 1..13 */
|
||||
// int32_t regClass = -1;
|
||||
// if (channel >= 1 && channel <= 11)
|
||||
// {
|
||||
// regClass = 81;
|
||||
// }
|
||||
// // lower 5 GHZ.
|
||||
// if (channel == 36 || channel == 40 || channel == 44 || channel == 48)
|
||||
// {
|
||||
// regClass = 115; // 20 Ghz
|
||||
// }
|
||||
// if (channel == 149 || channel == 153 || channel == 157 || channel == 161)
|
||||
// {
|
||||
// regClass = 124; // 20Ghz nomadic.
|
||||
// }
|
||||
// if (regClass == -1)
|
||||
// {
|
||||
// return -1;
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ConfigSerializer.hpp"
|
||||
|
||||
|
||||
using namespace config_serializer;
|
||||
using namespace config_serializer::detail;
|
||||
using namespace std;
|
||||
|
||||
std::string config_serializer::detail::trim(const std::string &v)
|
||||
{
|
||||
size_t start = v.find_first_not_of(' ');
|
||||
size_t end = v.find_last_not_of(' ');
|
||||
if (start >= end+1)
|
||||
return "";
|
||||
return v.substr(start, end+1 - start);
|
||||
}
|
||||
|
||||
std::vector<std::string> config_serializer::detail::split(const std::string &value, char delimiter)
|
||||
{
|
||||
size_t start = 0;
|
||||
std::vector<std::string> result;
|
||||
while (start < value.length())
|
||||
{
|
||||
size_t pos = value.find_first_of(delimiter, start);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
result.push_back(value.substr(start));
|
||||
break;
|
||||
}
|
||||
result.push_back(value.substr(start, pos - start));
|
||||
start = pos + 1;
|
||||
if (start == value.length())
|
||||
{
|
||||
// ends with delimieter? Then there's an empty-length value at the end.
|
||||
result.push_back("");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string config_serializer::detail::EncodeString(const std::string &s)
|
||||
{
|
||||
bool requiresEncoding = false;
|
||||
|
||||
for (char c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
break;
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\\':
|
||||
case ' ':
|
||||
case '\"':
|
||||
requiresEncoding = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!requiresEncoding)
|
||||
return s;
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
ss << '"';
|
||||
for (char c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\0':
|
||||
// discard.
|
||||
break;
|
||||
case '\r':
|
||||
ss << "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
ss << "\\t";
|
||||
break;
|
||||
case '\\':
|
||||
ss << "\\\\";
|
||||
break;
|
||||
case '\"':
|
||||
ss << "\\\"";
|
||||
break;
|
||||
default:
|
||||
ss << c;
|
||||
}
|
||||
}
|
||||
ss << '"';
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string config_serializer::detail::DecodeString(const std::string &value)
|
||||
{
|
||||
std::stringstream s;
|
||||
const char *p = value.c_str();
|
||||
char quoteChar;
|
||||
if (*p == '\'' || *p == '\"')
|
||||
{
|
||||
quoteChar = *p;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
if (*p != quoteChar)
|
||||
return value;
|
||||
if (value.at(value.length()-1) != quoteChar)
|
||||
{
|
||||
throw std::invalid_argument("Invalid quoted string.");
|
||||
}
|
||||
++p;
|
||||
while (*p != 0 && *p != quoteChar)
|
||||
{
|
||||
char c = *p++;
|
||||
if (c == '\\')
|
||||
{
|
||||
c = *p++;
|
||||
switch (c)
|
||||
{
|
||||
case 'r':
|
||||
s << '\r';
|
||||
break;
|
||||
case 't':
|
||||
s << '\t';
|
||||
break;
|
||||
case 'n':
|
||||
s << '\n';
|
||||
break;
|
||||
case '\0':
|
||||
throw std::invalid_argument("Invalid quoted string.");
|
||||
break;
|
||||
default:
|
||||
s << c;
|
||||
}
|
||||
if (c != 0)
|
||||
{
|
||||
s << c;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s << c;
|
||||
}
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ConfigUtil.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
static std::string getKey(const std::string &line, size_t length)
|
||||
{
|
||||
std::stringstream s;
|
||||
size_t start = 0;
|
||||
size_t end = length;
|
||||
char c;
|
||||
while (start < end && (line[start] == ' ' || line[start] == '\t'))
|
||||
++start;
|
||||
|
||||
while (end > start && (line[end - 1] == ' ' || line[end - 1] == '\t'))
|
||||
--end;
|
||||
|
||||
return line.substr(start, end - start);
|
||||
}
|
||||
static std::string trim(const std::string &value)
|
||||
{
|
||||
size_t start = 0;
|
||||
size_t end = value.length();
|
||||
|
||||
while (start < end && (value[start] == ' ' || value[start] == '\t'))
|
||||
++start;
|
||||
|
||||
while (end > start && (value[end - 1] == ' ' || value[end - 1] == '\t'))
|
||||
--end;
|
||||
|
||||
return value.substr(start, end - start);
|
||||
}
|
||||
static std::string unquote(const std::string &value)
|
||||
{
|
||||
if (value.length() > 2)
|
||||
{
|
||||
if (
|
||||
(value[0] == '\'' && value[value.length() - 1] == '\'') || (value[0] == '\"' && value[value.length() - 1] == '\"'))
|
||||
{
|
||||
char quotChar = value[0];
|
||||
|
||||
std::stringstream ss;
|
||||
for (size_t i = 1; i < value.length() - 1; ++i)
|
||||
{
|
||||
char c = value[i];
|
||||
if (c == '\\')
|
||||
{
|
||||
++i;
|
||||
c = value[i];
|
||||
switch (c)
|
||||
{
|
||||
case 'r':
|
||||
c = '\r';
|
||||
break;
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ss << c;
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << c;
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string ConfigUtil::QuoteString(const std::string &value)
|
||||
{
|
||||
std::stringstream s;
|
||||
|
||||
s << '"';
|
||||
for (char c : value)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\n':
|
||||
break;
|
||||
case '"':
|
||||
s << "\\\"";
|
||||
break;
|
||||
case '\\':
|
||||
s << "\\\\";
|
||||
break;
|
||||
default:
|
||||
s << c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s << '"';
|
||||
return s.str();
|
||||
}
|
||||
bool ConfigUtil::GetConfigLine(const std::string &filePath, const std::string &key, std::string *pValue)
|
||||
{
|
||||
|
||||
ifstream f;
|
||||
f.open(filePath);
|
||||
if (f.is_open())
|
||||
{
|
||||
std::string line;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (f.eof() || f.fail())
|
||||
{
|
||||
break;
|
||||
}
|
||||
getline(f, line);
|
||||
auto pos = line.find_first_of('#');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
line = line.substr(0, pos);
|
||||
}
|
||||
pos = line.find_first_of('=');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
std::string thisKey = getKey(line, pos);
|
||||
if (thisKey == key)
|
||||
{
|
||||
std::string value = line.substr(pos + 1);
|
||||
|
||||
*pValue = unquote(trim(value));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "HtmlHelper.hpp"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
// non-localizable. RFC 7231 Date Format
|
||||
;
|
||||
static const char*INVARIANT_WEEK_DAYS[] =
|
||||
{ "Sun","Mon", "Tue", "Wed","Thu", "Fri","Sat"};
|
||||
// non-localizable. RFC 7231 Date Format
|
||||
static const char*INVARIANT_MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep","Oct","Nov", "Dec"};
|
||||
// non-localizable. RFC 7231 Date Format
|
||||
static const char*FORMAT = "%s, %02d %s %04d %02d:%02d:%02d GMT";
|
||||
|
||||
std::string HtmlHelper::timeToHttpDate()
|
||||
{
|
||||
return timeToHttpDate(time(nullptr));
|
||||
}
|
||||
|
||||
std::string HtmlHelper::timeToHttpDate(time_t time)
|
||||
{
|
||||
// RFC 7231, IMF-fixdate.
|
||||
|
||||
struct tm gmTm;
|
||||
|
||||
if (gmtime_r(&time,&gmTm) == nullptr)
|
||||
{
|
||||
return "Mon, 01 Jan 1970 00:00:00 GMT";
|
||||
}
|
||||
char szBuffer[sizeof("Mon, 01 Jan 1970 00:00:00 GMT+")];
|
||||
snprintf(szBuffer,sizeof(szBuffer), FORMAT,
|
||||
INVARIANT_WEEK_DAYS[gmTm.tm_wday],
|
||||
gmTm.tm_mday,
|
||||
INVARIANT_MONTHS[gmTm.tm_mon],
|
||||
gmTm.tm_year+1900,
|
||||
gmTm.tm_hour,
|
||||
gmTm.tm_min,
|
||||
gmTm.tm_sec);
|
||||
return szBuffer;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static int hexN(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9') return c-'0';
|
||||
if (c >= 'a' && c <= 'f') return 10+c-'a';
|
||||
if (c >= 'A' && c <= 'F') return 10+c-'A';
|
||||
throw std::invalid_argument("Malformed URL.");
|
||||
}
|
||||
static char hexChar(char c1, char c2)
|
||||
{
|
||||
return (char)((hexN(c1) << 4) + hexN(c2));
|
||||
}
|
||||
|
||||
class SafeCharacterMap {
|
||||
bool safeCharacter[256];
|
||||
public:
|
||||
SafeCharacterMap()
|
||||
{
|
||||
for (int i = 0; i < 0x20; ++i) {
|
||||
safeCharacter[i] = false;
|
||||
}
|
||||
for (int i = 0x20; i < 0x80; ++i) {
|
||||
safeCharacter[i] = true;
|
||||
}
|
||||
for (int i = 0x80; i < 256; ++i)
|
||||
{
|
||||
safeCharacter[i] = false;
|
||||
}
|
||||
const char *unsafeCharacters = ":/+?=#%@ ";
|
||||
for (const char*p = unsafeCharacters; *p != 0; ++p)
|
||||
{
|
||||
safeCharacter[(uint8_t)*p] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool isSafeCharacter(char c) { return safeCharacter[(uint8_t)c];}
|
||||
} g_safeCharacterMap;
|
||||
|
||||
|
||||
std::string HtmlHelper::encode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment )
|
||||
{
|
||||
std::stringstream s;
|
||||
encode_url_segment(s,pStart,pEnd,isQuerySegment);
|
||||
return s.str();
|
||||
}
|
||||
|
||||
static char hexChars[] = "0123456789ABCDEF";
|
||||
|
||||
void HtmlHelper::encode_url_segment(std::ostream&os, const char*pStart, const char *pEnd, bool isQuerySegment)
|
||||
{
|
||||
while (pStart != pEnd)
|
||||
{
|
||||
char c = *pStart++;
|
||||
if (g_safeCharacterMap.isSafeCharacter(c))
|
||||
{
|
||||
os << c;
|
||||
} else if (c == ' ' && isQuerySegment)
|
||||
{
|
||||
os << '+';
|
||||
|
||||
os << c;
|
||||
} else {
|
||||
os << '%' << hexChars[(c >> 4) & 0x0f] << hexChars[c & 0x0F];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
std::string HtmlHelper::decode_url_segment(const char*pStart, const char*pEnd, bool isQuery)
|
||||
{
|
||||
std::stringstream s;
|
||||
auto p = pStart;
|
||||
while (p != pEnd)
|
||||
{
|
||||
char c = *p;
|
||||
++p;
|
||||
if (c == '+')
|
||||
{
|
||||
s << ' ';
|
||||
} else if (c != '%')
|
||||
{
|
||||
s << c;
|
||||
} else {
|
||||
if (pStart+2 < pEnd)
|
||||
{
|
||||
char c1 = *p; ++p;
|
||||
char c2 = *p; ++p;
|
||||
s << hexChar(c1,c2);
|
||||
} else {
|
||||
throw std::invalid_argument("Malformed URL.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
std::string HtmlHelper::decode_url_segment(const char*text, bool isQuery)
|
||||
{
|
||||
return decode_url_segment(text, text + strlen(text),isQuery);
|
||||
}
|
||||
|
||||
void HtmlHelper::utf32_to_utf8_stream(std::ostream &s, uint32_t uc)
|
||||
{
|
||||
if (uc < 0x80u)
|
||||
{
|
||||
s << (char)uc;
|
||||
} else if (uc < 0x800u) {
|
||||
s << (char)(0xC0 + (uc >> 6));
|
||||
s << (char)(0x80 + (uc & 0x3F));
|
||||
|
||||
} else if (uc < 0x10000u) {
|
||||
s << (char)(0xE0 + (uc >> 12));
|
||||
s << (char)(0x80 + ((uc >> 6) & 0x3F));
|
||||
s << (char)(0x80 + (uc & 0x3F));
|
||||
} else if (uc < 0x0110000) {
|
||||
s << (char)(0xF0 + (uc >> 18));
|
||||
s << (char)(0x80 + ((uc >> 12) & 0x3F));
|
||||
s << (char)(0x80 + ((uc >> 6) & 0x3F));
|
||||
s << (char)(0x80 + (uc & 0x3F));
|
||||
} else {
|
||||
throw std::range_error("Illegal UTF-32 character.");
|
||||
}
|
||||
}
|
||||
|
||||
const std::string ESPECIALS = "()<>@,;:\"/[]?.=";
|
||||
|
||||
#define MAX_FILENAME_LENGTH 96
|
||||
#define MAX_SEGMENT_LENGTH 74 // 75 per RFC2047.2. But leave space for a ';' to allow easier breaking of lines.
|
||||
|
||||
std::string HtmlHelper::Rfc5987EncodeFileName(const std::string&name)
|
||||
{
|
||||
// Encode all characters. Let the browser handle safe-ifying the actual name, since that's os dependent.
|
||||
std::stringstream s;
|
||||
std::string prefix = "UTF-8''";
|
||||
std::string postfix = "";
|
||||
s << prefix;
|
||||
for (char c: name)
|
||||
{
|
||||
uint8_t uc = (uint8_t)c;
|
||||
if (uc < 0x20 || uc >= 0x80 || ESPECIALS.find(c) != std::string::npos)
|
||||
{
|
||||
s << '%' << hexChars[uc >> 4] << hexChars[uc & 0x0F];
|
||||
} else {
|
||||
s << c;
|
||||
}
|
||||
}
|
||||
s << postfix;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
|
||||
#define MAX_FILE_NAME_LENGHT 96
|
||||
|
||||
const std::string SF_SPECIALS = " <>@;:\"\'/[]?=";
|
||||
|
||||
std::string HtmlHelper::SafeFileName(const std::string &name)
|
||||
{
|
||||
std::stringstream s;
|
||||
for (char c : name)
|
||||
{
|
||||
uint8_t uc = (uint8_t)c;
|
||||
if (uc < 0x20 || uc >= 0x80 || SF_SPECIALS.find(c) != std::string::npos)
|
||||
{
|
||||
s << '_';
|
||||
}
|
||||
else
|
||||
{
|
||||
s << c;
|
||||
}
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
|
||||
std::string HtmlHelper::HtmlEncode(const std::string& text)
|
||||
{
|
||||
std::stringstream os;
|
||||
for (char c: text)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case '<': os << "<"; break;
|
||||
case '>': os << ">"; break;
|
||||
case '&': os << "&"; break;
|
||||
default: os << c; break;
|
||||
|
||||
}
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,642 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "RegDb.hpp"
|
||||
#include "ss.hpp"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <unordered_set>
|
||||
#include <arpa/inet.h>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <stdexcept>
|
||||
#include <bit>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include <iostream>
|
||||
#define REGDB_MAGIC 0x52474442
|
||||
#define REGDB_VERSION 19 // pre-bookwork
|
||||
#define REGDB_VERSION_2 20 // bookworm and later.
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
static std::unique_ptr<RegDb> g_Instance;
|
||||
RegDb&RegDb::GetInstance()
|
||||
{
|
||||
if (!g_Instance)
|
||||
{
|
||||
g_Instance = std::make_unique<RegDb>();
|
||||
}
|
||||
return *g_Instance;
|
||||
}
|
||||
|
||||
const WifiRegulations& RegDb::getWifiRegulations(const std::string&countryIso3661) const
|
||||
{
|
||||
for (const auto ®ulation: this->regulations)
|
||||
{
|
||||
if (regulation.reg_alpha2 == countryIso3661)
|
||||
{
|
||||
return regulation;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Invalid country code.");
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
T NlSwap(T value)
|
||||
{
|
||||
if constexpr (std::endian::native == std::endian::big)
|
||||
{
|
||||
return value;
|
||||
} else {
|
||||
auto value_rep = std::bit_cast<std::array<std::uint8_t,sizeof(T)>,T >(value);
|
||||
std::ranges::reverse(value_rep);
|
||||
return std::bit_cast<T>(value_rep);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class BigEndian {
|
||||
public:
|
||||
BigEndian(): m_value(0) { }
|
||||
explicit BigEndian(T value): m_value(NlSwap(value)) {}
|
||||
T value() const { return NlSwap(m_value);}
|
||||
bool operator==(const BigEndian<T>&other) const { return m_value == other.m_value; }
|
||||
explicit operator bool() const { return m_value != 0; }
|
||||
private:
|
||||
T m_value;
|
||||
};
|
||||
|
||||
struct RegDbFileHeader19
|
||||
{
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint32_t countryOffset;
|
||||
uint32_t countryCount;
|
||||
uint32_t signatureLength;
|
||||
|
||||
void toNs()
|
||||
{
|
||||
this->magic = NlSwap(this->magic);
|
||||
this->version = NlSwap(this->version);
|
||||
this->countryOffset = NlSwap(this->countryOffset);
|
||||
this->countryCount = NlSwap(this->countryCount);
|
||||
this->signatureLength = NlSwap(this->signatureLength);
|
||||
}
|
||||
};
|
||||
|
||||
struct CountryHeader
|
||||
{
|
||||
char alpha2[2];
|
||||
uint8_t pad;
|
||||
uint8_t dfsRegion; // first 2 bits only.
|
||||
uint32_t rulesOffset;
|
||||
|
||||
void toNs()
|
||||
{
|
||||
this->rulesOffset = NlSwap(rulesOffset);
|
||||
}
|
||||
};
|
||||
|
||||
struct Rule
|
||||
{
|
||||
uint32_t frequencyRangeOffset;
|
||||
uint32_t powerRuleOffset;
|
||||
uint32_t flags;
|
||||
|
||||
void toNs()
|
||||
{
|
||||
frequencyRangeOffset = NlSwap(frequencyRangeOffset);
|
||||
powerRuleOffset = NlSwap(powerRuleOffset);
|
||||
flags = NlSwap(flags);
|
||||
}
|
||||
};
|
||||
|
||||
struct RulesCollection19
|
||||
{
|
||||
uint32_t ruleCount;
|
||||
uint32_t ruleOffsets[1];
|
||||
|
||||
void toNs()
|
||||
{
|
||||
this->ruleCount = NlSwap(this->ruleCount);
|
||||
}
|
||||
Rule*getRule(
|
||||
uint32_t nRule,
|
||||
char *pData
|
||||
)
|
||||
{
|
||||
uint32_t ruleOffset = NlSwap(ruleOffsets[nRule]);
|
||||
return (Rule*)(pData+ruleOffset);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct FrequencyRange
|
||||
{
|
||||
uint32_t startFrequency; // in khz.
|
||||
uint32_t endFrequency; // in khz.
|
||||
uint32_t maxBandwidth; // in khz.
|
||||
void toNs()
|
||||
{
|
||||
startFrequency = NlSwap(startFrequency);
|
||||
endFrequency = NlSwap(endFrequency);
|
||||
maxBandwidth = NlSwap(maxBandwidth);
|
||||
}
|
||||
};
|
||||
struct PowerRule
|
||||
{
|
||||
uint32_t maximumAntennaGain;
|
||||
uint32_t maximumEirp;
|
||||
void toNs()
|
||||
{
|
||||
maximumAntennaGain = NlSwap(maximumAntennaGain);
|
||||
maximumEirp = NlSwap(maximumEirp);
|
||||
}
|
||||
};
|
||||
|
||||
const char *filePaths[] =
|
||||
{
|
||||
"/lib/firmware/regulatory.db", // bookworm, buster.
|
||||
"/usr/lib/crda/regulatory.bin" // previous versions?.
|
||||
};
|
||||
|
||||
static std::filesystem::path getFilePath()
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(filePaths) / sizeof(filePaths[0]); ++i)
|
||||
{
|
||||
std::filesystem::path path(filePaths[i]);
|
||||
if (std::filesystem::exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Could not find 'regulatory.bin' file.");
|
||||
}
|
||||
|
||||
RegDb::~RegDb()
|
||||
{
|
||||
}
|
||||
|
||||
RegDb::RegDb()
|
||||
: RegDb(getFilePath())
|
||||
{
|
||||
}
|
||||
RegDb::RegDb(const std::filesystem::path &path)
|
||||
: pData(nullptr)
|
||||
{
|
||||
ifstream f(path);
|
||||
if (!f.is_open())
|
||||
{
|
||||
stringstream s;
|
||||
s << "Can't read " << path;
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
size_t fileSize = std::filesystem::file_size(path);
|
||||
data.resize(fileSize);
|
||||
f.read((char *)&data[0], fileSize);
|
||||
if (!f)
|
||||
{
|
||||
stringstream s;
|
||||
s << "Can't read " << path;
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
|
||||
RegDbFileHeader19 header = *(RegDbFileHeader19 *)ptr();
|
||||
header.toNs();
|
||||
|
||||
if (header.magic != REGDB_MAGIC)
|
||||
{
|
||||
throw std::runtime_error(SS("Invalid file format. " << path));
|
||||
}
|
||||
if (header.version == 19)
|
||||
{
|
||||
Load19();
|
||||
}
|
||||
else if (header.version == 20)
|
||||
{
|
||||
Load20();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error(SS("Unknown file version. " << path));
|
||||
}
|
||||
}
|
||||
void RegDb::Load19()
|
||||
{
|
||||
char *pData = (char *)ptr();
|
||||
RegDbFileHeader19 fileHeader = *(RegDbFileHeader19 *)ptr();
|
||||
fileHeader.toNs();
|
||||
|
||||
this->regulations.resize(0);
|
||||
|
||||
CountryHeader *pCountryHeader = (CountryHeader *)(pData + fileHeader.countryOffset);
|
||||
for (uint32_t i = 0; i < fileHeader.countryCount; ++i)
|
||||
{
|
||||
WifiRegulations wifiRegulations;
|
||||
|
||||
|
||||
CountryHeader countryHeader = pCountryHeader[i];
|
||||
countryHeader.toNs();
|
||||
wifiRegulations.reg_alpha2 = SS(countryHeader.alpha2[0] << countryHeader.alpha2[1]);
|
||||
wifiRegulations.dfs_region = (DfsRegion)(countryHeader.dfsRegion & 0x03);
|
||||
|
||||
RulesCollection19 *pRules = (RulesCollection19 *)(pData + countryHeader.rulesOffset);
|
||||
uint32_t ruleCount = NlSwap(pRules->ruleCount);
|
||||
for (uint32_t nRule = 0; nRule < ruleCount; ++nRule)
|
||||
{
|
||||
Rule *pRule = pRules->getRule(nRule,pData);
|
||||
Rule rule = *pRule;
|
||||
rule.toNs();
|
||||
|
||||
FrequencyRange fr = *(FrequencyRange *)(pData + rule.frequencyRangeOffset);
|
||||
fr.toNs();
|
||||
|
||||
PowerRule pr = *(PowerRule *)(pData + rule.powerRuleOffset);
|
||||
pr.toNs();
|
||||
|
||||
WifiRule wifiRegulation;
|
||||
wifiRegulation.flags = (RegRuleFlags)rule.flags;
|
||||
wifiRegulation.start_freq_khz = fr.startFrequency;
|
||||
wifiRegulation.end_freq_khz = fr.endFrequency;
|
||||
wifiRegulation.max_bandwidth_khz = fr.maxBandwidth;
|
||||
wifiRegulation.max_antenna_gain = pr.maximumAntennaGain;
|
||||
wifiRegulation.max_eirp = pr.maximumEirp;
|
||||
wifiRegulations.rules.push_back(wifiRegulation);
|
||||
}
|
||||
this->regulations.push_back(std::move(wifiRegulations));
|
||||
}
|
||||
this->isValid = true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// glue
|
||||
#define IEEE80211_NUM_ACS 4 // linux/ieee80211.h Number of hardware queues?
|
||||
|
||||
#define __aligned(n)
|
||||
#define __packed
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
|
||||
using __be32 = BigEndian<uint32_t>;
|
||||
using __be16 = BigEndian<uint16_t>;
|
||||
#define BIT(n) (1 << n)
|
||||
|
||||
template <typename T>
|
||||
T ALIGN(T value,int bits)
|
||||
{
|
||||
intptr_t v = (intptr_t)value;
|
||||
|
||||
intptr_t mask = (1 << bits)-1;
|
||||
v = (v + mask) & (~mask);
|
||||
return (T)v;
|
||||
|
||||
}
|
||||
|
||||
|
||||
inline BigEndian<uint32_t> cpu_to_be32(uint32_t value)
|
||||
{
|
||||
return BigEndian<uint32_t>(value);
|
||||
}
|
||||
|
||||
inline BigEndian<uint16_t> cpu_to_be16(uint16_t value)
|
||||
{
|
||||
return BigEndian<uint16_t>(value);
|
||||
}
|
||||
|
||||
|
||||
inline uint16_t be16_to_cpu(const BigEndian<uint16_t>&value)
|
||||
{
|
||||
return value.value();
|
||||
}
|
||||
inline uint32_t be32_to_cpu(const BigEndian<uint32_t>&value)
|
||||
{
|
||||
return value.value();
|
||||
}
|
||||
|
||||
bool regdb_has_valid_signature(const u8 *data, unsigned int size) { return true; }
|
||||
|
||||
#define offsetofend(TYPE,FIELD) ((uint32_t)(offsetof(TYPE,FIELD)+sizeof(((TYPE*)0)->FIELD)))
|
||||
#define ASSERT_RTNL() // in the original, assert that rtnl is not locked.
|
||||
|
||||
/////////////////////////
|
||||
// shamelessly lifted from linux net/wireless/reg.c
|
||||
|
||||
struct fwdb_country {
|
||||
u8 alpha2[2];
|
||||
__be16 coll_ptr;
|
||||
/* this struct cannot be extended */
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct fwdb_collection {
|
||||
u8 len;
|
||||
u8 n_rules;
|
||||
u8 dfs_region;
|
||||
/* no optional data yet */
|
||||
/* aligned to 2, then followed by __be16 array of rule pointers */
|
||||
} __packed __aligned(4);
|
||||
|
||||
struct fwdb_header {
|
||||
__be32 magic;
|
||||
__be32 version;
|
||||
struct fwdb_country country[];
|
||||
} __packed __aligned(4);
|
||||
|
||||
|
||||
enum fwdb_flags {
|
||||
FWDB_FLAG_NO_OFDM = BIT(0),
|
||||
FWDB_FLAG_NO_OUTDOOR = BIT(1),
|
||||
FWDB_FLAG_DFS = BIT(2),
|
||||
FWDB_FLAG_NO_IR = BIT(3),
|
||||
FWDB_FLAG_AUTO_BW = BIT(4),
|
||||
};
|
||||
|
||||
struct fwdb_wmm_ac {
|
||||
u8 ecw;
|
||||
u8 aifsn;
|
||||
__be16 cot;
|
||||
} __packed;
|
||||
|
||||
struct fwdb_wmm_rule {
|
||||
struct fwdb_wmm_ac client[IEEE80211_NUM_ACS];
|
||||
struct fwdb_wmm_ac ap[IEEE80211_NUM_ACS];
|
||||
} __packed;
|
||||
|
||||
struct fwdb_rule {
|
||||
u8 len;
|
||||
u8 flags;
|
||||
__be16 max_eirp;
|
||||
__be32 start, end, max_bw;
|
||||
/* start of optional data */
|
||||
__be16 cac_timeout;
|
||||
__be16 wmm_ptr;
|
||||
} __packed __aligned(4);
|
||||
|
||||
#define FWDB_MAGIC 0x52474442
|
||||
#define FWDB_VERSION 20
|
||||
|
||||
|
||||
static int ecw2cw(int ecw)
|
||||
{
|
||||
return (1 << ecw) - 1;
|
||||
}
|
||||
|
||||
static bool valid_wmm(struct fwdb_wmm_rule *rule)
|
||||
{
|
||||
struct fwdb_wmm_ac *ac = (struct fwdb_wmm_ac *)rule;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < IEEE80211_NUM_ACS * 2; i++) {
|
||||
u16 cw_min = ecw2cw((ac[i].ecw & 0xf0) >> 4);
|
||||
u16 cw_max = ecw2cw(ac[i].ecw & 0x0f);
|
||||
u8 aifsn = ac[i].aifsn;
|
||||
|
||||
if (cw_min >= cw_max)
|
||||
return false;
|
||||
|
||||
if (aifsn < 1)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool valid_rule(const u8 *data, unsigned int size, u16 rule_ptr)
|
||||
{
|
||||
struct fwdb_rule *rule = (fwdb_rule *)(data + (rule_ptr << 2));
|
||||
|
||||
if ((u8 *)rule + sizeof(rule->len) > data + size)
|
||||
return false;
|
||||
|
||||
/* mandatory fields */
|
||||
if (rule->len < offsetofend(struct fwdb_rule, max_bw))
|
||||
return false;
|
||||
if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr)) {
|
||||
u32 wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2;
|
||||
struct fwdb_wmm_rule *wmm;
|
||||
|
||||
if (wmm_ptr + sizeof(struct fwdb_wmm_rule) > size)
|
||||
return false;
|
||||
|
||||
wmm = (fwdb_wmm_rule *)(data + wmm_ptr);
|
||||
|
||||
if (!valid_wmm(wmm))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool valid_country(const u8 *data, unsigned int size,
|
||||
const struct fwdb_country *country)
|
||||
{
|
||||
unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
|
||||
struct fwdb_collection *coll = (struct fwdb_collection *)(data + ptr);
|
||||
__be16 *rules_ptr;
|
||||
unsigned int i;
|
||||
|
||||
/* make sure we can read len/n_rules */
|
||||
if ((u8 *)coll + offsetofend(typeof(*coll), n_rules) > data + size)
|
||||
return false;
|
||||
|
||||
/* make sure base struct and all rules fit */
|
||||
if ((u8 *)coll + ALIGN(coll->len, 2) +
|
||||
(coll->n_rules * 2) > data + size)
|
||||
return false;
|
||||
|
||||
/* mandatory fields must exist */
|
||||
if (coll->len < offsetofend(struct fwdb_collection, dfs_region))
|
||||
return false;
|
||||
|
||||
rules_ptr = (__be16 *)((u8 *)coll + ALIGN(coll->len, 2));
|
||||
|
||||
for (i = 0; i < coll->n_rules; i++) {
|
||||
u16 rule_ptr = be16_to_cpu(rules_ptr[i]);
|
||||
|
||||
if (!valid_rule(data, size, rule_ptr))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool valid_regdb(const u8 *data, unsigned int size)
|
||||
{
|
||||
const struct fwdb_header *hdr = (fwdb_header *)data;
|
||||
const struct fwdb_country *country;
|
||||
|
||||
if (size < sizeof(*hdr))
|
||||
return false;
|
||||
|
||||
if (hdr->magic != cpu_to_be32(FWDB_MAGIC))
|
||||
return false;
|
||||
|
||||
if (hdr->version != cpu_to_be32(FWDB_VERSION))
|
||||
return false;
|
||||
|
||||
if (!regdb_has_valid_signature(data, size))
|
||||
return false;
|
||||
|
||||
country = &hdr->country[0];
|
||||
while ((u8 *)(country + 1) <= data + size) {
|
||||
if (!country->coll_ptr)
|
||||
break;
|
||||
if (!valid_country(data, size, country))
|
||||
return false;
|
||||
country++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void set_wmm_rule(const struct fwdb_header *db,
|
||||
const struct fwdb_country *country,
|
||||
const struct fwdb_rule *rule,
|
||||
WifiRule*rrule)
|
||||
{
|
||||
// not implemented.
|
||||
return;
|
||||
}
|
||||
// struct ieee80211_wmm_rule *wmm_rule = &rrule->wmm_rule;
|
||||
// struct fwdb_wmm_rule *wmm;
|
||||
// unsigned int i, wmm_ptr;
|
||||
|
||||
// wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2;
|
||||
// wmm = (fwdb_wmm_rule *)((u8 *)db + wmm_ptr);
|
||||
|
||||
// if (!valid_wmm(wmm)) {
|
||||
// std::cout
|
||||
// << "Invalid regulatory WMM rule "
|
||||
// << be32_to_cpu(rule->start)
|
||||
// << "-" << be32_to_cpu(rule->end)
|
||||
// << " in domain " << country->alpha2[0] << country->alpha2[1]
|
||||
// << std::endl;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// for (i = 0; i < IEEE80211_NUM_ACS; i++) {
|
||||
// wmm_rule->client[i].cw_min =
|
||||
// ecw2cw((wmm->client[i].ecw & 0xf0) >> 4);
|
||||
// wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f);
|
||||
// wmm_rule->client[i].aifsn = wmm->client[i].aifsn;
|
||||
// wmm_rule->client[i].cot =
|
||||
// 1000 * be16_to_cpu(wmm->client[i].cot);
|
||||
// wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4);
|
||||
// wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f);
|
||||
// wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn;
|
||||
// wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot);
|
||||
// }
|
||||
|
||||
// rrule->has_wmm = true;
|
||||
// }
|
||||
|
||||
|
||||
static WifiRegulations regdb_load_country(const struct fwdb_header *db,
|
||||
const struct fwdb_country *country)
|
||||
{
|
||||
unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
|
||||
struct fwdb_collection *coll = (fwdb_collection *)((u8 *)db + ptr);
|
||||
unsigned int i;
|
||||
|
||||
WifiRegulations wifiRegulations;
|
||||
|
||||
wifiRegulations.reg_alpha2 = SS(country->alpha2[0] << country->alpha2[1]);
|
||||
wifiRegulations.dfs_region = (DfsRegion)(coll->dfs_region);
|
||||
unsigned int nRules = coll->n_rules;
|
||||
|
||||
for (i = 0; i < nRules; i++) {
|
||||
const __be16 *rules_ptr = (const __be16 *)((u8 *)coll + ALIGN(coll->len, 2));
|
||||
unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2;
|
||||
struct fwdb_rule *rule = (fwdb_rule *)((u8 *)db + rule_ptr);
|
||||
|
||||
WifiRule wifiRule;
|
||||
wifiRule.start_freq_khz = be32_to_cpu(rule->start);
|
||||
wifiRule.end_freq_khz = be32_to_cpu(rule->end);
|
||||
wifiRule.max_bandwidth_khz = be32_to_cpu(rule->max_bw);
|
||||
wifiRule.max_antenna_gain = 0;
|
||||
wifiRule.max_eirp = be16_to_cpu(rule->max_eirp);
|
||||
wifiRule.flags = RegRuleFlags::NONE;
|
||||
|
||||
if (rule->flags & FWDB_FLAG_NO_OFDM)
|
||||
wifiRule.flags |= RegRuleFlags::NO_OFDM;
|
||||
if (rule->flags & FWDB_FLAG_NO_OUTDOOR)
|
||||
wifiRule.flags |= RegRuleFlags::NO_OUTDOOR;
|
||||
if (rule->flags & FWDB_FLAG_DFS)
|
||||
wifiRule.flags |= RegRuleFlags::DFS;
|
||||
if (rule->flags & FWDB_FLAG_NO_IR)
|
||||
wifiRule.flags |= RegRuleFlags::NO_IR;
|
||||
if (rule->flags & FWDB_FLAG_AUTO_BW)
|
||||
wifiRule.flags |= RegRuleFlags::AUTO_BW;
|
||||
|
||||
wifiRule.dfs_cac_ms = 0;
|
||||
|
||||
/* handle optional data */
|
||||
if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout))
|
||||
wifiRule.dfs_cac_ms =
|
||||
1000 * be16_to_cpu(rule->cac_timeout);
|
||||
// if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr))
|
||||
// set_wmm_rule(db, country, rule, &wifiRule);
|
||||
wifiRegulations.rules.push_back(std::move(wifiRule));
|
||||
}
|
||||
|
||||
return wifiRegulations;
|
||||
}
|
||||
|
||||
|
||||
static std::vector<WifiRegulations> load_regdb(u8*pData)
|
||||
{
|
||||
const struct fwdb_header *hdr = (const fwdb_header*)pData;
|
||||
const struct fwdb_country *country;
|
||||
|
||||
ASSERT_RTNL();
|
||||
if (!pData)
|
||||
{
|
||||
throw std::runtime_error("Regdb not loaded.");
|
||||
}
|
||||
std::vector<WifiRegulations> result;
|
||||
|
||||
country = &hdr->country[0];
|
||||
while (country->coll_ptr) {
|
||||
result.push_back(regdb_load_country(hdr, country));
|
||||
country++;
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void RegDb::Load20()
|
||||
{
|
||||
u8*data = (u8*)ptr();
|
||||
unsigned int size = (unsigned int)this->data.size();
|
||||
if (!valid_regdb(data,size))
|
||||
{
|
||||
throw std::runtime_error("Invalid file format.");
|
||||
|
||||
}
|
||||
this->regulations = load_regdb(data);
|
||||
this->isValid = true;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "ServiceConfiguration.hpp"
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <grp.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <tuple>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
using namespace std;
|
||||
using namespace config_serializer;
|
||||
|
||||
|
||||
const char ServiceConfiguration::DEVICEID_FILE_NAME[] = "/etc/pipedal/config/service.conf";
|
||||
|
||||
#define SERIALIZER_ENTRY(MEMBER_NAME) \
|
||||
new ConfigSerializer<ServiceConfiguration,decltype(ServiceConfiguration::MEMBER_NAME)>(#MEMBER_NAME, &ServiceConfiguration::MEMBER_NAME, "")
|
||||
|
||||
|
||||
static p2p::autoptr_vector<ConfigSerializerBase<ServiceConfiguration>>
|
||||
deviceIdSerializers
|
||||
{
|
||||
SERIALIZER_ENTRY(uuid),
|
||||
SERIALIZER_ENTRY(deviceName),
|
||||
SERIALIZER_ENTRY(server_port)
|
||||
};
|
||||
|
||||
|
||||
ServiceConfiguration::ServiceConfiguration()
|
||||
: base(deviceIdSerializers)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ServiceConfiguration::Load()
|
||||
{
|
||||
base::Load(DEVICEID_FILE_NAME,false);
|
||||
}
|
||||
void ServiceConfiguration::Save()
|
||||
{
|
||||
base::Save(DEVICEID_FILE_NAME);
|
||||
{
|
||||
struct group *group_;
|
||||
group_ = getgrnam("pipedal_d");
|
||||
if (group_ == nullptr)
|
||||
{
|
||||
throw logic_error("Group not found: pipedal_d");
|
||||
}
|
||||
std::ignore = chown(DEVICEID_FILE_NAME,-1,group_->gr_gid);
|
||||
std::ignore = chmod(DEVICEID_FILE_NAME, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "SysExec.hpp"
|
||||
#include <sys/wait.h>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <linux/limits.h> // for PATH_MAX
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "ss.hpp"
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wunused-result"
|
||||
|
||||
// find on path, but ONLY /usr/bin and /usr/sbin
|
||||
|
||||
|
||||
static std::filesystem::path findOnSystemPath(const std::string &command)
|
||||
{
|
||||
if (command.length() != 0 && command[0] == '/')
|
||||
{
|
||||
return command;
|
||||
}
|
||||
std::string path = "/usr/bin:/usr/sbin";
|
||||
std::vector<std::string> paths;
|
||||
size_t t = 0;
|
||||
while (t < path.length())
|
||||
{
|
||||
size_t pos = path.find(':', t);
|
||||
if (pos == string::npos)
|
||||
{
|
||||
pos = path.length();
|
||||
}
|
||||
std::string thisPath = path.substr(t, pos - t);
|
||||
std::filesystem::path path = std::filesystem::path(thisPath) / command;
|
||||
if (std::filesystem::exists(path))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
t = pos + 1;
|
||||
}
|
||||
std::stringstream s;
|
||||
s << "'" << command << "' is not installed.";
|
||||
throw std::runtime_error(s.str());
|
||||
}
|
||||
|
||||
int pipedal::silentSysExec(const char *szCommand)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << szCommand << " 2>&1";
|
||||
|
||||
FILE *output = popen(s.str().c_str(), "r");
|
||||
char buffer[512];
|
||||
if (output)
|
||||
{
|
||||
while (!feof(output))
|
||||
{
|
||||
fgets(buffer, sizeof(buffer), output);
|
||||
}
|
||||
return pclose(output);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int pipedal::sysExec(const char *szCommand)
|
||||
{
|
||||
char *args = strdup(szCommand);
|
||||
int argc;
|
||||
std::vector<char *> argv;
|
||||
|
||||
char *p = args;
|
||||
while (*p)
|
||||
{
|
||||
argv.push_back(p);
|
||||
|
||||
while (*p && *p != ' ')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
if (*p)
|
||||
{
|
||||
*p++ = '\0';
|
||||
while (*p && *p == ' ')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
std::filesystem::path execPath = argv[0];
|
||||
if (execPath.is_relative())
|
||||
{
|
||||
execPath = findOnSystemPath(execPath);
|
||||
}
|
||||
if (!std::filesystem::exists(execPath))
|
||||
{
|
||||
free((void *)args);
|
||||
throw std::runtime_error(SS("Path does not exist: " << execPath << "."));
|
||||
}
|
||||
argv[0] = (char *)(execPath.c_str());
|
||||
|
||||
char **rawArgv = &argv[0];
|
||||
int pbPid;
|
||||
int returnValue = 0;
|
||||
|
||||
if ((pbPid = fork()) == 0)
|
||||
{
|
||||
execv(argv[0], rawArgv);
|
||||
exit(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
free((void *)args);
|
||||
waitpid(pbPid, &returnValue, 0);
|
||||
int exitStatus = WEXITSTATUS(returnValue);
|
||||
return exitStatus;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string pipedal::getSelfExePath()
|
||||
{
|
||||
char result[PATH_MAX+1];
|
||||
ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
|
||||
if (count < 0) throw std::runtime_error("Can't find EXE path.");
|
||||
result[count] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
ProcessId pipedal::sysExecAsync(const std::string&command)
|
||||
{
|
||||
char *args = strdup(command.c_str());
|
||||
int argc;
|
||||
std::vector<char *> argv;
|
||||
|
||||
char *p = args;
|
||||
while (*p)
|
||||
{
|
||||
argv.push_back(p);
|
||||
|
||||
while (*p && *p != ' ')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
if (*p)
|
||||
{
|
||||
*p++ = '\0';
|
||||
while (*p && *p == ' ')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
argv.push_back(nullptr);
|
||||
|
||||
std::filesystem::path execPath = argv[0];
|
||||
if (execPath.is_relative())
|
||||
{
|
||||
execPath = findOnSystemPath(execPath);
|
||||
}
|
||||
if (!std::filesystem::exists(execPath))
|
||||
{
|
||||
free((void *)args);
|
||||
throw std::runtime_error(SS("Path does not exist: " << execPath << "."));
|
||||
}
|
||||
argv[0] = (char *)(execPath.c_str());
|
||||
|
||||
char **rawArgv = &argv[0];
|
||||
int pbPid;
|
||||
|
||||
if ((pbPid = fork()) == 0)
|
||||
{
|
||||
// redirect output to /dev/null
|
||||
int null_fd = open("/dev/null",O_WRONLY);
|
||||
if (null_fd == -1)
|
||||
{
|
||||
std::cerr << "Failed to open /dev/null" << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
dup2(null_fd,STDOUT_FILENO);
|
||||
dup2(null_fd,STDERR_FILENO);
|
||||
close(null_fd);
|
||||
return execv(argv[0], rawArgv);
|
||||
}
|
||||
else
|
||||
{
|
||||
free((void *)args);
|
||||
return pbPid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void pipedal::sysExecTerminate(ProcessId pid_,int termTimeoutMs,int killTimeoutMs )
|
||||
{
|
||||
pid_t pid = (pid_t)pid_;
|
||||
if (pid == -1) return;
|
||||
if (kill(pid,SIGTERM) == -1) {
|
||||
throw std::runtime_error("Failed to send SIGTERM");
|
||||
}
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
int status;
|
||||
do {
|
||||
pid_t result;
|
||||
result = waitpid(pid,&status,WNOHANG);
|
||||
if (result > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
} while (
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now()-start).count() < termTimeoutMs
|
||||
);
|
||||
start = std::chrono::steady_clock::now();
|
||||
if (kill(pid,SIGKILL) == -1) // some odd async effect?
|
||||
{
|
||||
return;
|
||||
}
|
||||
do {
|
||||
pid_t result;
|
||||
result = waitpid(pid,&status,WNOHANG);
|
||||
if (result > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
} while (
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now()-start).count() < killTimeoutMs
|
||||
);
|
||||
|
||||
throw std::runtime_error("Failed to terminate process.");
|
||||
|
||||
}
|
||||
int pipedal::sysExecWait(ProcessId pid_)
|
||||
{
|
||||
pid_t pid = (pid_t)pid_;
|
||||
int returnValue;
|
||||
waitpid(pid, &returnValue, 0);
|
||||
int exitStatus = WEXITSTATUS(returnValue);
|
||||
return exitStatus;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "WifiConfigSettings.hpp"
|
||||
#include <stdexcept>
|
||||
#include "ss.hpp"
|
||||
#include "ChannelInfo.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
JSON_MAP_BEGIN(WifiConfigSettings)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,valid)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,wifiWarningGiven)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,rebootRequired)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,enable)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,hotspotName)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,mdnsName)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,hasPassword)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,password)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,countryCode)
|
||||
JSON_MAP_REFERENCE(WifiConfigSettings,channel)
|
||||
JSON_MAP_END()
|
||||
|
||||
bool WifiConfigSettings::ValidateCountryCode(const std::string&value)
|
||||
{
|
||||
if (value.size() < 2 || value.size() > 3) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t pipedal::ChannelToChannelNumber(const std::string&channel)
|
||||
{
|
||||
std::string t = channel;
|
||||
// remove dprecated band specs.
|
||||
if (t.size() > 1 && t[0] == 'a' || t[0] == 'g')
|
||||
{
|
||||
t = t.substr(1);
|
||||
}
|
||||
int32_t channelNumber = 1;
|
||||
std::stringstream ss(t);
|
||||
ss >> channelNumber;
|
||||
return channelNumber;
|
||||
|
||||
}
|
||||
|
||||
static uint32_t ParseChannel(const std::string & channel)
|
||||
{
|
||||
std::string t = channel;
|
||||
// remove dprecated band specs.
|
||||
if (t.size() > 1 && t[0] == 'a' || t[0] == 'g')
|
||||
{
|
||||
t = t.substr(1);
|
||||
}
|
||||
size_t size = t.length();
|
||||
|
||||
unsigned long long lChannel = std::stoull(t,&size);
|
||||
if (size != t.length())
|
||||
{
|
||||
throw invalid_argument("Expecting a number: '" + t + "'.");
|
||||
}
|
||||
return (uint32_t)lChannel;
|
||||
}
|
||||
|
||||
uint32_t pipedal::ChannelToWifiFrequency(const std::string &channel_)
|
||||
{
|
||||
uint32_t channel = ParseChannel(channel_);
|
||||
return ChannelToWifiFrequency(channel);
|
||||
}
|
||||
|
||||
uint32_t pipedal::ChannelToWifiFrequency(uint32_t channel)
|
||||
{
|
||||
if (channel > 1000) // must be a frequency.
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
// 2.4GHz.
|
||||
if (channel >= 1 && channel <= 13)
|
||||
{
|
||||
return 2412 + 5*(channel-1);
|
||||
}
|
||||
if (channel == 14)
|
||||
{
|
||||
return 2484;
|
||||
}
|
||||
// 802.11y
|
||||
if (channel >= 131 && channel < 137)
|
||||
{
|
||||
return 3660 + (channel-131)*5;
|
||||
}
|
||||
if (channel >= 32 && channel <= 68 && (channel & 1) == 0)
|
||||
{
|
||||
return 5160 + (channel-32)/2*10;
|
||||
}
|
||||
if (channel == 96) return 5480;
|
||||
|
||||
if (channel >= 100 && channel <= 196)
|
||||
{
|
||||
return 5500 + (channel-100)/5;
|
||||
}
|
||||
throw invalid_argument(SS("Invalid WiFi channel: " << channel));
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool WifiConfigSettings::ValidateChannel(const std::string&countryCode,const std::string&value)
|
||||
{
|
||||
if (value == "0") // = "Autoselect".
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// 1) unadorned channel number 1, 2,3 &c.
|
||||
// 2) With band annotated: g1, a51.
|
||||
if (countryCode.empty())
|
||||
{
|
||||
throw std::invalid_argument("Please supply a country code.");
|
||||
}
|
||||
if (countryCode.length() != 2)
|
||||
{
|
||||
throw std::invalid_argument(SS("Invalid country code: " << countryCode));
|
||||
}
|
||||
auto regDom = getWifiRegClass(countryCode,ParseChannel(value),40);
|
||||
if (regDom == -1) {
|
||||
std::vector<int32_t> valid_channels = getValidChannels(countryCode,40);
|
||||
std::stringstream ss;
|
||||
ss << "Channel " << value << " is not permitted in the selected country.\n Valid channels: ";
|
||||
bool first = true;
|
||||
for (auto channel: valid_channels)
|
||||
{
|
||||
if (!first) ss << ", ";
|
||||
first = false;
|
||||
ss << channel;
|
||||
}
|
||||
throw invalid_argument(ss.str());
|
||||
}
|
||||
|
||||
ChannelToWifiFrequency(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void WifiConfigSettings::ParseArguments(const std::vector<std::string> &argv)
|
||||
{
|
||||
this->valid_ = false;
|
||||
if (argv.size() != 4) {
|
||||
throw invalid_argument("Invalid number of arguments.");
|
||||
}
|
||||
this->enable_ = true;
|
||||
this->countryCode_ = argv[0];
|
||||
this->hotspotName_ = argv[1];
|
||||
this->mdnsName_ = this->hotspotName_;
|
||||
this->password_ = argv[2];
|
||||
this->channel_ = argv[3];
|
||||
this->hasPassword_ = this->password_.length() != 0;
|
||||
|
||||
if (!ValidateCountryCode(this->countryCode_))
|
||||
{
|
||||
throw invalid_argument("Invalid country code.");
|
||||
}
|
||||
if (this->hotspotName_.length() > 31) throw invalid_argument("Hotspot name is too long.");
|
||||
if (this->hotspotName_.length() < 1) throw invalid_argument("Hotspot name is too short.");
|
||||
if (this->password_.size() != 0 && this->password_.size() < 8) throw invalid_argument("Passphrase must be at least 8 characters long.");
|
||||
|
||||
if (!ValidateChannel(this->countryCode_,this->channel_))
|
||||
{
|
||||
throw invalid_argument("Channel is not valid.");
|
||||
}
|
||||
|
||||
// validate that the channel number is supported for the given country code.
|
||||
|
||||
|
||||
this->valid_ = true;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// #include "pch.h"
|
||||
#include "WifiDirectConfigSettings.hpp"
|
||||
#include "P2pConfigFiles.hpp"
|
||||
#include "ChannelInfo.hpp"
|
||||
|
||||
#include "WifiConfigSettings.hpp"
|
||||
#include <stdexcept>
|
||||
#include <random>
|
||||
#include "ConfigUtil.hpp"
|
||||
#include "ServiceConfiguration.hpp"
|
||||
#include "WriteTemplateFile.hpp"
|
||||
#include "ss.hpp"
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace std;
|
||||
|
||||
JSON_MAP_BEGIN(WifiDirectConfigSettings)
|
||||
JSON_MAP_REFERENCE(WifiDirectConfigSettings,valid)
|
||||
JSON_MAP_REFERENCE(WifiDirectConfigSettings,rebootRequired)
|
||||
JSON_MAP_REFERENCE(WifiDirectConfigSettings,enable)
|
||||
JSON_MAP_REFERENCE(WifiDirectConfigSettings,hotspotName)
|
||||
JSON_MAP_REFERENCE(WifiDirectConfigSettings,pinChanged)
|
||||
JSON_MAP_REFERENCE(WifiDirectConfigSettings,pin)
|
||||
JSON_MAP_REFERENCE(WifiDirectConfigSettings,countryCode)
|
||||
JSON_MAP_REFERENCE(WifiDirectConfigSettings,channel)
|
||||
JSON_MAP_END()
|
||||
|
||||
static std::string MakePin()
|
||||
{
|
||||
std::random_device rand_dev;
|
||||
|
||||
std::mt19937 gen(rand_dev());
|
||||
std::uniform_int_distribution<int> dist { 0,9};
|
||||
|
||||
std::stringstream s;
|
||||
for (size_t i = 0; i < 8; ++i)
|
||||
{
|
||||
int r = dist(gen);
|
||||
s << (char)('0' + r);
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
static void ValidatePin(const std::string&pin)
|
||||
{
|
||||
for (char c : pin)
|
||||
{
|
||||
if (c < '0' || c > '9')
|
||||
{
|
||||
throw invalid_argument("Pin must consist of digits only.");
|
||||
}
|
||||
}
|
||||
if (pin.length() != 8)
|
||||
{
|
||||
throw invalid_argument("Pin must have exctly 8 digits.");
|
||||
}
|
||||
}
|
||||
|
||||
void WifiDirectConfigSettings::ParseArguments(const std::vector<std::string> &arguments)
|
||||
{
|
||||
this->valid_ = false;
|
||||
|
||||
this->enable_ = true;
|
||||
|
||||
Load();
|
||||
|
||||
if (arguments.size() == 0)
|
||||
{
|
||||
this->enable_ = true;
|
||||
} else {
|
||||
if (arguments.size() < 2 || arguments.size() > 4)
|
||||
{
|
||||
throw invalid_argument("Incorrect number of arguments supplied");
|
||||
}
|
||||
this->countryCode_ = arguments[0];
|
||||
this->hotspotName_ = arguments[1];
|
||||
if (arguments.size() >= 3)
|
||||
{
|
||||
this->pin_ = arguments[2];
|
||||
}
|
||||
if (arguments.size() >= 4) {
|
||||
this->channel_ = arguments[3];
|
||||
}
|
||||
}
|
||||
if (this->channel_ == "")
|
||||
{
|
||||
this->channel_ = "1";
|
||||
}
|
||||
|
||||
size_t maxLength = 32-string("DIRECT-XX-").length();
|
||||
if (hotspotName_.size() >= maxLength) { throw invalid_argument("Device name is too long."); }
|
||||
if (hotspotName_.size() < 1) { throw invalid_argument("Device name is too short."); }
|
||||
if (!WifiConfigSettings::ValidateCountryCode(countryCode_))
|
||||
{
|
||||
throw invalid_argument("Invalid country code.");
|
||||
}
|
||||
if (pin_.size() == 0)
|
||||
{
|
||||
pin_ = MakePin();
|
||||
}
|
||||
ValidatePin(pin_); // throws.
|
||||
|
||||
// validate that the channel number is supported for the given country code.
|
||||
|
||||
if (!WifiConfigSettings::ValidateChannel(this->countryCode_,channel_))
|
||||
{
|
||||
throw invalid_argument(SS("Invalid WiFi channel: " << channel_));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WifiDirectConfigSettings::Save() const
|
||||
{
|
||||
// ******************* pipedal_p2pd ******
|
||||
{
|
||||
// ${COUNTRY_CODE}
|
||||
//${PIN}
|
||||
// ${DEVICE_NAME}
|
||||
// ${WIFI_GROUP_FREQUENCY}
|
||||
// ${INSTANCE_ID_FILE} /var/pipedal/instance_id
|
||||
|
||||
std::map<string, string> map;
|
||||
map["COUNTRY_CODE"] = this->countryCode_;
|
||||
map["PIN"] = this->pin_;
|
||||
map["DEVICE_NAME"] = ConfigUtil::QuoteString(this->hotspotName_);
|
||||
map["WIFI_CHANNEL"] = SS(this->channel_);
|
||||
map["ENABLED"] = this->enable_ ? "true": "false";
|
||||
map["WIFI_GROUP_FREQUENCY"] = SS(ChannelToWifiFrequency(this->channel_));
|
||||
map["INSTANCE_ID_FILE"] = DEVICE_GUID_FILE;
|
||||
map["WLAN"] = this->wlan_;
|
||||
|
||||
|
||||
WriteTemplateFile(map, PIPEDAL_P2PD_CONF_PATH);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
void WifiDirectConfigSettings::Load()
|
||||
{
|
||||
this->enable_ = false;
|
||||
std::string strEnable;
|
||||
if (ConfigUtil::GetConfigLine("/etc/pipedal/config/pipedal_p2pd.conf","enabled",&strEnable))
|
||||
{
|
||||
this->enable_ = (strEnable == "true" || strEnable == "1");
|
||||
}
|
||||
std::string strWlan;
|
||||
if (ConfigUtil::GetConfigLine("/etc/pipedal/config/pipedal_p2pd.conf","wlan",&strWlan))
|
||||
{
|
||||
this->wlan_ = strWlan;
|
||||
} else {
|
||||
this->wlan_ = "wlan0";
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!ConfigUtil::GetConfigLine("/etc/pipedal/config/pipedal_p2pd.conf","p2p_device_name",&this->hotspotName_))
|
||||
{
|
||||
this->hotspotName_ = "PiPedal";
|
||||
}
|
||||
|
||||
if (!ConfigUtil::GetConfigLine("/etc/pipedal/config/pipedal_p2pd.conf","country_code",&this->countryCode_))
|
||||
{
|
||||
this->countryCode_ = "US";
|
||||
}
|
||||
if (!ConfigUtil::GetConfigLine("/etc/pipedal/config/pipedal_p2pd.conf","p2p_pin",&this->pin_))
|
||||
{
|
||||
this->pin_ = "12345678";
|
||||
}
|
||||
if (!ConfigUtil::GetConfigLine("/etc/pipedal/config/pipedal_p2pd.conf","wifiChannel",&this->channel_))
|
||||
{
|
||||
this->channel_ = "6";
|
||||
}
|
||||
try {
|
||||
ServiceConfiguration deviceIdFile;
|
||||
deviceIdFile.Load();
|
||||
if (deviceIdFile.deviceName == "")
|
||||
{
|
||||
|
||||
}
|
||||
this->hotspotName_ = deviceIdFile.deviceName;
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
}
|
||||
this->valid_ = true;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2024 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "WifiRegs.hpp"
|
||||
#include "RegDb.hpp"
|
||||
#include <memory>
|
||||
#include "ss.hpp"
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "ChannelInfo.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
static std::unique_ptr<RegDb> g_regDb;
|
||||
|
||||
static std::unique_ptr<WifiInfo> g_wifiInfo;
|
||||
|
||||
|
||||
struct RegMHzToChannel {
|
||||
char band;
|
||||
int32_t channel;
|
||||
int32_t mhz;
|
||||
};
|
||||
|
||||
static std::vector<RegMHzToChannel> mhzToChannels = {
|
||||
{'g', 1, 2412},
|
||||
{'g',2, 2417},
|
||||
{'g',3, 2422},
|
||||
{'g',4, 2427},
|
||||
{'g',5, 2432},
|
||||
{'g',6, 2437},
|
||||
{'g',7, 2442},
|
||||
{'g',8, 2447},
|
||||
{'g',9, 2452},
|
||||
{'g',10, 2457},
|
||||
{'g',11, 2462},
|
||||
{'g',12, 2467},
|
||||
{'g',13, 2472},
|
||||
{'g',14, 2484},
|
||||
{'a',7, 5035},
|
||||
{'a',8, 5040},
|
||||
{'a',9, 5045},
|
||||
{'a',11, 5055},
|
||||
{'a',12, 5060},
|
||||
{'a',16, 5080},
|
||||
{'a',32, 5160},
|
||||
{'a',34, 5170},
|
||||
{'a',36, 5180},
|
||||
{'a',38, 5190},
|
||||
{'a',40, 5200},
|
||||
{'a',42, 5210},
|
||||
{'a',44, 5220},
|
||||
{'a',46, 5230},
|
||||
{'a',48, 5240},
|
||||
{'a',50, 5250},
|
||||
{'a',52, 5260},
|
||||
{'a',54, 5270},
|
||||
{'a',56, 5280},
|
||||
{'a',58, 5290},
|
||||
{'a',60, 5300},
|
||||
{'a',62, 5310},
|
||||
{'a',64, 5320},
|
||||
{'a',68, 5340},
|
||||
{'a',96, 5480},
|
||||
{'a',100, 5500},
|
||||
{'a',102, 5510},
|
||||
{'a',104, 5520},
|
||||
{'a',106, 5530},
|
||||
{'a',108, 5540},
|
||||
{'a',110, 5550},
|
||||
{'a',112, 5560},
|
||||
{'a',114, 5570},
|
||||
{'a',116, 5580},
|
||||
{'a',118, 5590},
|
||||
{'a',120, 5600},
|
||||
{'a',122, 5610},
|
||||
{'a',124, 5620},
|
||||
{'a',126, 5630},
|
||||
{'a',128, 5640},
|
||||
{'a',132, 5660},
|
||||
{'a',134, 5670},
|
||||
{'a',136, 5680},
|
||||
{'a',138, 5690},
|
||||
{'a',140, 5700},
|
||||
{'a',142, 5710},
|
||||
{'a',144, 5720},
|
||||
{'a',149, 5745},
|
||||
{'a',151, 5755},
|
||||
{'a',153, 5765},
|
||||
{'a',155, 5775},
|
||||
{'a',157, 5785},
|
||||
{'a',159, 5795},
|
||||
{'a',161, 5805},
|
||||
{'a',163, 5815},
|
||||
{'a',165, 5825},
|
||||
{'a',167, 5835},
|
||||
{'a',169, 5845},
|
||||
{'a',171, 5855},
|
||||
{'a',173, 5865},
|
||||
{'a',175, 5875},
|
||||
{'a',177, 5885},
|
||||
{'a',180, 5900},
|
||||
{'a',182, 5910},
|
||||
{'a',18, 5915},
|
||||
{'a',184, 5920},
|
||||
{'a',187, 5935},
|
||||
{'a',188, 5940},
|
||||
{'a',189, 5945},
|
||||
{'a',192, 5960},
|
||||
{'a',196, 5980},
|
||||
};
|
||||
|
||||
|
||||
int32_t pipedal::wifiFrequencyToChannel(int32_t freqMHz)
|
||||
{
|
||||
for (const auto&item: mhzToChannels)
|
||||
{
|
||||
if (std::abs(freqMHz == item.mhz) <= 1)
|
||||
{
|
||||
return item.channel;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t pipedal::wifiChannelToFrequency(int32_t channel)
|
||||
{
|
||||
for (const auto&item: mhzToChannels)
|
||||
{
|
||||
if (item.channel == channel)
|
||||
{
|
||||
return item.mhz;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "WifiRegulations.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
const WifiRule*WifiRegulations::GetRule(int32_t frequencyMhz) const
|
||||
{
|
||||
int32_t frequencyKhz = frequencyMhz*1000;
|
||||
|
||||
for (const auto&rule: rules) {
|
||||
if (frequencyKhz >= rule.start_freq_khz && frequencyKhz < rule.end_freq_khz)
|
||||
{
|
||||
return &rule;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
#include "WriteTemplateFile.hpp"
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
void pipedal::WriteTemplateFile(
|
||||
const std::map<std::string, std::string> &map,
|
||||
const std::filesystem::path &outputFile)
|
||||
{
|
||||
std::string inputFile = "/etc/pipedal/config/templates/" + outputFile.filename().string() + ".template";
|
||||
WriteTemplateFile(map, inputFile,outputFile);
|
||||
}
|
||||
|
||||
void pipedal::WriteTemplateFile(
|
||||
const std::map<std::string, std::string> &map,
|
||||
const std::filesystem::path &inputFile,
|
||||
const std::filesystem::path &outputFile)
|
||||
{
|
||||
std::ofstream out(outputFile);
|
||||
std::ifstream in(inputFile);
|
||||
|
||||
if (!in.is_open())
|
||||
{
|
||||
throw std::runtime_error("File not found: " + inputFile.string());
|
||||
}
|
||||
|
||||
while (in.peek() != -1)
|
||||
{
|
||||
char c = in.get();
|
||||
if (c == '$')
|
||||
{
|
||||
if (in.peek() == '{')
|
||||
{
|
||||
in.get();
|
||||
|
||||
std::stringstream sName;
|
||||
try
|
||||
{
|
||||
while ((c = in.get()) != '}')
|
||||
{
|
||||
sName.put(c);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
throw std::runtime_error("Unexpected end of file. Expecting '}'.");
|
||||
}
|
||||
std::string s = map.at(sName.str());
|
||||
out << s;
|
||||
}
|
||||
else if (in.peek() == '$')
|
||||
{
|
||||
out.put(in.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
out.put(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
out.put(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "WifiRegulations.hpp"
|
||||
struct nl_sock;
|
||||
struct nl_cache;
|
||||
struct genl_family;
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
WifiInfo getWifiInfo(const std::string&countryIso3661);
|
||||
int32_t getWifiRegClass(const std::string &countryIso3661, int32_t channel,int32_t maxChannelWidthMhz);
|
||||
std::vector<int32_t> getValidChannels(const std::string&countryIso3661,int32_t maxChannelWidthMhz);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include "autoptr_vector.h"
|
||||
#include <unordered_map>
|
||||
#include <fstream>
|
||||
|
||||
namespace config_serializer
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
std::string trim(const std::string &v);
|
||||
|
||||
std::vector<std::string> split(const std::string &v, char seperator);
|
||||
|
||||
/**
|
||||
* @brief Convert string to config file format.
|
||||
*
|
||||
* Adds quotes and escapes, but only if neccessary.
|
||||
*
|
||||
* Source text is assumed to be UTF-8. Escapes for the
|
||||
* following values only: \r \n \t \" \\.
|
||||
*
|
||||
*
|
||||
* @param s
|
||||
* @return std::string Encoded string.
|
||||
*/
|
||||
std::string EncodeString(const std::string &s);
|
||||
|
||||
/**
|
||||
* @brief Config file format to string.
|
||||
*
|
||||
* Decodes quotes and escapes, but only if neccessary.
|
||||
*
|
||||
* Source text is assumed to be UTF-8. Escapes for the
|
||||
* following values only: \r \n \t \" \\.
|
||||
*
|
||||
* @param s
|
||||
* @return std::string
|
||||
*/
|
||||
std::string DecodeString(const std::string &s);
|
||||
|
||||
/**
|
||||
* @brief Convert a string to an integer.
|
||||
*
|
||||
* @tparam T A signed or unsigned integral type.
|
||||
* @param value The string to parse.
|
||||
* @return T The parsed result.
|
||||
* @throws std::invalid_argument if the supplied string is not a valid integer.
|
||||
*/
|
||||
template <class T>
|
||||
T ToInt(const std::string &value)
|
||||
{
|
||||
std::stringstream s(value);
|
||||
T result;
|
||||
s >> result;
|
||||
if (s.fail())
|
||||
{
|
||||
throw std::invalid_argument("Invalid integer value.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using namespace detail;
|
||||
|
||||
template <class OBJ>
|
||||
class ConfigSerializerBase
|
||||
{
|
||||
protected:
|
||||
ConfigSerializerBase(const std::string &name, const std::string &comment)
|
||||
: name(name),
|
||||
comment(comment)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
using Obj = OBJ;
|
||||
virtual ~ConfigSerializerBase() {}
|
||||
|
||||
const std::string &GetName() const { return name; }
|
||||
const std::string &GetComment() const { return comment; }
|
||||
virtual void SetValue(Obj &configurable, const std::string &value) const = 0;
|
||||
virtual const std::string GetValue(Obj &configurable) const = 0;
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::string comment;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class ConfigConverter
|
||||
{
|
||||
public:
|
||||
static T FromString(const std::string &value)
|
||||
{
|
||||
T result;
|
||||
std::stringstream s(value);
|
||||
s >> result;
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string ToString(const T &value)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << value;
|
||||
return s.str();
|
||||
}
|
||||
};
|
||||
|
||||
// string specializations.
|
||||
template <>
|
||||
inline std::string ConfigConverter<std::string>::FromString(const std::string &value)
|
||||
{
|
||||
return DecodeString(value);
|
||||
}
|
||||
template <>
|
||||
inline std::string ConfigConverter<std::string>::ToString(const std::string &config)
|
||||
{
|
||||
return EncodeString(config);
|
||||
}
|
||||
// bool specializations.
|
||||
template <>
|
||||
inline bool ConfigConverter<bool>::FromString(const std::string &value)
|
||||
{
|
||||
bool result = false;
|
||||
if (value == "")
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
else if (value == "true")
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else if (value == "false")
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
int t = ToInt<int>(value);
|
||||
result = (t != 0);
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
throw std::invalid_argument("Expecting true or false.");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
template <>
|
||||
inline std::string ConfigConverter<bool>::ToString(const bool &value)
|
||||
{
|
||||
return value ? "true" : "false";
|
||||
}
|
||||
|
||||
template <class OBJ, class T>
|
||||
class ConfigSerializer : public ConfigSerializerBase<OBJ>
|
||||
{
|
||||
public:
|
||||
using Obj = OBJ;
|
||||
using pointer_type = T Obj::*;
|
||||
using base = ConfigSerializerBase<Obj>;
|
||||
using converter = ConfigConverter<T>;
|
||||
|
||||
ConfigSerializer(const std::string &name,
|
||||
pointer_type pMember,
|
||||
const std::string &comment)
|
||||
: base(name, comment),
|
||||
pMember(pMember)
|
||||
{
|
||||
}
|
||||
virtual void SetValue(Obj &configurable, const std::string &value) const
|
||||
{
|
||||
configurable.*pMember = ConfigConverter<T>::FromString(value);
|
||||
}
|
||||
virtual const std::string GetValue(Obj &configurable) const
|
||||
{
|
||||
return ConfigConverter<T>::ToString(configurable.*pMember);
|
||||
}
|
||||
|
||||
private:
|
||||
pointer_type pMember;
|
||||
};
|
||||
|
||||
template <class OBJ>
|
||||
class ConfigSerializable
|
||||
{
|
||||
private:
|
||||
using Obj = OBJ;
|
||||
|
||||
using serializer_t = ConfigSerializerBase<OBJ>;
|
||||
using serializers_t = p2p::autoptr_vector<serializer_t>;
|
||||
|
||||
const serializers_t &serializers;
|
||||
|
||||
protected:
|
||||
ConfigSerializable(const serializers_t &serializers)
|
||||
: serializers(serializers)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
virtual void Save(std::ostream &f)
|
||||
{
|
||||
bool firstLine = true;
|
||||
for (const serializer_t *serializer : serializers)
|
||||
{
|
||||
if (serializer->GetComment().length() != 0)
|
||||
{
|
||||
if (!firstLine)
|
||||
{
|
||||
f << std::endl;
|
||||
}
|
||||
firstLine = false;
|
||||
std::vector<std::string> comments = split(serializer->GetComment(), '\n');
|
||||
|
||||
if (comments.size() != 0)
|
||||
for (const std::string &comment : comments)
|
||||
{
|
||||
f << "# " << comment << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
f << serializer->GetName() << '=' << serializer->GetValue((Obj &)*this) << std::endl;
|
||||
}
|
||||
}
|
||||
void Save(const std::string &path)
|
||||
{
|
||||
std::ofstream f;
|
||||
f.open(path);
|
||||
if (!f.is_open())
|
||||
{
|
||||
throw std::invalid_argument("Can't write to file " + path);
|
||||
}
|
||||
try
|
||||
{
|
||||
Save(f);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
throw std::invalid_argument("Error writing to '" + path + "'. " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Load(std::istream &f)
|
||||
{
|
||||
std::unordered_map<std::string, serializer_t *> index;
|
||||
|
||||
for (serializer_t *serializer : serializers)
|
||||
{
|
||||
index[serializer->GetName()] = serializer;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
int nLine = 0;
|
||||
while (true)
|
||||
{
|
||||
if (f.eof())
|
||||
break;
|
||||
getline(f, line);
|
||||
++nLine;
|
||||
|
||||
// remove comments.
|
||||
auto npos = line.find_first_of('#');
|
||||
if (npos != std::string::npos)
|
||||
{
|
||||
line = line.substr(0, npos);
|
||||
}
|
||||
size_t start = 0;
|
||||
while (start < line.size() && line[start] == ' ')
|
||||
{
|
||||
++start;
|
||||
}
|
||||
if (start == line.size())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pos = line.find_first_of('=', start);
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
std::stringstream msg;
|
||||
|
||||
msg << "(" << nLine << "," << (start + 1) << "): Syntax error. Expecting '='.";
|
||||
throw std::logic_error(msg.str());
|
||||
}
|
||||
std::string label = trim(line.substr(start, pos - start));
|
||||
std::string value = trim(line.substr(pos + 1));
|
||||
|
||||
if (index.contains(label))
|
||||
{
|
||||
serializer_t *serializer = index[label];
|
||||
try
|
||||
{
|
||||
serializer->SetValue((Obj &)*this, value);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::stringstream msg;
|
||||
msg << "(" << nLine << ',' << (pos + 1) << "): " << e.what();
|
||||
throw std::logic_error(msg.str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::stringstream msg;
|
||||
msg << "(" << nLine << ',' << (start) << "): "
|
||||
<< "Invalid property: " + label;
|
||||
throw std::logic_error(
|
||||
msg.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Load(const std::string &path, bool throwIfNotFound = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::ifstream f;
|
||||
f.open(path);
|
||||
if (!f.is_open())
|
||||
{
|
||||
if (throwIfNotFound)
|
||||
{
|
||||
throw std::logic_error("Can't open file.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
Load(f);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
std::stringstream msg;
|
||||
msg << "Error: " << path << " " << e.what();
|
||||
throw std::logic_error(msg.str());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pipedal {
|
||||
class ConfigUtil {
|
||||
public:
|
||||
static bool GetConfigLine(const std::string & filePath,const std::string & key, std::string *pValue);
|
||||
|
||||
static std::string QuoteString(const std::string &value);
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#include <type_traits>
|
||||
|
||||
#define ENABLE_ENUM_FLAGS(enum_type)\
|
||||
template<> struct enum_traits<enum_type> :\
|
||||
enum_traits<>::allow_bitops {};\
|
||||
|
||||
|
||||
template<class T = void> struct enum_traits {};
|
||||
|
||||
template<> struct enum_traits<void> {
|
||||
struct _allow_bitops {
|
||||
static constexpr bool allow_bitops = true;
|
||||
};
|
||||
using allow_bitops = _allow_bitops;
|
||||
|
||||
template<class T, class R = T>
|
||||
using t = typename std::enable_if<std::is_enum<T>::value and
|
||||
enum_traits<T>::allow_bitops, R>::type;
|
||||
|
||||
template<class T>
|
||||
using u = typename std::underlying_type<T>::type;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
constexpr enum_traits<>::t<T> operator~(T a) {
|
||||
return static_cast<T>(~static_cast<enum_traits<>::u<T>>(a));
|
||||
}
|
||||
template<class T>
|
||||
constexpr enum_traits<>::t<T> operator|(T a, T b) {
|
||||
return static_cast<T>(
|
||||
static_cast<enum_traits<>::u<T>>(a) |
|
||||
static_cast<enum_traits<>::u<T>>(b));
|
||||
}
|
||||
template<class T>
|
||||
constexpr enum_traits<>::t<T> operator&(T a, T b) {
|
||||
return static_cast<T>(
|
||||
static_cast<enum_traits<>::u<T>>(a) &
|
||||
static_cast<enum_traits<>::u<T>>(b));
|
||||
}
|
||||
template<class T>
|
||||
constexpr enum_traits<>::t<T> operator^(T a, T b) {
|
||||
return static_cast<T>(
|
||||
static_cast<enum_traits<>::u<T>>(a) ^
|
||||
static_cast<enum_traits<>::u<T>>(b));
|
||||
}
|
||||
template<class T>
|
||||
constexpr enum_traits<>::t<T, T&> operator|=(T& a, T b) {
|
||||
a = a | b;
|
||||
return a;
|
||||
}
|
||||
template<class T>
|
||||
constexpr enum_traits<>::t<T, T&> operator&=(T& a, T b) {
|
||||
a = a & b;
|
||||
return a;
|
||||
}
|
||||
template<class T>
|
||||
constexpr enum_traits<>::t<T, T&> operator^=(T& a, T b) {
|
||||
a = a ^ b;
|
||||
return a;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class HtmlHelper {
|
||||
|
||||
public:
|
||||
static std::string timeToHttpDate();
|
||||
static std::string timeToHttpDate(time_t time);
|
||||
|
||||
|
||||
static std::string encode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment = false);
|
||||
static void encode_url_segment(std::ostream&os, const char*pStart, const char *pEnd, bool isQuerySegment = false);
|
||||
static void encode_url_segment(std::ostream&os, const std::string&segment, bool isQuerySegment = false)
|
||||
{
|
||||
const char*p = segment.c_str();
|
||||
encode_url_segment(os,p, p + segment.length(),isQuerySegment);
|
||||
}
|
||||
|
||||
|
||||
static std::string decode_url_segment(const char*pStart, const char*pEnd, bool isQuerySegment = false);
|
||||
|
||||
static std::string decode_url_segment(const char*text, bool isQuerySegment = false);
|
||||
|
||||
static void utf32_to_utf8_stream(std::ostream &s, uint32_t uc);
|
||||
|
||||
static std::string Rfc5987EncodeFileName(const std::string&name);
|
||||
|
||||
static std::string SafeFileName(const std::string &name);
|
||||
static std::string HtmlEncode(const std::string& text);
|
||||
|
||||
};
|
||||
|
||||
} // namespace.
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright (c) 2007, 2008 Johannes Berg
|
||||
Copyright (c) 2007 Andy Lutomirski
|
||||
Copyright (c) 2007 Mike Kershaw
|
||||
Copyright (c) 2008 Luis R. Rodriguez
|
||||
Copyright (c) 2024 Robin Davies
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
Heavily based on code borrowed from iw (1) command.
|
||||
https://kernel.googlesource.com/pub/scm/linux/kernel/git/jberg/iw/+/v0.9/COPYING
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "WifiRegulations.hpp"
|
||||
struct nl_sock;
|
||||
struct nl_cache;
|
||||
struct genl_family;
|
||||
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
WifiInfo getWifiInfo(const char *phyName, const char *countryIso3661);
|
||||
std::vector<WifiP2PChannelInfo> getWifiP2pInfo(const char *phyName, const char *countryIso3661);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright (c) 2007, 2008 Johannes Berg
|
||||
Copyright (c) 2007 Andy Lutomirski
|
||||
Copyright (c) 2007 Mike Kershaw
|
||||
Copyright (c) 2008 Luis R. Rodriguez
|
||||
Copyright (c) 2024 Robin Davies
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in sourcpe and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
Heavily based on code borrowed from iw (1) command.
|
||||
https://kernel.googlesource.com/pub/scm/linux/kernel/git/jberg/iw/+/v0.9/COPYING
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "WifiRegulations.hpp"
|
||||
struct nl_sock;
|
||||
struct nl_cache;
|
||||
struct genl_family;
|
||||
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
WifiInfo getWifiInfo(const char *phyName, const char *countryIso3661);
|
||||
std::vector<WifiP2PChannelInfo> getWifiP2pInfo(const char *phyName, const char *countryIso3661);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#define DEVICE_GUID_FILE "/etc/pipedal/config/device_uuid"
|
||||
|
||||
#define PIPEDAL_P2PD_CONF_PATH "/etc/pipedal/config/pipedal_p2pd.conf"
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "WifiRegulations.hpp"
|
||||
#include <filesystem>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
|
||||
|
||||
// struct FrequencyRule
|
||||
// {
|
||||
// uint32_t minFrequencyKHz;
|
||||
// uint32_t maxFrequencyKhz;
|
||||
// uint32_t maximumAntennaGain; // in mBiu (100*dBi)
|
||||
// uint32_t maximumEirp; // in mBm (100*dBm)
|
||||
// RegRuleFlags flags;
|
||||
|
||||
// bool hasFlag(RegRuleFlags flag) const { return (((int)flag) & (int)this->flags) != 0; }
|
||||
// };
|
||||
|
||||
|
||||
class RegDb
|
||||
{
|
||||
private:
|
||||
friend class CrdbRules;
|
||||
uint8_t *pData;
|
||||
|
||||
public:
|
||||
static RegDb&GetInstance();
|
||||
|
||||
RegDb();
|
||||
RegDb(const std::filesystem::path&path); // for testing only.
|
||||
~RegDb();
|
||||
|
||||
const std::vector<WifiRegulations>&Regulations() const { return regulations;}
|
||||
|
||||
bool IsValid() const { return isValid; }
|
||||
|
||||
const WifiRegulations&getWifiRegulations(const std::string&countryIso3661) const;
|
||||
|
||||
private:
|
||||
bool isValid = false;
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
void *ptr() { return (void *)&(data[0]); }
|
||||
void Load19();
|
||||
void Load20();
|
||||
|
||||
std::vector<WifiRegulations> regulations;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "ConfigSerializer.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
using namespace config_serializer;
|
||||
|
||||
class ServiceConfiguration: protected ConfigSerializable<ServiceConfiguration> {
|
||||
public:
|
||||
using base = ConfigSerializable<ServiceConfiguration>;
|
||||
|
||||
ServiceConfiguration();
|
||||
|
||||
static const char DEVICEID_FILE_NAME[];
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
|
||||
std::string uuid;
|
||||
std::string deviceName = "PiPedal";
|
||||
uint32_t server_port = 80;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace pipedal {
|
||||
// exec a command, returning the actual exit code (unlike execXX() or system() )
|
||||
int sysExec(const char*szCommand);
|
||||
|
||||
// execute a command, suppressing output.
|
||||
int silentSysExec(const char *szCommand);
|
||||
|
||||
|
||||
using ProcessId = int64_t; // platform-agnostic wrapper for pid_t;
|
||||
// Returns a pid or -1 on errror.
|
||||
ProcessId sysExecAsync(const std::string&command);
|
||||
|
||||
void sysExecTerminate(ProcessId pid_,int termTimeoutMs = 1000,int killTimeoutMs = 500 ); // returns the process's exit status.
|
||||
int sysExecWait(ProcessId pid);
|
||||
|
||||
std::string getSelfExePath();
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
|
||||
uint32_t ChannelToWifiFrequency(const std::string &channel);
|
||||
uint32_t ChannelToWifiFrequency(uint32_t channel);
|
||||
int32_t ChannelToChannelNumber(const std::string&channel);
|
||||
|
||||
class WifiConfigSettings {
|
||||
public:
|
||||
bool valid_ = false;
|
||||
bool wifiWarningGiven_ = false;
|
||||
bool rebootRequired_ = false;
|
||||
bool enable_ = false;
|
||||
std::string countryCode_ = "US"; // iso 3661
|
||||
std::string hotspotName_ = "pipedal";
|
||||
std::string mdnsName_ = "pipedal";
|
||||
bool hasPassword_ = false;
|
||||
std::string password_;
|
||||
std::string channel_ = "g6";
|
||||
|
||||
void ParseArguments(const std::vector<std::string> &arguments);
|
||||
static bool ValidateCountryCode(const std::string&value);
|
||||
static bool ValidateChannel(const std::string&countryCode,const std::string&value);
|
||||
public:
|
||||
DECLARE_JSON_MAP(WifiConfigSettings);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
class WifiDirectConfigSettings {
|
||||
public:
|
||||
bool valid_ = false;
|
||||
bool rebootRequired_ = false;
|
||||
bool enable_ = false;
|
||||
std::string countryCode_ = ""; // iso 3661
|
||||
std::string hotspotName_ = "pipedal";
|
||||
bool pinChanged_ = false;
|
||||
std::string pin_;
|
||||
std::string channel_ = "0"; // "0" -> select automatically.
|
||||
std::string wlan_ = "wlan0";
|
||||
|
||||
void ParseArguments(const std::vector<std::string> &arguments);
|
||||
void Save() const;
|
||||
void Load();
|
||||
|
||||
public:
|
||||
DECLARE_JSON_MAP(WifiDirectConfigSettings);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2024 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace pipedal {
|
||||
// returns frequency in MHz.
|
||||
int32_t wifiChannelToFrequency(int32_t channel);
|
||||
int32_t wifiFrequencyToChannel(
|
||||
int32_t frequency // frequency in Mhz
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "EnumFlags.hpp"
|
||||
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
enum class SecondaryChannelLocation
|
||||
{
|
||||
None,
|
||||
Above,
|
||||
Below
|
||||
};
|
||||
|
||||
enum class WifiMode
|
||||
{
|
||||
IEEE80211G,
|
||||
IEEE80211A,
|
||||
IEEE80211B,
|
||||
IEEE80211AD,
|
||||
IEEE80211AX,
|
||||
IEEE80211ANY,
|
||||
};
|
||||
|
||||
enum class WifiBandwidth
|
||||
{
|
||||
BW20,
|
||||
BW40MINUS, // g and a.
|
||||
BW40PLUS,
|
||||
BW40, // wifi 6 only. Must come after BW40PLUS, BW40MINUS.
|
||||
BW80,
|
||||
BW160,
|
||||
BW80P80,
|
||||
BW2160,
|
||||
BW4320,
|
||||
BW6480,
|
||||
BW8640
|
||||
};
|
||||
|
||||
enum class DfsRegion
|
||||
{
|
||||
Unset = 0,
|
||||
Fcc = 1,
|
||||
Etsi = 2,
|
||||
Japan = 3
|
||||
};
|
||||
|
||||
// sync with <linux/nl80211.h
|
||||
enum class RegRuleFlags: uint32_t
|
||||
{
|
||||
NONE = 0,
|
||||
NO_OFDM = 1 << 0,
|
||||
NO_CCK = 1 << 1,
|
||||
NO_INDOOR = 1 << 2,
|
||||
NO_OUTDOOR = 1 << 3,
|
||||
DFS = 1 << 4,
|
||||
PTP_ONLY = 1 << 5,
|
||||
PTMP_ONLY = 1 << 6,
|
||||
NO_IR = 1 << 7,
|
||||
__NO_IBSS = 1 << 8,
|
||||
AUTO_BW = 1 << 11,
|
||||
IR_CONCURRENT = 1 << 12,
|
||||
NO_HT40MINUS = 1 << 13,
|
||||
NO_HT40PLUS = 1 << 14,
|
||||
NO_80MHZ = 1 << 15,
|
||||
NO_160MHZ = 1 << 16,
|
||||
NO_HE = 1 << 17,
|
||||
};
|
||||
|
||||
struct WifiRule
|
||||
{
|
||||
RegRuleFlags flags = RegRuleFlags::NONE;
|
||||
|
||||
uint32_t start_freq_khz = 0;
|
||||
uint32_t end_freq_khz = 0;
|
||||
uint32_t max_bandwidth_khz = 0;
|
||||
uint32_t max_antenna_gain = 0;
|
||||
uint32_t max_eirp = 0;
|
||||
uint32_t dfs_cac_ms = 0;
|
||||
uint32_t psd = 0;
|
||||
|
||||
bool HasFlag(RegRuleFlags flag) const {
|
||||
return ((uint32_t)flag & (uint32_t)this->flags) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct WifiRegulations
|
||||
{
|
||||
|
||||
std::string reg_alpha2;
|
||||
DfsRegion dfs_region;
|
||||
std::vector<WifiRule> rules;
|
||||
const WifiRule*GetRule(int32_t frequencyMhz) const;
|
||||
};
|
||||
|
||||
struct WifiChannelInfo
|
||||
{
|
||||
int channelNumber = -1;
|
||||
WifiMode hardwareMode;
|
||||
int mhz = 0;
|
||||
bool disabled = false;
|
||||
bool ir = true;
|
||||
bool radarDetection = false;
|
||||
std::vector<float> bitrates;
|
||||
bool indoorOnly = false;
|
||||
bool outdoorsOnly = false;
|
||||
bool noHt40Minus = false;
|
||||
bool noHt40Plus = false;
|
||||
bool no10MHz = false;
|
||||
bool no20MHz = false;
|
||||
bool no80MHz = false;
|
||||
bool no160MHz = false;
|
||||
int32_t maxAntennaGain = 0;
|
||||
int32_t maxEirp = 0;
|
||||
WifiBandwidth bandwidth;
|
||||
int32_t regDomain = -1;
|
||||
};
|
||||
|
||||
struct WifiP2PChannelInfo : public WifiChannelInfo
|
||||
{
|
||||
};
|
||||
|
||||
struct WifiInfo
|
||||
{
|
||||
std::string reg_alpha2;
|
||||
std::vector<std::string> supportedIfTypes;
|
||||
DfsRegion dfsRegion;
|
||||
std::vector<WifiChannelInfo> channels;
|
||||
|
||||
const WifiChannelInfo *getChannelInfo(int channel) const;
|
||||
};
|
||||
|
||||
}
|
||||
ENABLE_ENUM_FLAGS(pipedal::RegRuleFlags)
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
namespace pipedal {
|
||||
void WriteTemplateFile(
|
||||
const std::map<std::string,std::string> &map,
|
||||
const std::filesystem::path& inputFile,
|
||||
const std::filesystem::path& outputFile);
|
||||
|
||||
/**
|
||||
* @brief Write template file.
|
||||
*
|
||||
* The input file is
|
||||
*
|
||||
* /etc/pipedal/config/template/<filename>.template
|
||||
*
|
||||
* @param map Dictionary of values to substitute.
|
||||
* @param outputFile The full path of the output file.
|
||||
*/
|
||||
void WriteTemplateFile(
|
||||
const std::map<std::string,std::string> &map,
|
||||
const std::filesystem::path &outputFile);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
||||
namespace p2p {
|
||||
|
||||
template <typename T> class autoptr_vector
|
||||
{
|
||||
std::vector<T*> v;
|
||||
private:
|
||||
// no copy no move.
|
||||
autoptr_vector(const autoptr_vector<T> &) = delete;
|
||||
autoptr_vector(autoptr_vector<T> && ) = delete;
|
||||
public:
|
||||
using iterator = std::vector<T*>::const_iterator;
|
||||
|
||||
autoptr_vector() { }
|
||||
autoptr_vector(std::initializer_list<T*> l)
|
||||
:v(l)
|
||||
{
|
||||
|
||||
}
|
||||
~autoptr_vector() {
|
||||
for (T *i: v)
|
||||
{
|
||||
delete i;
|
||||
}
|
||||
}
|
||||
void push_back(T*value) { v.push_back(value);}
|
||||
|
||||
iterator begin() const { return v.begin(); }
|
||||
iterator end() const { return v.end(); }
|
||||
|
||||
T*operator[](size_t index) const { return v[index]; }
|
||||
|
||||
size_t size() const { return v.size(); }
|
||||
};
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <map>
|
||||
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include "json.hpp"
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
class json_null
|
||||
{
|
||||
public:
|
||||
static json_null instance;
|
||||
bool operator==(const json_null &other) const { return true; }
|
||||
bool operator!=(const json_null &other) const { return (!((*this) == other)); }
|
||||
|
||||
private:
|
||||
int value = 0;
|
||||
};
|
||||
|
||||
class json_object;
|
||||
class json_array;
|
||||
|
||||
class json_variant
|
||||
: public JsonSerializable
|
||||
{
|
||||
public:
|
||||
enum class ContentType
|
||||
{
|
||||
Null,
|
||||
Bool,
|
||||
Number,
|
||||
String,
|
||||
Object,
|
||||
Array
|
||||
};
|
||||
using object_ptr = std::shared_ptr<json_object>;
|
||||
using array_ptr = std::shared_ptr<json_array>;
|
||||
|
||||
private:
|
||||
public:
|
||||
~json_variant();
|
||||
json_variant();
|
||||
json_variant(json_variant &&);
|
||||
json_variant(const json_variant &);
|
||||
|
||||
json_variant(json_null value);
|
||||
json_variant(bool value);
|
||||
json_variant(double value);
|
||||
json_variant(const std::string &value);
|
||||
json_variant(std::shared_ptr<json_object> &&value);
|
||||
json_variant(const std::shared_ptr<json_object> &value);
|
||||
json_variant(std::shared_ptr<json_array> &&value);
|
||||
json_variant(const std::shared_ptr<json_array> &value);
|
||||
json_variant(json_array &&array);
|
||||
json_variant(json_object &&object);
|
||||
json_variant(const char*sz);
|
||||
|
||||
json_variant(const void*) = delete; // do NOT allow implicit conversion of pointers to bool
|
||||
|
||||
json_variant &operator=(json_variant &&value);
|
||||
json_variant &operator=(const json_variant &value);
|
||||
json_variant &operator=(bool value);
|
||||
json_variant &operator=(double value);
|
||||
json_variant &operator=(const std::string &value);
|
||||
json_variant &operator=(std::string &&value);
|
||||
json_variant &operator=(json_object &&value);
|
||||
json_variant &operator=(json_array &&value);
|
||||
|
||||
json_variant &operator=(const char*sz) { return (*this) = std::string(sz); }
|
||||
|
||||
json_variant &operator=(void*) = delete; // do NOT allow implicit conversion of pointers to bool
|
||||
|
||||
void require_type(ContentType content_type) const
|
||||
{
|
||||
if (this->content_type != content_type)
|
||||
{
|
||||
throw std::logic_error("Content type is not valid.");
|
||||
}
|
||||
}
|
||||
bool is_null() const { return content_type == ContentType::Null; }
|
||||
bool is_bool() const { return content_type == ContentType::Bool; }
|
||||
bool is_number() const { return content_type == ContentType::Number; }
|
||||
bool is_string() const { return content_type == ContentType::String; }
|
||||
bool is_object() const { return content_type == ContentType::Object; }
|
||||
bool is_array() const { return content_type == ContentType::Array; }
|
||||
|
||||
const json_null &as_null() const
|
||||
{
|
||||
require_type(ContentType::Bool);
|
||||
return json_null::instance;
|
||||
}
|
||||
json_null &as_null()
|
||||
{
|
||||
require_type(ContentType::Null);
|
||||
return json_null::instance;
|
||||
}
|
||||
|
||||
bool as_bool() const
|
||||
{
|
||||
require_type(ContentType::Bool);
|
||||
return content.bool_value;
|
||||
}
|
||||
bool &as_bool()
|
||||
{
|
||||
require_type(ContentType::Bool);
|
||||
return content.bool_value;
|
||||
}
|
||||
|
||||
double as_number() const
|
||||
{
|
||||
require_type(ContentType::Number);
|
||||
return content.double_value;
|
||||
}
|
||||
double &as_number()
|
||||
{
|
||||
require_type(ContentType::Number);
|
||||
return content.double_value;
|
||||
}
|
||||
|
||||
const std::string &as_string() const;
|
||||
std::string &as_string();
|
||||
|
||||
const std::shared_ptr<json_object> &as_object() const;
|
||||
std::shared_ptr<json_object> &as_object();
|
||||
|
||||
const std::shared_ptr<json_array> &as_array() const;
|
||||
std::shared_ptr<json_array> &as_array();
|
||||
|
||||
template <typename U>
|
||||
U &as() { static_assert("Invalid type."); }
|
||||
|
||||
// convenience methods for object and array manipulation.
|
||||
static json_variant make_object();
|
||||
static json_variant make_array();
|
||||
|
||||
void resize(size_t size);
|
||||
size_t size() const;
|
||||
|
||||
bool contains(const std::string &index) const;
|
||||
|
||||
json_variant &at(size_t index);
|
||||
const json_variant &at(size_t index) const;
|
||||
|
||||
json_variant &operator[](size_t index);
|
||||
const json_variant &operator[](size_t index) const;
|
||||
|
||||
json_variant &operator[](const std::string &index);
|
||||
const json_variant &operator[](const std::string &index) const;
|
||||
|
||||
bool operator==(const json_variant &other) const;
|
||||
bool operator!=(const json_variant &other) const;
|
||||
|
||||
std::string to_string() const;
|
||||
private:
|
||||
void free();
|
||||
void write_double_value(json_writer &writer, double value) const;
|
||||
void write_float_value(json_writer &writer, double value) const;
|
||||
virtual void read_json(json_reader &reader);
|
||||
virtual void write_json(json_writer &writer) const;
|
||||
|
||||
static constexpr size_t stringSize = sizeof(std::string);
|
||||
static constexpr size_t objectSize = sizeof(std::shared_ptr<json_variant>);
|
||||
static constexpr size_t memSize = stringSize > objectSize ? stringSize : objectSize;
|
||||
union Content
|
||||
{
|
||||
bool bool_value;
|
||||
double double_value;
|
||||
float float_value;
|
||||
int32_t int32_value;
|
||||
uint8_t mem[memSize];
|
||||
};
|
||||
|
||||
ContentType content_type = ContentType::Null;
|
||||
Content content;
|
||||
|
||||
std::string &memString();
|
||||
object_ptr &memObject();
|
||||
array_ptr &memArray();
|
||||
|
||||
const std::string &memString() const;
|
||||
const object_ptr &memObject() const;
|
||||
const array_ptr &memArray() const;
|
||||
};
|
||||
class json_array : public JsonSerializable
|
||||
{
|
||||
private:
|
||||
json_array(const json_array&) { } // deleted.
|
||||
public:
|
||||
using ptr = std::shared_ptr<json_array>;
|
||||
|
||||
json_array() { ++allocation_count_; }
|
||||
json_array(json_array&&other);
|
||||
~json_array() { --allocation_count_; }
|
||||
|
||||
json_variant &at(size_t index);
|
||||
const json_variant &at(size_t index) const;
|
||||
|
||||
json_variant &operator[](size_t index);
|
||||
const json_variant &operator[](size_t &index) const;
|
||||
|
||||
void resize(size_t size) { values.resize(size); }
|
||||
size_t size() const { return values.size(); }
|
||||
void push_back(json_variant &&value) { values.push_back(std::move(value)); }
|
||||
template <typename U>
|
||||
void push_back(U &&value) { values.push_back(value); }
|
||||
void push_back(double value) { values.push_back(json_variant{value}); }
|
||||
void push_back(const std::string &value) { values.push_back(json_variant{value}); }
|
||||
void push_back(bool value) { values.push_back(json_variant{value}); }
|
||||
void push_back(const std::shared_ptr<json_array> &value) { values.push_back(json_variant(value)); }
|
||||
void push_back(const std::shared_ptr<json_object> &value) { values.push_back(json_variant(value)); }
|
||||
|
||||
bool operator==(const json_array &other) const;
|
||||
bool operator!=(const json_array &other) const { return (!((*this) == other)); }
|
||||
|
||||
// Strictly for testing purposes. Not thread-safe.
|
||||
static int64_t allocation_count()
|
||||
{
|
||||
return allocation_count_;
|
||||
}
|
||||
using iterator = std::vector<json_variant>::iterator;
|
||||
using const_iterator = std::vector<json_variant>::const_iterator;
|
||||
|
||||
iterator begin() { return values.begin(); }
|
||||
iterator end() { return values.end(); }
|
||||
const_iterator begin() const { return values.begin(); }
|
||||
const_iterator end() const { return values.end(); }
|
||||
|
||||
private:
|
||||
static int64_t allocation_count_; // strictly for testing purposes. not thread safe.
|
||||
|
||||
virtual void read_json(json_reader &reader);
|
||||
virtual void write_json(json_writer &writer) const;
|
||||
|
||||
void check_index(size_t size) const;
|
||||
|
||||
std::vector<json_variant> values;
|
||||
};
|
||||
class json_object : public JsonSerializable
|
||||
{
|
||||
private:
|
||||
json_object(const json_object&) { } // deleted.
|
||||
public:
|
||||
using ptr = std::shared_ptr<json_object>;
|
||||
|
||||
json_object() { ++ allocation_count_;}
|
||||
json_object(json_object&&other);
|
||||
~json_object() { --allocation_count_;}
|
||||
|
||||
size_t size() const { return values.size(); }
|
||||
json_variant &at(const std::string &index);
|
||||
const json_variant &at(const std::string &index) const;
|
||||
|
||||
json_variant &operator[](const std::string &index);
|
||||
const json_variant &operator[](const std::string &index) const;
|
||||
|
||||
bool operator==(const json_object &other) const;
|
||||
bool operator!=(const json_object &other) const { return (!((*this) == other)); }
|
||||
bool contains(const std::string &index) const;
|
||||
|
||||
|
||||
using values_t = std::vector< std::pair<std::string, json_variant> >;
|
||||
using iterator = values_t::iterator;
|
||||
using const_iterator = values_t::const_iterator;
|
||||
|
||||
iterator begin() { return values.begin(); }
|
||||
iterator end() { return values.end(); }
|
||||
const_iterator begin() const { return values.begin(); }
|
||||
const_iterator end() const { return values.end(); }
|
||||
|
||||
iterator find(const std::string& key);
|
||||
const_iterator find(const std::string& key) const;
|
||||
|
||||
|
||||
// strictly for testing purposes. Not thread-safe.
|
||||
static int64_t allocation_count()
|
||||
{
|
||||
return allocation_count_;
|
||||
}
|
||||
private:
|
||||
virtual void read_json(json_reader &reader);
|
||||
virtual void write_json(json_writer &writer) const;
|
||||
|
||||
static int64_t allocation_count_;
|
||||
values_t values;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
inline std::string &json_variant::memString() { return *(std::string *)content.mem; }
|
||||
inline const std::string &json_variant::memString() const { return *(const std::string *)content.mem; }
|
||||
|
||||
template <>
|
||||
inline json_null &json_variant::as<json_null>() { return as_null(); }
|
||||
|
||||
template <>
|
||||
inline bool &json_variant::as<bool>() { return as_bool(); }
|
||||
|
||||
template <>
|
||||
inline double &json_variant::as<double>() { return as_number(); }
|
||||
|
||||
template <>
|
||||
inline std::string &json_variant::as<std::string>() { return as_string(); }
|
||||
|
||||
template <>
|
||||
inline std::shared_ptr<json_object> &json_variant::as<std::shared_ptr<json_object>>() { return as_object(); }
|
||||
|
||||
template <>
|
||||
inline std::shared_ptr<json_array> &json_variant::as<std::shared_ptr<json_array>>() { return as_array(); }
|
||||
|
||||
template <>
|
||||
inline json_variant &json_variant::as<json_variant>() { return *this; }
|
||||
|
||||
inline json_variant::object_ptr &json_variant::memObject()
|
||||
{
|
||||
return *(object_ptr *)content.mem;
|
||||
}
|
||||
inline json_variant::array_ptr &json_variant::memArray()
|
||||
{
|
||||
return *(array_ptr *)content.mem;
|
||||
}
|
||||
|
||||
inline const json_variant::object_ptr &json_variant::memObject() const
|
||||
{
|
||||
return *(const object_ptr *)content.mem;
|
||||
}
|
||||
inline const json_variant::array_ptr &json_variant::memArray() const
|
||||
{
|
||||
return *(const array_ptr *)content.mem;
|
||||
}
|
||||
|
||||
inline json_variant::json_variant(json_array &&array)
|
||||
{
|
||||
this->content_type = ContentType::Null;
|
||||
new (content.mem) std::shared_ptr<json_array>{new json_array(std::move(array))};
|
||||
this->content_type = ContentType::Array;
|
||||
}
|
||||
inline json_variant::json_variant(json_object &&object)
|
||||
{
|
||||
this->content_type = ContentType::Null;
|
||||
new (content.mem) std::shared_ptr<json_object>{new json_object(std::move(object))};
|
||||
this->content_type = ContentType::Object;
|
||||
}
|
||||
|
||||
inline json_variant::json_variant(const std::string &value)
|
||||
{
|
||||
this->content_type = ContentType::Null;
|
||||
new (content.mem) std::string(value); // placement new.
|
||||
content_type = ContentType::String;
|
||||
}
|
||||
|
||||
inline json_variant::json_variant(std::shared_ptr<json_object> &&value)
|
||||
{
|
||||
this->content_type = ContentType::Null;
|
||||
new (content.mem) std::shared_ptr<json_object>(std::move(value)); // placement new.
|
||||
content_type = ContentType::Object;
|
||||
}
|
||||
inline json_variant::json_variant(const std::shared_ptr<json_object> &value)
|
||||
{
|
||||
// don't deep copy!
|
||||
std::shared_ptr<json_object> t = const_cast<std::shared_ptr<json_object> &>(value);
|
||||
this->content_type = ContentType::Null;
|
||||
new (content.mem) std::shared_ptr<json_object>(t); // placement new.
|
||||
content_type = ContentType::Object;
|
||||
}
|
||||
|
||||
inline json_variant::json_variant(const std::shared_ptr<json_array> &value)
|
||||
{
|
||||
// Make sure we don't deep copy!
|
||||
std::shared_ptr<json_array> t = const_cast<std::shared_ptr<json_array> &>(value);
|
||||
this->content_type = ContentType::Null;
|
||||
new (content.mem) std::shared_ptr<json_array>(t); // placement new.
|
||||
content_type = ContentType::Array;
|
||||
}
|
||||
inline json_variant::json_variant(array_ptr &&value)
|
||||
{
|
||||
this->content_type = ContentType::Null;
|
||||
new (content.mem) std::shared_ptr<json_array>(std::move(value)); // placement new.
|
||||
content_type = ContentType::Array;
|
||||
}
|
||||
inline json_variant::json_variant()
|
||||
{
|
||||
content_type = ContentType::Null;
|
||||
}
|
||||
inline json_variant::json_variant(json_null value)
|
||||
{
|
||||
content_type = ContentType::Null;
|
||||
}
|
||||
inline json_variant::json_variant(bool value)
|
||||
{
|
||||
content_type = ContentType::Bool;
|
||||
content.bool_value = value;
|
||||
}
|
||||
|
||||
inline json_variant::json_variant(double value)
|
||||
{
|
||||
content_type = ContentType::Number;
|
||||
content.double_value = value;
|
||||
}
|
||||
inline std::string &json_variant::as_string()
|
||||
{
|
||||
require_type(ContentType::String);
|
||||
return memString();
|
||||
}
|
||||
|
||||
inline const std::string &json_variant::as_string() const
|
||||
{
|
||||
require_type(ContentType::String);
|
||||
return memString();
|
||||
}
|
||||
|
||||
inline const json_variant::object_ptr &json_variant::as_object() const
|
||||
{
|
||||
require_type(ContentType::Object);
|
||||
return memObject();
|
||||
}
|
||||
|
||||
inline json_variant::object_ptr &json_variant::as_object()
|
||||
{
|
||||
require_type(ContentType::Object);
|
||||
return memObject();
|
||||
}
|
||||
|
||||
inline const json_variant::array_ptr &json_variant::as_array() const
|
||||
{
|
||||
require_type(ContentType::Array);
|
||||
return memArray();
|
||||
}
|
||||
|
||||
inline json_variant::array_ptr &json_variant::as_array()
|
||||
{
|
||||
require_type(ContentType::Array);
|
||||
return memArray();
|
||||
}
|
||||
|
||||
inline /*static*/ json_variant json_variant::make_object()
|
||||
{
|
||||
return json_variant{std::make_shared<json_object>()};
|
||||
};
|
||||
inline /*static */ json_variant json_variant::make_array()
|
||||
{
|
||||
return json_variant{std::make_shared<json_array>()};
|
||||
};
|
||||
|
||||
inline void json_variant::resize(size_t size)
|
||||
{
|
||||
as_array()->resize(size);
|
||||
}
|
||||
|
||||
inline json_variant &json_variant::at(size_t index)
|
||||
{
|
||||
return as_array()->at(index);
|
||||
}
|
||||
inline const json_variant &json_variant::at(size_t index) const
|
||||
{
|
||||
return as_array()->at(index);
|
||||
}
|
||||
|
||||
inline json_variant &json_variant::operator[](size_t index)
|
||||
{
|
||||
return as_array()->at(index);
|
||||
}
|
||||
inline const json_variant &json_variant::operator[](size_t index) const
|
||||
{
|
||||
return as_array()->at(index);
|
||||
}
|
||||
|
||||
inline const json_variant &json_variant::operator[](const std::string &index) const
|
||||
{
|
||||
return (*as_object())[index];
|
||||
}
|
||||
inline json_variant &json_variant::operator[](const std::string &index)
|
||||
{
|
||||
return (*as_object())[index];
|
||||
}
|
||||
|
||||
inline const json_variant &json_array::operator[](size_t &index) const
|
||||
{
|
||||
check_index(index);
|
||||
return values[index];
|
||||
}
|
||||
|
||||
inline json_variant &json_array::operator[](size_t index)
|
||||
{
|
||||
return at(index);
|
||||
}
|
||||
|
||||
inline void json_array::check_index(size_t size) const
|
||||
{
|
||||
if (size >= values.size())
|
||||
{
|
||||
throw std::out_of_range("index out of range.");
|
||||
}
|
||||
}
|
||||
|
||||
inline bool json_variant::operator!=(const json_variant &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
|
||||
// Holds a string but is json_read and json_written as an unqoted json object.
|
||||
class raw_json_string: public JsonSerializable {
|
||||
public:
|
||||
raw_json_string() { }
|
||||
raw_json_string(const std::string &value) : value(value) {}
|
||||
const std::string& as_string() const { return value; }
|
||||
|
||||
void Set(const std::string&value) { this->value = value; }
|
||||
|
||||
private:
|
||||
|
||||
virtual void write_json(json_writer &writer) const {
|
||||
writer.write_raw(value.c_str());
|
||||
}
|
||||
virtual void read_json(json_reader &reader) {
|
||||
throw std::logic_error("Not implemented.");
|
||||
}
|
||||
|
||||
std::string value;
|
||||
};
|
||||
|
||||
} // namespace pipedal
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
namespace pipedal {
|
||||
|
||||
inline bool endsWith(const std::string& str, const std::string& suffix)
|
||||
{
|
||||
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
|
||||
}
|
||||
void SetThreadName(const std::string &name);
|
||||
|
||||
std::u32string ToUtf32(const std::string &s);
|
||||
|
||||
inline bool isPathSeperator(char c) { return c == '/' || c == std::filesystem::path::preferred_separator;}
|
||||
}
|
||||
@@ -0,0 +1,639 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "json.hpp"
|
||||
#include <string_view>
|
||||
#include <cctype>
|
||||
#include "json_variant.hpp"
|
||||
#include "util.hpp"
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
|
||||
|
||||
uint32_t json_writer::continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end)
|
||||
{
|
||||
if (p == end)
|
||||
throw_encoding_error();
|
||||
uint8_t c = *p++;
|
||||
if ((c & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_BITS)
|
||||
throw_encoding_error();
|
||||
return c & 0x3F;
|
||||
}
|
||||
static char hex(int v)
|
||||
{
|
||||
if (v < 10)
|
||||
return (char)('0' + v);
|
||||
return (char)('A' + v - 10);
|
||||
}
|
||||
|
||||
void json_writer::throw_encoding_error()
|
||||
{
|
||||
throw std::invalid_argument("Invalid UTF-8 character sequence");
|
||||
}
|
||||
void json_writer::write_utf16_char(uint16_t uc)
|
||||
{
|
||||
os << "\\u"
|
||||
<< hex((int)((uc >> 12) & 0x0F))
|
||||
<< hex((int)((uc >> 8) & 0x0F))
|
||||
<< hex((int)((uc >> 4) & 0x0F))
|
||||
<< hex((int)((uc)&0x0F));
|
||||
}
|
||||
|
||||
void json_writer::write(string_view v,bool enforceValidUtf8Encoding)
|
||||
{
|
||||
// convert to utf-32.
|
||||
// convert utf-32 to normalized utf-16.
|
||||
// write non-7-bit and unsafe characters as \uHHHH.
|
||||
|
||||
auto p = v.begin();
|
||||
os << '"';
|
||||
while (p != v.end())
|
||||
{
|
||||
uint32_t uc;
|
||||
uint8_t c = (uint8_t)*p++;
|
||||
if ((c & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_BITS)
|
||||
{
|
||||
uc = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t c2 = continuation_byte(p, v.end());
|
||||
|
||||
if ((c & UTF8_TWO_BYTES_MASK) == UTF8_TWO_BYTES_BITS)
|
||||
{
|
||||
uint32_t c1 = c & (uint32_t)(~UTF8_TWO_BYTES_MASK);
|
||||
if (c1 <= 1 && enforceValidUtf8Encoding)
|
||||
{
|
||||
// overlong encoding.
|
||||
throw_encoding_error();
|
||||
}
|
||||
uc = (c1 << 6) | c2;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t c3 = continuation_byte(p, v.end());
|
||||
|
||||
if ((c & UTF8_THREE_BYTES_MASK) == UTF8_THREE_BYTES_BITS)
|
||||
{
|
||||
uint32_t c1 = c & (uint32_t)~UTF8_THREE_BYTES_MASK;
|
||||
if (c1 == 0 && c2 < 0x20 && enforceValidUtf8Encoding)
|
||||
{
|
||||
// overlong encoding.
|
||||
throw_encoding_error();
|
||||
}
|
||||
|
||||
uc = (c1) << 12 | (c2 << 6) | c3;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t c4 = continuation_byte(p, v.end());
|
||||
if ((c & UTF8_FOUR_BYTES_MASK) == UTF8_FOUR_BYTES_BITS)
|
||||
{
|
||||
uint32_t c1 = c & (uint32_t)~UTF8_FOUR_BYTES_MASK;
|
||||
if (c1 == 0 && c2 < 0x10 && enforceValidUtf8Encoding)
|
||||
{
|
||||
// overlong encoding.
|
||||
throw_encoding_error();
|
||||
}
|
||||
uc = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
|
||||
} else {
|
||||
// outside legal UCS range.
|
||||
throw_encoding_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((uc >= UTF16_SURROGATE_1_BASE && uc <= UTF16_SURROGATE_1_BASE + UTF16_SURROGATE_MASK) || (uc >= UTF16_SURROGATE_2_BASE && uc <= UTF16_SURROGATE_2_BASE + UTF16_SURROGATE_MASK))
|
||||
{
|
||||
// MUST not encode UTF16 surrogates in UTF8.
|
||||
throw_encoding_error();
|
||||
}
|
||||
|
||||
if (uc == '"' || uc == '\\')
|
||||
{
|
||||
os << (char)'\\';
|
||||
os << (char)uc;
|
||||
}
|
||||
else if (uc >= 0x20 && uc < 0x80)
|
||||
{
|
||||
os << (char)uc;
|
||||
}
|
||||
else if (uc == '\r')
|
||||
{
|
||||
os << "\\r";
|
||||
}
|
||||
else if (uc == '\n')
|
||||
{
|
||||
os << "\\n";
|
||||
}
|
||||
else if (uc == '\t')
|
||||
{
|
||||
os << "\\t";
|
||||
}
|
||||
else if (uc < 0x10000ul)
|
||||
{
|
||||
write_utf16_char((uint16_t)uc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// write UTF-16 surrogate pair.
|
||||
uc -= 0x10000;
|
||||
|
||||
uint16_t s1 = (uint16_t)(UTF16_SURROGATE_1_BASE + ((uc >> 10) & 0x3FFu));
|
||||
uint16_t s2 = (uint16_t)(UTF16_SURROGATE_2_BASE + (uc & 0x03FFu));
|
||||
// surrogate pair.
|
||||
write_utf16_char(s1);
|
||||
write_utf16_char(s2);
|
||||
}
|
||||
}
|
||||
os << '"';
|
||||
}
|
||||
|
||||
void json_writer::indent()
|
||||
{
|
||||
if (!compressed)
|
||||
{
|
||||
for (int i = 0; i < indent_level; ++i)
|
||||
{
|
||||
os << " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void json_writer::start_object()
|
||||
{
|
||||
os << "{" << CRLF;
|
||||
indent_level += TAB_SIZE;
|
||||
}
|
||||
void json_writer::end_object()
|
||||
{
|
||||
indent_level -= TAB_SIZE;
|
||||
os << CRLF;
|
||||
indent();
|
||||
os << "}";
|
||||
}
|
||||
|
||||
void json_writer::start_array()
|
||||
{
|
||||
indent();
|
||||
os << "[" << CRLF;
|
||||
indent_level += TAB_SIZE;
|
||||
}
|
||||
void json_writer::end_array()
|
||||
{
|
||||
indent_level -= TAB_SIZE;
|
||||
indent();
|
||||
os << "]" << CRLF;
|
||||
}
|
||||
|
||||
|
||||
void json_reader::skip_whitespace()
|
||||
{
|
||||
char c;
|
||||
while (true)
|
||||
{
|
||||
int ic = is_.peek();
|
||||
if (ic == -1)
|
||||
break;
|
||||
if (is_whitespace((char)ic))
|
||||
{
|
||||
c = get();
|
||||
}
|
||||
else if (ic == '/')
|
||||
{
|
||||
get();
|
||||
int c2 = is_.peek();
|
||||
if (c2 == '/') {
|
||||
// skip to end of line.
|
||||
get();
|
||||
while (true) {
|
||||
c2 = is_.peek();
|
||||
if (c2 == '\r' || c2 == '\n')
|
||||
{
|
||||
get(); // and continue.
|
||||
break;
|
||||
}
|
||||
if (c2 == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
get();
|
||||
}
|
||||
} else if (c2 == '*') {
|
||||
get();
|
||||
int level = 1;
|
||||
while (true)
|
||||
{
|
||||
c = get();
|
||||
if (c == '*' && is_.peek() == '/')
|
||||
{
|
||||
get();
|
||||
if (--level == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c == '/' && is_.peek() == '*')
|
||||
{
|
||||
get();
|
||||
++level;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
throw_format_error();
|
||||
}
|
||||
} else
|
||||
{
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string json_reader::read_string()
|
||||
{
|
||||
// To completely normalize UTF-32 values we must covert to UTF-16, resolve surrogate pairs, and then convert UTF-32 to UTF-8.
|
||||
|
||||
|
||||
skip_whitespace();
|
||||
char c;
|
||||
char startingCharacter;
|
||||
startingCharacter = get();
|
||||
if (startingCharacter != '\'' && startingCharacter != '\"')
|
||||
{
|
||||
throw_format_error();
|
||||
}
|
||||
std::stringstream s;
|
||||
|
||||
while (true)
|
||||
{
|
||||
c = get();
|
||||
if (c == startingCharacter)
|
||||
{
|
||||
if (is_.peek() == startingCharacter) // "" -> "
|
||||
{
|
||||
get();
|
||||
s.put(c);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c != '\\')
|
||||
{
|
||||
s << (char)c;
|
||||
}
|
||||
else
|
||||
{
|
||||
c = get();
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
default:
|
||||
s << c;
|
||||
break;
|
||||
case 'r':
|
||||
s << '\r';
|
||||
break;
|
||||
case 'b':
|
||||
s << '\b';
|
||||
break;
|
||||
case 'f':
|
||||
s << '\f';
|
||||
break;
|
||||
case 'n':
|
||||
s << '\n';
|
||||
break;
|
||||
case 't':
|
||||
s << '\t';
|
||||
break;
|
||||
case 'u':
|
||||
{
|
||||
uint32_t uc = read_u_escape();
|
||||
if (uc >= UTF16_SURROGATE_1_BASE && uc <= UTF16_SURROGATE_1_BASE + UTF16_SURROGATE_MASK)
|
||||
{
|
||||
// MUST be a UTF16_SURROGATE 2 to be legal.
|
||||
c = get();
|
||||
if (c != '\\')
|
||||
throw_format_error("Invalid UTF16 surrogate pair");
|
||||
c = get();
|
||||
if (c != 'u')
|
||||
throw_format_error("Invalid UTF16 surrogate pair");
|
||||
uint16_t uc2 = read_u_escape();
|
||||
if (uc2 < UTF16_SURROGATE_2_BASE || uc2 > UTF16_SURROGATE_2_BASE + UTF16_SURROGATE_MASK)
|
||||
{
|
||||
throw_format_error("Invalid UTF16 surrogate pair");
|
||||
}
|
||||
uc = ((uc & UTF16_SURROGATE_MASK) << 10) + (uc2 & UTF16_SURROGATE_MASK) + 0x10000U;
|
||||
}
|
||||
HtmlHelper::utf32_to_utf8_stream(s, uc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.str();
|
||||
}
|
||||
uint16_t json_reader::read_hex()
|
||||
{
|
||||
char c;
|
||||
c = get();
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
throw_format_error("Invalid \\u escape character");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t json_reader::read_u_escape()
|
||||
{
|
||||
uint16_t c1 = read_hex();
|
||||
uint16_t c2 = read_hex();
|
||||
uint16_t c3 = read_hex();
|
||||
uint16_t c4 = read_hex();
|
||||
|
||||
return (c1 << 12) + (c2 << 8) + (c3 << 4) + c4;
|
||||
}
|
||||
|
||||
bool json_reader::is_complete()
|
||||
{
|
||||
skip_whitespace();
|
||||
return is_.peek() == -1;
|
||||
}
|
||||
|
||||
void json_reader::consumeToken(const char*expectedToken, const char*errorMessage)
|
||||
{
|
||||
skip_whitespace();
|
||||
|
||||
const char*p = expectedToken;
|
||||
while (*p != '\0')
|
||||
{
|
||||
char expectedChar = *p++;
|
||||
int c = is_.get();
|
||||
if (expectedChar != c) {
|
||||
this->throw_format_error(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void json_reader::consume(char expected)
|
||||
{
|
||||
skip_whitespace();
|
||||
char c;
|
||||
c = get();
|
||||
if (c != expected)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "Expecting '" << expected << "'";
|
||||
throw_format_error(s.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void json_reader::skip_property()
|
||||
{
|
||||
skip_whitespace();
|
||||
int c = is_.peek();
|
||||
switch (c)
|
||||
{
|
||||
case -1:
|
||||
throw_format_error("Premature end of file.");
|
||||
break;
|
||||
case '[':
|
||||
skip_array();
|
||||
break;
|
||||
case '{':
|
||||
skip_object();
|
||||
break;
|
||||
case '\"':
|
||||
skip_string();
|
||||
break;
|
||||
case 't':
|
||||
case 'f':
|
||||
read_boolean();
|
||||
break;
|
||||
case 'n':
|
||||
read_null();
|
||||
break;
|
||||
default:
|
||||
skip_number();
|
||||
break;
|
||||
}
|
||||
}
|
||||
void json_reader::skip_string()
|
||||
{
|
||||
consume('"');
|
||||
while (true)
|
||||
{
|
||||
int c;
|
||||
c = get();
|
||||
if (c == -1) throw_format_error("Premature end of file.");
|
||||
|
||||
if (c == '\"')
|
||||
{
|
||||
if (peek() == '\"')
|
||||
{
|
||||
get();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (c == '\\')
|
||||
{
|
||||
c = get(); // all of standard escapes, enough to get past \u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void json_reader::skip_number()
|
||||
{
|
||||
skip_whitespace();
|
||||
int c;
|
||||
if (is_.peek() == '-')
|
||||
{
|
||||
get();
|
||||
}
|
||||
if (!std::isdigit(is_.peek()))
|
||||
{
|
||||
throw_format_error("Expecting a number.");
|
||||
}
|
||||
while (std::isdigit(is_.peek()))
|
||||
{
|
||||
get();
|
||||
}
|
||||
if (is_.peek() == '.')
|
||||
{
|
||||
get();
|
||||
}
|
||||
while (std::isdigit(is_.peek()))
|
||||
{
|
||||
get();
|
||||
}
|
||||
c = is_.peek();
|
||||
if (c == 'e' || c == 'E')
|
||||
{
|
||||
get();
|
||||
c = is_.peek();
|
||||
if (c == '+' || c == 'i')
|
||||
{
|
||||
get();
|
||||
}
|
||||
while (std::isdigit(is_.peek()))
|
||||
{
|
||||
get();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
void json_reader::skip_array()
|
||||
{
|
||||
int c;
|
||||
consume('[');
|
||||
|
||||
while (true)
|
||||
{
|
||||
c = peek();
|
||||
if (c == -1) throw_format_error("Premature end of file.");
|
||||
|
||||
if (c == ']')
|
||||
{
|
||||
c = get();
|
||||
break;
|
||||
}
|
||||
skip_property();
|
||||
skip_whitespace();
|
||||
if (is_.peek() == ',')
|
||||
{
|
||||
c = get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void json_reader::skip_object()
|
||||
{
|
||||
int c;
|
||||
consume('{');
|
||||
while (true)
|
||||
{
|
||||
c = peek();
|
||||
if (c == -1) throw_format_error("Premature end of file.");
|
||||
if (c == '}')
|
||||
{
|
||||
c = get();
|
||||
break;
|
||||
}
|
||||
skip_string(); // name.
|
||||
consume(':');
|
||||
skip_object();
|
||||
if (peek() == ',')
|
||||
{
|
||||
consume(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void json_reader::read_object_start()
|
||||
{
|
||||
skip_whitespace();
|
||||
|
||||
char c;
|
||||
c = get();
|
||||
if (c != '{')
|
||||
throw_format_error();
|
||||
}
|
||||
|
||||
std::string json_reader::readToken()
|
||||
{
|
||||
skip_whitespace();
|
||||
|
||||
std::stringstream s;
|
||||
while (true)
|
||||
{
|
||||
int ic = is_.peek();
|
||||
if (ic == -1)
|
||||
break;
|
||||
char c = (char)ic;
|
||||
if (!std::isalpha(c))
|
||||
break;
|
||||
is_.get();
|
||||
s << c;
|
||||
}
|
||||
return s.str();
|
||||
|
||||
}
|
||||
|
||||
void json_reader::read_null()
|
||||
{
|
||||
std::string s = readToken();
|
||||
if (s != "null")
|
||||
{
|
||||
throw_format_error("Format error. Expecting 'null'.");
|
||||
|
||||
}
|
||||
}
|
||||
bool json_reader::read_boolean()
|
||||
{
|
||||
std::string ss = readToken();
|
||||
if (ss == "true")
|
||||
return true;
|
||||
if (ss == "false")
|
||||
return false;
|
||||
|
||||
throw_format_error("Format error. Expectiong 'true' or 'false'");
|
||||
return false;
|
||||
}
|
||||
|
||||
void json_reader::throw_format_error(const char*error)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << error;
|
||||
s << ", near: '";
|
||||
skip_whitespace();
|
||||
if (is_.peek() == -1) {
|
||||
s << "<eof>";
|
||||
} else {
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
int c = get();
|
||||
if (c == -1) break;
|
||||
if (c == '\r') {
|
||||
s << "\\r";
|
||||
} else if (c == '\n')
|
||||
{
|
||||
s << "\\n";
|
||||
} else {
|
||||
s << (char)c;
|
||||
}
|
||||
}
|
||||
}
|
||||
s << "'.";
|
||||
std::string message = s.str();
|
||||
throw JsonException(message);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// void json_writer::write(const json_variant &value)
|
||||
// {
|
||||
// ((JsonSerializable *)&value)->write_json(*this);
|
||||
// }
|
||||
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2023 Robin E. R. Davies
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is furnished to do
|
||||
* so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "json_variant.hpp"
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
json_variant::~json_variant()
|
||||
{
|
||||
free();
|
||||
}
|
||||
|
||||
void json_variant::free()
|
||||
{
|
||||
// in-place deletion.
|
||||
switch (content_type)
|
||||
{
|
||||
case ContentType::String:
|
||||
memString().std::string::~string();
|
||||
break;
|
||||
case ContentType::Object:
|
||||
memObject().std::shared_ptr<json_object>::~shared_ptr();
|
||||
break;
|
||||
case ContentType::Array:
|
||||
memArray().std::shared_ptr<json_array>::~shared_ptr();
|
||||
break;
|
||||
}
|
||||
content_type = ContentType::Null;
|
||||
}
|
||||
|
||||
|
||||
json_variant::json_variant(json_variant &&other)
|
||||
{
|
||||
this->content_type = ContentType::Null;
|
||||
switch (other.content_type)
|
||||
{
|
||||
case ContentType::Null:
|
||||
break;
|
||||
case ContentType::Bool:
|
||||
this->content.bool_value = other.content.bool_value;
|
||||
break;
|
||||
case ContentType::Number:
|
||||
this->content.double_value = other.content.double_value;
|
||||
break;
|
||||
case ContentType::String:
|
||||
new (content.mem) std::string(std::move(other.memString()));
|
||||
this->content_type = ContentType::String;
|
||||
return;
|
||||
case ContentType::Object:
|
||||
new (content.mem) std::shared_ptr<json_object>{std::move(other.memObject())};
|
||||
this->content_type = ContentType::Object;
|
||||
return;
|
||||
case ContentType::Array:
|
||||
new (content.mem) std::shared_ptr<json_array>{std::move(other.memArray())};
|
||||
this->content_type = ContentType::Array;
|
||||
return;
|
||||
}
|
||||
this->content_type = other.content_type;
|
||||
other.content_type = ContentType::Null;
|
||||
}
|
||||
|
||||
json_variant &json_variant::operator=(const json_variant &other)
|
||||
{
|
||||
free();
|
||||
switch (other.content_type)
|
||||
{
|
||||
case ContentType::Null:
|
||||
break;
|
||||
case ContentType::Bool:
|
||||
this->content.bool_value = other.content.bool_value;
|
||||
break;
|
||||
case ContentType::Number:
|
||||
this->content.double_value = other.content.double_value;
|
||||
break;
|
||||
case ContentType::String:
|
||||
new (content.mem) std::string(other.memString());
|
||||
break;
|
||||
case ContentType::Object:
|
||||
new (content.mem) std::shared_ptr<json_object>{other.memObject()};
|
||||
break;
|
||||
case ContentType::Array:
|
||||
new (content.mem) std::shared_ptr<json_array>{other.memArray()};
|
||||
this->content_type = ContentType::Array;
|
||||
break;
|
||||
}
|
||||
this->content_type = other.content_type;
|
||||
return *this;
|
||||
|
||||
}
|
||||
|
||||
json_variant::json_variant(const json_variant &other)
|
||||
{
|
||||
this->content_type = ContentType::Null;
|
||||
switch (other.content_type)
|
||||
{
|
||||
case ContentType::Null:
|
||||
break;
|
||||
case ContentType::Bool:
|
||||
this->content.bool_value = other.content.bool_value;
|
||||
break;
|
||||
case ContentType::Number:
|
||||
this->content.double_value = other.content.double_value;
|
||||
break;
|
||||
case ContentType::String:
|
||||
new (content.mem) std::string(other.memString());
|
||||
this->content_type = ContentType::String;
|
||||
return;
|
||||
case ContentType::Object:
|
||||
new (content.mem) std::shared_ptr<json_object>{other.memObject()};
|
||||
this->content_type = ContentType::Object;
|
||||
return;
|
||||
case ContentType::Array:
|
||||
new (content.mem) std::shared_ptr<json_array>{other.memArray()};
|
||||
this->content_type = ContentType::Array;
|
||||
return;
|
||||
}
|
||||
this->content_type = other.content_type;
|
||||
}
|
||||
|
||||
void json_variant::write_float_value(json_writer &writer, double value) const
|
||||
{
|
||||
writer.write(value);
|
||||
}
|
||||
|
||||
void json_variant::write_double_value(json_writer &writer, double value) const
|
||||
{
|
||||
writer.write(value);
|
||||
}
|
||||
|
||||
void json_variant::read_json(json_reader &reader)
|
||||
{
|
||||
int v = reader.peek();
|
||||
if (v == '[')
|
||||
{
|
||||
json_array array;
|
||||
reader.read(&array);
|
||||
(*this) = std::move(array);
|
||||
}
|
||||
else if (v == '{')
|
||||
{
|
||||
json_object object;
|
||||
reader.read(&object);
|
||||
(*this) = std::move(object);
|
||||
}
|
||||
else if (v == '\"')
|
||||
{
|
||||
std::string s;
|
||||
reader.read(&s);
|
||||
(*this) = std::move(s);
|
||||
}
|
||||
else if (v == 'n')
|
||||
{
|
||||
reader.read_null();
|
||||
(*this) = json_null();
|
||||
}
|
||||
else if (v == 't' || v == 'f')
|
||||
{
|
||||
bool b;
|
||||
reader.read(&b);
|
||||
(*this) = b;
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's a number.
|
||||
double v;
|
||||
reader.read(&v);
|
||||
(*this) = v;
|
||||
}
|
||||
}
|
||||
void json_variant::write_json(json_writer &writer) const
|
||||
{
|
||||
switch (content_type)
|
||||
{
|
||||
case ContentType::Null:
|
||||
writer.write_raw("null");
|
||||
break;
|
||||
case ContentType::Bool:
|
||||
writer.write(as_bool());
|
||||
break;
|
||||
case ContentType::Number:
|
||||
write_double_value(writer, as_number());
|
||||
break;
|
||||
case ContentType::String:
|
||||
writer.write(as_string());
|
||||
break;
|
||||
case ContentType::Object:
|
||||
writer.write(as_object());
|
||||
break;
|
||||
case ContentType::Array:
|
||||
writer.write(as_array());
|
||||
break;
|
||||
default:
|
||||
throw std::logic_error("Invalid variant type");
|
||||
}
|
||||
}
|
||||
|
||||
bool json_array::operator==(const json_array &other) const
|
||||
{
|
||||
if (!(this->size() == other.size()))
|
||||
return false;
|
||||
for (size_t i = 0; i < this->size(); ++i)
|
||||
{
|
||||
if (!((*this)[i] == other[i]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void json_array::read_json(json_reader &reader)
|
||||
{
|
||||
reader.read(&(this->values));
|
||||
}
|
||||
|
||||
void json_array::write_json(json_writer &writer) const
|
||||
{
|
||||
writer.start_array();
|
||||
bool first = true;
|
||||
for (auto &value : values)
|
||||
{
|
||||
if (!first)
|
||||
writer.write_raw(",");
|
||||
first = false;
|
||||
writer.write(value);
|
||||
}
|
||||
writer.end_array();
|
||||
}
|
||||
|
||||
|
||||
json_object::iterator json_object::find(const std::string& key)
|
||||
{
|
||||
for (auto i = begin(); i != end(); ++i)
|
||||
{
|
||||
if (i->first == key)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return end();
|
||||
}
|
||||
json_object::const_iterator json_object::find(const std::string&key) const
|
||||
{
|
||||
for (auto i = begin(); i != end(); ++i)
|
||||
{
|
||||
if (i->first == key)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return end();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool json_object::operator==(const json_object &other) const
|
||||
{
|
||||
|
||||
for (const auto &pair : this->values)
|
||||
{
|
||||
auto index = other.find(pair.first);
|
||||
if (index == other.end())
|
||||
return false;
|
||||
if (!(index->second == pair.second))
|
||||
return false;
|
||||
}
|
||||
for (const auto &pair : other.values)
|
||||
{
|
||||
auto index = this->find(pair.first);
|
||||
if (index == this->end())
|
||||
return false;
|
||||
if (!(index->second == pair.second))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void json_object::read_json(json_reader &reader)
|
||||
{
|
||||
reader.start_object();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (reader.peek() == '}') break;
|
||||
|
||||
std::string key;
|
||||
json_variant value;
|
||||
reader.read(&key);
|
||||
reader.consume(':');
|
||||
reader.read(&value);
|
||||
|
||||
(*this)[key] = value;
|
||||
if (reader.peek() == ',')
|
||||
{
|
||||
reader.consume(',');
|
||||
}
|
||||
}
|
||||
|
||||
reader.end_object();
|
||||
}
|
||||
void json_object::write_json(json_writer &writer) const
|
||||
{
|
||||
writer.start_object();
|
||||
bool first = true;
|
||||
for (auto &value : values)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
writer.write_raw(",");
|
||||
}
|
||||
first = false;
|
||||
writer.write(value.first);
|
||||
writer.write_raw(": ");
|
||||
writer.writeRawWritable(value.second);
|
||||
}
|
||||
writer.end_object();
|
||||
}
|
||||
|
||||
json_variant &json_array::at(size_t index)
|
||||
{
|
||||
check_index(index);
|
||||
return values.at(index);
|
||||
}
|
||||
const json_variant &json_array::at(size_t index) const
|
||||
{
|
||||
check_index(index);
|
||||
return values.at(index);
|
||||
}
|
||||
|
||||
size_t json_variant::size() const
|
||||
{
|
||||
if (content_type == ContentType::Array)
|
||||
return as_array()->size();
|
||||
if (content_type == ContentType::Object)
|
||||
{
|
||||
return as_object()->size();
|
||||
}
|
||||
throw std::logic_error("Not supported.");
|
||||
}
|
||||
|
||||
json_variant &json_object::at(const std::string &index) {
|
||||
for (auto&entry: values)
|
||||
{
|
||||
if (entry.first == index)
|
||||
{
|
||||
return entry.second;
|
||||
}
|
||||
}
|
||||
throw std::logic_error("Not found.");
|
||||
values.push_back(std::pair(index,json_variant()));
|
||||
return values[values.size()-1].second;
|
||||
}
|
||||
const json_variant &json_object::at(const std::string &index) const {
|
||||
for (const auto&entry: values)
|
||||
{
|
||||
if (entry.first == index)
|
||||
{
|
||||
return entry.second;
|
||||
}
|
||||
}
|
||||
throw std::logic_error("Not found.");
|
||||
}
|
||||
|
||||
json_variant &json_object::operator[](const std::string &index)
|
||||
{
|
||||
for (auto&entry: values)
|
||||
{
|
||||
if (entry.first == index)
|
||||
{
|
||||
return entry.second;
|
||||
}
|
||||
}
|
||||
values.push_back(std::pair(index,json_variant()));
|
||||
return values[values.size()-1].second;
|
||||
}
|
||||
const json_variant &json_object::operator[](const std::string &index) const
|
||||
{
|
||||
return at(index);
|
||||
}
|
||||
bool json_object::contains(const std::string &index) const
|
||||
{
|
||||
return find(index) != end();
|
||||
}
|
||||
|
||||
json_variant &json_variant::operator=(bool value)
|
||||
{
|
||||
free();
|
||||
this->content_type = ContentType::Bool;
|
||||
this->content.bool_value = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
json_variant &json_variant::operator=(double value)
|
||||
{
|
||||
free();
|
||||
this->content_type = ContentType::Number;
|
||||
this->content.double_value = value;
|
||||
return *this;
|
||||
}
|
||||
json_variant &json_variant::operator=(const std::string &value)
|
||||
{
|
||||
free();
|
||||
this->content_type = ContentType::String;
|
||||
new (this->content.mem) std::string(value); // in-place constructor.
|
||||
return *this;
|
||||
}
|
||||
json_variant &json_variant::operator=(std::string &&value)
|
||||
{
|
||||
free();
|
||||
this->content_type = ContentType::String;
|
||||
new (this->content.mem) std::string(std::move(value)); // in-place constructor.
|
||||
return *this;
|
||||
}
|
||||
json_variant &json_variant::operator=(json_object &&value)
|
||||
{
|
||||
free();
|
||||
new (this->content.mem) std::shared_ptr<json_object>{new json_object(std::move(value))};
|
||||
this->content_type = ContentType::Object;
|
||||
return *this;
|
||||
}
|
||||
json_variant &json_variant::operator=(json_array &&value)
|
||||
{
|
||||
free();
|
||||
this->content_type = ContentType::Array;
|
||||
new (this->content.mem) std::shared_ptr<json_array>{new json_array(std::move(value))};
|
||||
return *this;
|
||||
}
|
||||
json_variant &json_variant::operator=(json_variant &&value)
|
||||
{
|
||||
if (this->content_type == value.content_type)
|
||||
{
|
||||
switch (this->content_type)
|
||||
{
|
||||
case ContentType::String:
|
||||
std::swap(this->memString(), value.memString());
|
||||
return *this;
|
||||
case ContentType::Array:
|
||||
std::swap(this->memArray(), value.memArray());
|
||||
return *this;
|
||||
case ContentType::Object:
|
||||
std::swap(this->memObject(), value.memObject());
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
free();
|
||||
switch (value.content_type)
|
||||
{
|
||||
case ContentType::String:
|
||||
new (content.mem) std::string(std::move(value.memString()));
|
||||
value.memString().std::string::~string();
|
||||
break;
|
||||
case ContentType::Object:
|
||||
new (content.mem) std::shared_ptr<json_object>(std::move(value.memObject()));
|
||||
value.memObject().std::shared_ptr<json_object>::~shared_ptr();
|
||||
break;
|
||||
case ContentType::Array:
|
||||
new (content.mem) std::shared_ptr<json_array>(std::move(value.memArray()));
|
||||
value.memArray().std::shared_ptr<json_array>::~shared_ptr();
|
||||
break;
|
||||
default:
|
||||
// undifferentiated copy of POD types.
|
||||
*(uint64_t *)(this->content.mem) = *(uint64_t *)(value.content.mem);
|
||||
break;
|
||||
}
|
||||
this->content_type = value.content_type;
|
||||
value.content_type = ContentType::Null;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool json_variant::operator==(const json_variant &other) const
|
||||
{
|
||||
if (this->content_type != other.content_type)
|
||||
return false;
|
||||
|
||||
switch (this->content_type)
|
||||
{
|
||||
case ContentType::Null:
|
||||
return true;
|
||||
case ContentType::Bool:
|
||||
return as_bool() == other.as_bool();
|
||||
break;
|
||||
case ContentType::Number:
|
||||
return as_number() == other.as_number();
|
||||
case ContentType::String:
|
||||
return as_string() == other.as_string();
|
||||
case ContentType::Array:
|
||||
return *(as_array().get()) == *(other.as_array().get());
|
||||
case ContentType::Object:
|
||||
return *(as_object().get()) == *(other.as_object().get());;
|
||||
default:
|
||||
throw std::logic_error("Invalid content_type.");
|
||||
}
|
||||
}
|
||||
json_array::json_array(json_array&&other)
|
||||
: values(std::move(other.values))
|
||||
{
|
||||
++allocation_count_;
|
||||
}
|
||||
json_object::json_object(json_object&&other)
|
||||
:values(std::move(other.values))
|
||||
{
|
||||
++allocation_count_;
|
||||
}
|
||||
|
||||
bool json_variant::contains(const std::string &index) const
|
||||
{
|
||||
if (is_object())
|
||||
{
|
||||
return as_object()->contains(index);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string json_variant::to_string() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(*this);
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
json_variant::json_variant(const char*sz)
|
||||
: json_variant(std::string(sz))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*static*/ json_null json_null::instance;
|
||||
/*static*/ int64_t json_array::allocation_count_ = 0; // strictly for testing purposes. not thread safe.
|
||||
/*static*/ int64_t json_object::allocation_count_ = 0; // strictly for testing purposes. not thread safe.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2022 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
// usage: SS("xyz" << 123 << 45.6) returning a std::string rvalue.
|
||||
#define SS(x) ( ((std::stringstream&)(std::stringstream() << x )).str())
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include "util.hpp"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <thread>
|
||||
|
||||
#include <unistd.h> // for gettid()
|
||||
#include <codecvt>
|
||||
#include <sstream>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
void pipedal::SetThreadName(const std::string &name)
|
||||
{
|
||||
std::string threadName = "ppdl_" + name;
|
||||
if (threadName.length () > 15)
|
||||
{
|
||||
threadName = threadName.substr(0,15);
|
||||
}
|
||||
pthread_t pid = pthread_self();
|
||||
pthread_setname_np(pid,threadName.c_str());
|
||||
}
|
||||
|
||||
|
||||
static const uint8_t utf8extraBytes[256] = {
|
||||
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,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,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,0,0,0,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
|
||||
};
|
||||
|
||||
constexpr char32_t ILLEGAL_CHAR32 = U'⊗';
|
||||
static const uint8_t utf8Offset[] = { 0,0b11000000, 0b11100000,0b11110000,0b11111000,0b11111100};
|
||||
|
||||
std::u32string pipedal::ToUtf32(const std::string &s)
|
||||
{
|
||||
std::basic_stringstream<char32_t> result;
|
||||
|
||||
auto p = s.begin();
|
||||
auto end = s.end();
|
||||
|
||||
while (p != end)
|
||||
{
|
||||
uint8_t c = (uint8_t)(*p++);
|
||||
if (c < 0x80)
|
||||
{
|
||||
result << (char32_t)c;
|
||||
} else {
|
||||
auto extraBytes = utf8extraBytes[c];
|
||||
if (extraBytes == 0)
|
||||
{
|
||||
result << ILLEGAL_CHAR32;
|
||||
} else if (p+extraBytes > end)
|
||||
{
|
||||
result << ILLEGAL_CHAR32;
|
||||
break;
|
||||
} else {
|
||||
char32_t cResult = c -= utf8Offset[extraBytes];
|
||||
while (extraBytes != 0)
|
||||
{
|
||||
c = *p++;
|
||||
--extraBytes;
|
||||
if (c < 0x80 || c >= 0xC0)
|
||||
{
|
||||
result << ILLEGAL_CHAR32;
|
||||
p += extraBytes;
|
||||
break;
|
||||
}
|
||||
cResult = (cResult << 6) + (c & 0x3F);
|
||||
}
|
||||
result << cResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.str();
|
||||
}
|
||||
Reference in New Issue
Block a user