Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View 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()

View 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

View 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

View 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

View 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

View 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

View 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
View 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
View 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

View 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

View 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
View 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

View 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

View 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 "")

View 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");
}

View 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
View 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

View 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

View 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)

View 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;
}

View 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
)

View 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

View 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));
}
}
}

View 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);
}