This repository has been archived by the owner on Aug 28, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmkreport.hpp
273 lines (242 loc) · 10 KB
/
mkreport.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
// Part of Measurement Kit <https://measurement-kit.github.io/>.
// Measurement Kit is free software under the BSD license. See AUTHORS
// and LICENSE for more information on the copying conditions.
#ifndef MEASUREMENT_KIT_MKREPORT_HPP
#define MEASUREMENT_KIT_MKREPORT_HPP
#include <map>
#include <string>
#include <vector>
namespace mk {
namespace report {
/// ooni_date_now formats the current date and time according to the
/// format that is expected by the OONI collector. This function will
/// be useful to init some Measurement and Report fields. The format
/// used by this function is "%Y-%m-%d %H:%M:%S". The returned time is
/// in UTC rather than in the local time.
std::string ooni_date_now() noexcept;
// TODO(bassosimone): monotonic_seconds_now should probably be
// shared and used also by other mk* libraries.
/// monotonic_seconds_now returns the current time in seconds according
/// to the C++11 monotonic clock. This function is useful to compute
/// the duration, in seconds, of an external network test.
double monotonic_seconds_now() noexcept;
/// Report is the report generated by one or more measurements. See
/// https://github.com/ooni/spec/tree/master/data-formats for the spec.
//
// # Implementation note
//
// Note that the Report class does not contain an ID field. This is
// done on purpose, because the ID is only obtained after interacting
// with a OONI collector. So, you should interact with a collector
// in some way to get an ID. Moreover, saving the ID inside the JSON
// report is not necessary, but rather is an implementation choice. An
// app could have a database where the report ID maps to the report
// file name on disk, and that would be fine. This explains why ID is
// not part of the data model implemented by this class.
class Report {
public:
/// annotation contains optional results annotations.
std::map<std::string, std::string> annotations;
/// probe_asn is the probe ASN. To fill this field, you can use for
/// example the github.com/measurement-kit/mkgeoip library.
std::string probe_asn;
/// probe_cc is the probe country code (CC). To fill this field, you can use
/// e.g. the github.com/measurement-kit/mkgeoip library.
std::string probe_cc;
/// software_name is the name of the application.
std::string software_name;
/// software_version is the version of the application.
std::string software_version;
/// test_helpers contains a mapping from a test helper name according
/// to the nettest specification to a test helper URL. This field
/// structure is less complex than that in the specification, however,
/// in MK we've traditionally just used this flat kind of mapping.
std::map<std::string, std::string> test_helpers;
/// test_name is the nettest name.
std::string test_name;
/// test_start_time is the time when the test started. To initialize
/// this field, you can use e.g. the ooni_date_now function.
std::string test_start_time;
/// test_version is the nettest version.
std::string test_version;
};
/// Measurement contains information about a measurement.
class Measurement {
public:
/// input is the measurement input. This field is optional and it's fine
/// to leave it empty for nettests that do not require any input.
std::string input;
/// report is the report to which this measurement belongs.
Report report;
/// start_time is the time when the measurement started. You must init
/// this field. You can use ooni_date_now to initialize it.
std::string start_time;
/// runtime is the time for which the measurement run. You can use
/// monotonic_seconds_now to compute this field. If you are not using
/// this function, please make sure you use a monotonic clock. You
/// may not initialize this field, but this will be odd because the
/// measurement runtime will be zero seconds.
double runtime = 0.0;
/// test_keys is a serialized JSON containing the results
/// of running this measurement. If you do not have any
/// relevant test key to put here, please initialize this
/// field to the empty object, i.e. `"{}"`. Each test
/// should specify the format of the JSON that is saved
/// here as a serialized string. See the specification
/// https://github.com/ooni/spec/tree/master/data-formats
/// for more information on each specific test.
std::string test_keys;
};
/// dump dumps @p measurement to @p str. Returns true on success
/// and false on failure. Check @p logs in that case. Note that
/// we will perform checks on the value of fields and we will
/// fail if these values do not conform with what we expect. Note
/// that we only support a subset of data_format_version 0.2.0,
/// and we emit legacy fields with conventional values rather than
/// allowing the user to set them. See the code for more info.
///
/// When you dump-ed a @p measurement, you can use a OONI collector
/// library like github.com/measurement-kit/mkreport to submit the
/// @p str serialized JSON string to the OONI collector.
bool dump(const Measurement &measurement, std::string &str,
std::string &logs) noexcept;
} // namespace report
} // namespace mk
// The implementation can be included inline by defining this preprocessor
// symbol. If you only care about API, you can stop reading here.
#ifdef MKREPORT_INLINE_IMPL
#include <exception>
#include <chrono>
#include <utility>
#include "date.h"
#include "json.hpp"
#include "mkuuid4.hpp"
namespace mk {
namespace report {
std::string ooni_date_now() noexcept {
// Implementation note: to avoid using the C standard library that has
// given us many headaches on Windows because of parameter validation we
// go for a fully C++11 solution based on <chrono> and on the C++11
// HowardHinnant/date library, which will be available as part of the
// C++ standard library starting from C++20.
//
// Explanation of the algorithm:
//
// 1. get the current system time
// 2. round the time point obtained in the previous step to an integral
// number of seconds since the EPOCH used by the system clock
// 3. create a system clock time point from the integral number of seconds
// 4. convert the previous result to string using HowardInnant/date
// 5. if there is a decimal component (there should be one given how the
// library we use works) remove it, because OONI doesn't like it
//
// (There was another way to deal with fractionary seconds, i.e. using '%OS',
// but this solution seems better to me because it's less obscure.)
using namespace std::chrono;
constexpr auto fmt = "%Y-%m-%d %H:%M:%S";
auto sys_point = system_clock::now(); // 1
auto as_seconds = duration_cast<seconds>(sys_point.time_since_epoch()); // 2
auto back_as_sys_point = system_clock::time_point(as_seconds); // 3
auto s = date::format(fmt, back_as_sys_point); // 4
if (s.find(".") != std::string::npos) s = s.substr(0, s.find(".")); // 5
return s;
}
double monotonic_seconds_now() noexcept {
auto now = std::chrono::steady_clock::now();
std::chrono::duration<double> elapsed = now.time_since_epoch();
return elapsed.count();
}
// ok tells you whether a @p measurement is okay. Returns true on success
// and false on failure. Check @p logs in the latter case.
static bool ok(const Measurement &measurement, std::string &logs) noexcept {
if (measurement.report.probe_asn.empty()) {
logs = "Please, initialize report.probe_asn";
return false;
}
if (measurement.report.probe_cc.empty()) {
logs = "Please, initialize report.probe_cc.";
return false;
}
if (measurement.report.software_name.empty()) {
logs = "Please, initialize report.software_name";
return false;
}
if (measurement.report.software_version.empty()) {
logs = "Please, initialize report.software_version";
return false;
}
if (measurement.report.test_name.empty()) {
logs = "Please, initialize report.test_name";
return false;
}
if (measurement.report.test_start_time.empty()) {
logs = "Please, initialize report.test_start_time";
return false;
}
if (measurement.report.test_version.empty()) {
logs = "Please, initialize report.test_version";
return false;
}
if (measurement.start_time.empty()) {
logs = "Please, initialize measurement.start_time";
return false;
}
if (measurement.test_keys.empty()) {
logs = "Please, initialize measurement.test_keys";
return false;
}
logs = {};
return true;
}
bool dump(const Measurement &measurement, std::string &str,
std::string &logs) noexcept {
str = {}, logs = {};
if (!ok(measurement, logs)) {
return false;
}
nlohmann::json tk;
try {
tk = nlohmann::json::parse(measurement.test_keys);
} catch (const std::exception &exc) {
logs = exc.what();
return false;
}
if (!tk.is_object()) {
logs = "test_keys is not a JSON object";
return false;
}
nlohmann::json m;
m["annotations"] = measurement.report.annotations;
m["data_format_version"] = "0.2.0";
m["id"] = mk::uuid4::gen();
if (!measurement.input.empty()) {
m["input"] = measurement.input;
} else {
m["input"] = nullptr;
}
m["input_hashes"] = nlohmann::json::array(); // conventional value
m["measurement_start_time"] = measurement.start_time;
m["options"] = nlohmann::json::array(); // conventional value
m["probe_asn"] = measurement.report.probe_asn;
m["probe_cc"] = measurement.report.probe_cc;
m["probe_city"] = nullptr; // conventional value
m["software_name"] = measurement.report.software_name;
m["software_version"] = measurement.report.software_version;
m["test_helpers"] = measurement.report.test_helpers;
m["test_keys"] = std::move(tk);
m["test_name"] = measurement.report.test_name;
m["test_runtime"] = measurement.runtime;
m["test_start_time"] = measurement.report.test_start_time;
m["test_version"] = measurement.report.test_version;
try {
str = m.dump();
} catch (const std::exception &exc) {
logs = exc.what();
return false;
}
return true;
}
} // namespace report
} // namespace mk
#endif // MKREPORT_INLINE_IMPL
#endif // MEASUREMENT_KIT_MKREPORT_HPP