PiPedal 1.2.33 initial commit

This commit is contained in:
Robin Davies
2024-08-08 12:53:02 -04:00
parent 0e378997a5
commit a3847c8184
255 changed files with 22008 additions and 1791 deletions
+71
View File
@@ -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} )
+370
View File
@@ -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 &regDb = RegDb::GetInstance();
const WifiRegulations &regulations = 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 &regDb = 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 &regDb = 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;
// }
}
+175
View File
@@ -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();
}
+164
View File
@@ -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;
}
+260
View File
@@ -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 << "&lt;"; break;
case '>': os << "&gt;"; break;
case '&': os << "&amp;"; 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
+642
View File
@@ -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 &regulation: 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);
}
}
+269
View File
@@ -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;
}
+188
View File
@@ -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;
}
+153
View File
@@ -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;
}
+16
View File
@@ -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;
}
+86
View File
@@ -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);
}
}
}
+19
View File
@@ -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());
}
}
};
}
+37
View File
@@ -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);
};
}
+60
View File
@@ -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;
}
+55
View File
@@ -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"
+75
View File
@@ -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;
};
}
+39
View File
@@ -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);
};
}
+29
View File
@@ -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
+550
View File
@@ -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
+38
View File
@@ -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;}
}
+639
View File
@@ -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);
// }
+560
View File
@@ -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.
+26
View File
@@ -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())
+102
View File
@@ -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();
}