Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2018 Endless Mobile, Inc.
4 : : *
5 : : * This library is free software; you can redistribute it and/or
6 : : * modify it under the terms of the GNU Lesser General Public
7 : : * License as published by the Free Software Foundation; either
8 : : * version 2.1 of the License, or (at your option) any later version.
9 : : *
10 : : * This library is distributed in the hope that it will be useful,
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : : * Lesser General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU Lesser General Public
16 : : * License along with this library; if not, write to the Free Software
17 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 : : *
19 : : * Authors:
20 : : * - Philip Withnall <withnall@endlessm.com>
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include <glib/gi18n-lib.h>
26 : : #include <glib.h>
27 : : #include <glib-object.h>
28 : : #include <gio/gio.h>
29 : : #include <libmogwai-tariff/period.h>
30 : : #include <libmogwai-tariff/tariff-loader.h>
31 : : #include <libmogwai-tariff/tariff.h>
32 : : #include <malloc.h>
33 : : #include <stdlib.h>
34 : :
35 : :
36 : : static void mwt_tariff_loader_dispose (GObject *object);
37 : :
38 : : /**
39 : : * MwtTariffLoader:
40 : : *
41 : : * A helper object for loading an #MwtTariff from its serialised form as a
42 : : * #GBytes. See #MwtTariffBuilder for the inverse operation.
43 : : *
44 : : * When using a #MwtTariffLoader, the tariff must be loaded from a #GBytes or
45 : : * a #GVariant using mwt_tariff_loader_load_from_bytes() or
46 : : * mwt_tariff_loader_load_from_variant(). If that succeeds, the #MwtTariff
47 : : * object may be retrieved using mwt_tariff_loader_get_tariff(). This will be
48 : : * %NULL if loading failed. Details of the failure will come from the #GError
49 : : * set by the loading function.
50 : : *
51 : : * A #MwtTariffLoader can be reused to load multiple tariffs. Subsequent calls
52 : : * to the loading functions will clear any previously loaded tariff on success
53 : : * or failure.
54 : : *
55 : : * Since: 0.1.0
56 : : */
57 : : struct _MwtTariffLoader
58 : : {
59 : : GObject parent;
60 : :
61 : : MwtTariff *final_tariff; /* (nullable) (owned) */
62 : : };
63 : :
64 [ + + + - : 106 : G_DEFINE_TYPE (MwtTariffLoader, mwt_tariff_loader, G_TYPE_OBJECT)
+ + ]
65 : :
66 : : static void
67 : 7 : mwt_tariff_loader_class_init (MwtTariffLoaderClass *klass)
68 : : {
69 : 7 : GObjectClass *object_class = (GObjectClass *) klass;
70 : :
71 : 7 : object_class->dispose = mwt_tariff_loader_dispose;
72 : 7 : }
73 : :
74 : : static void
75 : 13 : mwt_tariff_loader_init (MwtTariffLoader *self)
76 : : {
77 : : /* Nothing to do here. */
78 : 13 : }
79 : :
80 : : static void
81 : 13 : mwt_tariff_loader_dispose (GObject *object)
82 : : {
83 : 13 : MwtTariffLoader *self = MWT_TARIFF_LOADER (object);
84 : :
85 [ + + ]: 13 : g_clear_object (&self->final_tariff);
86 : :
87 : : /* Chain up to the parent class */
88 : 13 : G_OBJECT_CLASS (mwt_tariff_loader_parent_class)->dispose (object);
89 : 13 : }
90 : :
91 : : /**
92 : : * mwt_tariff_loader_new:
93 : : *
94 : : * Create a new, empty #MwtTariffLoader.
95 : : *
96 : : * Returns: (transfer full): a new #MwtTariffLoader
97 : : * Since: 0.1.0
98 : : */
99 : : MwtTariffLoader *
100 : 13 : mwt_tariff_loader_new (void)
101 : : {
102 : 13 : return g_object_new (MWT_TYPE_TARIFF_LOADER, NULL);
103 : : }
104 : :
105 : : /* Allocate an 8-aligned block of @size bytes of memory.
106 : : * FIXME: This should not be necessary; g_variant_new_from_bytes() should do
107 : : * this for us. */
108 : : static gpointer
109 : 1 : align_malloc (gsize size)
110 : : {
111 : : gpointer mem;
112 : :
113 [ - + ]: 1 : if (posix_memalign (&mem, 8, size))
114 : 0 : g_error ("posix_memalign failed");
115 : :
116 : 1 : return mem;
117 : : }
118 : :
119 : : static void
120 : 1 : align_free (gpointer mem)
121 : : {
122 : 1 : free (mem);
123 : 1 : }
124 : :
125 : : /**
126 : : * mwt_tariff_loader_load_from_bytes:
127 : : * @self: a #MwtTariffLoader
128 : : * @bytes: the data to load
129 : : * @error: return location for a #GError, or %NULL
130 : : *
131 : : * Try to load a tariff from its serialised form in @bytes. The data in @bytes
132 : : * must be exactly as produced by a #MwtTariffBuilder, without any additional
133 : : * byte swapping or zero padding.
134 : : *
135 : : * On success, the loaded tariff will be available by calling
136 : : * mwt_tariff_loader_get_tariff().
137 : : *
138 : : * Note: @bytes must be backed by memory that is at least 8-byte aligned (even
139 : : * on platforms with narrower native alignment). Otherwise, this function will
140 : : * internally create a copy of the memory.
141 : : *
142 : : * Returns: %TRUE on success, %FALSE otherwise
143 : : * Since: 0.1.0
144 : : */
145 : : gboolean
146 : 17 : mwt_tariff_loader_load_from_bytes (MwtTariffLoader *self,
147 : : GBytes *bytes,
148 : : GError **error)
149 : : {
150 [ - + ]: 17 : g_return_val_if_fail (MWT_IS_TARIFF_LOADER (self), FALSE);
151 [ - + ]: 17 : g_return_val_if_fail (bytes != NULL, FALSE);
152 [ + - - + ]: 17 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
153 : :
154 : : /* Clear any existing result. */
155 [ - + ]: 17 : g_clear_object (&self->final_tariff);
156 : :
157 : : /* FIXME: GLib really should handle this itself. We need to 8-align as the
158 : : * type of our variant is (sqv), and `v` must be 8-aligned. */
159 : 17 : g_autoptr(GBytes) aligned_bytes = NULL;
160 : : gsize unaligned_data_len;
161 : 17 : gconstpointer unaligned_data = g_bytes_get_data (bytes, &unaligned_data_len);
162 [ + + ]: 17 : if (((guintptr) unaligned_data) % 8 != 0)
163 : : {
164 : 1 : guint8 *aligned_data = align_malloc (unaligned_data_len);
165 : 1 : memcpy (aligned_data, unaligned_data, unaligned_data_len);
166 : 1 : aligned_bytes = g_bytes_new_with_free_func (aligned_data, unaligned_data_len,
167 : : align_free, NULL);
168 : : }
169 : : else
170 : 16 : aligned_bytes = g_bytes_ref (bytes);
171 : :
172 : 17 : g_autoptr(GVariant) variant = NULL;
173 : 17 : variant = g_variant_new_from_bytes (G_VARIANT_TYPE ("(sqv)"), aligned_bytes, FALSE);
174 : :
175 : 17 : return mwt_tariff_loader_load_from_variant (self, variant, error);
176 : : }
177 : :
178 : : /* Construct a #GDateTime from the given @unix_timestamp (always in UTC) and
179 : : * @timezone_identifier (for example, ‘Europe/London’; note that this is *not*
180 : : * a timezone abbreviation like ‘AST’). This will return %NULL if an invalid
181 : : * time results. */
182 : : static GDateTime *
183 : 22 : date_time_new_from_unix (guint64 unix_timestamp,
184 : : const gchar *timezone_identifier)
185 : : {
186 : 44 : g_autoptr(GDateTime) utc = g_date_time_new_from_unix_utc (unix_timestamp);
187 [ - + ]: 22 : if (utc == NULL)
188 : 0 : return NULL;
189 : :
190 : 22 : g_autoptr(GTimeZone) tz = NULL;
191 [ - + ]: 22 : if (*timezone_identifier == '\0')
192 : 0 : tz = g_time_zone_new_local ();
193 : : else
194 : : #if GLIB_CHECK_VERSION(2, 68, 0)
195 : 22 : tz = g_time_zone_new_identifier (timezone_identifier);
196 : : #else
197 : : tz = g_time_zone_new (timezone_identifier);
198 : : #endif
199 : :
200 : : #if GLIB_CHECK_VERSION(2, 68, 0)
201 [ - + ]: 22 : if (tz == NULL)
202 : 0 : return NULL;
203 : : #endif
204 : :
205 : 22 : g_debug ("%s: Created timezone ‘%s’ for ‘%s’, with offset %d at interval 0",
206 : : G_STRFUNC, g_time_zone_get_identifier (tz), timezone_identifier,
207 : : g_time_zone_get_offset (tz, 0));
208 : :
209 : : #if !GLIB_CHECK_VERSION(2, 68, 0)
210 : : /* Creating a timezone can’t actually fail, but if we fail to load the
211 : : * timezone information, the #GTimeZone will just represent UTC. Catch that. */
212 : : g_assert (tz != NULL);
213 : : if (!g_str_equal (g_time_zone_get_identifier (tz), timezone_identifier))
214 : : return NULL;
215 : : #endif
216 : :
217 : 22 : return g_date_time_to_timezone (utc, tz);
218 : : }
219 : :
220 : : /**
221 : : * mwt_tariff_loader_load_from_variant:
222 : : * @self: a #MwtTariffLoader
223 : : * @variant: the variant to load
224 : : * @error: return location for a #GError, or %NULL
225 : : *
226 : : * Version of mwt_tariff_loader_load_from_bytes() which loads from a
227 : : * deserialised #GVariant. mwt_tariff_loader_load_from_bytes() is essentially a
228 : : * wrapper around g_variant_new_from_bytes() and this function.
229 : : *
230 : : * It is not a programming error if the given @variant is not in normal form,
231 : : * or is of the wrong type.
232 : : *
233 : : * Returns: %TRUE on success, %FALSE otherwise
234 : : * Since: 0.1.0
235 : : */
236 : : gboolean
237 : 24 : mwt_tariff_loader_load_from_variant (MwtTariffLoader *self,
238 : : GVariant *variant,
239 : : GError **error)
240 : : {
241 [ - + ]: 24 : g_return_val_if_fail (MWT_IS_TARIFF_LOADER (self), FALSE);
242 [ - + ]: 24 : g_return_val_if_fail (variant != NULL, FALSE);
243 [ + - - + ]: 24 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
244 : :
245 : : /* Clear any existing result. */
246 [ - + ]: 24 : g_clear_object (&self->final_tariff);
247 : :
248 : : /* This also checks whether the inner variant is in normal form. */
249 [ + + ]: 24 : if (!g_variant_is_normal_form (variant))
250 : : {
251 : 6 : g_set_error_literal (error, MWT_TARIFF_ERROR, MWT_TARIFF_ERROR_INVALID,
252 : : _("Input data is not in normal form."));
253 : 6 : return FALSE;
254 : : }
255 [ + + ]: 18 : if (!g_variant_is_of_type (variant, G_VARIANT_TYPE ("(sqv)")))
256 : : {
257 : 2 : g_set_error_literal (error, MWT_TARIFF_ERROR, MWT_TARIFF_ERROR_INVALID,
258 : : _("Input data does not have correct type."));
259 : 2 : return FALSE;
260 : : }
261 : :
262 : : guint16 format_version;
263 : : const gchar *format_magic;
264 : 16 : g_autoptr(GVariant) inner_variant = NULL;
265 : 16 : g_variant_get (variant, "(&sqv)",
266 : : &format_magic, &format_version, &inner_variant);
267 : :
268 : : /* Check the magic first. */
269 [ + + ]: 16 : if (!g_str_equal (format_magic, "Mogwai tariff"))
270 : : {
271 : 2 : g_set_error (error, MWT_TARIFF_ERROR, MWT_TARIFF_ERROR_INVALID,
272 : : _("Unknown file format magic ‘%s’."), format_magic);
273 : 2 : return FALSE;
274 : : }
275 : :
276 : : /* Is the version number byteswapped? It should be 0x0001. */
277 [ + + - + ]: 14 : if (format_version == 0x0100 || format_version == 0x0200)
278 : 1 : {
279 : : /* FIXME: Mess around with refs because g_variant_byteswap() had a bug
280 : : * with how it handled floating refs.
281 : : * See: https://bugzilla.gnome.org/show_bug.cgi?id=792612 */
282 : 2 : g_autoptr(GVariant) inner_variant_swapped = NULL;
283 : 1 : inner_variant_swapped = g_variant_byteswap (inner_variant);
284 : 1 : g_variant_unref (inner_variant);
285 : 1 : inner_variant = g_steal_pointer (&inner_variant_swapped);
286 : 1 : format_version = GUINT16_SWAP_LE_BE (format_version);
287 : : }
288 [ + + + + ]: 13 : else if (format_version != 0x0001 && format_version != 0x0002)
289 : : {
290 : 2 : g_set_error (error, MWT_TARIFF_ERROR, MWT_TARIFF_ERROR_INVALID,
291 : : _("Unknown file format version %x02."),
292 : : format_version);
293 : 2 : return FALSE;
294 : : }
295 : :
296 : : /* Load version 1 of the file format. */
297 [ + + + + ]: 16 : if ((format_version == 1 &&
298 : 4 : !g_variant_is_of_type (inner_variant, G_VARIANT_TYPE ("(sa(ttqut))"))) ||
299 [ + + + + ]: 19 : (format_version == 2 &&
300 : 8 : !g_variant_is_of_type (inner_variant, G_VARIANT_TYPE ("(sa(ttssqut))"))))
301 : : {
302 : 2 : g_set_error (error, MWT_TARIFF_ERROR, MWT_TARIFF_ERROR_INVALID,
303 : : _("Input data does not have correct type."));
304 : 2 : return FALSE;
305 : : }
306 : :
307 : : const gchar *name;
308 : 10 : g_autoptr(GVariantIter) iter = NULL;
309 : 10 : g_variant_get (inner_variant, "(&sa*)", &name, &iter);
310 : :
311 : : guint64 start_unix, end_unix, capacity_limit;
312 : : const gchar *start_timezone, *end_timezone;
313 : : guint16 repeat_type_uint16;
314 : : guint32 repeat_period;
315 : :
316 : 20 : g_autoptr(GPtrArray) periods = g_ptr_array_new_with_free_func (g_object_unref);
317 : 10 : gsize i = 0;
318 : :
319 [ + + ]: 31 : while (i++,
320 [ + + ]: 21 : (format_version == 2) ?
321 : 16 : g_variant_iter_loop (iter, "(tt&s&squt)",
322 : : &start_unix,
323 : : &end_unix,
324 : : &start_timezone,
325 : : &end_timezone,
326 : : &repeat_type_uint16,
327 : : &repeat_period,
328 : : &capacity_limit) :
329 : 5 : g_variant_iter_loop (iter, "(ttqut)",
330 : : &start_unix,
331 : : &end_unix,
332 : : &repeat_type_uint16,
333 : : &repeat_period,
334 : : &capacity_limit))
335 : : {
336 : : /* Version 1 only supported UTC timezones. */
337 [ + + ]: 11 : if (format_version == 1)
338 : : {
339 : 2 : start_timezone = "Z";
340 : 2 : end_timezone = "Z";
341 : : }
342 : :
343 : : /* Note: @start and @end might be %NULL. mwt_period_validate() handles that. */
344 [ + - ]: 22 : g_autoptr(GDateTime) start = date_time_new_from_unix (start_unix, start_timezone);
345 [ + - ]: 22 : g_autoptr(GDateTime) end = date_time_new_from_unix (end_unix, end_timezone);
346 : 11 : MwtPeriodRepeatType repeat_type = (MwtPeriodRepeatType) repeat_type_uint16;
347 : :
348 [ + - ]: 11 : g_autoptr(GError) local_error = NULL;
349 [ - + ]: 11 : if (!mwt_period_validate (start, end, repeat_type, repeat_period, &local_error))
350 : : {
351 : 0 : g_propagate_prefixed_error (error, g_steal_pointer (&local_error),
352 : : _("Error parsing period %" G_GSIZE_FORMAT ": "),
353 : : i);
354 : 0 : return FALSE;
355 : : }
356 : :
357 : 11 : g_autoptr(MwtPeriod) period = NULL;
358 : 11 : period = mwt_period_new (start, end, repeat_type, repeat_period,
359 : : "capacity-limit", capacity_limit,
360 : : NULL);
361 : 11 : g_ptr_array_add (periods, g_steal_pointer (&period));
362 : : }
363 : :
364 : 10 : g_autoptr(GError) local_error = NULL;
365 [ + + ]: 10 : if (!mwt_tariff_validate (name, periods, &local_error))
366 : : {
367 : 2 : g_propagate_prefixed_error (error, g_steal_pointer (&local_error),
368 : : _("Error parsing tariff: "));
369 : 2 : return FALSE;
370 : : }
371 : :
372 : 8 : self->final_tariff = mwt_tariff_new (name, periods);
373 : :
374 : 8 : return TRUE;
375 : : }
376 : :
377 : : /**
378 : : * mwt_tariff_loader_get_tariff:
379 : : * @self: a #MwtTariffLoader
380 : : *
381 : : * Get the loaded #MwtTariff, or %NULL if nothing has been loaded yet or if
382 : : * loading the tariff failed.
383 : : *
384 : : * Returns: (transfer none) (nullable): the loaded #MwtTariff, or %NULL
385 : : * Since: 0.1.0
386 : : */
387 : : MwtTariff *
388 : 25 : mwt_tariff_loader_get_tariff (MwtTariffLoader *self)
389 : : {
390 [ - + ]: 25 : g_return_val_if_fail (MWT_IS_TARIFF_LOADER (self), NULL);
391 : :
392 : 25 : return self->final_tariff;
393 : : }
|