IBR-DTNSuite
0.8
|
00001 /* 00002 * SQLiteDatabase.cpp 00003 * 00004 * Created on: 26.03.2012 00005 * Author: morgenro 00006 */ 00007 00008 #include "storage/SQLiteDatabase.h" 00009 #include "storage/SQLiteConfigure.h" 00010 #include <ibrdtn/data/ScopeControlHopLimitBlock.h> 00011 #include <ibrdtn/utils/Clock.h> 00012 #include <ibrcommon/Logger.h> 00013 00014 namespace dtn 00015 { 00016 namespace storage 00017 { 00018 void sql_tracer(void*, const char * pQuery) 00019 { 00020 IBRCOMMON_LOGGER_DEBUG(50) << "sqlite trace: " << pQuery << IBRCOMMON_LOGGER_ENDL; 00021 } 00022 00023 const std::string SQLiteDatabase::_tables[SQL_TABLE_END] = 00024 { "bundles", "blocks", "routing", "routing_bundles", "routing_nodes", "properties" }; 00025 00026 const int SQLiteDatabase::DBSCHEMA_VERSION = 1; 00027 const std::string SQLiteDatabase::QUERY_SCHEMAVERSION = "SELECT `value` FROM " + SQLiteDatabase::_tables[SQLiteDatabase::SQL_TABLE_PROPERTIES] + " WHERE `key` = 'version' LIMIT 0,1;"; 00028 const std::string SQLiteDatabase::SET_SCHEMAVERSION = "INSERT INTO " + SQLiteDatabase::_tables[SQLiteDatabase::SQL_TABLE_PROPERTIES] + " (`key`, `value`) VALUES ('version', ?);"; 00029 00030 const std::string SQLiteDatabase::_sql_queries[SQL_QUERIES_END] = 00031 { 00032 "SELECT source, destination, reportto, custodian, procflags, timestamp, sequencenumber, lifetime, fragmentoffset, appdatalength, hopcount, payloadlength FROM " + _tables[SQL_TABLE_BUNDLE] + " ORDER BY priority DESC LIMIT ?,?;", 00033 "SELECT source, destination, reportto, custodian, procflags, timestamp, sequencenumber, lifetime, fragmentoffset, appdatalength, hopcount, payloadlength FROM "+ _tables[SQL_TABLE_BUNDLE] +" WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset IS NULL LIMIT 1;", 00034 "SELECT source, destination, reportto, custodian, procflags, timestamp, sequencenumber, lifetime, fragmentoffset, appdatalength, hopcount, payloadlength FROM "+ _tables[SQL_TABLE_BUNDLE] +" WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset = ? LIMIT 1;", 00035 "SELECT * FROM " + _tables[SQL_TABLE_BUNDLE] + " WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset != NULL ORDER BY fragmentoffset ASC;", 00036 00037 "SELECT source, timestamp, sequencenumber, fragmentoffset, procflags FROM "+ _tables[SQL_TABLE_BUNDLE] +" WHERE expiretime <= ?;", 00038 "SELECT filename FROM "+ _tables[SQL_TABLE_BUNDLE] +" as a, "+ _tables[SQL_TABLE_BLOCK] +" as b WHERE a.source_id = b.source_id AND a.timestamp = b.timestamp AND a.sequencenumber = b.sequencenumber AND ((a.fragmentoffset = b.fragmentoffset) OR ((a.fragmentoffset IS NULL) AND (b.fragmentoffset IS NULL))) AND a.expiretime <= ?;", 00039 "DELETE FROM "+ _tables[SQL_TABLE_BUNDLE] +" WHERE expiretime <= ?;", 00040 "SELECT expiretime FROM "+ _tables[SQL_TABLE_BUNDLE] +" ORDER BY expiretime ASC LIMIT 1;", 00041 00042 "SELECT ROWID FROM "+ _tables[SQL_TABLE_BUNDLE] +" LIMIT 1;", 00043 "SELECT COUNT(ROWID) FROM "+ _tables[SQL_TABLE_BUNDLE] +";", 00044 00045 "DELETE FROM "+ _tables[SQL_TABLE_BUNDLE] +" WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset IS NULL;", 00046 "DELETE FROM "+ _tables[SQL_TABLE_BUNDLE] +" WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset = ?;", 00047 "DELETE FROM "+ _tables[SQL_TABLE_BUNDLE] +";", 00048 "INSERT INTO "+ _tables[SQL_TABLE_BUNDLE] +" (source_id, timestamp, sequencenumber, fragmentoffset, source, destination, reportto, custodian, procflags, lifetime, appdatalength, expiretime, priority, hopcount, payloadlength) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);", 00049 "UPDATE "+ _tables[SQL_TABLE_BUNDLE] +" SET custodian = ? WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset IS NULL;", 00050 "UPDATE "+ _tables[SQL_TABLE_BUNDLE] +" SET custodian = ? WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset = ?;", 00051 00052 "UPDATE "+ _tables[SQL_TABLE_BUNDLE] +" SET procflags = ? WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset = ?;", 00053 00054 "SELECT filename, blocktype FROM "+ _tables[SQL_TABLE_BLOCK] +" WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset IS NULL ORDER BY ordernumber ASC;", 00055 "SELECT filename, blocktype FROM "+ _tables[SQL_TABLE_BLOCK] +" WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset = ? ORDER BY ordernumber ASC;", 00056 "SELECT filename, blocktype FROM "+ _tables[SQL_TABLE_BLOCK] +" WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset IS NULL AND ordernumber = ?;", 00057 "SELECT filename, blocktype FROM "+ _tables[SQL_TABLE_BLOCK] +" WHERE source_id = ? AND timestamp = ? AND sequencenumber = ? AND fragmentoffset = ? AND ordernumber = ?;", 00058 "DELETE FROM "+ _tables[SQL_TABLE_BLOCK] +";", 00059 "INSERT INTO "+ _tables[SQL_TABLE_BLOCK] +" (source_id, timestamp, sequencenumber, fragmentoffset, blocktype, filename, ordernumber) VALUES (?,?,?,?,?,?,?);", 00060 00061 "VACUUM;", 00062 }; 00063 00064 const std::string SQLiteDatabase::_db_structure[11] = 00065 { 00066 "CREATE TABLE IF NOT EXISTS `" + _tables[SQL_TABLE_BLOCK] + "` ( `key` INTEGER PRIMARY KEY ASC, `source_id` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `sequencenumber` INTEGER NOT NULL, `fragmentoffset` INTEGER DEFAULT NULL, `blocktype` INTEGER NOT NULL, `filename` TEXT NOT NULL, `ordernumber` INTEGER NOT NULL);", 00067 "CREATE TABLE IF NOT EXISTS `" + _tables[SQL_TABLE_BUNDLE] + "` ( `key` INTEGER PRIMARY KEY ASC, `source_id` TEXT NOT NULL, `source` TEXT NOT NULL, `destination` TEXT NOT NULL, `reportto` TEXT NOT NULL, `custodian` TEXT NOT NULL, `procflags` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `sequencenumber` INTEGER NOT NULL, `lifetime` INTEGER NOT NULL, `fragmentoffset` INTEGER DEFAULT NULL, `appdatalength` INTEGER DEFAULT NULL, `expiretime` INTEGER NOT NULL, `priority` INTEGER NOT NULL, `hopcount` INTEGER DEFAULT NULL, `payloadlength` INTEGER NOT NULL);", 00068 "create table if not exists "+ _tables[SQL_TABLE_ROUTING] +" (INTEGER PRIMARY KEY ASC, Key int, Routing text);", 00069 "create table if not exists "+ _tables[SQL_TABLE_BUNDLE_ROUTING_INFO] +" (INTEGER PRIMARY KEY ASC, BundleID text, Key int, Routing text);", 00070 "create table if not exists "+ _tables[SQL_TABLE_NODE_ROUTING_INFO] +" (INTEGER PRIMARY KEY ASC, EID text, Key int, Routing text);", 00071 "CREATE TRIGGER IF NOT EXISTS blocks_autodelete AFTER DELETE ON " + _tables[SQL_TABLE_BUNDLE] + " FOR EACH ROW BEGIN DELETE FROM " + _tables[SQL_TABLE_BLOCK] + " WHERE " + _tables[SQL_TABLE_BLOCK] + ".source_id = OLD.source_id AND " + _tables[SQL_TABLE_BLOCK] + ".timestamp = OLD.timestamp AND " + _tables[SQL_TABLE_BLOCK] + ".sequencenumber = OLD.sequencenumber AND ((" + _tables[SQL_TABLE_BLOCK] + ".fragmentoffset IS NULL AND old.fragmentoffset IS NULL) OR (" + _tables[SQL_TABLE_BLOCK] + ".fragmentoffset = old.fragmentoffset)); END;", 00072 "CREATE UNIQUE INDEX IF NOT EXISTS blocks_bid ON " + _tables[SQL_TABLE_BLOCK] + " (source_id, timestamp, sequencenumber, fragmentoffset);", 00073 "CREATE INDEX IF NOT EXISTS bundles_destination ON " + _tables[SQL_TABLE_BUNDLE] + " (destination);", 00074 "CREATE INDEX IF NOT EXISTS bundles_destination_priority ON " + _tables[SQL_TABLE_BUNDLE] + " (destination, priority);", 00075 "CREATE UNIQUE INDEX IF NOT EXISTS bundles_id ON " + _tables[SQL_TABLE_BUNDLE] + " (source_id, timestamp, sequencenumber, fragmentoffset);" 00076 "CREATE INDEX IF NOT EXISTS bundles_expire ON " + _tables[SQL_TABLE_BUNDLE] + " (source_id, timestamp, sequencenumber, fragmentoffset, expiretime);", 00077 "CREATE TABLE IF NOT EXISTS '" + _tables[SQL_TABLE_PROPERTIES] + "' ( `key` TEXT PRIMARY KEY ASC ON CONFLICT REPLACE, `value` TEXT NOT NULL);" 00078 }; 00079 00080 SQLiteDatabase::SQLBundleQuery::SQLBundleQuery() 00081 { } 00082 00083 SQLiteDatabase::SQLBundleQuery::~SQLBundleQuery() 00084 { 00085 } 00086 00087 SQLiteDatabase::Statement::Statement(sqlite3 *database, const std::string &query) 00088 : _database(database), _st(NULL), _query(query) 00089 { 00090 prepare(); 00091 } 00092 00093 SQLiteDatabase::Statement::~Statement() 00094 { 00095 if (_st != NULL) { 00096 sqlite3_finalize(_st); 00097 } 00098 } 00099 00100 sqlite3_stmt* SQLiteDatabase::Statement::operator*() 00101 { 00102 return _st; 00103 } 00104 00105 void SQLiteDatabase::Statement::reset() 00106 { 00107 if (_st != NULL) { 00108 sqlite3_reset(_st); 00109 sqlite3_clear_bindings(_st); 00110 } 00111 } 00112 00113 int SQLiteDatabase::Statement::step() 00114 { 00115 if (_st == NULL) 00116 throw SQLiteQueryException("statement not prepared"); 00117 00118 return sqlite3_step(_st); 00119 } 00120 00121 void SQLiteDatabase::Statement::prepare() 00122 { 00123 if (_st != NULL) 00124 throw SQLiteQueryException("already prepared"); 00125 00126 int err = sqlite3_prepare_v2(_database, _query.c_str(), _query.length(), &_st, 0); 00127 00128 if ( err != SQLITE_OK ) 00129 throw SQLiteQueryException("failed to prepare statement: " + _query); 00130 } 00131 00132 SQLiteDatabase::SQLiteDatabase(const ibrcommon::File &file, const size_t &size) 00133 : _file(file), _size(size), _next_expiration(0) 00134 { 00135 } 00136 00137 SQLiteDatabase::~SQLiteDatabase() 00138 { 00139 } 00140 00141 int SQLiteDatabase::getVersion() 00142 { 00143 // Check version of SQLiteDB 00144 int version = 0; 00145 00146 // prepare the statement 00147 Statement st(_database, QUERY_SCHEMAVERSION); 00148 00149 // execute statement for version query 00150 int err = st.step(); 00151 00152 // Query finished no table found 00153 if (err == SQLITE_ROW) 00154 { 00155 std::string dbversion = (const char*) sqlite3_column_text(*st, 0); 00156 std::stringstream ss(dbversion); 00157 ss >> version; 00158 } 00159 00160 return version; 00161 } 00162 00163 void SQLiteDatabase::setVersion(int version) 00164 { 00165 std::stringstream ss; ss << version; 00166 Statement st(_database, SET_SCHEMAVERSION); 00167 00168 // bind version text to the statement 00169 sqlite3_bind_text(*st, 1, ss.str().c_str(), ss.str().length(), SQLITE_TRANSIENT); 00170 00171 int err = st.step(); 00172 if(err != SQLITE_DONE) 00173 { 00174 IBRCOMMON_LOGGER(error) << "SQLiteDatabase: failed to set version " << err << IBRCOMMON_LOGGER_ENDL; 00175 } 00176 } 00177 00178 void SQLiteDatabase::doUpgrade(int oldVersion, int newVersion) 00179 { 00180 if (oldVersion > newVersion) 00181 { 00182 IBRCOMMON_LOGGER(error) << "SQLiteDatabase: Downgrade from version " << oldVersion << " to version " << newVersion << " is not possible." << IBRCOMMON_LOGGER_ENDL; 00183 throw ibrcommon::Exception("Downgrade not possible."); 00184 } 00185 00186 IBRCOMMON_LOGGER(info) << "SQLiteDatabase: Upgrade from version " << oldVersion << " to version " << newVersion << IBRCOMMON_LOGGER_ENDL; 00187 00188 for (int i = oldVersion; i < newVersion; i++) 00189 { 00190 switch (i) 00191 { 00192 // if there is no version field, drop all tables 00193 case 0: 00194 for (size_t i = 0; i < SQL_TABLE_END; i++) 00195 { 00196 Statement st(_database, "DROP TABLE IF EXISTS " + _tables[i] + ";"); 00197 int err = st.step(); 00198 if(err != SQLITE_DONE) 00199 { 00200 IBRCOMMON_LOGGER(error) << "SQLiteDatabase: drop table " << _tables[i] << " failed " << err << IBRCOMMON_LOGGER_ENDL; 00201 } 00202 } 00203 00204 // create all tables 00205 for (size_t i = 0; i < 11; i++) 00206 { 00207 Statement st(_database, _db_structure[i]); 00208 int err = st.step(); 00209 if(err != SQLITE_DONE) 00210 { 00211 IBRCOMMON_LOGGER(error) << "SQLiteDatabase: failed to create table " << _tables[i] << "; err: " << err << IBRCOMMON_LOGGER_ENDL; 00212 } 00213 } 00214 00215 // set new database version 00216 setVersion(1); 00217 00218 break; 00219 } 00220 } 00221 } 00222 00223 void SQLiteDatabase::open() 00224 { 00225 //Configure SQLite Library 00226 SQLiteConfigure::configure(); 00227 00228 // check if SQLite is thread-safe 00229 if (sqlite3_threadsafe() == 0) 00230 { 00231 IBRCOMMON_LOGGER(critical) << "sqlite library has not compiled with threading support." << IBRCOMMON_LOGGER_ENDL; 00232 throw ibrcommon::Exception("need threading support in sqlite!"); 00233 } 00234 00235 //open the database 00236 if (sqlite3_open_v2(_file.getPath().c_str(), &_database, SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)) 00237 { 00238 IBRCOMMON_LOGGER(error) << "Can't open database: " << sqlite3_errmsg(_database) << IBRCOMMON_LOGGER_ENDL; 00239 sqlite3_close(_database); 00240 throw ibrcommon::Exception("Unable to open sqlite database"); 00241 } 00242 00243 try { 00244 // check database version and upgrade if necessary 00245 int version = getVersion(); 00246 00247 IBRCOMMON_LOGGER(info) << "SQLiteDatabase: Database version " << version << " found." << IBRCOMMON_LOGGER_ENDL; 00248 00249 if (version != DBSCHEMA_VERSION) 00250 { 00251 doUpgrade(version, DBSCHEMA_VERSION); 00252 } 00253 } catch (const SQLiteQueryException&) { 00254 doUpgrade(0, DBSCHEMA_VERSION); 00255 } 00256 00257 // disable synchronous mode 00258 sqlite3_exec(_database, "PRAGMA synchronous = OFF;", NULL, NULL, NULL); 00259 00260 // enable sqlite tracing if debug level is higher than 50 00261 if (IBRCOMMON_LOGGER_LEVEL >= 50) 00262 { 00263 sqlite3_trace(_database, &sql_tracer, NULL); 00264 } 00265 00266 // //Check if the database is consistent with the filesystem 00267 // check_consistency(); 00268 00269 // calculate next Bundleexpiredtime 00270 update_expire_time(); 00271 } 00272 00273 void SQLiteDatabase::close() 00274 { 00275 //close Databaseconnection 00276 if (sqlite3_close(_database) != SQLITE_OK) 00277 { 00278 IBRCOMMON_LOGGER(error) << "unable to close database" << IBRCOMMON_LOGGER_ENDL; 00279 } 00280 00281 // shutdown sqlite library 00282 SQLiteConfigure::shutdown(); 00283 } 00284 00285 // void SQLiteDatabase::check_consistency() 00286 // { 00287 // /* 00288 // * check if the database is consistent with the files on the HDD. Therefore every file of the database is put in a list. 00289 // * When the files of the database are scanned it is checked if the actual file is in the list of files of the filesystem. if it is so the entry 00290 // * of the list of files of the filesystem is deleted. if not the file is put in a seperate list. After this procedure there are 2 lists containing file 00291 // * which are inconsistent and can be deleted. 00292 // */ 00293 // set<string> blockFiles,payloadfiles; 00294 // string datei; 00295 // 00296 // list<ibrcommon::File> blist; 00297 // list<ibrcommon::File>::iterator file_it; 00298 // 00299 // //1. Durchsuche die Dateien 00300 // _blockPath.getFiles(blist); 00301 // for(file_it = blist.begin(); file_it != blist.end(); file_it++) 00302 // { 00303 // datei = (*file_it).getPath(); 00304 // if(datei[datei.size()-1] != '.') 00305 // { 00306 // if(((*file_it).getBasename())[0] == 'b') 00307 // { 00308 // blockFiles.insert(datei); 00309 // } 00310 // else 00311 // payloadfiles.insert(datei); 00312 // } 00313 // } 00314 // 00315 // //3. Check consistency of the bundles 00316 // check_bundles(blockFiles); 00317 // 00318 // //4. Check consistency of the fragments 00319 // check_fragments(payloadfiles); 00320 // } 00321 00322 // void SQLiteDatabase::check_fragments(std::set<std::string> &payloadFiles) 00323 // { 00324 // int err; 00325 // size_t timestamp, sequencenumber, fragmentoffset; 00326 // string filename, source, dest, custody, repto; 00327 // set<string> consistenFiles, inconsistenSources; 00328 // set<size_t> inconsistentTimestamp, inconsistentSeq_number; 00329 // set<string>::iterator file_it, consisten_it; 00330 // 00331 // sqlite3_stmt *getPayloadfiles = prepare("SELECT source, destination, reportto, custodian, procflags, timestamp, sequencenumber, lifetime, fragmentoffset, appdatalength, hopcount FROM "+_tables[SQL_TABLE_BUNDLE]+" WHERE fragmentoffset != NULL;"); 00332 // 00333 // for(err = sqlite3_step(getPayloadfiles); err == SQLITE_ROW; err = sqlite3_step(getPayloadfiles)) 00334 // { 00335 // filename = (const char*)sqlite3_column_text(getPayloadfiles,10); 00336 // file_it = payloadFiles.find(filename); 00337 // 00338 // 00339 // if (file_it == payloadFiles.end()) 00340 // { 00341 // consisten_it = consistenFiles.find(*file_it); 00342 // 00343 // //inconsistent DB entry 00344 // if(consisten_it == consistenFiles.end()) 00345 // { 00346 // //Generate Report 00347 // source = (const char*) sqlite3_column_text(getPayloadfiles, 0); 00348 // dest = (const char*) sqlite3_column_text(getPayloadfiles, 1); 00349 // repto = (const char*) sqlite3_column_text(getPayloadfiles, 2); 00350 // custody = (const char*) sqlite3_column_text(getPayloadfiles, 3); 00351 // 00352 // timestamp = sqlite3_column_int64(getPayloadfiles, 5); 00353 // sequencenumber = sqlite3_column_int64(getPayloadfiles, 6); 00354 // fragmentoffset = sqlite3_column_int64(getPayloadfiles, 8); 00355 // 00356 // dtn::data::BundleID id( dtn::data::EID(source), timestamp, sequencenumber, true, fragmentoffset ); 00357 // dtn::data::MetaBundle mb(id); 00358 // 00359 // mb.procflags = sqlite3_column_int64(getPayloadfiles, 4); 00360 // mb.lifetime = sqlite3_column_int64(getPayloadfiles, 7); 00361 // mb.destination = dest; 00362 // mb.reportto = repto; 00363 // mb.custodian = custody; 00364 // mb.appdatalength = sqlite3_column_int64(getPayloadfiles, 9); 00365 // mb.expiretime = dtn::utils::Clock::getExpireTime(timestamp, mb.lifetime); 00366 // 00367 // if (sqlite3_column_type(getPayloadfiles, 10) != SQLITE_NULL) 00368 // { 00369 // mb.hopcount = sqlite3_column_int64(getPayloadfiles, 10); 00370 // } 00371 // 00372 // dtn::core::BundleEvent::raise(mb, dtn::core::BUNDLE_DELETED, dtn::data::StatusReportBlock::DEPLETED_STORAGE); 00373 // } 00374 // } 00375 // //consistent DB entry 00376 // else 00377 // consistenFiles.insert(*file_it); 00378 // payloadFiles.erase(file_it); 00379 // } 00380 // 00381 // sqlite3_reset(getPayloadfiles); 00382 // sqlite3_finalize(getPayloadfiles); 00383 // } 00384 // 00385 // void SQLiteDatabase::check_bundles(std::set<std::string> &blockFiles) 00386 // { 00387 // std::set<dtn::data::BundleID> corrupt_bundle_ids; 00388 // 00389 // sqlite3_stmt *blockConistencyCheck = prepare("SELECT source_id, timestamp, sequencenumber, fragmentoffset, filename, ordernumber FROM "+ _tables[SQL_TABLE_BLOCK] +";"); 00390 // 00391 // while (sqlite3_step(blockConistencyCheck) == SQLITE_ROW) 00392 // { 00393 // dtn::data::BundleID id; 00394 // 00395 // // get the bundleid 00396 // get_bundleid(blockConistencyCheck, id); 00397 // 00398 // // get the filename 00399 // std::string filename = (const char*)sqlite3_column_text(blockConistencyCheck, 4); 00400 // 00401 // // if the filename is not in the list of block files 00402 // if (blockFiles.find(filename) == blockFiles.end()) 00403 // { 00404 // // add the bundle to the deletion list 00405 // corrupt_bundle_ids.insert(id); 00406 // } 00407 // else 00408 // { 00409 // // file is available, everything is fine 00410 // blockFiles.erase(filename); 00411 // } 00412 // } 00413 // sqlite3_reset(blockConistencyCheck); 00414 // sqlite3_finalize(blockConistencyCheck); 00415 // 00416 // for(std::set<dtn::data::BundleID>::const_iterator iter = corrupt_bundle_ids.begin(); iter != corrupt_bundle_ids.end(); iter++) 00417 // { 00418 // try { 00419 // const dtn::data::BundleID &id = (*iter); 00420 // 00421 // // get meta data of this bundle 00422 // const dtn::data::MetaBundle m = get_meta(id); 00423 // 00424 // // remove the hole bundle 00425 // remove(id); 00426 // } catch (const dtn::storage::SQLiteDatabase::SQLiteQueryException&) { }; 00427 // } 00428 // 00429 // // delete block files still in the blockfile list 00430 // for (std::set<std::string>::const_iterator iter = blockFiles.begin(); iter != blockFiles.end(); iter++) 00431 // { 00432 // ibrcommon::File blockfile(*iter); 00433 // blockfile.remove(); 00434 // } 00435 // } 00436 00437 void SQLiteDatabase::get(const dtn::data::BundleID &id, dtn::data::MetaBundle &meta) const 00438 { 00439 // check if the bundle is already on the deletion list 00440 if (contains_deletion(id)) throw dtn::storage::BundleStorage::NoBundleFoundException(); 00441 00442 size_t stmt_key = BUNDLE_GET_ID; 00443 if (id.fragment) stmt_key = FRAGMENT_GET_ID; 00444 00445 // lock the prepared statement 00446 Statement st(_database, _sql_queries[stmt_key]); 00447 00448 // bind bundle id to the statement 00449 set_bundleid(st, id); 00450 00451 // execute the query and check for error 00452 if (st.step() != SQLITE_ROW) 00453 { 00454 stringstream error; 00455 error << "SQLiteDatabase: No Bundle found with BundleID: " << id.toString(); 00456 IBRCOMMON_LOGGER_DEBUG(15) << error.str() << IBRCOMMON_LOGGER_ENDL; 00457 throw SQLiteQueryException(error.str()); 00458 } 00459 00460 // query bundle data 00461 get(st, meta); 00462 } 00463 00464 void SQLiteDatabase::get(Statement &st, dtn::data::MetaBundle &bundle, size_t offset) const 00465 { 00466 bundle.source = dtn::data::EID( (const char*) sqlite3_column_text(*st, offset + 0) ); 00467 bundle.destination = dtn::data::EID( (const char*) sqlite3_column_text(*st, offset + 1) ); 00468 bundle.reportto = dtn::data::EID( (const char*) sqlite3_column_text(*st, offset + 2) ); 00469 bundle.custodian = dtn::data::EID( (const char*) sqlite3_column_text(*st, offset + 3) ); 00470 bundle.procflags = sqlite3_column_int(*st, offset + 4); 00471 bundle.timestamp = sqlite3_column_int64(*st, offset + 5); 00472 bundle.sequencenumber = sqlite3_column_int64(*st, offset + 6); 00473 bundle.lifetime = sqlite3_column_int64(*st, offset + 7); 00474 bundle.expiretime = dtn::utils::Clock::getExpireTime(bundle.timestamp, bundle.lifetime); 00475 00476 if (bundle.procflags & data::Bundle::FRAGMENT) 00477 { 00478 bundle.offset = sqlite3_column_int64(*st, offset + 8); 00479 bundle.appdatalength = sqlite3_column_int64(*st, offset + 9); 00480 } 00481 00482 if (sqlite3_column_type(*st, offset + 10) != SQLITE_NULL) 00483 { 00484 bundle.hopcount = sqlite3_column_int64(*st, 10); 00485 } 00486 00487 bundle.payloadlength = sqlite3_column_int64(*st, 11); 00488 } 00489 00490 void SQLiteDatabase::get(Statement &st, dtn::data::Bundle &bundle, size_t offset) const 00491 { 00492 bundle._source = dtn::data::EID( (const char*) sqlite3_column_text(*st, offset + 0) ); 00493 bundle._destination = dtn::data::EID( (const char*) sqlite3_column_text(*st, offset + 1) ); 00494 bundle._reportto = dtn::data::EID( (const char*) sqlite3_column_text(*st, offset + 2) ); 00495 bundle._custodian = dtn::data::EID( (const char*) sqlite3_column_text(*st, offset + 3) ); 00496 bundle._procflags = sqlite3_column_int(*st, offset + 4); 00497 bundle._timestamp = sqlite3_column_int64(*st, offset + 5); 00498 bundle._sequencenumber = sqlite3_column_int64(*st, offset + 6); 00499 bundle._lifetime = sqlite3_column_int64(*st, offset + 7); 00500 00501 if (bundle._procflags & data::Bundle::FRAGMENT) 00502 { 00503 bundle._fragmentoffset = sqlite3_column_int64(*st, offset + 8); 00504 bundle._appdatalength = sqlite3_column_int64(*st, offset + 9); 00505 } 00506 } 00507 00508 const std::list<dtn::data::MetaBundle> SQLiteDatabase::get(dtn::storage::BundleStorage::BundleFilterCallback &cb) const 00509 { 00510 std::list<dtn::data::MetaBundle> ret; 00511 00512 const std::string base_query = 00513 "SELECT source, destination, reportto, custodian, procflags, timestamp, sequencenumber, lifetime, fragmentoffset, appdatalength, hopcount, payloadlength FROM " + _tables[SQL_TABLE_BUNDLE]; 00514 00515 size_t offset = 0; 00516 bool unlimited = (cb.limit() <= 0); 00517 size_t query_limit = (cb.limit() > 0) ? cb.limit() : 10; 00518 00519 try { 00520 try { 00521 SQLBundleQuery &query = dynamic_cast<SQLBundleQuery&>(cb); 00522 00523 // custom query string 00524 std::string query_string = base_query + " WHERE " + query.getWhere() + " ORDER BY priority DESC LIMIT ?,?;"; 00525 00526 // create statement for custom query 00527 Statement st(_database, query_string); 00528 00529 while (unlimited || (ret.size() < query_limit)) 00530 { 00531 // bind the statement parameter 00532 size_t bind_offset = query.bind(*st, 1); 00533 00534 // query the database 00535 __get(cb, st, ret, bind_offset, offset); 00536 00537 // increment the offset, because we might not have enough 00538 offset += query_limit; 00539 } 00540 } catch (const std::bad_cast&) { 00541 Statement st(_database, _sql_queries[BUNDLE_GET_FILTER]); 00542 00543 while (unlimited || (ret.size() < query_limit)) 00544 { 00545 // query the database 00546 __get(cb, st, ret, 1, offset); 00547 00548 // increment the offset, because we might not have enough 00549 offset += query_limit; 00550 } 00551 } 00552 } catch (const dtn::storage::BundleStorage::NoBundleFoundException&) { } 00553 00554 return ret; 00555 } 00556 00557 void SQLiteDatabase::__get(const dtn::storage::BundleStorage::BundleFilterCallback &cb, Statement &st, std::list<dtn::data::MetaBundle> &ret, size_t bind_offset, size_t offset) const 00558 { 00559 bool unlimited = (cb.limit() <= 0); 00560 size_t query_limit = (cb.limit() > 0) ? cb.limit() : 10; 00561 00562 // limit result according to callback result 00563 sqlite3_bind_int64(*st, bind_offset, offset); 00564 sqlite3_bind_int64(*st, bind_offset + 1, query_limit); 00565 00566 // returned no result 00567 if (st.step() == SQLITE_DONE) 00568 throw dtn::storage::BundleStorage::NoBundleFoundException(); 00569 00570 // abort if enough bundles are found 00571 while (unlimited || (ret.size() < query_limit)) 00572 { 00573 dtn::data::MetaBundle m; 00574 00575 // extract the primary values and set them in the bundle object 00576 get(st, m, 0); 00577 00578 // check if the bundle is already on the deletion list 00579 if (!contains_deletion(m)) 00580 { 00581 // ask the filter if this bundle should be added to the return list 00582 if (cb.shouldAdd(m)) 00583 { 00584 IBRCOMMON_LOGGER_DEBUG(40) << "add bundle to query selection list: " << m.toString() << IBRCOMMON_LOGGER_ENDL; 00585 00586 // add the bundle to the list 00587 ret.push_back(m); 00588 } 00589 } 00590 00591 if (st.step() != SQLITE_ROW) break; 00592 } 00593 00594 st.reset(); 00595 } 00596 00597 void SQLiteDatabase::get(const dtn::data::BundleID &id, dtn::data::Bundle &bundle, blocklist &blocks) const 00598 { 00599 int err = 0; 00600 00601 IBRCOMMON_LOGGER_DEBUG(25) << "get bundle from sqlite storage " << id.toString() << IBRCOMMON_LOGGER_ENDL; 00602 00603 // if bundle is deleted? 00604 if (contains_deletion(id)) throw dtn::storage::BundleStorage::NoBundleFoundException(); 00605 00606 size_t stmt_key = BUNDLE_GET_ID; 00607 if (id.fragment) stmt_key = FRAGMENT_GET_ID; 00608 00609 // do this while db is locked 00610 Statement st(_database, _sql_queries[stmt_key]); 00611 00612 // set the bundle key values 00613 set_bundleid(st, id); 00614 00615 // execute the query and check for error 00616 if ((err = st.step()) != SQLITE_ROW) 00617 { 00618 IBRCOMMON_LOGGER_DEBUG(15) << "sql error: " << err << "; No bundle found with id: " << id.toString() << IBRCOMMON_LOGGER_ENDL; 00619 throw dtn::storage::BundleStorage::NoBundleFoundException(); 00620 } 00621 00622 // read bundle data 00623 get(st, bundle); 00624 00625 try { 00626 00627 int err = 0; 00628 string file; 00629 00630 // select the right statement to use 00631 const size_t stmt_key = id.fragment ? BLOCK_GET_ID_FRAGMENT : BLOCK_GET_ID; 00632 00633 Statement st(_database, _sql_queries[stmt_key]); 00634 00635 // set the bundle key values 00636 set_bundleid(st, id); 00637 00638 // query the database and step through all blocks 00639 while ((err = st.step()) == SQLITE_ROW) 00640 { 00641 const ibrcommon::File f( (const char*) sqlite3_column_text(*st, 0) ); 00642 int blocktyp = sqlite3_column_int(*st, 1); 00643 00644 blocks.push_back( blocklist_entry(blocktyp, f) ); 00645 } 00646 00647 if (err == SQLITE_DONE) 00648 { 00649 if (blocks.size() == 0) 00650 { 00651 IBRCOMMON_LOGGER(error) << "get_blocks: no blocks found for: " << id.toString() << IBRCOMMON_LOGGER_ENDL; 00652 throw SQLiteQueryException("no blocks found"); 00653 } 00654 } 00655 else 00656 { 00657 IBRCOMMON_LOGGER(error) << "get_blocks() failure: "<< err << " " << sqlite3_errmsg(_database) << IBRCOMMON_LOGGER_ENDL; 00658 throw SQLiteQueryException("can not query for blocks"); 00659 } 00660 00661 } catch (const ibrcommon::Exception &ex) { 00662 IBRCOMMON_LOGGER(error) << "could not get bundle blocks: " << ex.what() << IBRCOMMON_LOGGER_ENDL; 00663 throw dtn::storage::BundleStorage::NoBundleFoundException(); 00664 } 00665 } 00666 00667 void SQLiteDatabase::store(const dtn::data::Bundle &bundle) 00668 { 00669 int err; 00670 00671 const dtn::data::EID _sourceid = bundle._source; 00672 size_t TTL = bundle._timestamp + bundle._lifetime; 00673 00674 Statement st(_database, _sql_queries[BUNDLE_STORE]); 00675 00676 set_bundleid(st, bundle); 00677 00678 sqlite3_bind_text(*st, 5, bundle._source.getString().c_str(), bundle._source.getString().length(), SQLITE_TRANSIENT); 00679 sqlite3_bind_text(*st, 6, bundle._destination.getString().c_str(), bundle._destination.getString().length(), SQLITE_TRANSIENT); 00680 sqlite3_bind_text(*st, 7, bundle._reportto.getString().c_str(), bundle._reportto.getString().length(), SQLITE_TRANSIENT); 00681 sqlite3_bind_text(*st, 8, bundle._custodian.getString().c_str(), bundle._custodian.getString().length(), SQLITE_TRANSIENT); 00682 sqlite3_bind_int(*st, 9, bundle._procflags); 00683 sqlite3_bind_int64(*st, 10, bundle._lifetime); 00684 00685 if (bundle.get(dtn::data::Bundle::FRAGMENT)) 00686 { 00687 sqlite3_bind_int64(*st, 11, bundle._appdatalength); 00688 } 00689 else 00690 { 00691 sqlite3_bind_null(*st, 4); 00692 sqlite3_bind_null(*st, 11); 00693 } 00694 00695 sqlite3_bind_int64(*st, 12, TTL); 00696 sqlite3_bind_int64(*st, 13, dtn::data::MetaBundle(bundle).getPriority()); 00697 00698 try { 00699 const dtn::data::ScopeControlHopLimitBlock &schl = bundle.getBlock<const dtn::data::ScopeControlHopLimitBlock>(); 00700 sqlite3_bind_int64(*st, 14, schl.getHopsToLive() ); 00701 } catch (const dtn::data::Bundle::NoSuchBlockFoundException&) { 00702 sqlite3_bind_null(*st, 14 ); 00703 }; 00704 00705 try { 00706 const dtn::data::PayloadBlock &pblock = bundle.getBlock<const dtn::data::PayloadBlock>(); 00707 sqlite3_bind_int64(*st, 15, pblock.getLength() ); 00708 } catch (const dtn::data::Bundle::NoSuchBlockFoundException&) { 00709 sqlite3_bind_int64(*st, 15, 0 ); 00710 }; 00711 00712 err = st.step(); 00713 00714 if (err == SQLITE_CONSTRAINT) 00715 { 00716 IBRCOMMON_LOGGER(warning) << "Bundle is already in the storage" << IBRCOMMON_LOGGER_ENDL; 00717 00718 stringstream error; 00719 error << "SQLiteDatabase: store() failure: " << err << " " << sqlite3_errmsg(_database); 00720 throw SQLiteQueryException(error.str()); 00721 } 00722 else if (err != SQLITE_DONE) 00723 { 00724 stringstream error; 00725 error << "SQLiteDatabase: store() failure: " << err << " " << sqlite3_errmsg(_database); 00726 IBRCOMMON_LOGGER(error) << error.str() << IBRCOMMON_LOGGER_ENDL; 00727 00728 throw SQLiteQueryException(error.str()); 00729 } 00730 00731 // set new expire time 00732 new_expire_time(TTL); 00733 } 00734 00735 void SQLiteDatabase::store(const dtn::data::BundleID &id, int index, const dtn::data::Block &block, const ibrcommon::File &file) 00736 { 00737 int blocktyp = (int)block.getType(); 00738 00739 // protect this query from concurrent access and enable the auto-reset feature 00740 Statement st(_database, _sql_queries[BLOCK_STORE]); 00741 00742 // set bundle key data 00743 set_bundleid(st, id); 00744 00745 // set the column four to null if this is not a fragment 00746 if (!id.fragment) sqlite3_bind_null(*st, 4); 00747 00748 // set the block type 00749 sqlite3_bind_int(*st, 5, blocktyp); 00750 00751 // the filename of the block data 00752 sqlite3_bind_text(*st, 6, file.getPath().c_str(), file.getPath().size(), SQLITE_TRANSIENT); 00753 00754 // the ordering number 00755 sqlite3_bind_int(*st, 7, index); 00756 00757 // execute the query and store the block in the database 00758 if (st.step() != SQLITE_DONE) 00759 { 00760 throw SQLiteQueryException("can not store block of bundle"); 00761 } 00762 } 00763 00764 void SQLiteDatabase::transaction() 00765 { 00766 // start a transaction 00767 sqlite3_exec(_database, "BEGIN TRANSACTION;", NULL, NULL, NULL); 00768 } 00769 00770 void SQLiteDatabase::rollback() 00771 { 00772 // rollback the whole transaction 00773 sqlite3_exec(_database, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); 00774 } 00775 00776 void SQLiteDatabase::commit() 00777 { 00778 // commit the transaction 00779 sqlite3_exec(_database, "END TRANSACTION;", NULL, NULL, NULL); 00780 } 00781 00782 void SQLiteDatabase::remove(const dtn::data::BundleID &id) 00783 { 00784 { 00785 // select the right statement to use 00786 const size_t stmt_key = id.fragment ? BLOCK_GET_ID_FRAGMENT : BLOCK_GET_ID; 00787 00788 // lock the database 00789 Statement st(_database, _sql_queries[stmt_key]); 00790 00791 // set the bundle key values 00792 set_bundleid(st, id); 00793 00794 // step through all blocks 00795 while (st.step() == SQLITE_ROW) 00796 { 00797 // delete each referenced block file 00798 ibrcommon::File blockfile( (const char*)sqlite3_column_text(*st, 0) ); 00799 blockfile.remove(); 00800 } 00801 } 00802 00803 { 00804 const size_t stmt_key = id.fragment ? FRAGMENT_DELETE : BUNDLE_DELETE; 00805 00806 // lock the database 00807 Statement st(_database, _sql_queries[stmt_key]); 00808 00809 // then remove the bundle data 00810 set_bundleid(st, id); 00811 st.step(); 00812 00813 IBRCOMMON_LOGGER_DEBUG(10) << "bundle " << id.toString() << " deleted" << IBRCOMMON_LOGGER_ENDL; 00814 } 00815 00816 //update deprecated timer 00817 update_expire_time(); 00818 00819 // remove it from the deletion list 00820 remove_deletion(id); 00821 } 00822 00823 void SQLiteDatabase::clear() 00824 { 00825 Statement vacuum(_database, _sql_queries[VACUUM]); 00826 Statement bundle_clear(_database, _sql_queries[BUNDLE_CLEAR]); 00827 Statement block_clear(_database, _sql_queries[BLOCK_CLEAR]); 00828 00829 bundle_clear.step(); 00830 block_clear.step(); 00831 00832 if (SQLITE_DONE != vacuum.step()) 00833 { 00834 throw SQLiteQueryException("SQLiteBundleStore: clear(): vacuum failed."); 00835 } 00836 00837 reset_expire_time(); 00838 } 00839 00840 bool SQLiteDatabase::empty() const 00841 { 00842 Statement st(_database, _sql_queries[EMPTY_CHECK]); 00843 00844 if (SQLITE_DONE == st.step()) 00845 { 00846 return true; 00847 } 00848 else 00849 { 00850 return false; 00851 } 00852 } 00853 00854 unsigned int SQLiteDatabase::count() const 00855 { 00856 int rows = 0; 00857 int err = 0; 00858 00859 Statement st(_database, _sql_queries[COUNT_ENTRIES]); 00860 00861 if ((err = st.step()) == SQLITE_ROW) 00862 { 00863 rows = sqlite3_column_int(*st, 0); 00864 } 00865 else 00866 { 00867 stringstream error; 00868 error << "SQLiteDatabase: count: failure " << err << " " << sqlite3_errmsg(_database); 00869 IBRCOMMON_LOGGER(error) << error.str() << IBRCOMMON_LOGGER_ENDL; 00870 throw SQLiteQueryException(error.str()); 00871 } 00872 00873 return rows; 00874 } 00875 00876 const std::set<dtn::data::EID> SQLiteDatabase::getDistinctDestinations() 00877 { 00878 std::set<dtn::data::EID> ret; 00879 return ret; 00880 } 00881 00882 void SQLiteDatabase::update_expire_time() 00883 { 00884 Statement st(_database, _sql_queries[EXPIRE_NEXT_TIMESTAMP]); 00885 00886 int err = st.step(); 00887 00888 if (err == SQLITE_ROW) 00889 { 00890 _next_expiration = sqlite3_column_int64(*st, 0); 00891 } 00892 else 00893 { 00894 _next_expiration = 0; 00895 } 00896 } 00897 00898 void SQLiteDatabase::expire(size_t timestamp) 00899 { 00900 /* 00901 * Only if the actual time is bigger or equal than the time when the next bundle expires, deleteexpired is called. 00902 */ 00903 size_t exp_time = get_expire_time(); 00904 if ((timestamp < exp_time) || (exp_time == 0)) return; 00905 00906 /* 00907 * Performanceverbesserung: Damit die Abfragen nicht jede Sekunde ausgeführt werden müssen, speichert man den Zeitpunkt an dem 00908 * das nächste Bündel gelöscht werden soll in eine Variable und führt deleteexpired erst dann aus wenn ein Bündel abgelaufen ist. 00909 * Nach dem Löschen wird die DB durchsucht und der nächste Ablaufzeitpunkt wird in die Variable gesetzt. 00910 */ 00911 00912 { 00913 Statement st(_database, _sql_queries[EXPIRE_BUNDLE_FILENAMES]); 00914 00915 // query for blocks of expired bundles 00916 sqlite3_bind_int64(*st, 1, timestamp); 00917 while (st.step() == SQLITE_ROW) 00918 { 00919 ibrcommon::File block((const char*)sqlite3_column_text(*st,0)); 00920 block.remove(); 00921 } 00922 } 00923 00924 { 00925 Statement st(_database, _sql_queries[EXPIRE_BUNDLES]); 00926 00927 // query expired bundles 00928 sqlite3_bind_int64(*st, 1, timestamp); 00929 while (st.step() == SQLITE_ROW) 00930 { 00931 dtn::data::BundleID id; 00932 get_bundleid(st, id); 00933 dtn::core::BundleExpiredEvent::raise(id); 00934 } 00935 } 00936 00937 { 00938 Statement st(_database, _sql_queries[EXPIRE_BUNDLE_DELETE]); 00939 00940 // delete all expired db entries (bundles and blocks) 00941 sqlite3_bind_int64(*st, 1, timestamp); 00942 st.step(); 00943 } 00944 00945 //update deprecated timer 00946 update_expire_time(); 00947 } 00948 00949 void SQLiteDatabase::vacuum() 00950 { 00951 Statement st(_database, _sql_queries[VACUUM]); 00952 st.step(); 00953 } 00954 00955 void SQLiteDatabase::update(UPDATE_VALUES mode, const dtn::data::BundleID &id, const dtn::data::EID &eid) 00956 { 00957 if (mode == UPDATE_CUSTODIAN) 00958 { 00959 // select query with or without fragmentation extension 00960 STORAGE_STMT query = BUNDLE_UPDATE_CUSTODIAN; 00961 if (id.fragment) query = FRAGMENT_UPDATE_CUSTODIAN; 00962 00963 Statement st(_database, _sql_queries[query]); 00964 00965 sqlite3_bind_text(*st, 1, eid.getString().c_str(), eid.getString().length(), SQLITE_TRANSIENT); 00966 set_bundleid(st, id, 1); 00967 00968 // update the custodian in the database 00969 int err = st.step(); 00970 00971 if (err != SQLITE_DONE) 00972 { 00973 stringstream error; 00974 error << "SQLiteDatabase: update_custodian() failure: " << err << " " << sqlite3_errmsg(_database); 00975 IBRCOMMON_LOGGER(error) << error.str() << IBRCOMMON_LOGGER_ENDL; 00976 } 00977 } 00978 } 00979 00980 void SQLiteDatabase::new_expire_time(size_t ttl) 00981 { 00982 if (_next_expiration == 0 || ttl < _next_expiration) 00983 { 00984 _next_expiration = ttl; 00985 } 00986 } 00987 00988 void SQLiteDatabase::reset_expire_time() 00989 { 00990 _next_expiration = 0; 00991 } 00992 00993 size_t SQLiteDatabase::get_expire_time() 00994 { 00995 return _next_expiration; 00996 } 00997 00998 void SQLiteDatabase::add_deletion(const dtn::data::BundleID &id) 00999 { 01000 _deletion_list.insert(id); 01001 } 01002 01003 void SQLiteDatabase::remove_deletion(const dtn::data::BundleID &id) 01004 { 01005 _deletion_list.erase(id); 01006 } 01007 01008 bool SQLiteDatabase::contains_deletion(const dtn::data::BundleID &id) const 01009 { 01010 return (_deletion_list.find(id) != _deletion_list.end()); 01011 } 01012 01013 void SQLiteDatabase::set_bundleid(Statement &st, const dtn::data::BundleID &id, size_t offset) const 01014 { 01015 const std::string source_id = id.source.getString(); 01016 sqlite3_bind_text(*st, offset + 1, source_id.c_str(), source_id.length(), SQLITE_TRANSIENT); 01017 sqlite3_bind_int64(*st, offset + 2, id.timestamp); 01018 sqlite3_bind_int64(*st, offset + 3, id.sequencenumber); 01019 01020 if (id.fragment) 01021 { 01022 sqlite3_bind_int64(*st, offset + 4, id.offset); 01023 } 01024 } 01025 01026 void SQLiteDatabase::get_bundleid(Statement &st, dtn::data::BundleID &id, size_t offset) const 01027 { 01028 id.source = dtn::data::EID((const char*)sqlite3_column_text(*st, offset + 0)); 01029 id.timestamp = sqlite3_column_int64(*st, offset + 1); 01030 id.sequencenumber = sqlite3_column_int64(*st, offset + 2); 01031 01032 id.fragment = (sqlite3_column_text(*st, offset + 3) != NULL); 01033 id.offset = sqlite3_column_int64(*st, offset + 3); 01034 } 01035 } /* namespace storage */ 01036 } /* namespace dtn */