#include "local_ads/campaign_serialization.hpp" #include "coding/byte_stream.hpp" #include "coding/reader.hpp" #include "coding/varint.hpp" #include "coding/write_to_sink.hpp" #include "base/exception.hpp" #include "base/logging.hpp" #include "base/stl_helpers.hpp" #include #include #include #include namespace { using namespace local_ads; DECLARE_EXCEPTION(UnknownVersion, RootException); auto const kHalfByteShift = CHAR_BIT / 2; auto const kHalfByteMaxValue = 15; auto const kLowerMask = 0x0F; auto const kUpperMask = 0xF0; auto const kMinZoomLevel = 10; auto const kMaxZoomLevel = 17; auto const kMaxPriority = 7; template ::value, void *> = nullptr> std::vector ReadVarUintArray(Source & s, size_t chunksNumber) { std::vector result; for (size_t i = 0; i < chunksNumber; ++i) result.emplace_back(static_cast(ReadVarUint(s))); return result; } template std::vector ReadArray(Source & s, size_t chunksNumber) { std::vector result; for (size_t i = 0; i < chunksNumber; ++i) { result.emplace_back(ReadPrimitiveFromSource(s)); } return result; } std::vector SerializeV1(std::vector const & campaigns) { std::vector buff; PushBackByteSink dst(buff); WriteToSink(dst, Version::V1); WriteToSink(dst, campaigns.size()); for (auto const & c : campaigns) WriteVarUint(dst, c.m_featureId); for (auto const & c : campaigns) WriteVarUint(dst, c.m_iconId); for (auto const & c : campaigns) WriteVarUint(dst, c.m_daysBeforeExpired); return buff; } std::vector DeserializeV1(std::vector const & bytes) { ReaderSource src({bytes.data(), bytes.size()}); CHECK_EQUAL(ReadPrimitiveFromSource(src), Version::V1, ()); auto const chunksNumber = ReadPrimitiveFromSource(src); auto const featureIds = ReadVarUintArray(src, chunksNumber); auto const icons = ReadVarUintArray(src, chunksNumber); auto const expirations = ReadVarUintArray(src, chunksNumber); std::vector campaigns; campaigns.reserve(chunksNumber); for (size_t i = 0; i < chunksNumber; ++i) { campaigns.emplace_back(featureIds[i], icons[i], expirations[i]); } return campaigns; } uint8_t ZoomIndex(uint8_t zoomValue) { return zoomValue - kMinZoomLevel; } uint8_t ZoomValue(uint8_t zoomIndex) { return zoomIndex + kMinZoomLevel; } uint8_t PackZoomAndPriority(uint8_t minZoomLevel, uint8_t priority) { UNUSED_VALUE(kMaxZoomLevel); UNUSED_VALUE(kMaxPriority); UNUSED_VALUE(kHalfByteMaxValue); ASSERT_GREATER_OR_EQUAL(minZoomLevel, kMinZoomLevel, ("Unsupported zoom level")); ASSERT_LESS_OR_EQUAL(minZoomLevel, kMaxZoomLevel, ("Unsupported zoom level")); ASSERT_LESS_OR_EQUAL(priority, kMaxPriority, ("Unsupported priority value")); auto const zoomIndex = ZoomIndex(minZoomLevel); ASSERT_LESS_OR_EQUAL(zoomIndex, kHalfByteMaxValue, ()); ASSERT_LESS_OR_EQUAL(priority, kHalfByteMaxValue, ()); // Pack zoom and priority into single byte. return (zoomIndex & kLowerMask) | ((priority << kHalfByteShift) & kUpperMask); } uint8_t UnpackZoom(uint8_t src) { return ZoomValue(src & kLowerMask); } uint8_t UnpackPriority(uint8_t src) { return (src >> kHalfByteShift) & kLowerMask; } std::vector SerializeV2(std::vector const & campaigns) { std::vector buff; PushBackByteSink dst(buff); WriteToSink(dst, Version::V2); WriteToSink(dst, campaigns.size()); for (auto const & c : campaigns) WriteVarUint(dst, c.m_featureId); for (auto const & c : campaigns) WriteVarUint(dst, c.m_iconId); for (auto const & c : campaigns) WriteVarUint(dst, c.m_daysBeforeExpired); for (auto const & c : campaigns) WriteToSink(dst, PackZoomAndPriority(c.m_minZoomLevel, c.m_priority)); return buff; } std::vector DeserializeV2(std::vector const & bytes) { ReaderSource src({bytes.data(), bytes.size()}); CHECK_EQUAL(ReadPrimitiveFromSource(src), Version::V2, ()); auto const chunksNumber = ReadPrimitiveFromSource(src); auto const featureIds = ReadVarUintArray(src, chunksNumber); auto const icons = ReadVarUintArray(src, chunksNumber); auto const expirations = ReadVarUintArray(src, chunksNumber); auto const zoomAndPriority = ReadArray(src, chunksNumber); std::vector campaigns; campaigns.reserve(chunksNumber); for (size_t i = 0; i < chunksNumber; ++i) { campaigns.emplace_back(featureIds[i], icons[i], expirations[i], UnpackZoom(zoomAndPriority[i]), UnpackPriority(zoomAndPriority[i])); ASSERT_GREATER_OR_EQUAL(campaigns.back().m_minZoomLevel, kMinZoomLevel, ("Unsupported zoom level")); ASSERT_LESS_OR_EQUAL(campaigns.back().m_minZoomLevel, kMaxZoomLevel, ("Unsupported zoom level")); ASSERT_LESS_OR_EQUAL(campaigns.back().m_priority, kMaxPriority, ("Unsupported priority value")); } return campaigns; } } // namespace namespace local_ads { std::vector Serialize(std::vector const & campaigns, Version const version) { try { switch (version) { case Version::V1: return SerializeV1(campaigns); case Version::V2: return SerializeV2(campaigns); default: MYTHROW(UnknownVersion, (version)); } } catch (RootException const & e) { LOG(LERROR, ("Cannot to serialize campaigns", e.what(), e.Msg())); } return {}; } std::vector Serialize(std::vector const & campaigns) { return Serialize(campaigns, Version::Latest); } std::vector Deserialize(std::vector const & bytes) { try { ReaderSource src({bytes.data(), bytes.size()}); auto const version = ReadPrimitiveFromSource(src); switch (version) { case Version::V1: return DeserializeV1(bytes); case Version::V2: return DeserializeV2(bytes); default: MYTHROW(UnknownVersion, (version)); } } catch (RootException const & e) { LOG(LERROR, ("Cannot to deserialize received data", e.what(), e.Msg())); } catch (std::bad_alloc const & e) { LOG(LERROR, ("Cannot to allocate memory for local ads campaigns", e.what())); } return {}; } std::string DebugPrint(local_ads::Version version) { using local_ads::Version; switch (version) { case Version::Unknown: return "Unknown"; case Version::V1: return "Version 1"; case Version::V2: return "Version 2"; default: ASSERT(false, ("Unknown version")); } return {}; } } // namespace local_ads