Introduce object to store an AS
[people/ms/libloc.git] / src / database.c
1 /*
2         libloc - A library to determine the location of someone on the Internet
3
4         Copyright (C) 2017 IPFire Development Team <info@ipfire.org>
5
6         This library is free software; you can redistribute it and/or
7         modify it under the terms of the GNU Lesser General Public
8         License as published by the Free Software Foundation; either
9         version 2.1 of the License, or (at your option) any later version.
10
11         This library is distributed in the hope that it will be useful,
12         but WITHOUT ANY WARRANTY; without even the implied warranty of
13         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14         Lesser General Public License for more details.
15 */
16
17 #include <arpa/inet.h>
18 #include <errno.h>
19 #include <stddef.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/types.h>
25
26 #include <loc/libloc.h>
27 #include <loc/format.h>
28
29 #include "libloc-private.h"
30 #include "as.h"
31 #include "database.h"
32 #include "stringpool.h"
33
34 struct loc_database {
35         struct loc_ctx* ctx;
36         int refcount;
37
38         unsigned int version;
39         off_t vendor;
40         off_t description;
41
42         // ASes in the database
43         struct loc_as** as;
44         size_t as_count;
45
46         struct loc_stringpool* pool;
47 };
48
49 const char* LOC_DATABASE_MAGIC = "LOCDBXX";
50 unsigned int LOC_DATABASE_VERSION = 0;
51
52 struct loc_database_magic {
53         char magic[7];
54
55         // Database version information
56         uint8_t version;
57 };
58
59 LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, size_t pool_size) {
60         struct loc_database* db = calloc(1, sizeof(*db));
61         if (!db)
62                 return -ENOMEM;
63
64         // Reference context
65         db->ctx = loc_ref(ctx);
66         db->refcount = 1;
67
68         DEBUG(db->ctx, "Database allocated at %p\n", db);
69
70         // Create string pool
71         int r = loc_stringpool_new(db->ctx, &db->pool, pool_size);
72         if (r) {
73                 loc_database_unref(db);
74                 return r;
75         }
76
77         *database = db;
78
79         return 0;
80 }
81
82 LOC_EXPORT int loc_database_open(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
83         int r = loc_database_new(ctx, database, 0);
84         if (r)
85                 return r;
86
87         return loc_database_read(*database, f);
88 }
89
90 LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
91         db->refcount++;
92
93         return db;
94 }
95
96 static void loc_database_free(struct loc_database* db) {
97         DEBUG(db->ctx, "Releasing database %p\n", db);
98
99         // Remove references to all ASes
100         if (db->as) {
101                 for (unsigned int i = 0; i < db->as_count; i++) {
102                         loc_as_unref(db->as[i]);
103                 }
104                 free(db->as);
105         }
106
107         loc_stringpool_unref(db->pool);
108
109         loc_unref(db->ctx);
110         free(db);
111 }
112
113 LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
114         if (--db->refcount > 0)
115                 return NULL;
116
117         loc_database_free(db);
118         return NULL;
119 }
120
121 LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
122         return loc_stringpool_get(db->pool, db->vendor);
123 }
124
125 LOC_EXPORT int loc_database_set_vendor(struct loc_database* db, const char* vendor) {
126         // Add the string to the string pool
127         off_t offset = loc_stringpool_add(db->pool, vendor);
128         if (offset < 0)
129                 return offset;
130
131         db->vendor = offset;
132         return 0;
133 }
134
135 LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
136         return loc_stringpool_get(db->pool, db->description);
137 }
138
139 LOC_EXPORT int loc_database_set_description(struct loc_database* db, const char* description) {
140         // Add the string to the string pool
141         off_t offset = loc_stringpool_add(db->pool, description);
142         if (offset < 0)
143                 return offset;
144
145         db->description = offset;
146         return 0;
147 }
148
149 LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
150         return db->as_count;
151 }
152
153 static int loc_database_has_as(struct loc_database* db, struct loc_as* as) {
154         for (unsigned int i = 0; i < db->as_count; i++) {
155                 if (loc_as_cmp(as, db->as[i]) == 0)
156                         return i;
157         }
158
159         return -1;
160 }
161
162 static int __loc_as_cmp(const void* as1, const void* as2) {
163         return loc_as_cmp(*(struct loc_as**)as1, *(struct loc_as**)as2);
164 }
165
166 static void loc_database_sort_ases(struct loc_database* db) {
167         qsort(db->as, db->as_count, sizeof(*db->as), __loc_as_cmp);
168 }
169
170 static struct loc_as* __loc_database_add_as(struct loc_database* db, struct loc_as* as) {
171         // Check if AS exists already
172         int i = loc_database_has_as(db, as);
173         if (i >= 0) {
174                 loc_as_unref(as);
175
176                 // Select already existing AS
177                 as = db->as[i];
178
179                 return loc_as_ref(as);
180         }
181
182         db->as_count++;
183
184         // Make space for the new entry
185         db->as = realloc(db->as, sizeof(*db->as) * db->as_count);
186
187         // Add the new entry at the end
188         db->as[db->as_count - 1] = loc_as_ref(as);
189
190         // Sort everything
191         loc_database_sort_ases(db);
192
193         return as;
194 }
195
196 LOC_EXPORT struct loc_as* loc_database_add_as(struct loc_database* db, uint32_t number) {
197         struct loc_as* as;
198         int r = loc_as_new(db->ctx, db->pool, &as, number);
199         if (r)
200                 return NULL;
201
202         return __loc_database_add_as(db, as);
203 }
204
205 static int loc_database_read_magic(struct loc_database* db, FILE* f) {
206         struct loc_database_magic magic;
207
208         // Read from file
209         size_t bytes_read = fread(&magic, 1, sizeof(magic), f);
210
211         // Check if we have been able to read enough data
212         if (bytes_read < sizeof(magic)) {
213                 ERROR(db->ctx, "Could not read enough data to validate magic bytes\n");
214                 DEBUG(db->ctx, "Read %zu bytes, but needed %zu\n", bytes_read, sizeof(magic));
215                 return -ENOMSG;
216         }
217
218         // Compare magic bytes
219         if (memcmp(LOC_DATABASE_MAGIC, magic.magic, strlen(LOC_DATABASE_MAGIC)) == 0) {
220                 DEBUG(db->ctx, "Magic value matches\n");
221
222                 // Parse version
223                 db->version = ntohs(magic.version);
224                 DEBUG(db->ctx, "Database version is %u\n", db->version);
225
226                 return 0;
227         }
228
229         ERROR(db->ctx, "Database format is not compatible\n");
230
231         // Return an error
232         return 1;
233 }
234
235 static int loc_database_read_as_section_v0(struct loc_database* db,
236                 off_t as_offset, size_t as_length, FILE* f) {
237         struct loc_database_as_v0 dbobj;
238
239         // Read from the start of the section
240         int r = fseek(f, as_offset, SEEK_SET);
241         if (r)
242                 return r;
243
244         // Read all ASes
245         size_t as_count = as_length / sizeof(dbobj);
246         for (unsigned int i = 0; i < as_count; i++) {
247                 size_t bytes_read = fread(&dbobj, 1, sizeof(dbobj), f);
248                 if (bytes_read < sizeof(dbobj)) {
249                         ERROR(db->ctx, "Could not read an AS object\n");
250                         return -ENOMSG;
251                 }
252
253                 // Allocate a new AS
254                 struct loc_as* as;
255                 r = loc_as_new_from_database_v0(db->ctx, db->pool, &as, &dbobj);
256                 if (r)
257                         return r;
258
259                 // Attach it to the database
260                 as = __loc_database_add_as(db, as);
261                 loc_as_unref(as);
262         }
263
264         INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
265
266         return 0;
267 }
268
269 static int loc_database_read_header_v0(struct loc_database* db, FILE* f) {
270         struct loc_database_header_v0 header;
271
272         // Read from file
273         size_t size = fread(&header, 1, sizeof(header), f);
274
275         if (size < sizeof(header)) {
276                 ERROR(db->ctx, "Could not read enough data for header\n");
277                 return -ENOMSG;
278         }
279
280         // Copy over data
281         db->vendor      = ntohl(header.vendor);
282         db->description = ntohl(header.description);
283
284         // Open pool
285         off_t pool_offset  = ntohl(header.pool_offset);
286         size_t pool_length = ntohl(header.pool_length);
287
288         int r = loc_stringpool_read(db->pool, f, pool_offset, pool_length);
289         if (r)
290                 return r;
291
292         // AS section
293         off_t as_offset  = ntohl(header.as_offset);
294         size_t as_length = ntohl(header.as_length);
295
296         r = loc_database_read_as_section_v0(db, as_offset, as_length, f);
297         if (r)
298                 return r;
299
300         return 0;
301 }
302
303 static int loc_database_read_header(struct loc_database* db, FILE* f) {
304         switch (db->version) {
305                 case 0:
306                         return loc_database_read_header_v0(db, f);
307
308                 default:
309                         ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
310                         return 1;
311         }
312 }
313
314 LOC_EXPORT int loc_database_read(struct loc_database* db, FILE* f) {
315         int r = fseek(f, 0, SEEK_SET);
316         if (r)
317                 return r;
318
319         // Read magic bytes
320         r = loc_database_read_magic(db, f);
321         if (r)
322                 return r;
323
324         // Read the header
325         r = loc_database_read_header(db, f);
326         if (r)
327                 return r;
328
329         return 0;
330 }
331
332 static void loc_database_make_magic(struct loc_database* db, struct loc_database_magic* magic) {
333         // Copy magic bytes
334         for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++)
335                 magic->magic[i] = LOC_DATABASE_MAGIC[i];
336
337         // Set version
338         magic->version = htons(LOC_DATABASE_VERSION);
339 }
340
341 LOC_EXPORT int loc_database_write(struct loc_database* db, FILE* f) {
342         struct loc_database_magic magic;
343         loc_database_make_magic(db, &magic);
344
345         // Make the header
346         struct loc_database_header_v0 header;
347         header.vendor      = htonl(db->vendor);
348         header.description = htonl(db->description);
349
350         int r;
351         off_t offset = 0;
352
353         // Start writing at the beginning of the file
354         r = fseek(f, 0, SEEK_SET);
355         if (r)
356                 return r;
357
358         // Write the magic
359         offset += fwrite(&magic, 1, sizeof(magic), f);
360
361         // Skip the space we need to write the header later
362         r = fseek(f, sizeof(header), SEEK_CUR);
363         if (r) {
364                 DEBUG(db->ctx, "Could not seek to position after header\n");
365                 return r;
366         }
367         offset += sizeof(header);
368
369         // Write all ASes
370         header.as_offset = htonl(offset);
371
372         struct loc_database_as_v0 dbas;
373         for (unsigned int i = 0; i < db->as_count; i++) {
374                 // Convert AS into database format
375                 loc_as_to_database_v0(db->as[i], &dbas);
376
377                 // Write to disk
378                 offset += fwrite(&dbas, 1, sizeof(dbas), f);
379         }
380         header.as_length = htonl(db->as_count * sizeof(dbas));
381
382         // Save the offset of the pool section
383         DEBUG(db->ctx, "Pool starts at %jd bytes\n", offset);
384         header.pool_offset = htonl(offset);
385
386         // Size of the pool
387         size_t pool_length = loc_stringpool_write(db->pool, f);
388         DEBUG(db->ctx, "Pool has a length of %zu bytes\n", pool_length);
389         header.pool_length = htonl(pool_length);
390
391         // Write the header
392         r = fseek(f, sizeof(magic), SEEK_SET);
393         if (r)
394                 return r;
395
396         offset += fwrite(&header, 1, sizeof(header), f);
397
398         return 0;
399 }