Adding WifiClientHistory.

This commit is contained in:
stephb9959
2022-05-15 12:32:05 -07:00
parent 70797cd436
commit c6bd651cba
11 changed files with 336 additions and 53 deletions

View File

@@ -84,6 +84,7 @@ add_executable(owanalytics
src/Daemon.cpp src/Daemon.h
src/Dashboard.h src/Dashboard.cpp
src/StorageService.cpp src/StorageService.h
src/WifiClientCache.cpp src/WifiClientCache.h
src/RESTObjects/RESTAPI_AnalyticsObjects.cpp src/RESTObjects/RESTAPI_AnalyticsObjects.h
src/StateReceiver.cpp src/StateReceiver.h
src/VenueWatcher.cpp src/VenueWatcher.h
@@ -100,7 +101,9 @@ add_executable(owanalytics
src/HealthReceiver.cpp src/HealthReceiver.h
src/StatFunc.h
src/RESTAPI/RESTAPI_board_timepoint_handler.cpp src/RESTAPI/RESTAPI_board_timepoint_handler.h
src/storage/storage_timepoints.cpp src/storage/storage_timepoints.h src/storage/storage_wificlients.cpp src/storage/storage_wificlients.h src/RESTAPI/RESTAPI_wificlienthistory_handler.cpp src/RESTAPI/RESTAPI_wificlienthistory_handler.h)
src/storage/storage_timepoints.cpp src/storage/storage_timepoints.h
src/storage/storage_wificlients.cpp src/storage/storage_wificlients.h
src/RESTAPI/RESTAPI_wificlienthistory_handler.cpp src/RESTAPI/RESTAPI_wificlienthistory_handler.h)
target_link_libraries(owanalytics PUBLIC
${Poco_LIBRARIES}

2
build
View File

@@ -1 +1 @@
63
65

View File

@@ -661,6 +661,14 @@ components:
items:
$ref: '#/components/schemas/WifiClientHistory'
MacList:
type: object
properties:
entries:
type: array
items:
type: string
#########################################################################################
##
## These are endpoints that all services in the OPenWiFI stack must provide
@@ -1196,9 +1204,30 @@ paths:
schema:
type: integer
required: false
- in: query
description: Maximum number of entries to return (if absent, no limit is assumed)
name: macsOnly
schema:
type: boolean
default: false
required: false
- in: query
description: Maximum number of entries to return (if absent, no limit is assumed)
name: macFilter
schema:
type: string
example:
112233445566, 11223344*, *5566
required: false
responses:
200:
$ref: '#/components/schemas/WifiClientHistoryList'
description: Successfully returning list of records
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/WifiClientHistoryList'
- $ref: '#/components/schemas/StringList'
403:
$ref: '#/components/responses/Unauthorized'
404:

View File

@@ -5,6 +5,7 @@
#include "APStats.h"
#include "dict_ssid.h"
#include "StorageService.h"
#include "WifiClientCache.h"
namespace OpenWifi {
@@ -259,6 +260,7 @@ namespace OpenWifi {
GetJSON("inactive",association,WFH.inactive,(uint64_t)0);
GetJSON("tx_retries",association,WFH.tx_retries,(uint64_t)0);
WifiClientCache()->AddSerialNumber(WFH.stationId);
StorageService()->WifiClientHistoryDB().CreateRecord(WFH);
if(association.contains("tid_stats") && association["tid_stats"].is_array()) {

View File

@@ -16,6 +16,7 @@
#include "StateReceiver.h"
#include "DeviceStatusReceiver.h"
#include "HealthReceiver.h"
#include "WifiClientCache.h"
namespace OpenWifi {
class Daemon *Daemon::instance_ = nullptr;
@@ -32,7 +33,8 @@ namespace OpenWifi {
StateReceiver(),
DeviceStatusReceiver(),
HealthReceiver(),
VenueCoordinator()
VenueCoordinator(),
WifiClientCache()
});
}
return instance_;

View File

@@ -3,12 +3,24 @@
//
#include "RESTAPI_wificlienthistory_handler.h"
#include "RESTAPI/RESTAPI_analytics_db_helpers.h"
#include "WifiClientCache.h"
namespace OpenWifi {
void RESTAPI_wificlienthistory_handler::DoGet() {
if(GetBoolParameter("macsOnly")) {
auto macFilter = GetParameter("macFilter","");
std::vector<uint64_t> Macs;
WifiClientCache()->FindNumbers(macFilter,500,Macs);
Poco::JSON::Array Arr;
for(const auto &mac: Macs)
Arr.add(Utils::IntToSerialNumber(mac));
Poco::JSON::Object Answer;
Answer.set("entries", Arr);
return ReturnObject(Answer);
}
auto stationId = GetBinding("client");
if(!Utils::ValidSerialNumber(stationId)) {
return BadRequest(RESTAPI::Errors::InvalidSerialNumber);

132
src/WifiClientCache.cpp Normal file
View File

@@ -0,0 +1,132 @@
//
// Created by stephane bourque on 2021-08-11.
//
#include "WifiClientCache.h"
#include <mutex>
#include "StorageService.h"
namespace OpenWifi {
int WifiClientCache::Start() {
TimerCallback_ = std::make_unique<Poco::TimerCallback<WifiClientCache>>(*this,&WifiClientCache::onTimer);
Timer_.setStartInterval( 30 * 1000); // first run in 20 seconds
Timer_.setPeriodicInterval( 60 * 60 * 1000); // 1 hours
Timer_.start(*TimerCallback_);
return 0;
}
void WifiClientCache::Stop() {
Timer_.stop();
}
void WifiClientCache::onTimer([[maybe_unused]] Poco::Timer & timer) {
std::vector<std::string> WifiClients;
if(StorageService()->WifiClientHistoryDB().GetClientMacs(WifiClients)) {
// Let's replace current cache...
std::lock_guard G(Mutex_);
SNs_.clear();
Reverse_SNs_.clear();
for(const auto &mac:WifiClients)
AddSerialNumber(mac,G);
}
}
void WifiClientCache::AddSerialNumber(const std::string &S) {
std::lock_guard G(Mutex_);
AddSerialNumber(S,G);
}
void WifiClientCache::AddSerialNumber(const std::string &S, [[maybe_unused]] std::lock_guard<std::recursive_mutex> & G) {
uint64_t SN = std::stoull(S, nullptr, 16);
if (std::find(std::begin(SNs_), std::end(SNs_), SN) == std::end(SNs_)) {
auto insert_point = std::lower_bound(SNs_.begin(), SNs_.end(), SN);
SNs_.insert(insert_point, SN);
auto R = ReverseSerialNumber(S);
uint64_t RSN = std::stoull(R, nullptr, 16);
auto rev_insert_point = std::lower_bound(Reverse_SNs_.begin(), Reverse_SNs_.end(), RSN);
Reverse_SNs_.insert(rev_insert_point, RSN);
}
}
void WifiClientCache::DeleteSerialNumber(const std::string &S) {
std::lock_guard G(Mutex_);
uint64_t SN = std::stoull(S,nullptr,16);
auto It = std::find(SNs_.begin(),SNs_.end(),SN);
if(It != SNs_.end()) {
SNs_.erase(It);
auto R = ReverseSerialNumber(S);
uint64_t RSN = std::stoull(R, nullptr, 16);
auto RIt = std::find(Reverse_SNs_.begin(),Reverse_SNs_.end(),RSN);
if(RIt != Reverse_SNs_.end()) {
Reverse_SNs_.erase(RIt);
}
}
}
uint64_t Reverse(uint64_t N) {
uint64_t Res = 0;
for (int i = 0; i < 16; i++) {
Res = (Res << 4) + (N & 0x000000000000000f);
N >>= 4;
}
Res >>= 16;
return Res;
}
void WifiClientCache::ReturnNumbers(const std::string &S, uint HowMany, const std::vector<uint64_t> &SNArr, std::vector<uint64_t> &A, bool ReverseResult) {
std::lock_guard G(Mutex_);
if (S.length() == 12) {
uint64_t SN = std::stoull(S, nullptr, 16);
auto It = std::find(SNArr.begin(), SNArr.end(), SN);
if (It != SNArr.end()) {
A.push_back(ReverseResult ? Reverse(*It) : *It);
}
} else if (S.length() < 12) {
std::string SS{S};
SS.insert(SS.end(), 12 - SS.size(), '0');
uint64_t SN = std::stoull(SS, nullptr, 16);
auto LB = std::lower_bound(SNArr.begin(), SNArr.end(), SN);
if (LB != SNArr.end()) {
for (; LB != SNArr.end() && HowMany; ++LB, --HowMany) {
if(ReverseResult) {
const auto TSN = ReverseSerialNumber(Utils::IntToSerialNumber(Reverse(*LB)));
if (S == TSN.substr(0,S.size())) {
A.emplace_back(Reverse(*LB));
} else {
break;
}
} else {
const auto TSN = Utils::IntToSerialNumber(*LB);
if (S == TSN.substr(0, S.size())) {
A.emplace_back(*LB);
} else {
break;
}
}
}
}
}
}
void WifiClientCache::FindNumbers(const std::string &S, uint HowMany, std::vector<uint64_t> &A) {
if(S.empty())
return;
if (S[0] == '*') {
std::string Reversed;
std::copy(rbegin(S), rend(S)-1, std::back_inserter(Reversed));
if(Reversed.empty())
return;
return ReturnNumbers(Reversed, HowMany, Reverse_SNs_, A, true);
} else {
return ReturnNumbers(S, HowMany, SNs_, A, false);
}
}
}

55
src/WifiClientCache.h Normal file
View File

@@ -0,0 +1,55 @@
//
// Created by stephane bourque on 2021-08-11.
//
#pragma once
#include "framework/MicroService.h"
#include "Poco/Timer.h"
namespace OpenWifi {
class WifiClientCache : public SubSystemServer {
public:
static auto instance() {
static auto instance_ = new WifiClientCache;
return instance_;
}
int Start() override;
void Stop() override;
void AddSerialNumber(const std::string &SerialNumber);
void DeleteSerialNumber(const std::string &SerialNumber);
void FindNumbers(const std::string &SerialNumber, uint HowMany, std::vector<uint64_t> &A);
inline bool NumberExists(uint64_t SerialNumber) {
std::lock_guard G(Mutex_);
return std::find(SNs_.begin(),SNs_.end(),SerialNumber)!=SNs_.end();
}
static inline std::string ReverseSerialNumber(const std::string &S) {
std::string ReversedString;
std::copy(rbegin(S),rend(S),std::back_inserter(ReversedString));
return ReversedString;
}
void onTimer(Poco::Timer & timer);
private:
std::vector<uint64_t> SNs_;
std::vector<uint64_t> Reverse_SNs_;
Poco::Timer Timer_;
std::unique_ptr<Poco::TimerCallback<WifiClientCache>> TimerCallback_;
void AddSerialNumber(const std::string &S, std::lock_guard<std::recursive_mutex> & G);
void ReturnNumbers(const std::string &S, uint HowMany, const std::vector<uint64_t> & SNArr, std::vector<uint64_t> &A, bool ReverseResult);
WifiClientCache() noexcept:
SubSystemServer("SerialNumberCache", "SNCACHE-SVR", "serialcache")
{
SNs_.reserve(2000);
}
};
inline auto WifiClientCache() { return WifiClientCache::instance(); }
} // namespace OpenWiFi

View File

@@ -74,6 +74,30 @@ namespace OpenWifi {
to = 2;
return true;
}
bool WifiClientHistoryDB::GetClientMacs(std::vector<std::string> &Macs) {
try {
Poco::Data::Session Session = Pool_.get();
Poco::Data::Statement Select(Session);
std::string St = "Select distinct(stationId) from " + TableName_;
typedef Poco::Tuple< std::string > Record;
std::vector<Record> RecordList;
Select << St,
Poco::Data::Keywords::into(RecordList);
Select.execute();
for(const auto &i:RecordList)
Macs.push_back(i.get<0>());
return true;
} catch (const Poco::Exception &E) {
Logger().log(E);
}
return false;
}
}
template<> void ORM::DB<OpenWifi::WifiClientHistoryDBRecordType, OpenWifi::AnalyticsObjects::WifiClientHistory>::Convert(const OpenWifi::WifiClientHistoryDBRecordType &In, OpenWifi::AnalyticsObjects::WifiClientHistory &Out) {

View File

@@ -51,6 +51,7 @@ namespace OpenWifi {
public:
WifiClientHistoryDB( OpenWifi::DBType T, Poco::Data::SessionPool & P, Poco::Logger &L);
virtual ~WifiClientHistoryDB() {};
bool GetClientMacs(std::vector<std::string> &Macs);
private:
bool Upgrade(uint32_t from, uint32_t &to) override;
};

View File

@@ -85,6 +85,7 @@ findbrowser() {
setprov() {
if [ -z ${OWPROV_OVERRIDE+x} ]; then
curl ${FLAGS} -X GET "https://${OWSEC}/api/v1/systemEndpoints" \
-H "Content-Type: application/json" \
-H "accept: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
rawurl="$(cat ${result_file} | jq -r '.endpoints[] | select( .type == "owprov" ) | .uri')"
@@ -111,7 +112,8 @@ fi
setanalytics() {
if [ -z ${OWANALYTICS_OVERRIDE+x} ]; then
curl ${FLAGS} -X GET "https://${OWSEC}/api/v1/systemEndpoints" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
rawurl="$(cat ${result_file} | jq -r '.endpoints[] | select( .type == "owanalytics" ) | .uri')"
if [[ ! -z "${rawurl}" ]]; then
@@ -136,9 +138,9 @@ setanalytics() {
logout() {
curl ${FLAGS} -X DELETE -H "Content-Type: application/json" \
curl ${FLAGS} -X DELETE "https://${OWSEC}/api/v1/oauth2/${token}" \
-H "Authorization: Bearer ${token}" \
"https://${OWSEC}/api/v1/oauth2/${token}"
-H "Content-Type: application/json"
rm -rf token.json
}
@@ -154,14 +156,15 @@ contactcount() {
curl ${FLAGS} "https://${OWPROV}/api/v1/contact?countOnly=true" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-H "accept: application/json" > ${result_file}
-H "Accept: application/json" > ${result_file}
jq < ${result_file}
}
setloglevel() {
payload="{ \"command\" : \"setloglevel\" , \"subsystems\" : [ { \"tag\" : \"$1\" , \"value\" : \"$2\" } ] }"
curl ${FLAGS} -X POST "https://${OWPROV}/api/v1/system" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-d "$payload"
}
@@ -169,7 +172,8 @@ setloglevel() {
getloglevels() {
payload="{ \"command\" : \"getloglevels\" }"
curl ${FLAGS} -X POST "https://${OWPROV}/api/v1/system" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-d "$payload"
}
@@ -177,7 +181,8 @@ getloglevels() {
getloglevelnames() {
payload="{ \"command\" : \"getloglevelnames\" }"
curl ${FLAGS} -X POST "https://${OWPROV}/api/v1/system" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-d "$payload"
}
@@ -185,14 +190,15 @@ getloglevelnames() {
getsubsystemnames() {
payload="{ \"command\" : \"getsubsystemnames\" }"
curl ${FLAGS} -X POST "https://${OWPROV}/api/v1/system" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-d "$payload"
}
systeminfo() {
curl ${FLAGS} -X GET "https://${OWPROV}/api/v1/system?command=info" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
@@ -200,70 +206,86 @@ systeminfo() {
reloadsubsystem() {
payload="{ \"command\" : \"reload\", \"subsystems\" : [ \"$1\" ] }"
curl ${FLAGS} -X POST "https://${OWPROV}/api/v1/system" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-d "$payload"
}
tree() {
curl ${FLAGS} "https://${OWPROV}/api/v1/entity?getTree=true" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" \
-H "accept: application/json" > ${result_file}
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
createboard() {
payload="{ \"name\" : \"MegaLab\", \"venueList\" : [ { \"id\" : \"$1\" , \"name\" : \"MegaLab Venue\" , \"retention\" : 86000 , \"interval\" : 60, \"monitorSubVenues\" : true } ] }"
curl ${FLAGS} -X POST "https://${OWANALYTICS}/api/v1/board/0" \
-H "accept: application/json" \
-H "Authorization: Bearer ${token}" \
-d "$payload" > ${result_file}
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
deleteboard() {
curl ${FLAGS} -X DELETE "https://${OWANALYTICS}/api/v1/board/$1" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
listboards() {
curl ${FLAGS} -X GET "https://${OWANALYTICS}/api/v1/boards" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
boardsforvenue() {
curl ${FLAGS} -X GET "https://${OWANALYTICS}/api/v1/boards?forVenue=$1" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
boarddevices() {
curl ${FLAGS} -X GET "https://${OWANALYTICS}/api/v1/board/$1/devices" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
points() {
curl ${FLAGS} -X GET "https://${OWANALYTICS}/api/v1/board/$1/timepoints?pointsStatsOnly=true" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
pointstats() {
curl ${FLAGS} -X GET "https://${OWANALYTICS}/api/v1/board/$1/timepoints?stats=true" \
-H "accept: application/json" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
wificlientsonly() {
curl ${FLAGS} -X GET "https://${OWANALYTICS}/api/v1/wifiClientHistory?macsOnly=true&macFilter=$1" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${token}" > ${result_file}
jq < ${result_file}
}
shopt -s nocasematch
case "$1" in
"login") login; echo "You are logged in..." ; logout ;;
@@ -281,6 +303,7 @@ case "$1" in
"boardsforvenue") login; boardsforvenue $2; logout;;
"points") login; points $2; logout;;
"pointstats") login; pointstats $2; logout;;
"wificlientsonly") login; wificlientsonly $2; logout;;
*) help ;;
esac