blob: 1a3ed1c100965deca0010532c4475e2a1489efe0 [file] [log] [blame]
Alexander Afanasyev71b43e72012-12-27 01:03:43 -08001/* -*- Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil -*- */
2/*
3 * Copyright (c) 2012 University of California, Los Angeles
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation;
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 *
18 * Author: Alexander Afanasyev <alexander.afanasyev@ucla.edu>
19 * Zhenkai Zhu <zhenkai@cs.ucla.edu>
20 */
21
22#include "sqlite-helper.h"
23// #include "sync-log.h"
Alexander Afanasyevae43c502012-12-29 17:26:37 -080024#include <boost/make_shared.hpp>
25#include <boost/ref.hpp>
Alexander Afanasyev71b43e72012-12-27 01:03:43 -080026
27// Other options: VP_md2, EVP_md5, EVP_sha, EVP_sha1, EVP_sha256, EVP_dss, EVP_dss1, EVP_mdc2, EVP_ripemd160
28#define HASH_FUNCTION EVP_sha256
29
30#include <boost/throw_exception.hpp>
31typedef boost::error_info<struct tag_errmsg, std::string> errmsg_info_str;
32// typedef boost::error_info<struct tag_errmsg, int> errmsg_info_int;
33
Alexander Afanasyevae43c502012-12-29 17:26:37 -080034using namespace boost;
Alexander Afanasyev71b43e72012-12-27 01:03:43 -080035
36const std::string INIT_DATABASE = "\
Alexander Afanasyevae43c502012-12-29 17:26:37 -080037PRAGMA foreign_keys = ON; \
38 \
Alexander Afanasyev71b43e72012-12-27 01:03:43 -080039CREATE TABLE \
40 SyncNodes( \
41 device_id INTEGER PRIMARY KEY AUTOINCREMENT, \
42 device_name TEXT NOT NULL, \
43 description TEXT, \
44 seq_no INTEGER NOT NULL, \
45 last_known_tdi TEXT, \
46 last_update TIMESTAMP \
47 ); \
48 \
Alexander Afanasyevae43c502012-12-29 17:26:37 -080049CREATE TRIGGER SyncNodesUpdater_trigger \
50 BEFORE INSERT ON SyncNodes \
51 FOR EACH ROW \
52 WHEN (SELECT device_id \
53 FROM SyncNodes \
54 WHERE device_name=NEW.device_name) \
55 IS NOT NULL \
56 BEGIN \
57 UPDATE SyncNodes \
58 SET seq_no=max(seq_no,NEW.seq_no) \
59 WHERE device_name=NEW.device_name; \
60 SELECT RAISE(IGNORE); \
61 END; \
62 \
63CREATE INDEX SyncNodes_device_name ON SyncNodes (device_name); \
64 \
65CREATE TABLE SyncLog( \
66 state_id INTEGER PRIMARY KEY AUTOINCREMENT, \
67 state_hash BLOB NOT NULL UNIQUE, \
68 last_update TIMESTAMP NOT NULL \
69 ); \
70 \
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -080071CREATE TABLE \
72 SyncStateNodes( \
73 id INTEGER PRIMARY KEY AUTOINCREMENT, \
74 state_id INTEGER NOT NULL \
75 REFERENCES SyncLog (state_id) ON UPDATE CASCADE ON DELETE CASCADE, \
Alexander Afanasyev71b43e72012-12-27 01:03:43 -080076 device_id INTEGER NOT NULL \
77 REFERENCES SyncNodes (device_id) ON UPDATE CASCADE ON DELETE CASCADE, \
78 seq_no INTEGER NOT NULL \
79 ); \
80 \
81CREATE INDEX SyncStateNodes_device_id ON SyncStateNodes (device_id); \
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -080082CREATE INDEX SyncStateNodes_state_id ON SyncStateNodes (state_id); \
83CREATE INDEX SyncStateNodes_seq_no ON SyncStateNodes (seq_no); \
Alexander Afanasyev71b43e72012-12-27 01:03:43 -080084 \
85CREATE TRIGGER SyncLogGuard_trigger \
86 BEFORE INSERT ON SyncLog \
87 FOR EACH ROW \
88 WHEN (SELECT state_hash \
89 FROM SyncLog \
90 WHERE state_hash=NEW.state_hash) \
91 IS NOT NULL \
92 BEGIN \
93 DELETE FROM SyncLog WHERE state_hash=NEW.state_hash; \
94 END; \
95";
96
97DbHelper::DbHelper (const std::string &path)
98{
99 int res = sqlite3_open((path+"chronoshare.db").c_str (), &m_db);
100 if (res != SQLITE_OK)
101 {
102 BOOST_THROW_EXCEPTION (Error::Db ()
103 << errmsg_info_str ("Cannot open/create dabatabase: [" + path + "chronoshare.db" + "]"));
104 }
105
106 res = sqlite3_create_function (m_db, "hash", 2, SQLITE_ANY, 0, 0,
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800107 DbHelper::hash_xStep, DbHelper::hash_xFinal);
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800108 if (res != SQLITE_OK)
109 {
110 BOOST_THROW_EXCEPTION (Error::Db ()
111 << errmsg_info_str ("Cannot create function ``hash''"));
112 }
113
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800114 res = sqlite3_create_function (m_db, "hash2str", 2, SQLITE_ANY, 0,
115 DbHelper::hash2str_Func,
116 0, 0);
117 if (res != SQLITE_OK)
118 {
119 BOOST_THROW_EXCEPTION (Error::Db ()
120 << errmsg_info_str ("Cannot create function ``hash''"));
121 }
122
123 res = sqlite3_create_function (m_db, "str2hash", 2, SQLITE_ANY, 0,
124 DbHelper::str2hash_Func,
125 0, 0);
126 if (res != SQLITE_OK)
127 {
128 BOOST_THROW_EXCEPTION (Error::Db ()
129 << errmsg_info_str ("Cannot create function ``hash''"));
130 }
131
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800132 // Alex: determine if tables initialized. if not, initialize... not sure what is the best way to go...
133 // for now, just attempt to create everything
134
135 char *errmsg = 0;
136 res = sqlite3_exec (m_db, INIT_DATABASE.c_str (), NULL, NULL, &errmsg);
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800137 if (res != SQLITE_OK && errmsg != 0)
138 {
139 std::cerr << "DEBUG: " << errmsg << std::endl;
140 sqlite3_free (errmsg);
141 }
142}
143
144DbHelper::~DbHelper ()
145{
146 int res = sqlite3_close (m_db);
147 if (res != SQLITE_OK)
148 {
149 // complain
150 }
151}
152
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800153HashPtr
154DbHelper::RememberStateInStateLog ()
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800155{
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800156 int res = sqlite3_exec (m_db, "BEGIN TRANSACTION;", 0,0,0);
157
158 res += sqlite3_exec (m_db, "\
159INSERT INTO SyncLog \
160 (state_hash, last_update) \
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800161 SELECT \
162 hash(device_name, seq_no), datetime('now') \
163 FROM SyncNodes \
164 ORDER BY device_name; \
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800165", 0,0,0);
166
167 if (res != SQLITE_OK)
168 {
169 BOOST_THROW_EXCEPTION (Error::Db ()
170 << errmsg_info_str ("1"));
171 }
172
173 sqlite3_int64 rowId = sqlite3_last_insert_rowid (m_db);
174
175 sqlite3_stmt *insertStmt;
176 res += sqlite3_prepare (m_db, "\
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800177INSERT INTO SyncStateNodes \
178 (state_id, device_id, seq_no) \
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800179 SELECT ?, device_id, seq_no \
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800180 FROM SyncNodes; \
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800181", -1, &insertStmt, 0);
182
183 res += sqlite3_bind_int64 (insertStmt, 1, rowId);
184 sqlite3_step (insertStmt);
185
186 if (res != SQLITE_OK)
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800187 {
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800188 BOOST_THROW_EXCEPTION (Error::Db ()
189 << errmsg_info_str ("4"));
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800190 }
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800191 sqlite3_finalize (insertStmt);
192
193 sqlite3_stmt *getHashStmt;
194 res += sqlite3_prepare (m_db, "\
195SELECT state_hash FROM SyncLog WHERE state_id = ?\
196", -1, &getHashStmt, 0);
197 res += sqlite3_bind_int64 (getHashStmt, 1, rowId);
198
199 HashPtr retval;
200 int stepRes = sqlite3_step (getHashStmt);
201 if (stepRes == SQLITE_ROW)
202 {
203 retval = make_shared<Hash> (sqlite3_column_blob (getHashStmt, 0),
204 sqlite3_column_bytes (getHashStmt, 0));
205 }
206 sqlite3_finalize (getHashStmt);
207 res += sqlite3_exec (m_db, "COMMIT;", 0,0,0);
208
209 if (res != SQLITE_OK)
210 {
211 BOOST_THROW_EXCEPTION (Error::Db ()
212 << errmsg_info_str ("Some error with rememberStateInStateLog"));
213 }
214
215 return retval;
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800216}
217
218
219void
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800220DbHelper::hash_xStep (sqlite3_context *context, int argc, sqlite3_value **argv)
221{
222 if (argc != 2)
223 {
224 // _LOG_ERROR ("Wrong arguments are supplied for ``hash'' function");
225 sqlite3_result_error (context, "Wrong arguments are supplied for ``hash'' function", -1);
226 return;
227 }
228 if (sqlite3_value_type (argv[0]) != SQLITE_TEXT ||
229 sqlite3_value_type (argv[1]) != SQLITE_INTEGER)
230 {
231 // _LOG_ERROR ("Hash expects (text,integer) parameters");
232 sqlite3_result_error (context, "Hash expects (text,integer) parameters", -1);
233 return;
234 }
235
236 EVP_MD_CTX **hash_context = reinterpret_cast<EVP_MD_CTX **> (sqlite3_aggregate_context (context, sizeof (EVP_MD_CTX *)));
237
238 if (hash_context == 0)
239 {
240 sqlite3_result_error_nomem (context);
241 return;
242 }
243
244 if (*hash_context == 0)
245 {
246 *hash_context = EVP_MD_CTX_create ();
247 EVP_DigestInit_ex (*hash_context, HASH_FUNCTION (), 0);
248 }
249
250 int nameBytes = sqlite3_value_bytes (argv[0]);
251 const unsigned char *name = sqlite3_value_text (argv[0]);
252 sqlite3_int64 seqno = sqlite3_value_int64 (argv[1]);
253
254 EVP_DigestUpdate (*hash_context, name, nameBytes);
255 EVP_DigestUpdate (*hash_context, &seqno, sizeof(sqlite3_int64));
256}
257
258void
259DbHelper::hash_xFinal (sqlite3_context *context)
260{
261 EVP_MD_CTX **hash_context = reinterpret_cast<EVP_MD_CTX **> (sqlite3_aggregate_context (context, sizeof (EVP_MD_CTX *)));
262
263 if (hash_context == 0)
264 {
265 sqlite3_result_error_nomem (context);
266 return;
267 }
268
269 if (*hash_context == 0) // no rows
270 {
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800271 char charNullResult = 0;
272 sqlite3_result_blob (context, &charNullResult, 1, SQLITE_TRANSIENT); //SQLITE_TRANSIENT forces to make a copy
Alexander Afanasyev71b43e72012-12-27 01:03:43 -0800273 return;
274 }
275
276 unsigned char *hash = new unsigned char [EVP_MAX_MD_SIZE];
277 unsigned int hashLength = 0;
278
279 int ok = EVP_DigestFinal_ex (*hash_context,
280 hash, &hashLength);
281
282 sqlite3_result_blob (context, hash, hashLength, SQLITE_TRANSIENT); //SQLITE_TRANSIENT forces to make a copy
283 delete [] hash;
284
285 EVP_MD_CTX_destroy (*hash_context);
286}
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800287
288void
289DbHelper::hash2str_Func (sqlite3_context *context, int argc, sqlite3_value **argv)
290{
291 if (argc != 1 || sqlite3_value_type (argv[0]) != SQLITE_BLOB)
292 {
293 sqlite3_result_error (context, "Wrong arguments are supplied for ``hash2str'' function", -1);
294 return;
295 }
296
297 int hashBytes = sqlite3_value_bytes (argv[0]);
298 const void *hash = sqlite3_value_blob (argv[0]);
299
300 std::ostringstream os;
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800301 Hash tmpHash (hash, hashBytes);
302 os << tmpHash;
Alexander Afanasyevde1cdd02012-12-29 14:41:46 -0800303 sqlite3_result_text (context, os.str ().c_str (), -1, SQLITE_TRANSIENT);
304}
305
306void
307DbHelper::str2hash_Func (sqlite3_context *context, int argc, sqlite3_value **argv)
308{
309 if (argc != 1 || sqlite3_value_type (argv[0]) != SQLITE_TEXT)
310 {
311 sqlite3_result_error (context, "Wrong arguments are supplied for ``str2hash'' function", -1);
312 return;
313 }
314
315 size_t hashTextBytes = sqlite3_value_bytes (argv[0]);
316 const unsigned char *hashText = sqlite3_value_text (argv[0]);
317
318 Hash hash (std::string (reinterpret_cast<const char*> (hashText), hashTextBytes));
319 sqlite3_result_blob (context, hash.GetHash (), hash.GetHashBytes (), SQLITE_TRANSIENT);
320}
321
Alexander Afanasyevae43c502012-12-29 17:26:37 -0800322
323sqlite3_int64
324DbHelper::LookupSyncLog (const std::string &stateHash)
325{
326 Hash tmpHash (stateHash);
327 return LookupSyncLog (stateHash);
328}
329
330sqlite3_int64
331DbHelper::LookupSyncLog (const Hash &stateHash)
332{
333 sqlite3_stmt *stmt;
334 int res = sqlite3_prepare (m_db, "SELECT state_id FROM SyncLog WHERE state_hash = ?",
335 -1, &stmt, 0);
336
337 if (res != SQLITE_OK)
338 {
339 BOOST_THROW_EXCEPTION (Error::Db ()
340 << errmsg_info_str ("Cannot prepare statement"));
341 }
342
343 res = sqlite3_bind_blob (stmt, 1, stateHash.GetHash (), stateHash.GetHashBytes (), SQLITE_STATIC);
344 if (res != SQLITE_OK)
345 {
346 BOOST_THROW_EXCEPTION (Error::Db ()
347 << errmsg_info_str ("Cannot bind"));
348 }
349
350 sqlite3_int64 row = 0; // something bad
351
352 if (sqlite3_step (stmt) == SQLITE_ROW)
353 {
354 row = sqlite3_column_int64 (stmt, 0);
355 }
356
357 sqlite3_finalize (stmt);
358
359 return row;
360}
361
362void
363DbHelper::UpdateDeviceSeqno (const std::string &name, uint64_t seqNo)
364{
365 sqlite3_stmt *stmt;
366 // update is performed using trigger
367 int res = sqlite3_prepare (m_db, "INSERT INTO SyncNodes (device_name, seq_no) VALUES (?,?);",
368 -1, &stmt, 0);
369
370 res += sqlite3_bind_text (stmt, 1, name.c_str (), name.size (), SQLITE_STATIC);
371 res += sqlite3_bind_int64 (stmt, 2, seqNo);
372 sqlite3_step (stmt);
373
374 if (res != SQLITE_OK)
375 {
376 BOOST_THROW_EXCEPTION (Error::Db ()
377 << errmsg_info_str ("Some error with UpdateDeviceSeqno"));
378 }
379 sqlite3_finalize (stmt);
380}
381
382void
383DbHelper::FindStateDifferences (const std::string &oldHash, const std::string &newHash)
384{
385 Hash tmpOldHash (oldHash);
386 Hash tmpNewHash (newHash);
387
388 FindStateDifferences (tmpOldHash, tmpNewHash);
389}
390
391void
392DbHelper::FindStateDifferences (const Hash &oldHash, const Hash &newHash)
393{
394 sqlite3_stmt *stmt;
395
396 int res = sqlite3_prepare_v2 (m_db, "\
397SELECT sn.device_name, s_old.seq_no, s_new.seq_no \
398 FROM (SELECT * \
399 FROM SyncStateNodes \
400 WHERE state_id=(SELECT state_id \
401 FROM SyncLog \
402 WHERE state_hash=:old_hash)) s_old \
403 LEFT JOIN (SELECT * \
404 FROM SyncStateNodes \
405 WHERE state_id=(SELECT state_id \
406 FROM SyncLog \
407 WHERE state_hash=:new_hash)) s_new \
408 \
409 ON s_old.device_id = s_new.device_id \
410 JOIN SyncNodes sn ON sn.device_id = s_old.device_id \
411 \
412 WHERE s_new.seq_no IS NULL OR \
413 s_old.seq_no != s_new.seq_no \
414 \
415UNION ALL \
416 \
417SELECT sn.device_name, s_old.seq_no, s_new.seq_no \
418 FROM (SELECT * \
419 FROM SyncStateNodes \
420 WHERE state_id=(SELECT state_id \
421 FROM SyncLog \
422 WHERE state_hash=:new_hash )) s_new \
423 LEFT JOIN (SELECT * \
424 FROM SyncStateNodes \
425 WHERE state_id=(SELECT state_id \
426 FROM SyncLog \
427 WHERE state_hash=:old_hash)) s_old \
428 \
429 ON s_old.device_id = s_new.device_id \
430 JOIN SyncNodes sn ON sn.device_id = s_new.device_id \
431 \
432 WHERE s_old.seq_no IS NULL \
433", -1, &stmt, 0);
434
435 if (res != SQLITE_OK)
436 {
437 BOOST_THROW_EXCEPTION (Error::Db ()
438 << errmsg_info_str ("Some error with FindStateDifferences"));
439 }
440
441 res += sqlite3_bind_blob (stmt, 1, oldHash.GetHash (), oldHash.GetHashBytes (), SQLITE_STATIC);
442 res += sqlite3_bind_blob (stmt, 2, newHash.GetHash (), newHash.GetHashBytes (), SQLITE_STATIC);
443
444 while (sqlite3_step (stmt) == SQLITE_ROW)
445 {
446 std::cout << sqlite3_column_text (stmt, 0) <<
447 ": from " << sqlite3_column_int64 (stmt, 1) <<
448 " to " << sqlite3_column_int64 (stmt, 2) <<
449 std::endl;
450 }
451 sqlite3_finalize (stmt);
452
453}