1/*
2 * Copyright (C) 2019 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#pragma once
27
28#include "RegistrableDomain.h"
29#include <wtf/CompletionHandler.h>
30#include <wtf/Optional.h>
31#include <wtf/URL.h>
32#include <wtf/WallTime.h>
33#include <wtf/text/StringHash.h>
34#include <wtf/text/WTFString.h>
35
36namespace WebCore {
37
38class AdClickAttribution {
39public:
40 using CampaignId = uint32_t;
41 using ConversionData = uint32_t;
42 using PriorityValue = uint32_t;
43
44 static constexpr uint32_t MaxEntropy = 63;
45
46 struct Campaign {
47 Campaign() = default;
48 explicit Campaign(CampaignId id)
49 : id { id }
50 {
51 }
52
53 bool isValid() const
54 {
55 return id <= MaxEntropy;
56 }
57
58 CampaignId id { 0 };
59 };
60
61 struct Source {
62 Source() = default;
63 explicit Source(const URL& url)
64 : registrableDomain { url }
65 {
66 }
67
68 explicit Source(const RegistrableDomain& domain)
69 : registrableDomain { domain }
70 {
71 }
72
73 explicit Source(WTF::HashTableDeletedValueType)
74 : registrableDomain(WTF::HashTableDeletedValue)
75 {
76 }
77
78 bool operator==(const Source& other) const
79 {
80 return registrableDomain == other.registrableDomain;
81 }
82
83 bool matches(const URL& url) const
84 {
85 return registrableDomain.matches(url);
86 }
87
88 bool isHashTableDeletedValue() const
89 {
90 return registrableDomain.isHashTableDeletedValue();
91 }
92
93 static Source deletedValue()
94 {
95 return Source { WTF::HashTableDeletedValue };
96 }
97
98 static void constructDeletedValue(Source& source)
99 {
100 new (&source) Source;
101 source = Source::deletedValue();
102 }
103
104 void deleteValue()
105 {
106 registrableDomain = RegistrableDomain { WTF::HashTableDeletedValue };
107 }
108
109 bool isDeletedValue() const
110 {
111 return isHashTableDeletedValue();
112 }
113
114 RegistrableDomain registrableDomain;
115 };
116
117 struct SourceHash {
118 static unsigned hash(const Source& source)
119 {
120 return source.registrableDomain.hash();
121 }
122
123 static bool equal(const Source& a, const Source& b)
124 {
125 return a == b;
126 }
127
128 static const bool safeToCompareToEmptyOrDeleted = false;
129 };
130
131 struct Destination {
132 Destination() = default;
133 explicit Destination(const URL& url)
134 : registrableDomain { RegistrableDomain { url } }
135 {
136 }
137
138 explicit Destination(WTF::HashTableDeletedValueType)
139 : registrableDomain { WTF::HashTableDeletedValue }
140 {
141 }
142
143 explicit Destination(RegistrableDomain&& domain)
144 : registrableDomain { WTFMove(domain) }
145 {
146 }
147
148 bool operator==(const Destination& other) const
149 {
150 return registrableDomain == other.registrableDomain;
151 }
152
153 bool matches(const URL& url) const
154 {
155 return registrableDomain == RegistrableDomain { url };
156 }
157
158 bool isHashTableDeletedValue() const
159 {
160 return registrableDomain.isHashTableDeletedValue();
161 }
162
163 static Destination deletedValue()
164 {
165 return Destination { WTF::HashTableDeletedValue };
166 }
167
168 static void constructDeletedValue(Destination& destination)
169 {
170 new (&destination) Destination;
171 destination = Destination::deletedValue();
172 }
173
174 void deleteValue()
175 {
176 registrableDomain = RegistrableDomain { WTF::HashTableDeletedValue };
177 }
178
179 bool isDeletedValue() const
180 {
181 return isHashTableDeletedValue();
182 }
183
184 RegistrableDomain registrableDomain;
185 };
186
187 struct DestinationHash {
188 static unsigned hash(const Destination& destination)
189 {
190 return destination.registrableDomain.hash();
191 }
192
193 static bool equal(const Destination& a, const Destination& b)
194 {
195 return a == b;
196 }
197
198 static const bool safeToCompareToEmptyOrDeleted = false;
199 };
200
201 struct Priority {
202 explicit Priority(PriorityValue value)
203 : value { value }
204 {
205 }
206
207 PriorityValue value;
208 };
209
210 struct Conversion {
211 enum class WasSent : bool { No, Yes };
212
213 Conversion(ConversionData data, Priority priority, WasSent wasSent = WasSent::No)
214 : data { data }
215 , priority { priority.value }
216 , wasSent { wasSent }
217 {
218 }
219
220 bool isValid() const
221 {
222 return data <= MaxEntropy && priority <= MaxEntropy;
223 }
224
225 ConversionData data;
226 PriorityValue priority;
227 WasSent wasSent = WasSent::No;
228
229 template<class Encoder> void encode(Encoder&) const;
230 template<class Decoder> static Optional<Conversion> decode(Decoder&);
231 };
232
233 AdClickAttribution() = default;
234 AdClickAttribution(Campaign campaign, const Source& source, const Destination& destination)
235 : m_campaign { campaign }
236 , m_source { source }
237 , m_destination { destination }
238 , m_timeOfAdClick { WallTime::now() }
239 {
240 }
241
242 WEBCORE_EXPORT static Optional<Conversion> parseConversionRequest(const URL& redirectURL);
243 WEBCORE_EXPORT Optional<Seconds> convertAndGetEarliestTimeToSend(Conversion&&);
244 WEBCORE_EXPORT bool hasHigherPriorityThan(const AdClickAttribution&) const;
245 WEBCORE_EXPORT URL url() const;
246 WEBCORE_EXPORT URL urlForTesting(const URL& baseURLForTesting) const;
247 WEBCORE_EXPORT URL referrer() const;
248 const Source& source() const { return m_source; };
249 const Destination& destination() const { return m_destination; };
250 Optional<WallTime> earliestTimeToSend() const { return m_earliestTimeToSend; };
251 WEBCORE_EXPORT void markAsExpired();
252 WEBCORE_EXPORT bool hasExpired() const;
253 WEBCORE_EXPORT void markConversionAsSent();
254 WEBCORE_EXPORT bool wasConversionSent() const;
255
256 bool isEmpty() const { return m_source.registrableDomain.isEmpty(); };
257
258 WEBCORE_EXPORT String toString() const;
259
260 template<class Encoder> void encode(Encoder&) const;
261 template<class Decoder> static Optional<AdClickAttribution> decode(Decoder&);
262
263private:
264 bool isValid() const;
265 static bool debugModeEnabled();
266
267 Campaign m_campaign;
268 Source m_source;
269 Destination m_destination;
270 WallTime m_timeOfAdClick;
271
272 Optional<Conversion> m_conversion;
273 Optional<WallTime> m_earliestTimeToSend;
274};
275
276template<class Encoder>
277void AdClickAttribution::encode(Encoder& encoder) const
278{
279 encoder << m_campaign.id << m_source.registrableDomain << m_destination.registrableDomain << m_timeOfAdClick << m_conversion << m_earliestTimeToSend;
280}
281
282template<class Decoder>
283Optional<AdClickAttribution> AdClickAttribution::decode(Decoder& decoder)
284{
285 Optional<CampaignId> campaignId;
286 decoder >> campaignId;
287 if (!campaignId)
288 return WTF::nullopt;
289
290 Optional<RegistrableDomain> sourceRegistrableDomain;
291 decoder >> sourceRegistrableDomain;
292 if (!sourceRegistrableDomain)
293 return WTF::nullopt;
294
295 Optional<RegistrableDomain> destinationRegistrableDomain;
296 decoder >> destinationRegistrableDomain;
297 if (!destinationRegistrableDomain)
298 return WTF::nullopt;
299
300 Optional<WallTime> timeOfAdClick;
301 decoder >> timeOfAdClick;
302 if (!timeOfAdClick)
303 return WTF::nullopt;
304
305 Optional<Optional<Conversion>> conversion;
306 decoder >> conversion;
307 if (!conversion)
308 return WTF::nullopt;
309
310 Optional<Optional<WallTime>> earliestTimeToSend;
311 decoder >> earliestTimeToSend;
312 if (!earliestTimeToSend)
313 return WTF::nullopt;
314
315 AdClickAttribution attribution { Campaign { WTFMove(*campaignId) }, Source { WTFMove(*sourceRegistrableDomain) }, Destination { WTFMove(*destinationRegistrableDomain) } };
316 attribution.m_conversion = WTFMove(*conversion);
317 attribution.m_earliestTimeToSend = WTFMove(*earliestTimeToSend);
318
319 return attribution;
320}
321
322template<class Encoder>
323void AdClickAttribution::Conversion::encode(Encoder& encoder) const
324{
325 encoder << data << priority << wasSent;
326}
327
328template<class Decoder>
329Optional<AdClickAttribution::Conversion> AdClickAttribution::Conversion::decode(Decoder& decoder)
330{
331 Optional<ConversionData> data;
332 decoder >> data;
333 if (!data)
334 return WTF::nullopt;
335
336 Optional<PriorityValue> priority;
337 decoder >> priority;
338 if (!priority)
339 return WTF::nullopt;
340
341 Optional<WasSent> wasSent;
342 decoder >> wasSent;
343 if (!wasSent)
344 return WTF::nullopt;
345
346 return Conversion { WTFMove(*data), Priority { *priority }, *wasSent };
347}
348
349} // namespace WebCore
350
351namespace WTF {
352template<typename T> struct DefaultHash;
353
354template<> struct DefaultHash<WebCore::AdClickAttribution::Source> {
355 typedef WebCore::AdClickAttribution::SourceHash Hash;
356};
357template<> struct HashTraits<WebCore::AdClickAttribution::Source> : GenericHashTraits<WebCore::AdClickAttribution::Source> {
358 static WebCore::AdClickAttribution::Source emptyValue() { return { }; }
359 static void constructDeletedValue(WebCore::AdClickAttribution::Source& slot) { WebCore::AdClickAttribution::Source::constructDeletedValue(slot); }
360 static bool isDeletedValue(const WebCore::AdClickAttribution::Source& slot) { return slot.isDeletedValue(); }
361};
362
363template<> struct DefaultHash<WebCore::AdClickAttribution::Destination> {
364 typedef WebCore::AdClickAttribution::DestinationHash Hash;
365};
366template<> struct HashTraits<WebCore::AdClickAttribution::Destination> : GenericHashTraits<WebCore::AdClickAttribution::Destination> {
367 static WebCore::AdClickAttribution::Destination emptyValue() { return { }; }
368 static void constructDeletedValue(WebCore::AdClickAttribution::Destination& slot) { WebCore::AdClickAttribution::Destination::constructDeletedValue(slot); }
369 static bool isDeletedValue(const WebCore::AdClickAttribution::Destination& slot) { return slot.isDeletedValue(); }
370};
371}
372