Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
38
libs/tracking/CMakeLists.txt
Normal file
38
libs/tracking/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
project(tracking)
|
||||
|
||||
set(
|
||||
SRC
|
||||
connection.cpp
|
||||
connection.hpp
|
||||
protocol.cpp
|
||||
protocol.hpp
|
||||
reporter.cpp
|
||||
reporter.hpp
|
||||
archival_file.cpp
|
||||
archival_file.hpp
|
||||
archival_manager.cpp
|
||||
archival_manager.hpp
|
||||
archival_reporter.cpp
|
||||
archival_reporter.hpp
|
||||
archive.cpp
|
||||
archive.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
routing
|
||||
traffic
|
||||
|
||||
PRIVATE
|
||||
base
|
||||
coding
|
||||
geometry
|
||||
platform
|
||||
)
|
||||
|
||||
omim_add_test_subdirectory(tracking_tests)
|
||||
|
||||
if (USE_LIBFUZZER)
|
||||
add_subdirectory(tracking_fuzz_tests)
|
||||
endif()
|
||||
133
libs/tracking/archival_file.cpp
Normal file
133
libs/tracking/archival_file.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include "tracking/archival_file.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/zip_creator.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
#ifdef DEBUG
|
||||
size_t constexpr kMaxFilesTotalSizeToSendBytes = 100 * 1024; // 0.1 Mb
|
||||
#else
|
||||
size_t constexpr kMaxFilesTotalSizeToSendBytes = 1000 * 1024; // 1 Mb
|
||||
#endif
|
||||
|
||||
char constexpr kDelimiter = '_';
|
||||
} // namespace
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
namespace archival_file
|
||||
{
|
||||
void FilesAccumulator::HandleFile(std::string const & fileName)
|
||||
{
|
||||
uint64_t fileSize = 0;
|
||||
if (!Platform::GetFileSizeByFullPath(fileName, fileSize))
|
||||
{
|
||||
LOG(LDEBUG, ("File does not exist", fileName));
|
||||
return;
|
||||
}
|
||||
if (fileSize == 0)
|
||||
{
|
||||
LOG(LDEBUG, ("File is empty", fileName));
|
||||
base::DeleteFileX(fileName);
|
||||
return;
|
||||
}
|
||||
if (fileSize > kMaxFilesTotalSizeToSendBytes)
|
||||
{
|
||||
LOG(LDEBUG, ("File is too large", fileName, fileSize));
|
||||
base::DeleteFileX(fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
FileInfo const meta = ParseArchiveFilename(fileName);
|
||||
auto const insData = m_filesByType.emplace(meta.m_trackType, FilesBatch());
|
||||
auto const it = insData.first;
|
||||
auto & fileBatch = it->second;
|
||||
if (!insData.second)
|
||||
{
|
||||
if (fileBatch.m_totalSize + fileSize > kMaxFilesTotalSizeToSendBytes)
|
||||
return;
|
||||
}
|
||||
|
||||
fileBatch.m_totalSize += fileSize;
|
||||
fileBatch.m_files.push_back(fileName);
|
||||
}
|
||||
|
||||
std::vector<std::string> FilesAccumulator::PrepareArchives(std::string const & path)
|
||||
{
|
||||
std::vector<std::string> archives;
|
||||
try
|
||||
{
|
||||
for (auto const & it : m_filesByType)
|
||||
{
|
||||
if (it.second.m_files.empty())
|
||||
continue;
|
||||
std::string const archivePath = base::JoinPath(
|
||||
path, base::GetNameFromFullPathWithoutExt(it.second.m_files[0]) + ARCHIVE_TRACKS_ZIPPED_FILE_EXTENSION);
|
||||
|
||||
if (CreateZipFromFiles(it.second.m_files, archivePath, CompressionLevel::NoCompression))
|
||||
archives.emplace_back(archivePath);
|
||||
}
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Error while archiving files", e.what()));
|
||||
}
|
||||
return archives;
|
||||
}
|
||||
|
||||
void FilesAccumulator::DeleteProcessedFiles()
|
||||
{
|
||||
for (auto const & it : m_filesByType)
|
||||
for (auto const & file : it.second.m_files)
|
||||
base::DeleteFileX(file);
|
||||
}
|
||||
|
||||
std::string GetArchiveFilename(uint8_t protocolVersion, std::chrono::seconds timestamp,
|
||||
routing::RouterType const & trackType)
|
||||
{
|
||||
std::string filename;
|
||||
size_t constexpr kTrackFilenameSize = 20;
|
||||
filename.reserve(kTrackFilenameSize); // All filename parts have fixed length.
|
||||
filename = std::to_string(protocolVersion) + kDelimiter + std::to_string(timestamp.count()) + kDelimiter +
|
||||
std::to_string(static_cast<uint8_t>(trackType)) + ARCHIVE_TRACKS_FILE_EXTENSION;
|
||||
CHECK_EQUAL(filename.size(), kTrackFilenameSize, ());
|
||||
return filename;
|
||||
}
|
||||
|
||||
FileInfo ParseArchiveFilename(std::string const & fileName)
|
||||
{
|
||||
std::string const metaData = base::GetNameFromFullPathWithoutExt(fileName);
|
||||
size_t const indexFirstDelim = metaData.find(kDelimiter);
|
||||
size_t const indexLastDelim = metaData.rfind(kDelimiter);
|
||||
if (indexFirstDelim != 1 || indexLastDelim != 12)
|
||||
{
|
||||
LOG(LWARNING, ("Could not find delimiters in filename", fileName));
|
||||
return {};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FileInfo res;
|
||||
res.m_protocolVersion = static_cast<uint32_t>(std::stoul(metaData.substr(0, indexFirstDelim)));
|
||||
res.m_timestamp = std::stoul(metaData.substr(indexFirstDelim + 1, indexLastDelim - indexFirstDelim - 1));
|
||||
res.m_trackType = static_cast<routing::RouterType>(std::stoul(metaData.substr(indexLastDelim + 1)));
|
||||
return res;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Error while parsing filename", e.what()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace archival_file
|
||||
} // namespace tracking
|
||||
51
libs/tracking/archival_file.hpp
Normal file
51
libs/tracking/archival_file.hpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing/router.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
namespace archival_file
|
||||
{
|
||||
/// \brief Data contained in the tracks filename
|
||||
struct FileInfo
|
||||
{
|
||||
uint64_t m_timestamp = 0;
|
||||
routing::RouterType m_trackType = routing::RouterType::Vehicle;
|
||||
uint32_t m_protocolVersion = std::numeric_limits<uint8_t>::max();
|
||||
};
|
||||
|
||||
/// \brief Tracks files with total size for archiving in single zip
|
||||
struct FilesBatch
|
||||
{
|
||||
size_t m_totalSize;
|
||||
std::vector<std::string> m_files;
|
||||
};
|
||||
|
||||
/// \brief Helper-class for zipping tracks and deleting respective source files
|
||||
class FilesAccumulator
|
||||
{
|
||||
public:
|
||||
void HandleFile(std::string const & fileName);
|
||||
std::vector<std::string> PrepareArchives(std::string const & path);
|
||||
void DeleteProcessedFiles();
|
||||
|
||||
private:
|
||||
std::map<routing::RouterType, FilesBatch> m_filesByType;
|
||||
};
|
||||
|
||||
/// \returns file name with extension containing |protocolVersion|, |timestamp| and |trackType|
|
||||
/// of the archived tracks, separated by the delimiter. Example: 1_1573635326_0.track
|
||||
std::string GetArchiveFilename(uint8_t protocolVersion, std::chrono::seconds timestamp,
|
||||
routing::RouterType const & trackType);
|
||||
|
||||
/// \returns meta-information about the archived tracks saved in the |fileName|.
|
||||
FileInfo ParseArchiveFilename(std::string const & fileName);
|
||||
} // namespace archival_file
|
||||
} // namespace tracking
|
||||
206
libs/tracking/archival_manager.cpp
Normal file
206
libs/tracking/archival_manager.cpp
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
#include "tracking/archival_manager.hpp"
|
||||
|
||||
#include "tracking/archival_file.hpp"
|
||||
|
||||
#include "platform/http_payload.hpp"
|
||||
#include "platform/http_uploader_background.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string const kTracksArchive = "tracks_archive";
|
||||
std::string const kFileTimestampName = "latest_upload";
|
||||
|
||||
std::chrono::seconds GetTimestamp()
|
||||
{
|
||||
auto const now = std::chrono::system_clock::now();
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
|
||||
}
|
||||
|
||||
std::string GetTimestampFile(std::string const & tracksDir)
|
||||
{
|
||||
return base::JoinPath(tracksDir, kFileTimestampName);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
std::string GetTracksDirectory()
|
||||
{
|
||||
return base::JoinPath(GetPlatform().WritableDir(), kTracksArchive);
|
||||
}
|
||||
|
||||
ArchivalManager::ArchivalManager(std::string const & url)
|
||||
: m_url(url)
|
||||
, m_tracksDir(GetTracksDirectory())
|
||||
, m_timestampFile(GetTimestampFile(m_tracksDir))
|
||||
{}
|
||||
|
||||
void ArchivalManager::SetSettings(ArchivingSettings const & settings)
|
||||
{
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
std::optional<FileWriter> ArchivalManager::GetFileWriter(routing::RouterType const & trackType) const
|
||||
{
|
||||
std::string const fileName = archival_file::GetArchiveFilename(m_settings.m_version, GetTimestamp(), trackType);
|
||||
try
|
||||
{
|
||||
return std::optional<FileWriter>(base::JoinPath(m_tracksDir, fileName));
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool ArchivalManager::CreateTracksDir() const
|
||||
{
|
||||
if (!Platform::MkDirChecked(m_tracksDir))
|
||||
{
|
||||
LOG(LWARNING, ("Directory could not be created", m_tracksDir));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t ArchivalManager::IntervalBetweenDumpsSeconds()
|
||||
{
|
||||
return m_settings.m_dumpIntervalSeconds;
|
||||
}
|
||||
|
||||
std::vector<std::string> ArchivalManager::GetFilesOrderedByCreation(std::string const & extension) const
|
||||
{
|
||||
Platform::FilesList files;
|
||||
Platform::GetFilesByExt(m_tracksDir, extension, files);
|
||||
|
||||
for (auto & file : files)
|
||||
file = base::JoinPath(m_tracksDir, file);
|
||||
|
||||
// Protocol version and timestamp are contained in the filenames so they are ordered from
|
||||
// oldest to newest.
|
||||
std::sort(files.begin(), files.end());
|
||||
return files;
|
||||
}
|
||||
|
||||
size_t ArchivalManager::GetTimeFromLastUploadSeconds()
|
||||
{
|
||||
auto const timeStamp = GetTimestamp();
|
||||
auto const timeStampPrev = ReadTimestamp(m_timestampFile);
|
||||
return static_cast<size_t>((timeStamp - timeStampPrev).count());
|
||||
}
|
||||
|
||||
bool ArchivalManager::ReadyToUpload()
|
||||
{
|
||||
return GetTimeFromLastUploadSeconds() > m_settings.m_uploadIntervalSeconds;
|
||||
}
|
||||
|
||||
void ArchivalManager::PrepareUpload(std::vector<std::string> const & files)
|
||||
{
|
||||
if (files.empty())
|
||||
return;
|
||||
|
||||
archival_file::FilesAccumulator accum;
|
||||
|
||||
for (auto const & file : files)
|
||||
accum.HandleFile(file);
|
||||
|
||||
auto const archives = accum.PrepareArchives(m_tracksDir);
|
||||
for (auto const & archive : archives)
|
||||
CreateUploadTask(archive);
|
||||
|
||||
WriteTimestamp(m_timestampFile);
|
||||
|
||||
accum.DeleteProcessedFiles();
|
||||
}
|
||||
|
||||
void ArchivalManager::CreateUploadTask(std::string const & filePath)
|
||||
{
|
||||
platform::HttpPayload payload;
|
||||
payload.m_url = m_url;
|
||||
payload.m_filePath = filePath;
|
||||
payload.m_headers = {};
|
||||
platform::HttpUploaderBackground uploader(payload);
|
||||
uploader.Upload();
|
||||
}
|
||||
|
||||
bool ArchivalManager::CanDumpToDisk(size_t neededFreeSpace) const
|
||||
{
|
||||
size_t const neededSize = std::max(m_settings.m_minFreeSpaceOnDiskBytes, neededFreeSpace);
|
||||
auto const storageStatus = GetPlatform().GetWritableStorageStatus(neededSize);
|
||||
if (storageStatus != Platform::TStorageStatus::STORAGE_OK)
|
||||
{
|
||||
LOG(LWARNING, ("Can not dump stats to disk. Storage status:", storageStatus));
|
||||
return false;
|
||||
}
|
||||
return CreateTracksDir();
|
||||
}
|
||||
|
||||
std::chrono::seconds ArchivalManager::ReadTimestamp(std::string const & filePath)
|
||||
{
|
||||
if (!Platform::IsFileExistsByFullPath(filePath))
|
||||
return std::chrono::seconds(0);
|
||||
|
||||
try
|
||||
{
|
||||
FileReader reader(filePath);
|
||||
ReaderSource<FileReader> src(reader);
|
||||
uint64_t ts = 0;
|
||||
ReadPrimitiveFromSource(src, ts);
|
||||
return std::chrono::seconds(ts);
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Error reading timestamp from file", e.what()));
|
||||
}
|
||||
return std::chrono::seconds(0);
|
||||
}
|
||||
|
||||
size_t ArchivalManager::GetMaxSavedFilesCount(std::string const & extension) const
|
||||
{
|
||||
if (extension == ARCHIVE_TRACKS_FILE_EXTENSION)
|
||||
return m_settings.m_maxFilesToSave;
|
||||
if (extension == ARCHIVE_TRACKS_ZIPPED_FILE_EXTENSION)
|
||||
return m_settings.m_maxArchivesToSave;
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void ArchivalManager::DeleteOldDataByExtension(std::string const & extension) const
|
||||
{
|
||||
auto const files = GetFilesOrderedByCreation(extension);
|
||||
auto const maxCount = GetMaxSavedFilesCount(extension);
|
||||
if (files.size() > maxCount)
|
||||
for (size_t i = 0; i < files.size() - maxCount; ++i)
|
||||
base::DeleteFileX(files[i]);
|
||||
}
|
||||
|
||||
void ArchivalManager::WriteTimestamp(std::string const & filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
FileWriter writer(filePath);
|
||||
uint64_t const ts = GetTimestamp().count();
|
||||
WriteToSink(writer, ts);
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Error writing timestamp to file", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void ArchivalManager::PrepareUpload()
|
||||
{
|
||||
if (!ReadyToUpload())
|
||||
return;
|
||||
|
||||
auto files = GetFilesOrderedByCreation(ARCHIVE_TRACKS_FILE_EXTENSION);
|
||||
PrepareUpload(files);
|
||||
}
|
||||
} // namespace tracking
|
||||
91
libs/tracking/archival_manager.hpp
Normal file
91
libs/tracking/archival_manager.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#pragma once
|
||||
|
||||
#include "routing/router.hpp"
|
||||
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
struct ArchivingSettings
|
||||
{
|
||||
size_t m_minFreeSpaceOnDiskBytes = 30 * 1024 * 1024; // 30 Mb
|
||||
size_t m_dumpIntervalSeconds = 60;
|
||||
size_t m_maxFilesToSave = 100;
|
||||
size_t m_maxArchivesToSave = 10;
|
||||
size_t m_uploadIntervalSeconds = 60 * 60 * 24;
|
||||
uint32_t m_version = 1;
|
||||
};
|
||||
|
||||
/// \returns path to the directory with the tracks.
|
||||
std::string GetTracksDirectory();
|
||||
|
||||
class ArchivalManager
|
||||
{
|
||||
public:
|
||||
ArchivalManager(std::string const & url);
|
||||
|
||||
ArchivalManager(ArchivalManager const &) = delete;
|
||||
ArchivalManager & operator=(ArchivalManager const &) = delete;
|
||||
|
||||
void SetSettings(ArchivingSettings const & settings);
|
||||
|
||||
/// \brief Saves to file contents of the |archive| if it is necessary to |dumpAnyway|
|
||||
/// or the |archive| is ready to be dumped.
|
||||
template <class T>
|
||||
void Dump(T & archive, routing::RouterType const & trackType, bool dumpAnyway);
|
||||
|
||||
/// \returns time span between Archive dumps.
|
||||
size_t IntervalBetweenDumpsSeconds();
|
||||
|
||||
/// \brief Prepares zipped files and creates task for uploading them.
|
||||
void PrepareUpload();
|
||||
|
||||
/// \brief Deletes the oldest files if the total number of archive files is exceeded.
|
||||
void DeleteOldDataByExtension(std::string const & extension) const;
|
||||
|
||||
private:
|
||||
bool ReadyToUpload();
|
||||
|
||||
size_t GetTimeFromLastUploadSeconds();
|
||||
size_t GetMaxSavedFilesCount(std::string const & extension) const;
|
||||
std::chrono::seconds ReadTimestamp(std::string const & filePath);
|
||||
void WriteTimestamp(std::string const & filePath);
|
||||
|
||||
std::optional<FileWriter> GetFileWriter(routing::RouterType const & trackType) const;
|
||||
|
||||
std::vector<std::string> GetFilesOrderedByCreation(std::string const & extension) const;
|
||||
bool CreateTracksDir() const;
|
||||
bool CanDumpToDisk(size_t neededFreeSpace) const;
|
||||
|
||||
void PrepareUpload(std::vector<std::string> const & files);
|
||||
void CreateUploadTask(std::string const & filePath);
|
||||
|
||||
std::string const m_url;
|
||||
ArchivingSettings m_settings;
|
||||
std::string m_tracksDir;
|
||||
std::string m_timestampFile;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void ArchivalManager::Dump(T & archive, routing::RouterType const & trackType, bool dumpAnyway)
|
||||
{
|
||||
if (!(archive.ReadyToDump() || (dumpAnyway && archive.Size() > 0)))
|
||||
return;
|
||||
|
||||
if (!CanDumpToDisk(archive.Size()))
|
||||
return;
|
||||
|
||||
if (auto dst = GetFileWriter(trackType))
|
||||
archive.Write(*dst);
|
||||
}
|
||||
} // namespace tracking
|
||||
100
libs/tracking/archival_reporter.cpp
Normal file
100
libs/tracking/archival_reporter.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include "tracking/archival_reporter.hpp"
|
||||
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace
|
||||
{
|
||||
double constexpr kRequiredHorizontalAccuracyM = 15.0;
|
||||
} // namespace
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
ArchivalReporter::ArchivalReporter(std::string const & host)
|
||||
: m_archiveBicycle(kItemsForDump, kMinDelaySecondsBicycle)
|
||||
, m_archivePedestrian(kItemsForDump, kMinDelaySecondsPedestrian)
|
||||
, m_archiveCar(kItemsForDump, kMinDelaySecondsCar)
|
||||
, m_manager(host)
|
||||
, m_isAlive(true)
|
||||
, m_threadDump([this] { Run(); })
|
||||
{}
|
||||
|
||||
ArchivalReporter::~ArchivalReporter()
|
||||
{
|
||||
DumpToDisk(true /* dumpAnyway */);
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutexRun);
|
||||
m_isAlive = false;
|
||||
}
|
||||
m_cvDump.notify_one();
|
||||
m_threadDump.join();
|
||||
}
|
||||
|
||||
void ArchivalReporter::SetArchivalManagerSettings(ArchivingSettings const & settings)
|
||||
{
|
||||
m_manager.SetSettings(settings);
|
||||
}
|
||||
|
||||
void ArchivalReporter::Run()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutexRun);
|
||||
m_manager.DeleteOldDataByExtension(ARCHIVE_TRACKS_FILE_EXTENSION);
|
||||
m_manager.DeleteOldDataByExtension(ARCHIVE_TRACKS_ZIPPED_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
while (m_isAlive)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutexRun);
|
||||
m_cvDump.wait_for(lock, std::chrono::seconds(m_manager.IntervalBetweenDumpsSeconds()),
|
||||
[this] { return !m_isAlive; });
|
||||
DumpToDisk(true);
|
||||
m_manager.PrepareUpload();
|
||||
}
|
||||
|
||||
LOG(LDEBUG, ("Exiting thread for dumping and uploading tracks"));
|
||||
}
|
||||
|
||||
void ArchivalReporter::Insert(routing::RouterType const & trackType, location::GpsInfo const & info,
|
||||
traffic::SpeedGroup const & speedGroup)
|
||||
{
|
||||
if (info.m_horizontalAccuracy > kRequiredHorizontalAccuracyM)
|
||||
return;
|
||||
|
||||
switch (trackType)
|
||||
{
|
||||
case routing::RouterType::Vehicle:
|
||||
{
|
||||
if (!m_archiveCar.Add(info, speedGroup))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case routing::RouterType::Bicycle:
|
||||
{
|
||||
if (!m_archiveBicycle.Add(info))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case routing::RouterType::Pedestrian:
|
||||
{
|
||||
if (!m_archivePedestrian.Add(info))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
|
||||
DumpToDisk();
|
||||
}
|
||||
|
||||
void ArchivalReporter::DumpToDisk(bool dumpAnyway)
|
||||
{
|
||||
m_manager.Dump(m_archiveCar, routing::RouterType::Vehicle, dumpAnyway);
|
||||
m_manager.Dump(m_archiveBicycle, routing::RouterType::Bicycle, dumpAnyway);
|
||||
m_manager.Dump(m_archivePedestrian, routing::RouterType::Pedestrian, dumpAnyway);
|
||||
}
|
||||
} // namespace tracking
|
||||
65
libs/tracking/archival_reporter.hpp
Normal file
65
libs/tracking/archival_reporter.hpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#pragma once
|
||||
|
||||
#include "tracking/archival_manager.hpp"
|
||||
#include "tracking/archive.hpp"
|
||||
|
||||
#include "traffic/speed_groups.hpp"
|
||||
|
||||
#include "routing/router.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "base/thread.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
double constexpr kMinDelaySecondsCar = 1.0;
|
||||
double constexpr kMinDelaySecondsBicycle = 2.0;
|
||||
double constexpr kMinDelaySecondsPedestrian = 3.0;
|
||||
|
||||
double constexpr kMinDelaySeconds =
|
||||
std::min(kMinDelaySecondsCar, std::min(kMinDelaySecondsBicycle, kMinDelaySecondsPedestrian));
|
||||
|
||||
// Number of items for at least 20 minutes.
|
||||
auto constexpr kItemsForDump = static_cast<size_t>(20.0 * 60.0 / kMinDelaySeconds);
|
||||
|
||||
// Archive template instances.
|
||||
using Archive = BasicArchive<Packet>;
|
||||
using ArchiveCar = BasicArchive<PacketCar>;
|
||||
|
||||
class ArchivalReporter
|
||||
{
|
||||
public:
|
||||
explicit ArchivalReporter(std::string const & host);
|
||||
~ArchivalReporter();
|
||||
|
||||
ArchivalReporter(ArchivalReporter const &) = delete;
|
||||
ArchivalReporter & operator=(ArchivalReporter const &) = delete;
|
||||
|
||||
void SetArchivalManagerSettings(ArchivingSettings const & settings);
|
||||
|
||||
void Insert(routing::RouterType const & trackType, location::GpsInfo const & info,
|
||||
traffic::SpeedGroup const & speedGroup);
|
||||
void DumpToDisk(bool dumpAnyway = false);
|
||||
|
||||
private:
|
||||
void Run();
|
||||
|
||||
Archive m_archiveBicycle;
|
||||
Archive m_archivePedestrian;
|
||||
ArchiveCar m_archiveCar;
|
||||
ArchivalManager m_manager;
|
||||
|
||||
bool m_isAlive;
|
||||
std::mutex m_mutexRun;
|
||||
std::condition_variable m_cvDump;
|
||||
|
||||
threads::SimpleThread m_threadDump;
|
||||
};
|
||||
} // namespace tracking
|
||||
46
libs/tracking/archive.cpp
Normal file
46
libs/tracking/archive.cpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#include "tracking/archive.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
namespace helpers
|
||||
{
|
||||
double const Restrictions::kMinDeltaLat = ms::LatLon::kMinLat - ms::LatLon::kMaxLat;
|
||||
double const Restrictions::kMaxDeltaLat = ms::LatLon::kMaxLat - ms::LatLon::kMinLat;
|
||||
double const Restrictions::kMinDeltaLon = ms::LatLon::kMinLon - ms::LatLon::kMaxLon;
|
||||
double const Restrictions::kMaxDeltaLon = ms::LatLon::kMaxLon - ms::LatLon::kMinLon;
|
||||
|
||||
Limits GetLimits(bool isDelta)
|
||||
{
|
||||
if (isDelta)
|
||||
{
|
||||
return {Restrictions::kMinDeltaLat, Restrictions::kMaxDeltaLat, Restrictions::kMinDeltaLon,
|
||||
Restrictions::kMaxDeltaLon};
|
||||
}
|
||||
return {ms::LatLon::kMinLat, ms::LatLon::kMaxLat, ms::LatLon::kMinLon, ms::LatLon::kMaxLon};
|
||||
}
|
||||
} // namespace helpers
|
||||
|
||||
Packet::Packet() : m_lat(0.0), m_lon(0.0), m_timestamp(0) {}
|
||||
|
||||
Packet::Packet(location::GpsInfo const & info)
|
||||
: m_lat(info.m_latitude)
|
||||
, m_lon(info.m_longitude)
|
||||
, m_timestamp(info.m_timestamp)
|
||||
{}
|
||||
|
||||
Packet::Packet(double lat, double lon, uint32_t timestamp) : m_lat(lat), m_lon(lon), m_timestamp(timestamp) {}
|
||||
|
||||
PacketCar::PacketCar() : m_speedGroup(traffic::SpeedGroup::Unknown) {}
|
||||
|
||||
PacketCar::PacketCar(location::GpsInfo const & info, traffic::SpeedGroup const & speedGroup)
|
||||
: Packet(info)
|
||||
, m_speedGroup(speedGroup)
|
||||
{}
|
||||
|
||||
PacketCar::PacketCar(double lat, double lon, uint32_t timestamp, traffic::SpeedGroup speed)
|
||||
: Packet(lat, lon, timestamp)
|
||||
, m_speedGroup(speed)
|
||||
{}
|
||||
} // namespace tracking
|
||||
308
libs/tracking/archive.hpp
Normal file
308
libs/tracking/archive.hpp
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
#pragma once
|
||||
|
||||
#include "traffic/speed_groups.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
|
||||
#include "coding/point_coding.hpp"
|
||||
#include "coding/reader.hpp"
|
||||
#include "coding/varint.hpp"
|
||||
#include "coding/writer.hpp"
|
||||
#include "coding/zlib.hpp"
|
||||
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-copy"
|
||||
#endif
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
namespace helpers
|
||||
{
|
||||
struct Restrictions
|
||||
{
|
||||
static double const kMinDeltaLat;
|
||||
static double const kMaxDeltaLat;
|
||||
static double const kMinDeltaLon;
|
||||
static double const kMaxDeltaLon;
|
||||
};
|
||||
|
||||
struct Limits
|
||||
{
|
||||
double m_minLat;
|
||||
double m_maxLat;
|
||||
double m_minLon;
|
||||
double m_maxLon;
|
||||
};
|
||||
|
||||
Limits GetLimits(bool isDelta);
|
||||
} // namespace helpers
|
||||
|
||||
struct Packet
|
||||
{
|
||||
Packet();
|
||||
explicit Packet(location::GpsInfo const & info);
|
||||
Packet(double lat, double lon, uint32_t timestamp);
|
||||
|
||||
double m_lat;
|
||||
double m_lon;
|
||||
uint32_t m_timestamp;
|
||||
};
|
||||
|
||||
struct PacketCar : Packet
|
||||
{
|
||||
PacketCar();
|
||||
PacketCar(location::GpsInfo const & info, traffic::SpeedGroup const & speedGroup);
|
||||
PacketCar(double lat, double lon, uint32_t timestamp, traffic::SpeedGroup speed);
|
||||
|
||||
traffic::SpeedGroup m_speedGroup;
|
||||
};
|
||||
|
||||
template <typename Pack>
|
||||
class BasicArchive
|
||||
{
|
||||
public:
|
||||
BasicArchive(size_t maxSize, double minDelaySeconds);
|
||||
|
||||
template <typename Writer>
|
||||
bool Write(Writer & dst);
|
||||
|
||||
template <typename Reader>
|
||||
bool Read(Reader & src);
|
||||
|
||||
template <typename... PackParameters>
|
||||
bool Add(PackParameters &&... params);
|
||||
|
||||
size_t Size() const;
|
||||
bool ReadyToDump() const;
|
||||
std::vector<Pack> Extract() const;
|
||||
|
||||
private:
|
||||
boost::circular_buffer<Pack> m_buffer;
|
||||
double const m_minDelaySeconds;
|
||||
};
|
||||
|
||||
// Abstract packet traits.
|
||||
template <typename Pack>
|
||||
struct TraitsPacket
|
||||
{};
|
||||
|
||||
template <>
|
||||
struct TraitsPacket<Packet>
|
||||
{
|
||||
static size_t constexpr kCoordBits = 32;
|
||||
|
||||
template <typename Writer>
|
||||
static void Write(Writer & writer, Packet const & packet, bool isDelta)
|
||||
{
|
||||
auto const lim = helpers::GetLimits(isDelta);
|
||||
|
||||
WriteVarUint(writer, DoubleToUint32(packet.m_lat, lim.m_minLat, lim.m_maxLat, kCoordBits));
|
||||
WriteVarUint(writer, DoubleToUint32(packet.m_lon, lim.m_minLon, lim.m_maxLon, kCoordBits));
|
||||
WriteVarUint(writer, packet.m_timestamp);
|
||||
}
|
||||
|
||||
template <typename Reader>
|
||||
static Packet Read(Reader & src, bool isDelta)
|
||||
{
|
||||
auto const lim = helpers::GetLimits(isDelta);
|
||||
|
||||
double const lat = Uint32ToDouble(ReadVarUint<uint32_t>(src), lim.m_minLat, lim.m_maxLat, kCoordBits);
|
||||
double const lon = Uint32ToDouble(ReadVarUint<uint32_t>(src), lim.m_minLon, lim.m_maxLon, kCoordBits);
|
||||
uint32_t const timestamp = ReadVarUint<uint32_t>(src);
|
||||
return Packet(lat, lon, timestamp);
|
||||
}
|
||||
|
||||
static Packet GetDelta(Packet const & current, Packet const & previous)
|
||||
{
|
||||
uint32_t const deltaTimestamp = current.m_timestamp - previous.m_timestamp;
|
||||
double const deltaLat = current.m_lat - previous.m_lat;
|
||||
double const deltaLon = current.m_lon - previous.m_lon;
|
||||
return Packet(deltaLat, deltaLon, deltaTimestamp);
|
||||
}
|
||||
|
||||
static Packet Combine(Packet const & previous, Packet const & delta)
|
||||
{
|
||||
double const lat = previous.m_lat + delta.m_lat;
|
||||
double const lon = previous.m_lon + delta.m_lon;
|
||||
uint32_t const timestamp = previous.m_timestamp + delta.m_timestamp;
|
||||
return Packet(lat, lon, timestamp);
|
||||
}
|
||||
|
||||
static traffic::SpeedGroup GetSpeedGroup(Packet const & packet) { return traffic::SpeedGroup::Unknown; }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TraitsPacket<PacketCar>
|
||||
{
|
||||
template <typename Writer>
|
||||
static void Write(Writer & writer, PacketCar const & packet, bool isDelta)
|
||||
{
|
||||
TraitsPacket<Packet>::Write(writer, packet, isDelta);
|
||||
static_assert(std::is_same<uint8_t, std::underlying_type_t<decltype(packet.m_speedGroup)>>::value, "");
|
||||
WriteVarUint(writer, static_cast<uint8_t>(packet.m_speedGroup));
|
||||
}
|
||||
|
||||
template <typename Reader>
|
||||
static PacketCar Read(Reader & src, bool isDelta)
|
||||
{
|
||||
Packet const base = TraitsPacket<Packet>::Read(src, isDelta);
|
||||
auto const speedGroup = static_cast<traffic::SpeedGroup>(ReadPrimitiveFromSource<uint8_t>(src));
|
||||
return PacketCar(base.m_lat, base.m_lon, base.m_timestamp, speedGroup);
|
||||
}
|
||||
|
||||
static PacketCar GetDelta(PacketCar const & current, PacketCar const & previous)
|
||||
{
|
||||
Packet const delta = TraitsPacket<Packet>::GetDelta(current, previous);
|
||||
return PacketCar(delta.m_lat, delta.m_lon, delta.m_timestamp, current.m_speedGroup);
|
||||
}
|
||||
|
||||
static PacketCar Combine(PacketCar const & previous, PacketCar const & delta)
|
||||
{
|
||||
Packet const base = TraitsPacket<Packet>::Combine(previous, delta);
|
||||
return PacketCar(base.m_lat, base.m_lon, base.m_timestamp, delta.m_speedGroup);
|
||||
}
|
||||
|
||||
static traffic::SpeedGroup GetSpeedGroup(PacketCar const & packet) { return packet.m_speedGroup; }
|
||||
};
|
||||
|
||||
template <typename Reader>
|
||||
std::vector<uint8_t> InflateToBuffer(Reader & src)
|
||||
{
|
||||
std::vector<uint8_t> deflatedBuf(base::checked_cast<size_t>(src.Size()));
|
||||
src.Read(deflatedBuf.data(), deflatedBuf.size());
|
||||
|
||||
coding::ZLib::Inflate inflate(coding::ZLib::Inflate::Format::ZLib);
|
||||
std::vector<uint8_t> buffer;
|
||||
inflate(deflatedBuf.data(), deflatedBuf.size(), std::back_inserter(buffer));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
void DeflateToDst(Writer & dst, std::vector<uint8_t> const & buf)
|
||||
{
|
||||
coding::ZLib::Deflate deflate(coding::ZLib::Deflate::Format::ZLib, coding::ZLib::Deflate::Level::BestCompression);
|
||||
std::vector<uint8_t> deflatedBuf;
|
||||
deflate(buf.data(), buf.size(), std::back_inserter(deflatedBuf));
|
||||
dst.Write(deflatedBuf.data(), deflatedBuf.size());
|
||||
}
|
||||
|
||||
template <typename Pack>
|
||||
BasicArchive<Pack>::BasicArchive(size_t maxSize, double minDelaySeconds)
|
||||
: m_buffer(maxSize)
|
||||
, m_minDelaySeconds(minDelaySeconds)
|
||||
{}
|
||||
|
||||
template <typename Pack>
|
||||
template <typename... PackParameters>
|
||||
bool BasicArchive<Pack>::Add(PackParameters &&... params)
|
||||
{
|
||||
Pack newPacket = Pack(std::forward<PackParameters>(params)...);
|
||||
if (!m_buffer.empty() && newPacket.m_timestamp < m_buffer.back().m_timestamp + m_minDelaySeconds)
|
||||
return false;
|
||||
|
||||
m_buffer.push_back(std::move(newPacket));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Pack>
|
||||
size_t BasicArchive<Pack>::Size() const
|
||||
{
|
||||
return m_buffer.size();
|
||||
}
|
||||
|
||||
template <typename Pack>
|
||||
bool BasicArchive<Pack>::ReadyToDump() const
|
||||
{
|
||||
return m_buffer.full();
|
||||
}
|
||||
|
||||
template <typename Pack>
|
||||
std::vector<Pack> BasicArchive<Pack>::Extract() const
|
||||
{
|
||||
std::vector<Pack> res;
|
||||
res.reserve(m_buffer.size());
|
||||
res.insert(res.end(), m_buffer.begin(), m_buffer.end());
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename Pack>
|
||||
template <typename Writer>
|
||||
bool BasicArchive<Pack>::Write(Writer & dst)
|
||||
{
|
||||
if (m_buffer.empty())
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
std::vector<uint8_t> buf;
|
||||
MemWriter<std::vector<uint8_t>> writer(buf);
|
||||
// For aggregating tracks we use two-step approach: at first we delta-code the coordinates and
|
||||
// timestamps; then we use coding::ZLib::Inflate to compress data.
|
||||
TraitsPacket<Pack>::Write(writer, m_buffer[0], false /* isDelta */);
|
||||
|
||||
for (size_t i = 1; i < m_buffer.size(); ++i)
|
||||
{
|
||||
Pack const delta = TraitsPacket<Pack>::GetDelta(m_buffer[i], m_buffer[i - 1]);
|
||||
TraitsPacket<Pack>::Write(writer, delta, true /* isDelta */);
|
||||
}
|
||||
|
||||
DeflateToDst(dst, buf);
|
||||
|
||||
LOG(LDEBUG, ("Dumped to disk", m_buffer.size(), "items"));
|
||||
|
||||
m_buffer.clear();
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Error writing to file", e.what()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Pack>
|
||||
template <typename Reader>
|
||||
bool BasicArchive<Pack>::Read(Reader & src)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto const buffer = InflateToBuffer(src);
|
||||
ReaderSource<MemReaderWithExceptions> reader(MemReaderWithExceptions(buffer.data(), buffer.size()));
|
||||
if (reader.Size() == 0)
|
||||
return false;
|
||||
|
||||
m_buffer.clear();
|
||||
|
||||
// Read first point.
|
||||
m_buffer.push_back(TraitsPacket<Pack>::Read(reader, false /* isDelta */));
|
||||
|
||||
// Read with delta.
|
||||
while (reader.Size() > 0)
|
||||
{
|
||||
Pack const delta = TraitsPacket<Pack>::Read(reader, true /* isDelta */);
|
||||
m_buffer.push_back(TraitsPacket<Pack>::Combine(m_buffer.back(), delta));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (std::exception const & e)
|
||||
{
|
||||
LOG(LWARNING, ("Error reading file", e.what()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace tracking
|
||||
68
libs/tracking/connection.cpp
Normal file
68
libs/tracking/connection.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#include "tracking/connection.hpp"
|
||||
|
||||
#include "tracking/protocol.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
#include "platform/socket.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
uint32_t constexpr kSocketTimeoutMs = 10000;
|
||||
} // namespace
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
Connection::Connection(std::unique_ptr<platform::Socket> socket, std::string const & host, uint16_t port,
|
||||
bool isHistorical)
|
||||
: m_socket(std::move(socket))
|
||||
, m_host(host)
|
||||
, m_port(port)
|
||||
{
|
||||
if (!m_socket)
|
||||
return;
|
||||
|
||||
m_socket->SetTimeout(kSocketTimeoutMs);
|
||||
}
|
||||
|
||||
// TODO: implement handshake
|
||||
bool Connection::Reconnect()
|
||||
{
|
||||
if (!m_socket)
|
||||
return false;
|
||||
|
||||
m_socket->Close();
|
||||
|
||||
if (!m_socket->Open(m_host, m_port))
|
||||
return false;
|
||||
|
||||
// TODO: generate unique client id here.
|
||||
std::string clientId = "TODO";
|
||||
auto packet = Protocol::CreateAuthPacket(clientId);
|
||||
if (!m_socket->Write(packet.data(), static_cast<uint32_t>(packet.size())))
|
||||
return false;
|
||||
|
||||
std::string check(std::begin(Protocol::kFail), std::end(Protocol::kFail));
|
||||
bool const isSuccess = m_socket->Read(reinterpret_cast<uint8_t *>(&check[0]), static_cast<uint32_t>(check.size()));
|
||||
if (!isSuccess || check != std::string(std::begin(Protocol::kOk), std::end(Protocol::kOk)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Connection::Shutdown()
|
||||
{
|
||||
if (!m_socket)
|
||||
return;
|
||||
|
||||
m_socket->Close();
|
||||
}
|
||||
|
||||
bool Connection::Send(boost::circular_buffer<DataPoint> const & points)
|
||||
{
|
||||
if (!m_socket)
|
||||
return false;
|
||||
|
||||
auto packet = Protocol::CreateDataPacket(points, tracking::Protocol::PacketType::CurrentData);
|
||||
return m_socket->Write(packet.data(), static_cast<uint32_t>(packet.size()));
|
||||
}
|
||||
} // namespace tracking
|
||||
40
libs/tracking/connection.hpp
Normal file
40
libs/tracking/connection.hpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "coding/traffic.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-copy"
|
||||
#endif
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace platform
|
||||
{
|
||||
class Socket;
|
||||
}
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
using DataPoint = coding::TrafficGPSEncoder::DataPoint;
|
||||
|
||||
class Connection final
|
||||
{
|
||||
public:
|
||||
Connection(std::unique_ptr<platform::Socket> socket, std::string const & host, uint16_t port, bool isHistorical);
|
||||
bool Reconnect();
|
||||
void Shutdown();
|
||||
bool Send(boost::circular_buffer<DataPoint> const & points);
|
||||
|
||||
private:
|
||||
std::unique_ptr<platform::Socket> m_socket;
|
||||
std::string const m_host;
|
||||
uint16_t const m_port;
|
||||
};
|
||||
} // namespace tracking
|
||||
160
libs/tracking/protocol.cpp
Normal file
160
libs/tracking/protocol.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#include "tracking/protocol.hpp"
|
||||
|
||||
#include "coding/endianness.hpp"
|
||||
#include "coding/reader.hpp"
|
||||
#include "coding/writer.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename Container>
|
||||
vector<uint8_t> CreateDataPacketImpl(Container const & points, tracking::Protocol::PacketType const type)
|
||||
{
|
||||
vector<uint8_t> buffer;
|
||||
MemWriter<decltype(buffer)> writer(buffer);
|
||||
|
||||
uint32_t version = tracking::Protocol::Encoder::kLatestVersion;
|
||||
switch (type)
|
||||
{
|
||||
case tracking::Protocol::PacketType::DataV0: version = 0; break;
|
||||
case tracking::Protocol::PacketType::DataV1: version = 1; break;
|
||||
case tracking::Protocol::PacketType::Error:
|
||||
case tracking::Protocol::PacketType::AuthV0:
|
||||
LOG(LERROR, ("Can't create a non-DATA packet as a DATA packet. PacketType =", type));
|
||||
return {};
|
||||
}
|
||||
|
||||
tracking::Protocol::Encoder::SerializeDataPoints(version, writer, points);
|
||||
|
||||
auto packet = tracking::Protocol::CreateHeader(type, static_cast<uint32_t>(buffer.size()));
|
||||
packet.insert(packet.end(), begin(buffer), end(buffer));
|
||||
|
||||
return packet;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
uint8_t const Protocol::kOk[4] = {'O', 'K', '\n', '\n'};
|
||||
uint8_t const Protocol::kFail[4] = {'F', 'A', 'I', 'L'};
|
||||
|
||||
static_assert(sizeof(Protocol::kFail) >= sizeof(Protocol::kOk), "");
|
||||
|
||||
// static
|
||||
vector<uint8_t> Protocol::CreateHeader(PacketType type, uint32_t payloadSize)
|
||||
{
|
||||
vector<uint8_t> header;
|
||||
InitHeader(header, type, payloadSize);
|
||||
return header;
|
||||
}
|
||||
|
||||
// static
|
||||
vector<uint8_t> Protocol::CreateAuthPacket(string const & clientId)
|
||||
{
|
||||
vector<uint8_t> packet;
|
||||
|
||||
InitHeader(packet, PacketType::CurrentAuth, static_cast<uint32_t>(clientId.size()));
|
||||
packet.insert(packet.end(), begin(clientId), end(clientId));
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
// static
|
||||
vector<uint8_t> Protocol::CreateDataPacket(DataElementsCirc const & points, PacketType type)
|
||||
{
|
||||
return CreateDataPacketImpl(points, type);
|
||||
}
|
||||
|
||||
// static
|
||||
vector<uint8_t> Protocol::CreateDataPacket(DataElementsVec const & points, PacketType type)
|
||||
{
|
||||
return CreateDataPacketImpl(points, type);
|
||||
}
|
||||
|
||||
// static
|
||||
pair<Protocol::PacketType, size_t> Protocol::DecodeHeader(vector<uint8_t> const & data)
|
||||
{
|
||||
if (data.size() < sizeof(uint32_t /* header */))
|
||||
{
|
||||
LOG(LWARNING, ("Header size is too small", data.size(), sizeof(uint32_t /* header */)));
|
||||
return make_pair(PacketType::Error, data.size());
|
||||
}
|
||||
|
||||
uint32_t size = (*reinterpret_cast<uint32_t const *>(data.data())) & 0xFFFFFF00;
|
||||
if (!IsBigEndianMacroBased())
|
||||
size = ReverseByteOrder(size);
|
||||
return make_pair(PacketType(static_cast<uint8_t>(data[0])), size);
|
||||
}
|
||||
|
||||
// static
|
||||
string Protocol::DecodeAuthPacket(Protocol::PacketType type, vector<uint8_t> const & data)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Protocol::PacketType::AuthV0: return string(begin(data), end(data));
|
||||
case Protocol::PacketType::Error:
|
||||
case Protocol::PacketType::DataV0:
|
||||
case Protocol::PacketType::DataV1: LOG(LERROR, ("Error decoding AUTH packet. PacketType =", type)); break;
|
||||
}
|
||||
return string();
|
||||
}
|
||||
|
||||
// static
|
||||
Protocol::DataElementsVec Protocol::DecodeDataPacket(PacketType type, vector<uint8_t> const & data)
|
||||
{
|
||||
DataElementsVec points;
|
||||
MemReaderWithExceptions memReader(data.data(), data.size());
|
||||
ReaderSource<MemReaderWithExceptions> src(memReader);
|
||||
try
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Protocol::PacketType::DataV0: Encoder::DeserializeDataPoints(0 /* version */, src, points); break;
|
||||
case Protocol::PacketType::DataV1: Encoder::DeserializeDataPoints(1 /* version */, src, points); break;
|
||||
case Protocol::PacketType::Error:
|
||||
case Protocol::PacketType::AuthV0: LOG(LERROR, ("Error decoding DATA packet. PacketType =", type)); return {};
|
||||
}
|
||||
return points;
|
||||
}
|
||||
catch (Reader::SizeException const & ex)
|
||||
{
|
||||
LOG(LWARNING, ("Wrong packet. SizeException. Msg:", ex.Msg(), ". What:", ex.what()));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void Protocol::InitHeader(vector<uint8_t> & packet, PacketType type, uint32_t payloadSize)
|
||||
{
|
||||
packet.resize(sizeof(uint32_t));
|
||||
uint32_t & size = *reinterpret_cast<uint32_t *>(packet.data());
|
||||
size = payloadSize;
|
||||
|
||||
ASSERT_LESS(size, 0x00FFFFFF, ());
|
||||
|
||||
if (!IsBigEndianMacroBased())
|
||||
size = ReverseByteOrder(size);
|
||||
|
||||
packet[0] = static_cast<uint8_t>(type);
|
||||
}
|
||||
|
||||
string DebugPrint(Protocol::PacketType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Protocol::PacketType::Error: return "Error";
|
||||
case Protocol::PacketType::AuthV0: return "AuthV0";
|
||||
case Protocol::PacketType::DataV0: return "DataV0";
|
||||
case Protocol::PacketType::DataV1: return "DataV1";
|
||||
}
|
||||
stringstream ss;
|
||||
ss << "Unknown(" << static_cast<uint32_t>(type) << ")";
|
||||
return ss.str();
|
||||
}
|
||||
} // namespace tracking
|
||||
56
libs/tracking/protocol.hpp
Normal file
56
libs/tracking/protocol.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include "coding/traffic.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-copy"
|
||||
#endif
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
class Protocol
|
||||
{
|
||||
public:
|
||||
using Encoder = coding::TrafficGPSEncoder;
|
||||
using DataElementsCirc = boost::circular_buffer<Encoder::DataPoint>;
|
||||
using DataElementsVec = std::vector<Encoder::DataPoint>;
|
||||
|
||||
static uint8_t const kOk[4];
|
||||
static uint8_t const kFail[4];
|
||||
|
||||
enum class PacketType
|
||||
{
|
||||
Error = 0x0,
|
||||
AuthV0 = 0x81,
|
||||
DataV0 = 0x82,
|
||||
DataV1 = 0x92,
|
||||
|
||||
CurrentAuth = AuthV0,
|
||||
CurrentData = DataV1
|
||||
};
|
||||
|
||||
static std::vector<uint8_t> CreateHeader(PacketType type, uint32_t payloadSize);
|
||||
static std::vector<uint8_t> CreateAuthPacket(std::string const & clientId);
|
||||
static std::vector<uint8_t> CreateDataPacket(DataElementsCirc const & points, PacketType type);
|
||||
static std::vector<uint8_t> CreateDataPacket(DataElementsVec const & points, PacketType type);
|
||||
|
||||
static std::pair<PacketType, size_t> DecodeHeader(std::vector<uint8_t> const & data);
|
||||
static std::string DecodeAuthPacket(PacketType type, std::vector<uint8_t> const & data);
|
||||
static DataElementsVec DecodeDataPacket(PacketType type, std::vector<uint8_t> const & data);
|
||||
|
||||
private:
|
||||
static void InitHeader(std::vector<uint8_t> & packet, PacketType type, uint32_t payloadSize);
|
||||
};
|
||||
|
||||
std::string DebugPrint(Protocol::PacketType type);
|
||||
} // namespace tracking
|
||||
25
libs/tracking/pytracking/CMakeLists.txt
Normal file
25
libs/tracking/pytracking/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
project(pytracking)
|
||||
|
||||
set(
|
||||
SRC
|
||||
bindings.cpp
|
||||
)
|
||||
|
||||
include_directories(${CMAKE_BINARY_DIR})
|
||||
|
||||
omim_add_library(${PROJECT_NAME} MODULE ${SRC})
|
||||
|
||||
omim_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
${Boost_LIBRARIES}
|
||||
tracking
|
||||
coding
|
||||
geometry
|
||||
base
|
||||
)
|
||||
|
||||
if (PLATFORM_MAC)
|
||||
omim_link_libraries(${PROJECT_NAME} "-Wl,-undefined,dynamic_lookup")
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
|
||||
61
libs/tracking/pytracking/bindings.cpp
Normal file
61
libs/tracking/pytracking/bindings.cpp
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#include "tracking/protocol.hpp"
|
||||
|
||||
#include "coding/traffic.hpp"
|
||||
|
||||
#include "pyhelpers/module_version.hpp"
|
||||
#include "pyhelpers/pair.hpp"
|
||||
#include "pyhelpers/vector_uint8.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
|
||||
|
||||
BOOST_PYTHON_MODULE(pytracking)
|
||||
{
|
||||
using namespace boost::python;
|
||||
scope().attr("__version__") = PYBINDINGS_VERSION;
|
||||
|
||||
using tracking::Protocol;
|
||||
|
||||
// Register the to-python converters.
|
||||
pair_to_python_converter<Protocol::PacketType, size_t>();
|
||||
to_python_converter<std::vector<uint8_t>, vector_uint8t_to_str>();
|
||||
vector_uint8t_from_python_str();
|
||||
|
||||
class_<Protocol::DataElementsVec>("DataElementsVec").def(vector_indexing_suite<Protocol::DataElementsVec>());
|
||||
|
||||
class_<ms::LatLon>("LatLon").def_readwrite("lat", &ms::LatLon::m_lat).def_readwrite("lon", &ms::LatLon::m_lon);
|
||||
|
||||
class_<coding::TrafficGPSEncoder::DataPoint>("DataPoint")
|
||||
.def(init<uint64_t, ms::LatLon const &, uint8_t>())
|
||||
.def_readwrite("timestamp", &coding::TrafficGPSEncoder::DataPoint::m_timestamp)
|
||||
.def_readwrite("coords", &coding::TrafficGPSEncoder::DataPoint::m_latLon)
|
||||
.def_readwrite("traffic", &coding::TrafficGPSEncoder::DataPoint::m_traffic);
|
||||
|
||||
enum_<Protocol::PacketType>("PacketType")
|
||||
.value("Error", Protocol::PacketType::Error)
|
||||
.value("AuthV0", Protocol::PacketType::AuthV0)
|
||||
.value("DataV0", Protocol::PacketType::DataV0)
|
||||
.value("DataV1", Protocol::PacketType::DataV1)
|
||||
.value("CurrentAuth", Protocol::PacketType::CurrentAuth)
|
||||
.value("CurrentData", Protocol::PacketType::CurrentData);
|
||||
|
||||
std::vector<uint8_t> (*CreateDataPacket1)(Protocol::DataElementsCirc const &, tracking::Protocol::PacketType) =
|
||||
&Protocol::CreateDataPacket;
|
||||
std::vector<uint8_t> (*CreateDataPacket2)(Protocol::DataElementsVec const &, tracking::Protocol::PacketType) =
|
||||
&Protocol::CreateDataPacket;
|
||||
|
||||
class_<Protocol>("Protocol")
|
||||
.def("CreateAuthPacket", &Protocol::CreateAuthPacket)
|
||||
.staticmethod("CreateAuthPacket")
|
||||
.def("CreateDataPacket", CreateDataPacket1)
|
||||
.def("CreateDataPacket", CreateDataPacket2)
|
||||
.staticmethod("CreateDataPacket")
|
||||
.def("CreateHeader", &Protocol::CreateHeader)
|
||||
.staticmethod("CreateHeader")
|
||||
.def("DecodeHeader", &Protocol::DecodeHeader)
|
||||
.staticmethod("DecodeHeader")
|
||||
.def("DecodeDataPacket", &Protocol::DecodeDataPacket)
|
||||
.staticmethod("DecodeDataPacket");
|
||||
}
|
||||
13
libs/tracking/pytracking/setup.py
Normal file
13
libs/tracking/pytracking/setup.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
module_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
sys.path.insert(0, os.path.join(module_dir, '..', '..'))
|
||||
|
||||
from pyhelpers.setup import setup_omim_pybinding
|
||||
|
||||
|
||||
NAME = "pytracking"
|
||||
|
||||
setup_omim_pybinding(name=NAME)
|
||||
141
libs/tracking/reporter.cpp
Normal file
141
libs/tracking/reporter.cpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#include "tracking/reporter.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
#include "platform/platform.hpp"
|
||||
#include "platform/socket.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
#include "base/timer.hpp"
|
||||
|
||||
#include "std/target_os.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
namespace
|
||||
{
|
||||
double constexpr kRequiredHorizontalAccuracy = 10.0;
|
||||
double constexpr kMinDelaySeconds = 1.0;
|
||||
double constexpr kReconnectDelaySeconds = 40.0;
|
||||
double constexpr kNotChargingEventPeriod = 5 * 60.0;
|
||||
|
||||
static_assert(kMinDelaySeconds != 0, "");
|
||||
} // namespace
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
char const Reporter::kEnableTrackingKey[] = "StatisticsEnabled";
|
||||
|
||||
// static
|
||||
milliseconds const Reporter::kPushDelayMs = milliseconds(20000);
|
||||
|
||||
// Set m_points size to be enough to keep all points even if one reconnect attempt failed.
|
||||
Reporter::Reporter(unique_ptr<platform::Socket> socket, string const & host, uint16_t port, milliseconds pushDelay)
|
||||
: m_allowSendingPoints(true)
|
||||
, m_realtimeSender(std::move(socket), host, port, false)
|
||||
, m_pushDelay(pushDelay)
|
||||
, m_points(ceil(duration_cast<seconds>(pushDelay).count() + kReconnectDelaySeconds) / kMinDelaySeconds)
|
||||
, m_thread([this] { Run(); })
|
||||
{}
|
||||
|
||||
Reporter::~Reporter()
|
||||
{
|
||||
{
|
||||
lock_guard<mutex> lg(m_mutex);
|
||||
m_isFinished = true;
|
||||
}
|
||||
m_cv.notify_one();
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void Reporter::AddLocation(location::GpsInfo const & info, traffic::SpeedGroup traffic)
|
||||
{
|
||||
lock_guard<mutex> lg(m_mutex);
|
||||
|
||||
if (info.m_horizontalAccuracy > kRequiredHorizontalAccuracy)
|
||||
return;
|
||||
|
||||
if (info.m_timestamp < m_lastGpsTime + kMinDelaySeconds)
|
||||
return;
|
||||
|
||||
if (Platform::GetChargingStatus() != Platform::ChargingStatus::Plugged)
|
||||
{
|
||||
double const currentTime = base::Timer::LocalTime();
|
||||
if (currentTime < m_lastNotChargingEvent + kNotChargingEventPeriod)
|
||||
return;
|
||||
|
||||
m_lastNotChargingEvent = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastGpsTime = info.m_timestamp;
|
||||
m_input.push_back(DataPoint(info.m_timestamp, ms::LatLon(info.m_latitude, info.m_longitude),
|
||||
static_cast<std::underlying_type<traffic::SpeedGroup>::type>(traffic)));
|
||||
}
|
||||
|
||||
void Reporter::Run()
|
||||
{
|
||||
LOG(LINFO, ("Tracking Reporter started"));
|
||||
|
||||
unique_lock<mutex> lock(m_mutex);
|
||||
|
||||
while (!m_isFinished)
|
||||
{
|
||||
auto const startTime = steady_clock::now();
|
||||
|
||||
// Fetch input.
|
||||
m_points.insert(m_points.end(), m_input.begin(), m_input.end());
|
||||
m_input.clear();
|
||||
|
||||
lock.unlock();
|
||||
if (m_points.empty() && m_idleFn)
|
||||
m_idleFn();
|
||||
else if (SendPoints())
|
||||
m_points.clear();
|
||||
lock.lock();
|
||||
|
||||
auto const passedMs = duration_cast<milliseconds>(steady_clock::now() - startTime);
|
||||
if (passedMs < m_pushDelay)
|
||||
m_cv.wait_for(lock, m_pushDelay - passedMs, [this] { return m_isFinished; });
|
||||
}
|
||||
|
||||
LOG(LINFO, ("Tracking Reporter finished"));
|
||||
}
|
||||
|
||||
bool Reporter::SendPoints()
|
||||
{
|
||||
if (!m_allowSendingPoints)
|
||||
{
|
||||
if (m_wasConnected)
|
||||
{
|
||||
m_realtimeSender.Shutdown();
|
||||
m_wasConnected = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_points.empty())
|
||||
return true;
|
||||
|
||||
if (m_wasConnected)
|
||||
m_wasConnected = m_realtimeSender.Send(m_points);
|
||||
|
||||
if (m_wasConnected)
|
||||
return true;
|
||||
|
||||
double const currentTime = base::Timer::LocalTime();
|
||||
if (currentTime < m_lastConnectionAttempt + kReconnectDelaySeconds)
|
||||
return false;
|
||||
|
||||
m_lastConnectionAttempt = currentTime;
|
||||
|
||||
m_wasConnected = m_realtimeSender.Reconnect();
|
||||
if (!m_wasConnected)
|
||||
return false;
|
||||
|
||||
m_wasConnected = m_realtimeSender.Send(m_points);
|
||||
return m_wasConnected;
|
||||
}
|
||||
} // namespace tracking
|
||||
79
libs/tracking/reporter.hpp
Normal file
79
libs/tracking/reporter.hpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include "tracking/connection.hpp"
|
||||
|
||||
#include "traffic/speed_groups.hpp"
|
||||
|
||||
#include "base/thread.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-copy"
|
||||
#endif
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
namespace location
|
||||
{
|
||||
class GpsInfo;
|
||||
}
|
||||
|
||||
namespace platform
|
||||
{
|
||||
class Socket;
|
||||
}
|
||||
|
||||
namespace tracking
|
||||
{
|
||||
class Reporter final
|
||||
{
|
||||
public:
|
||||
static std::chrono::milliseconds const kPushDelayMs;
|
||||
static char const kEnableTrackingKey[];
|
||||
|
||||
Reporter(std::unique_ptr<platform::Socket> socket, std::string const & host, uint16_t port,
|
||||
std::chrono::milliseconds pushDelay);
|
||||
~Reporter();
|
||||
|
||||
void AddLocation(location::GpsInfo const & info, traffic::SpeedGroup traffic);
|
||||
|
||||
void SetAllowSendingPoints(bool allow) { m_allowSendingPoints = allow; }
|
||||
|
||||
void SetIdleFunc(std::function<void()> fn) { m_idleFn = fn; }
|
||||
|
||||
private:
|
||||
void Run();
|
||||
bool SendPoints();
|
||||
|
||||
std::atomic<bool> m_allowSendingPoints;
|
||||
Connection m_realtimeSender;
|
||||
std::chrono::milliseconds m_pushDelay;
|
||||
bool m_wasConnected = false;
|
||||
double m_lastConnectionAttempt = 0.0;
|
||||
double m_lastNotChargingEvent = 0.0;
|
||||
// Function to be called every |kPushDelayMs| in
|
||||
// case no points were sent.
|
||||
std::function<void()> m_idleFn;
|
||||
// Input buffer for incoming points. Worker thread steals it contents.
|
||||
std::vector<DataPoint> m_input;
|
||||
// Last collected points, sends periodically to server.
|
||||
boost::circular_buffer<DataPoint> m_points;
|
||||
double m_lastGpsTime = 0.0;
|
||||
bool m_isFinished = false;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
threads::SimpleThread m_thread;
|
||||
};
|
||||
} // namespace tracking
|
||||
9
libs/tracking/tracking_fuzz_tests/CMakeLists.txt
Normal file
9
libs/tracking/tracking_fuzz_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
project(tracking_fuzz_tests)
|
||||
|
||||
set(SRC
|
||||
tracking_fuzz_tests.cpp
|
||||
)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} tracking)
|
||||
69
libs/tracking/tracking_fuzz_tests/tracking_fuzz_tests.cpp
Normal file
69
libs/tracking/tracking_fuzz_tests/tracking_fuzz_tests.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#include "tracking/protocol.hpp"
|
||||
|
||||
#include "coding/traffic.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
using namespace coding;
|
||||
using namespace tracking;
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
T PopType(std::vector<uint8_t> & data)
|
||||
{
|
||||
T t{};
|
||||
if (data.empty())
|
||||
return t;
|
||||
|
||||
if (data.size() < sizeof(T))
|
||||
{
|
||||
data.clear();
|
||||
return t;
|
||||
}
|
||||
|
||||
t = *reinterpret_cast<T *>(data.data());
|
||||
data.erase(data.begin(), std::next(data.begin(), sizeof(T)));
|
||||
return t;
|
||||
}
|
||||
|
||||
TrafficGPSEncoder::DataPoint PopDataPoint(std::vector<uint8_t> & data)
|
||||
{
|
||||
auto const timestamp = PopType<uint64_t>(data);
|
||||
auto const lat = PopType<double>(data);
|
||||
auto const lon = PopType<double>(data);
|
||||
auto const traffic = PopType<uint8_t>(data);
|
||||
return TrafficGPSEncoder::DataPoint(timestamp, ms::LatLon(lat, lon), traffic);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(uint8_t const * data, size_t size)
|
||||
{
|
||||
base::ScopedLogLevelChanger scopedLogLevelChanger(base::LCRITICAL);
|
||||
|
||||
std::vector<uint8_t> const dataVec(data, data + size);
|
||||
|
||||
auto dataVecToConv = dataVec;
|
||||
Protocol::DataElementsVec dataElementsVec;
|
||||
while (!dataVecToConv.empty())
|
||||
dataElementsVec.push_back(PopDataPoint(dataVecToConv));
|
||||
Protocol::DataElementsCirc dataElementsCirc(dataElementsVec.cbegin(), dataElementsVec.cend());
|
||||
|
||||
Protocol::DecodeHeader(dataVec);
|
||||
for (auto const type : {Protocol::PacketType::Error, Protocol::PacketType::AuthV0, Protocol::PacketType::DataV0,
|
||||
Protocol::PacketType::DataV1})
|
||||
{
|
||||
Protocol::CreateDataPacket(dataElementsVec, type);
|
||||
Protocol::CreateDataPacket(dataElementsCirc, type);
|
||||
Protocol::DecodeAuthPacket(type, dataVec);
|
||||
Protocol::DecodeDataPacket(type, dataVec);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
16
libs/tracking/tracking_tests/CMakeLists.txt
Normal file
16
libs/tracking/tracking_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
project(tracking_tests)
|
||||
|
||||
set(
|
||||
SRC
|
||||
archival_reporter_tests.cpp
|
||||
protocol_test.cpp
|
||||
reporter_test.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
omim_link_libraries(${PROJECT_NAME}
|
||||
platform_tests_support
|
||||
tracking
|
||||
routing
|
||||
)
|
||||
164
libs/tracking/tracking_tests/archival_reporter_tests.cpp
Normal file
164
libs/tracking/tracking_tests/archival_reporter_tests.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "tracking/archival_file.hpp"
|
||||
#include "tracking/archive.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/file_reader.hpp"
|
||||
#include "coding/file_writer.hpp"
|
||||
|
||||
#include "geometry/latlon.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/timer.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <random>
|
||||
|
||||
using namespace tracking;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr size_t kItemsForDump = 2 * 60 * 60; // 2 hours of travelling
|
||||
constexpr double kAccuracyEps = 1e-4;
|
||||
|
||||
location::GpsInfo GetStartingPoint()
|
||||
{
|
||||
location::GpsInfo start;
|
||||
start.m_timestamp = 1573227904;
|
||||
start.m_horizontalAccuracy = 5;
|
||||
start.m_latitude = 11.67281;
|
||||
start.m_longitude = 5.22804;
|
||||
return start;
|
||||
}
|
||||
|
||||
void UpdateSpeedGroup(traffic::SpeedGroup & sg)
|
||||
{
|
||||
static std::random_device randomDevice;
|
||||
static std::mt19937 gen(randomDevice());
|
||||
static std::uniform_int_distribution<> dis(0, base::Underlying(traffic::SpeedGroup::Count));
|
||||
sg = static_cast<traffic::SpeedGroup>(dis(gen));
|
||||
}
|
||||
|
||||
void UpdateLocation(location::GpsInfo & loc)
|
||||
{
|
||||
static std::random_device randomDevice;
|
||||
static std::mt19937 gen(randomDevice());
|
||||
static std::uniform_int_distribution<> dis(0, 50);
|
||||
loc.m_latitude += dis(gen) * 0.0001;
|
||||
loc.m_longitude += dis(gen) * 0.0001;
|
||||
CHECK_GREATER_OR_EQUAL(loc.m_latitude, ms::LatLon::kMinLat, ());
|
||||
CHECK_LESS_OR_EQUAL(loc.m_latitude, ms::LatLon::kMaxLat, ());
|
||||
CHECK_GREATER_OR_EQUAL(loc.m_longitude, ms::LatLon::kMinLon, ());
|
||||
CHECK_LESS_OR_EQUAL(loc.m_longitude, ms::LatLon::kMaxLon, ());
|
||||
|
||||
double constexpr kMinIntervalBetweenLocationsS = 3.0;
|
||||
loc.m_timestamp += kMinIntervalBetweenLocationsS + dis(gen);
|
||||
}
|
||||
|
||||
UNIT_TEST(PacketCar_OperationsConsistency)
|
||||
{
|
||||
BasicArchive<PacketCar> archive(kItemsForDump, 1.0 /* m_minDelaySeconds */);
|
||||
location::GpsInfo point = GetStartingPoint();
|
||||
traffic::SpeedGroup sg = traffic::SpeedGroup::G0;
|
||||
|
||||
base::HighResTimer timer;
|
||||
|
||||
for (size_t i = 0; i < kItemsForDump; ++i)
|
||||
{
|
||||
archive.Add(point, sg);
|
||||
UpdateLocation(point);
|
||||
UpdateSpeedGroup(sg);
|
||||
}
|
||||
|
||||
auto const track = archive.Extract();
|
||||
|
||||
LOG(LINFO, ("Duration of dumping", timer.ElapsedMilliseconds(), "ms"));
|
||||
|
||||
timer.Reset();
|
||||
std::string const fileName = "archival_reporter_car.track";
|
||||
{
|
||||
FileWriter writer(fileName);
|
||||
CHECK(archive.Write(writer), ());
|
||||
}
|
||||
|
||||
LOG(LINFO, ("Duration of serializing", timer.ElapsedMilliseconds(), "ms"));
|
||||
|
||||
uint64_t sizeBytes;
|
||||
CHECK(GetPlatform().GetFileSizeByFullPath(fileName, sizeBytes), ());
|
||||
LOG(LINFO, ("File size", sizeBytes, "bytes"));
|
||||
|
||||
FileReader reader(fileName);
|
||||
ReaderSource<FileReader> src(reader);
|
||||
|
||||
CHECK(archive.Read(src), ());
|
||||
CHECK_EQUAL(kItemsForDump, archive.Size(), ());
|
||||
auto const newTrack = archive.Extract();
|
||||
for (size_t i = 0; i < kItemsForDump; ++i)
|
||||
{
|
||||
TEST_ALMOST_EQUAL_ABS(track[i].m_lat, newTrack[i].m_lat, kAccuracyEps, ("failed index", i));
|
||||
TEST_ALMOST_EQUAL_ABS(track[i].m_lon, newTrack[i].m_lon, kAccuracyEps, ("failed index", i));
|
||||
CHECK_EQUAL(track[i].m_timestamp, newTrack[i].m_timestamp, ("failed index", i));
|
||||
CHECK_EQUAL(track[i].m_speedGroup, newTrack[i].m_speedGroup, ("failed index", i));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(PacketPedestrianBicycle_OperationsConsistency)
|
||||
{
|
||||
BasicArchive<Packet> archive(kItemsForDump, 3.0 /* m_minDelaySeconds */);
|
||||
location::GpsInfo point = GetStartingPoint();
|
||||
|
||||
for (size_t i = 0; i < kItemsForDump; ++i)
|
||||
{
|
||||
archive.Add(point);
|
||||
UpdateLocation(point);
|
||||
}
|
||||
auto const track = archive.Extract();
|
||||
|
||||
std::string const fileName = "archival_reporter_pedestrian_bicycle.track";
|
||||
{
|
||||
FileWriter writer(fileName);
|
||||
CHECK(archive.Write(writer), (fileName));
|
||||
}
|
||||
uint64_t sizeBytes;
|
||||
CHECK(GetPlatform().GetFileSizeByFullPath(fileName, sizeBytes), (fileName, sizeBytes));
|
||||
LOG(LINFO, ("File size", sizeBytes, "bytes"));
|
||||
|
||||
FileReader reader(fileName);
|
||||
ReaderSource<FileReader> src(reader);
|
||||
|
||||
CHECK(archive.Read(src), ());
|
||||
CHECK_EQUAL(kItemsForDump, archive.Size(), ());
|
||||
auto const newTrack = archive.Extract();
|
||||
for (size_t i = 0; i < kItemsForDump; ++i)
|
||||
{
|
||||
TEST_ALMOST_EQUAL_ABS(track[i].m_lat, newTrack[i].m_lat, kAccuracyEps, ("failed index", i));
|
||||
TEST_ALMOST_EQUAL_ABS(track[i].m_lon, newTrack[i].m_lon, kAccuracyEps, ("failed index", i));
|
||||
CHECK_EQUAL(track[i].m_timestamp, newTrack[i].m_timestamp, ("failed index", i));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(ArchiveName_Create)
|
||||
{
|
||||
routing::RouterType const routerType = routing::RouterType::Pedestrian;
|
||||
uint32_t const version = 1;
|
||||
std::chrono::seconds const timestamp(1573635326);
|
||||
std::string const filename = tracking::archival_file::GetArchiveFilename(version, timestamp, routerType);
|
||||
|
||||
CHECK_EQUAL(filename, "1_1573635326_1.track", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(ArchiveName_Extract)
|
||||
{
|
||||
std::string const filename = "1_1573635326_2.track";
|
||||
auto const meta = tracking::archival_file::ParseArchiveFilename(filename);
|
||||
|
||||
CHECK_EQUAL(meta.m_protocolVersion, 1, ());
|
||||
CHECK_EQUAL(meta.m_timestamp, 1573635326, ());
|
||||
CHECK_EQUAL(meta.m_trackType, routing::RouterType::Bicycle, ());
|
||||
}
|
||||
} // namespace
|
||||
142
libs/tracking/tracking_tests/protocol_test.cpp
Normal file
142
libs/tracking/tracking_tests/protocol_test.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "tracking/protocol.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
using namespace tracking;
|
||||
|
||||
UNIT_TEST(Protocol_CreateAuthPacket)
|
||||
{
|
||||
auto packet = Protocol::CreateAuthPacket("ABC");
|
||||
TEST_EQUAL(packet.size(), 7, ());
|
||||
TEST_EQUAL(Protocol::PacketType(packet[0]), Protocol::PacketType::CurrentAuth, ());
|
||||
TEST_EQUAL(packet[1], 0x00, ());
|
||||
TEST_EQUAL(packet[2], 0x00, ());
|
||||
TEST_EQUAL(packet[3], 0x03, ());
|
||||
TEST_EQUAL(packet[4], 'A', ());
|
||||
TEST_EQUAL(packet[5], 'B', ());
|
||||
TEST_EQUAL(packet[6], 'C', ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Protocol_DecodeHeader)
|
||||
{
|
||||
string id_str("ABC");
|
||||
auto packet = Protocol::CreateAuthPacket(id_str);
|
||||
TEST_EQUAL(packet.size(), 7, ());
|
||||
TEST_EQUAL(Protocol::PacketType(packet[0]), Protocol::PacketType::CurrentAuth, ());
|
||||
|
||||
{
|
||||
auto header = Protocol::DecodeHeader(packet);
|
||||
TEST_EQUAL(header.first, Protocol::PacketType::CurrentAuth, ());
|
||||
TEST_EQUAL(header.second, id_str.size(), ());
|
||||
}
|
||||
|
||||
{
|
||||
auto header = Protocol::DecodeHeader({});
|
||||
TEST_EQUAL(header.first, Protocol::PacketType::Error, ());
|
||||
TEST_EQUAL(header.second, 0, ());
|
||||
}
|
||||
|
||||
{
|
||||
auto header = Protocol::DecodeHeader({7, 9});
|
||||
TEST_EQUAL(header.first, Protocol::PacketType::Error, ());
|
||||
TEST_EQUAL(header.second, 2, ());
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Protocol_CreateDataPacket)
|
||||
{
|
||||
using Container = Protocol::DataElementsCirc;
|
||||
Container buffer(5);
|
||||
buffer.push_back(Container::value_type(1, ms::LatLon(10, 10), 1));
|
||||
buffer.push_back(Container::value_type(2, ms::LatLon(15, 15), 2));
|
||||
|
||||
auto packetV0 = Protocol::CreateDataPacket(buffer, Protocol::PacketType::DataV0);
|
||||
TEST_EQUAL(packetV0.size(), 26, ());
|
||||
TEST_EQUAL(Protocol::PacketType(packetV0[0]), Protocol::PacketType::DataV0, ());
|
||||
TEST_EQUAL(packetV0[1], 0x00, ());
|
||||
TEST_EQUAL(packetV0[2], 0x00, ());
|
||||
TEST_EQUAL(packetV0[3], 22, ());
|
||||
|
||||
TEST_EQUAL(packetV0[4], 1, ());
|
||||
TEST_EQUAL(packetV0[5], 227, ());
|
||||
TEST_EQUAL(packetV0[6], 241, ());
|
||||
|
||||
auto packetV1 = Protocol::CreateDataPacket(buffer, Protocol::PacketType::DataV1);
|
||||
TEST_EQUAL(packetV1.size(), 28, ());
|
||||
TEST_EQUAL(Protocol::PacketType(packetV1[0]), Protocol::PacketType::DataV1, ());
|
||||
TEST_EQUAL(packetV1[1], 0x00, ());
|
||||
TEST_EQUAL(packetV1[2], 0x00, ());
|
||||
TEST_EQUAL(packetV1[3], 24, ());
|
||||
|
||||
TEST_EQUAL(packetV1[4], 1, ());
|
||||
TEST_EQUAL(packetV1[5], 227, ());
|
||||
TEST_EQUAL(packetV1[6], 241, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Protocol_DecodeAuthPacket)
|
||||
{
|
||||
auto packet = Protocol::CreateAuthPacket("ABC");
|
||||
TEST_EQUAL(packet.size(), 7, ());
|
||||
TEST_EQUAL(Protocol::PacketType(packet[0]), Protocol::PacketType::CurrentAuth, ());
|
||||
|
||||
auto payload = vector<uint8_t>(begin(packet) + sizeof(uint32_t /* header */), end(packet));
|
||||
auto result = Protocol::DecodeAuthPacket(Protocol::PacketType::CurrentAuth, payload);
|
||||
TEST_EQUAL(result, "ABC", ());
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
void DecodeDataPacketVersionTest(Container const & points, Protocol::PacketType version)
|
||||
{
|
||||
double const kEps = 1e-5;
|
||||
|
||||
auto packet = Protocol::CreateDataPacket(points, version);
|
||||
TEST_GREATER(packet.size(), 0, ());
|
||||
TEST_EQUAL(Protocol::PacketType(packet[0]), version, ());
|
||||
|
||||
auto payload = vector<uint8_t>(begin(packet) + sizeof(uint32_t /* header */), end(packet));
|
||||
Container result = Protocol::DecodeDataPacket(version, payload);
|
||||
|
||||
TEST_EQUAL(points.size(), result.size(), ());
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
{
|
||||
TEST_EQUAL(points[i].m_timestamp, result[i].m_timestamp, (points[i].m_timestamp, result[i].m_timestamp));
|
||||
TEST(AlmostEqualAbsOrRel(points[i].m_latLon.m_lat, result[i].m_latLon.m_lat, kEps),
|
||||
(points[i].m_latLon.m_lat, result[i].m_latLon.m_lat));
|
||||
TEST(AlmostEqualAbsOrRel(points[i].m_latLon.m_lon, result[i].m_latLon.m_lon, kEps),
|
||||
(points[i].m_latLon.m_lon, result[i].m_latLon.m_lon));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Protocol_DecodeDataPacket)
|
||||
{
|
||||
using Container = Protocol::DataElementsVec;
|
||||
|
||||
Container points;
|
||||
points.push_back(Container::value_type(1, ms::LatLon(10, 10), 1));
|
||||
points.push_back(Container::value_type(2, ms::LatLon(15, 15), 2));
|
||||
|
||||
DecodeDataPacketVersionTest(points, Protocol::PacketType::DataV0);
|
||||
DecodeDataPacketVersionTest(points, Protocol::PacketType::DataV1);
|
||||
}
|
||||
|
||||
UNIT_TEST(Protocol_DecodeWrongDataPacket)
|
||||
{
|
||||
vector<vector<uint8_t>> payloads = {
|
||||
vector<uint8_t>{},
|
||||
vector<uint8_t>{0x25},
|
||||
vector<uint8_t>{0x0},
|
||||
vector<uint8_t>{0x0, 0x0, 0x23, 0xFF},
|
||||
vector<uint8_t>{0xFF, 0x1, 0x23, 0xFF, 0x1, 0x0, 0x27, 0x63, 0x32, 0x9, 0xFF},
|
||||
};
|
||||
for (auto const packetType : {Protocol::PacketType::DataV0, Protocol::PacketType::DataV1})
|
||||
{
|
||||
for (auto const & payload : payloads)
|
||||
{
|
||||
auto result = Protocol::DecodeDataPacket(packetType, payload);
|
||||
TEST(result.empty(), (packetType, payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
106
libs/tracking/tracking_tests/reporter_test.cpp
Normal file
106
libs/tracking/tracking_tests/reporter_test.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "tracking/protocol.hpp"
|
||||
#include "tracking/reporter.hpp"
|
||||
|
||||
#include "coding/traffic.hpp"
|
||||
|
||||
#include "platform/location.hpp"
|
||||
#include "platform/platform_tests_support/test_socket.hpp"
|
||||
#include "platform/socket.hpp"
|
||||
|
||||
#include "base/math.hpp"
|
||||
#include "base/thread.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace tracking;
|
||||
using namespace platform::tests_support;
|
||||
|
||||
namespace
|
||||
{
|
||||
void TransferLocation(Reporter & reporter, TestSocket & testSocket, double timestamp, double latidute,
|
||||
double longtitude)
|
||||
{
|
||||
location::GpsInfo gpsInfo;
|
||||
gpsInfo.m_timestamp = timestamp;
|
||||
gpsInfo.m_latitude = latidute;
|
||||
gpsInfo.m_longitude = longtitude;
|
||||
gpsInfo.m_horizontalAccuracy = 1.0;
|
||||
reporter.AddLocation(gpsInfo, traffic::SpeedGroup::Unknown);
|
||||
|
||||
using Packet = tracking::Protocol::PacketType;
|
||||
vector<uint8_t> buffer;
|
||||
size_t readSize = 0;
|
||||
size_t attempts = 3;
|
||||
do
|
||||
{
|
||||
readSize = testSocket.ReadServer(buffer);
|
||||
if (attempts-- && readSize == 0)
|
||||
continue;
|
||||
switch (Packet(buffer[0]))
|
||||
{
|
||||
case Packet::CurrentAuth:
|
||||
{
|
||||
buffer.clear();
|
||||
testSocket.WriteServer(tracking::Protocol::kOk);
|
||||
break;
|
||||
}
|
||||
case Packet::Error:
|
||||
case Packet::DataV0:
|
||||
case Packet::DataV1:
|
||||
{
|
||||
readSize = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (readSize);
|
||||
|
||||
TEST(!buffer.empty(), ());
|
||||
vector<coding::TrafficGPSEncoder::DataPoint> points;
|
||||
MemReader memReader(buffer.data(), buffer.size());
|
||||
ReaderSource<MemReader> src(memReader);
|
||||
src.Skip(sizeof(uint32_t /* header */));
|
||||
coding::TrafficGPSEncoder::DeserializeDataPoints(coding::TrafficGPSEncoder::kLatestVersion, src, points);
|
||||
|
||||
TEST_EQUAL(points.size(), 1, ());
|
||||
auto const & point = points[0];
|
||||
TEST_EQUAL(point.m_timestamp, timestamp, ());
|
||||
TEST(AlmostEqualAbs(point.m_latLon.m_lat, latidute, 0.001), ());
|
||||
TEST(AlmostEqualAbs(point.m_latLon.m_lon, longtitude, 0.001), ());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UNIT_TEST(Reporter_Smoke)
|
||||
{
|
||||
{
|
||||
unique_ptr<platform::Socket> socket;
|
||||
Reporter reporter(std::move(socket), "localhost", 0, milliseconds(10) /* pushDelay */);
|
||||
}
|
||||
{
|
||||
auto socket = make_unique<TestSocket>();
|
||||
Reporter reporter(std::move(socket), "localhost", 0, milliseconds(10) /* pushDelay */);
|
||||
}
|
||||
{
|
||||
auto socket = platform::CreateSocket();
|
||||
Reporter reporter(std::move(socket), "localhost", 0, milliseconds(10) /* pushDelay */);
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Reporter_TransferLocations)
|
||||
{
|
||||
auto socket = make_unique<TestSocket>();
|
||||
TestSocket & testSocket = *socket.get();
|
||||
|
||||
Reporter reporter(std::move(socket), "localhost", 0, milliseconds(10) /* pushDelay */);
|
||||
TransferLocation(reporter, testSocket, 1.0, 2.0, 3.0);
|
||||
TransferLocation(reporter, testSocket, 4.0, 5.0, 6.0);
|
||||
TransferLocation(reporter, testSocket, 7.0, 8.0, 9.0);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue