python: Add Database class
[people/ms/libloc.git] / src / database.c
CommitLineData
2601e83e
MT
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
0676cd80 17#include <endian.h>
2601e83e
MT
18#include <errno.h>
19#include <stddef.h>
20#include <stdint.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
c182393f 24#include <sys/mman.h>
2601e83e 25#include <sys/types.h>
96ea74a5 26#include <time.h>
3f35869a 27#include <unistd.h>
2601e83e
MT
28
29#include <loc/libloc.h>
a5db3e49 30#include <loc/format.h>
2601e83e
MT
31
32#include "libloc-private.h"
a5db3e49 33#include "as.h"
2601e83e
MT
34#include "database.h"
35#include "stringpool.h"
36
37struct loc_database {
38 struct loc_ctx* ctx;
39 int refcount;
40
41 unsigned int version;
96ea74a5 42 time_t created_at;
2601e83e
MT
43 off_t vendor;
44 off_t description;
45
a5db3e49 46 // ASes in the database
c182393f 47 struct loc_database_as_v0* as_v0;
a5db3e49
MT
48 size_t as_count;
49
2601e83e
MT
50 struct loc_stringpool* pool;
51};
52
a7431f1a 53static int loc_database_read_magic(struct loc_database* db, FILE* f) {
2601e83e
MT
54 struct loc_database_magic magic;
55
56 // Read from file
a7431f1a 57 size_t bytes_read = fread(&magic, 1, sizeof(magic), f);
2601e83e
MT
58
59 // Check if we have been able to read enough data
60 if (bytes_read < sizeof(magic)) {
61 ERROR(db->ctx, "Could not read enough data to validate magic bytes\n");
62 DEBUG(db->ctx, "Read %zu bytes, but needed %zu\n", bytes_read, sizeof(magic));
63 return -ENOMSG;
64 }
65
66 // Compare magic bytes
67 if (memcmp(LOC_DATABASE_MAGIC, magic.magic, strlen(LOC_DATABASE_MAGIC)) == 0) {
68 DEBUG(db->ctx, "Magic value matches\n");
69
70 // Parse version
0676cd80 71 db->version = be16toh(magic.version);
2601e83e
MT
72 DEBUG(db->ctx, "Database version is %u\n", db->version);
73
74 return 0;
75 }
76
77 ERROR(db->ctx, "Database format is not compatible\n");
78
79 // Return an error
80 return 1;
81}
82
a5db3e49 83static int loc_database_read_as_section_v0(struct loc_database* db,
a7431f1a 84 FILE* f, off_t as_offset, size_t as_length) {
c182393f 85 DEBUG(db->ctx, "Reading AS section from %jd (%zu bytes)\n", as_offset, as_length);
a5db3e49 86
c182393f
MT
87 if (as_length > 0) {
88 db->as_v0 = mmap(NULL, as_length, PROT_READ,
a7431f1a 89 MAP_SHARED, fileno(f), as_offset);
a5db3e49 90
c182393f
MT
91 if (db->as_v0 == MAP_FAILED)
92 return -errno;
a5db3e49
MT
93 }
94
c182393f
MT
95 db->as_count = as_length / sizeof(*db->as_v0);
96
a5db3e49
MT
97 INFO(db->ctx, "Read %zu ASes from the database\n", db->as_count);
98
99 return 0;
100}
101
a7431f1a 102static int loc_database_read_header_v0(struct loc_database* db, FILE* f) {
2601e83e
MT
103 struct loc_database_header_v0 header;
104
105 // Read from file
a7431f1a 106 size_t size = fread(&header, 1, sizeof(header), f);
2601e83e
MT
107
108 if (size < sizeof(header)) {
109 ERROR(db->ctx, "Could not read enough data for header\n");
110 return -ENOMSG;
111 }
112
113 // Copy over data
96ea74a5 114 db->created_at = be64toh(header.created_at);
0676cd80
MT
115 db->vendor = be32toh(header.vendor);
116 db->description = be32toh(header.description);
2601e83e
MT
117
118 // Open pool
0676cd80
MT
119 off_t pool_offset = be32toh(header.pool_offset);
120 size_t pool_length = be32toh(header.pool_length);
2601e83e 121
0e974d4b 122 int r = loc_stringpool_open(db->ctx, &db->pool,
a7431f1a 123 f, pool_length, pool_offset);
2601e83e
MT
124 if (r)
125 return r;
126
a5db3e49 127 // AS section
0676cd80
MT
128 off_t as_offset = be32toh(header.as_offset);
129 size_t as_length = be32toh(header.as_length);
a5db3e49 130
a7431f1a 131 r = loc_database_read_as_section_v0(db, f, as_offset, as_length);
a5db3e49
MT
132 if (r)
133 return r;
134
2601e83e
MT
135 return 0;
136}
137
a7431f1a 138static int loc_database_read_header(struct loc_database* db, FILE* f) {
2601e83e
MT
139 switch (db->version) {
140 case 0:
a7431f1a 141 return loc_database_read_header_v0(db, f);
2601e83e
MT
142
143 default:
144 ERROR(db->ctx, "Incompatible database version: %u\n", db->version);
145 return 1;
146 }
147}
148
a7431f1a 149static int loc_database_read(struct loc_database* db, FILE* f) {
02879100
MT
150 clock_t start = clock();
151
152 // Read magic bytes
a7431f1a 153 int r = loc_database_read_magic(db, f);
02879100
MT
154 if (r)
155 return r;
156
157 // Read the header
a7431f1a 158 r = loc_database_read_header(db, f);
02879100
MT
159 if (r)
160 return r;
161
162 clock_t end = clock();
163
164 INFO(db->ctx, "Opened database in %.8fs\n",
165 (double)(end - start) / CLOCKS_PER_SEC);
166
167 return 0;
168}
169
c182393f 170LOC_EXPORT int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* f) {
a7431f1a
MT
171 // Fail on invalid file handle
172 if (!f)
173 return -EINVAL;
174
c182393f
MT
175 struct loc_database* db = calloc(1, sizeof(*db));
176 if (!db)
177 return -ENOMEM;
178
179 // Reference context
180 db->ctx = loc_ref(ctx);
181 db->refcount = 1;
182
183 DEBUG(db->ctx, "Database object allocated at %p\n", db);
184
a7431f1a 185 int r = loc_database_read(db, f);
02879100
MT
186 if (r) {
187 loc_database_unref(db);
2601e83e 188 return r;
02879100 189 }
2601e83e 190
c182393f
MT
191 *database = db;
192
2601e83e 193 return 0;
2601e83e
MT
194}
195
c182393f
MT
196LOC_EXPORT struct loc_database* loc_database_ref(struct loc_database* db) {
197 db->refcount++;
198
199 return db;
8f5b676a
MT
200}
201
c182393f
MT
202static void loc_database_free(struct loc_database* db) {
203 DEBUG(db->ctx, "Releasing database %p\n", db);
c34e76f1 204
c182393f
MT
205 // Removing all ASes
206 if (db->as_v0) {
207 int r = munmap(db->as_v0, db->as_count * sizeof(*db->as_v0));
208 if (r)
209 ERROR(db->ctx, "Could not unmap AS section: %s\n", strerror(errno));
210 }
c34e76f1 211
c182393f 212 loc_stringpool_unref(db->pool);
c34e76f1 213
c182393f
MT
214 loc_unref(db->ctx);
215 free(db);
c34e76f1
MT
216}
217
c182393f
MT
218LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) {
219 if (--db->refcount > 0)
220 return NULL;
78ace4ed 221
c182393f
MT
222 loc_database_free(db);
223 return NULL;
224}
78ace4ed 225
c182393f
MT
226LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) {
227 return db->created_at;
228}
78ace4ed 229
c182393f
MT
230LOC_EXPORT const char* loc_database_get_vendor(struct loc_database* db) {
231 return loc_stringpool_get(db->pool, db->vendor);
232}
78ace4ed 233
c182393f
MT
234LOC_EXPORT const char* loc_database_get_description(struct loc_database* db) {
235 return loc_stringpool_get(db->pool, db->description);
236}
78ace4ed 237
c182393f
MT
238LOC_EXPORT size_t loc_database_count_as(struct loc_database* db) {
239 return db->as_count;
78ace4ed
MT
240}
241
c182393f
MT
242// Returns the AS at position pos
243static int loc_database_fetch_as(struct loc_database* db, struct loc_as** as, off_t pos) {
244 if ((size_t)pos >= db->as_count)
245 return -EINVAL;
2601e83e 246
c182393f 247 DEBUG(db->ctx, "Fetching AS at position %jd\n", pos);
2601e83e
MT
248
249 int r;
c182393f
MT
250 switch (db->version) {
251 case 0:
252 r = loc_as_new_from_database_v0(db->ctx, db->pool, as, db->as_v0 + pos);
253 break;
2601e83e 254
c182393f
MT
255 default:
256 return -1;
257 }
2601e83e 258
c182393f
MT
259 if (r == 0) {
260 DEBUG(db->ctx, "Got AS%u\n", loc_as_get_number(*as));
2601e83e 261 }
2601e83e 262
c182393f
MT
263 return r;
264}
c34e76f1 265
c182393f
MT
266// Performs a binary search to find the AS in the list
267LOC_EXPORT int loc_database_get_as(struct loc_database* db, struct loc_as** as, uint32_t number) {
268 off_t lo = 0;
269 off_t hi = db->as_count - 1;
c34e76f1 270
8f3e2a06
MT
271 // Save start time
272 clock_t start = clock();
273
c182393f
MT
274 while (lo <= hi) {
275 off_t i = (lo + hi) / 2;
8f5b676a 276
c182393f
MT
277 // Fetch AS in the middle between lo and hi
278 int r = loc_database_fetch_as(db, as, i);
279 if (r)
280 return r;
a5db3e49 281
c182393f
MT
282 // Check if this is a match
283 uint32_t as_number = loc_as_get_number(*as);
8f3e2a06
MT
284 if (as_number == number) {
285 clock_t end = clock();
286
287 // Log how fast this has been
288 DEBUG(db->ctx, "Found AS%u in %.8fs\n", as_number,
289 (double)(end - start) / CLOCKS_PER_SEC);
290
c182393f 291 return 0;
8f3e2a06 292 }
c182393f
MT
293
294 // If it wasn't, we release the AS and
295 // adjust our search pointers
296 loc_as_unref(*as);
297
298 if (as_number < number) {
299 lo = i + 1;
300 } else
301 hi = i - 1;
302 }
2601e83e 303
c182393f
MT
304 // Nothing found
305 *as = NULL;
2601e83e 306
8f3e2a06 307 return 1;
2601e83e 308}