Sleds/libeye/eyeencrypteddb.cpp

620 lines
23 KiB
C++
Raw Normal View History

2025-03-13 21:28:38 +00:00
// Copyright (c) 2013-2015 IONU Security, Inc. All rights reserved
//
// Encrypted DB class for off-line cache and other local database storage
#include <cstring>
#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include "eyeencrypteddb.h"
#include "eyelog.h"
#include "eyeutils.h"
using namespace sequencelogic;
namespace sequencelogic {
static char IONU_EYEDB_SALT[] = "74734f616e6c554949556c6e614f7374";
}
EyeEncryptedDB::EyeEncryptedDB (const std::string& filename, const unsigned char* key)
:EyeDB()
{
_filename = "";
_DEK = NULL;
_IV = NULL;
_db = NULL;
EVP_CIPHER_CTX_init (&_ctx);
int ec = SQLITE_OK;
std::string sql;
char* errmsg = NULL;
sqlite3_stmt* stmt = NULL;
int oflags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX;
int cflags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
size_t bytes = 0;
unsigned char* ciphertext = NULL;
char magic[256];
if (key && sequencelogic::IsValidFilename (filename)) {
unsigned char salt[17];
sequencelogic::HexToBinary (IONU_EYEDB_SALT, salt);
salt[16] = '\0';
//printf ("Salt: %s\n", (char*)salt);
_filename = filename;
_DEK = new unsigned char[SL_AES_KEY_LEN + SL_AES_BLOCK_LEN + 4];
_IV = new unsigned char[SL_AES_BLOCK_LEN + 4];
if (sqlite3_open_v2 (filename.c_str(), &_db, oflags, NULL) != SQLITE_OK) {
// Close this and create new db
if (_db) sqlite3_close_v2 (_db);
if (sqlite3_open_v2 (filename.c_str(), &_db, cflags, NULL) != SQLITE_OK) {
delete[] _DEK;
_DEK = NULL;
IONUDEBUG ("EyeEncryptedDB(%s) - Create failed", filename.c_str());
}
else {
// Create the "Header" table and insert Version, Magic, DBK, IV
sql = "CREATE TABLE Header (Parameter TEXT UNIQUE, Value TEXT);";
if (sqlite3_exec (_db, sql.c_str(), NULL, NULL, &errmsg) != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - CREATE TABLE Header - %s\n", errmsg);
}
sql = "INSERT INTO Header VALUES ('Version', '1.0');";
if (sqlite3_exec (_db, sql.c_str(), NULL, NULL, &errmsg) != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT INTO Header Version - %s\n", errmsg);
}
// Generate random values for DEK (Database Encryption Key) and IV
if (sequencelogic::RandomBytes (SL_AES_KEY_LEN, _DEK)) {
ciphertext = new unsigned char [SL_AES_KEY_LEN + SL_AES_BLOCK_LEN];
bytes = sequencelogic::Encryptor (&_ctx, SL_AES_KEY_LEN, _DEK, ciphertext, key, salt);
sql = "INSERT INTO Header VALUES ('DEK', ?);";
ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, prepare: %d", ec);
}
ec = sqlite3_bind_blob(stmt, 1, (const void*)ciphertext, (int)bytes, SQLITE_TRANSIENT);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, bind: %d", ec);
}
ec = sqlite3_step (stmt);
if (ec != SQLITE_DONE && ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, step: %d", ec);
}
ec = sqlite3_clear_bindings(stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, clear: %d", ec);
}
ec = sqlite3_finalize (stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of DEK failed, finalize: %d", ec);
}
//sequencelogic::Base64Encode (ciphertext, bytes, b64);
//printf ("dek: %s\n", b64);
delete[] ciphertext;
ciphertext = NULL;
}
else {
IONUDEBUG ("EyeEncryptedDB() - DEK generation failed");
}
if (sequencelogic::RandomBytes (SL_AES_BLOCK_LEN, _IV)) {
sql = "INSERT INTO Header VALUES ('IV', ?);";
ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, prepare: %d", ec);
}
ec = sqlite3_bind_blob(stmt, 1, (const void*)_IV, SL_AES_BLOCK_LEN, SQLITE_TRANSIENT);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, bind: %d", ec);
}
ec = sqlite3_step (stmt);
if (ec != SQLITE_DONE && ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, step: %d", ec);
}
ec = sqlite3_clear_bindings(stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, clear: %d", ec);
}
ec = sqlite3_finalize (stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of IV failed, finalize: %d", ec);
}
}
else {
IONUDEBUG ("EyeEncryptedDB() - IV generation failed");
}
// Encrypt the magic value and insert into Header
bytes = sequencelogic::Encryptor (&_ctx, 5, (unsigned char*)"IOnU", (unsigned char*)magic, _DEK, _IV);
//sequencelogic::Base64Encode ((unsigned char*)magic, bytes, b64);
//printf ("Encrypted Magic, %d bytes, %s\n", (int)bytes, b64);
sql = "INSERT INTO Header VALUES ('Magic', ?);";
//std::cout << sql << std::endl;
ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, prepare: %d", ec);
}
ec = sqlite3_bind_blob(stmt, 1, (const void*)magic, (int)bytes, SQLITE_TRANSIENT);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, bind: %d", ec);
}
ec = sqlite3_step (stmt);
if (ec != SQLITE_DONE && ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, step: %d", ec);
}
ec = sqlite3_clear_bindings(stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, clear: %d", ec);
}
ec = sqlite3_finalize (stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - INSERT of Magic failed, finalize: %d", ec);
}
}
//sequencelogic::Base64Encode (_DEK, SL_AES_KEY_LEN, b64);
//printf ("DEK: %s\n", b64);
//sequencelogic::Base64Encode (_IV, SL_AES_BLOCK_LEN, b64);
//printf ("IV: %s\n", b64);
}
// Opened existing db
else {
sql = "SELECT * FROM Header WHERE Parameter == 'DEK';";
ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of DEK failed, prepare: %d", ec);
}
ec = sqlite3_step (stmt);
if (ec != SQLITE_ROW) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of DEK failed, step: %d", ec);
}
bytes = sqlite3_column_bytes (stmt, 1);
ciphertext = (unsigned char*) sqlite3_column_blob (stmt, 1);
if (ciphertext) {
bytes = Decryptor (&_ctx, bytes, ciphertext, _DEK, key, salt);
if (bytes != SL_AES_KEY_LEN) {
IONUDEBUG ("EyeEncryptedDB() - get of DEK failed, wrong number of bytes: %d", (int)bytes);
}
}
else {
IONUDEBUG ("EyeEncryptedDB() - get of DEK failed");
}
ec = sqlite3_finalize (stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of DEK failed, finalize: %d", ec);
}
sql = "SELECT * FROM Header WHERE Parameter == 'IV';";
ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of IV failed: %d", ec);
}
ec = sqlite3_step (stmt);
if (ec != SQLITE_ROW) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of IV failed, step: %d", ec);
}
unsigned char* iv = (unsigned char*) sqlite3_column_blob (stmt, 1);
bytes = sqlite3_column_bytes (stmt, 1);
if (iv && bytes == SL_AES_BLOCK_LEN) {
sequencelogic::MemCpy (_IV, iv, SL_AES_BLOCK_LEN);
}
else {
IONUDEBUG ("EyeEncryptedDB() - get of IV failed %d bytes", bytes);
}
ec = sqlite3_finalize (stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of IV failed, finalize: %d", ec);
}
sql = "SELECT * FROM Header WHERE Parameter == 'Magic';";
ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of Magic failed, prepare: %d", ec);
}
ec = sqlite3_step (stmt);
if (ec != SQLITE_ROW) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of Magic failed, step: %d", ec);
}
bytes = sqlite3_column_bytes (stmt, 1);
ciphertext = (unsigned char*) sqlite3_column_blob (stmt, 1);
bool goodMagic = false;
if (ciphertext) {
bytes = Decryptor (&_ctx, bytes, ciphertext, (unsigned char*)magic, _DEK, _IV);
if (bytes == 5 && sequencelogic::StrCmp (magic, "IOnU") == 0)
goodMagic = true;
}
if (!goodMagic) {
IONUDEBUG ("EyeEncryptedDB() - bad magic");
if (_DEK) {
delete[] _DEK;
_DEK = NULL;
}
}
ec = sqlite3_finalize (stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB() - SELECT of Magic failed, finalize: %d", ec);
}
//sequencelogic::Base64Encode (_DEK, SL_AES_KEY_LEN, b64);
//printf ("DEK: %s\n", b64);
//sequencelogic::Base64Encode (_IV, SL_AES_BLOCK_LEN, b64);
//printf ("IV: %s\n", b64);
}
}
}
EyeEncryptedDB::~EyeEncryptedDB ()
{
if (_DEK) {
sequencelogic::MemSet (_DEK, 0, SL_AES_KEY_LEN);
delete[] _DEK;
_DEK = NULL;
}
if (_IV) {
sequencelogic::MemSet (_IV, 0, SL_AES_BLOCK_LEN);
delete[] _IV;
_IV = NULL;
}
if (_db) {
sqlite3_close_v2 (_db);
_db = NULL;
}
EVP_CIPHER_CTX_cleanup (&_ctx);
}
bool EyeEncryptedDB::Put (const std::string& collection, const std::string& key, const std::string& value)
{
if (!_DEK) {
IONUDEBUG ("EyeEncryptedDB::Put() - DB not open");
return false;
}
else if (collection.size() < 1 || key.size() < 1 || value.size() < 1) {
IONUDEBUG ("EyeEncryptedDB::Put() - Invalid collection, key and/or value");
return false;
}
else {
size_t bytes = 0;
size_t len = value.size();
//create table if necessary
std::stringstream table;
table << "CREATE TABLE IF NOT EXISTS " << collection << " (id TEXT UNIQUE, value TEXT);";
if (!executeQuery(table.str())) {
IONUDEBUG ("EyeEncryptedDB::Put(%s, %s) - executeQuery failed", collection.c_str(), key.c_str());
return false;
}
//prepare statement
std::stringstream sql;
sql << "INSERT OR REPLACE INTO " << collection << " VALUES (?,?);";
statement stmt(*this, sql.str());
if (!stmt.isPrepared()) {
IONUDEBUG ("EyeEncryptedDB::Put(%s, %s) - isPrepared failed", collection.c_str(), key.c_str());
return false;
}
//encrypt value
std::unique_ptr<unsigned char[]> ciphertext(new unsigned char[len + 2*SL_AES_BLOCK_LEN]);
bytes = sequencelogic::Encryptor (&_ctx, len, (unsigned char*)value.data(), ciphertext.get(), _DEK, _IV);
//bind arguments and insert row
if (stmt.bind(1, key)
&& stmt.bind(2, ciphertext.get(), (int)bytes))
{
int ec = stmt.step();
if (ec != SQLITE_ROW && ec != SQLITE_DONE) {
IONUDEBUG ("EyeEncryptedDB::Put(%s, %s) - step failed: %d", collection.c_str(), key.c_str(), ec);
}
}
}
return true;
}
char* EyeEncryptedDB::Get (const std::string& collection, const std::string& key)
{
if (!_DEK) {
IONUDEBUG ("EyeEncryptedDB::Get() - DB not open");
return NULL;
}
else if (collection.size() < 1 || key.size() < 1) {
IONUDEBUG ("EyeEncryptedDB::Get() - Invalid collection and/or key");
return NULL;
}
else {
unsigned char* value = NULL;
size_t bytes = 0;
std::stringstream sql;
sql << "SELECT * FROM " << collection << " WHERE id == ?";
statement stmt(*this, sql.str());
if (!stmt.isPrepared()) {
IONUDEBUG ("EyeEncryptedDB::Get(%s, %s) - prepare failed", collection.c_str(), key.c_str());
return NULL;
}
if (stmt.bind(1, key))
{
int ec = stmt.step();
if (ec == SQLITE_ROW) {
//value = sequencelogic::StrDup (sqlite3_column_text (stmt, 1));
unsigned char* ciphertext = (unsigned char*) stmt.columnBlob(1);
bytes = (size_t) stmt.columnBytesInt(1);
//char b64[256];
//sequencelogic::Base64Encode (ciphertext, bytes, b64);
//printf ("dec: %s.%s %s\n", collection, key, b64);
if (ciphertext) {
value = new unsigned char[bytes+1];
bytes = Decryptor (&_ctx, bytes, ciphertext, value, _DEK, _IV);
value[bytes] = '\0';
}
}
else if (ec == SQLITE_DONE) {
IONUDEBUG ("EyeEncryptedDB::Get(%s, %s) - key not found", collection.c_str(), key.c_str());
} else {
IONUDEBUG ("EyeEncryptedDB::Get(%s, %s) - step failed %d", collection.c_str(), key.c_str(), ec);
}
}
return (char*)value;
}
}
bool EyeEncryptedDB::PutAll (const std::string& collection, const std::map<std::string, std::string>& entries, bool remove)
{
if (!_DEK) {
IONUDEBUG ("EyeEncryptedDB::Put() - DB not open");
return false;
}
else if (collection.size() < 1) {
IONUDEBUG ("EyeEncryptedDB::PutAll() - Invalid collection");
return false;
}
else {
//Create the table if necessary
std::stringstream table;
table << "CREATE TABLE IF NOT EXISTS " << collection << " (id TEXT UNIQUE, value TEXT);";
if (!executeQuery(table.str())) {
return false;
}
// Protect optional remove and puts in a transaction, on failure this will rollback to unchanged version
{
transaction transaction(*this);
//remove existing rows if requested
if (remove) {
std::stringstream sql;
sql << "DELETE FROM " << collection;
if (!executeQuery(table.str())) {
return false;
}
}
//Create query to insert/replace rows
std::stringstream sql;
sql << "INSERT OR REPLACE INTO " << collection << " VALUES (?, ?)";
statement stmt(*this, sql.str());
if (!stmt.isPrepared()) {
IONUDEBUG ("EyeEncryptedDB::PutAll(%s) - prepare failed", collection.c_str());
return false;
}
//iterate new key/values and update table
for (std::map<std::string, std::string>::const_iterator itr = entries.begin(); itr != entries.end(); ++itr) {
//encrypt value
size_t len = itr->second.size();
std::unique_ptr<unsigned char[]> ciphertext(new unsigned char[len + 2*SL_AES_BLOCK_LEN]);
size_t bytes = sequencelogic::Encryptor (&_ctx, len, (unsigned char*) itr->second.c_str(), ciphertext.get(), _DEK, _IV);
//bind key/value and step
if (stmt.bind(1, itr->first)
&& stmt.bind(2, ciphertext.get(), (int)bytes))
{
//insert/update row
int result = stmt.step();
if (result != SQLITE_DONE) {
IONUDEBUG ("EyeEncryptedDB::PutAll(%s) - insert failed", collection.c_str());
return false;
}
stmt.reset();
} else {
IONUDEBUG ("EyeEncryptedDB::PutAll(%s) - bind failed", collection.c_str());
return false;
}
}
transaction.commit();
}
return true;
}
}
STRING_STRING_MAP EyeEncryptedDB::GetAll (const std::string& collection)
{
STRING_STRING_MAP entries;
if (!_DEK) {
IONUDEBUG ("EyeEncryptedDB::GetAll() - DB not open");
}
else if (collection.size() < 1) {
IONUDEBUG ("EyeEncryptedDB::GetAll() - Invalid collection");
}
else {
std::stringstream sql;
sql << "SELECT * FROM " << collection;
statement stmt(*this, sql.str());
if (stmt.isPrepared()) {
while (stmt.step() == SQLITE_ROW) {
//key
std::string key = (const char*)(stmt.columnText(0));
//decrypt value from ciphertext in column
unsigned char* ciphertext = (unsigned char*) stmt.columnBlob(1);
size_t bytes = (size_t) stmt.columnBytesInt(1);
if (ciphertext) {
std::unique_ptr<char[]> value(new char[bytes+1]);
bytes = Decryptor (&_ctx, bytes, ciphertext, (unsigned char*)value.get(), _DEK, _IV);
entries[key] = std::string(value.get(), bytes);
}
}
} else {
IONUDEBUG ("EyeEncryptedDB::GetAll() - prepare failed");
}
}
return entries;
}
bool EyeEncryptedDB::ReKey (const unsigned char* newTGIkey)
{
if (!_DEK) {
IONUDEBUG ("EyeEncryptedDB::ReKey() - DB not open");
return false;
}
else if (!newTGIkey) {
IONUDEBUG ("EyeEncryptedDB::ReKey() - Invalid TGI key");
return false;
}
if (!_DEK) {
IONUDEBUG ("EyeDB::ReKey() - DB not open");
return false;
}
else if (!newTGIkey) {
IONUDEBUG ("EyeDB::ReKey() - Invalid TGI key");
return false;
}
size_t bytes = 0;
unsigned char salt[17];
sequencelogic::HexToBinary (IONU_EYEDB_SALT, salt);
unsigned char ciphertext[256];
bytes = sequencelogic::Encryptor (&_ctx, SL_AES_KEY_LEN, _DEK, ciphertext, newTGIkey, salt);
std::string sql = "REPLACE INTO Header VALUES ('DEK', ?);";
statement stmt(*this, sql);
if (stmt.isPrepared() && stmt.bind(1, ciphertext, (int)bytes)) {
int ec = stmt.step();
if (ec == SQLITE_DONE)
return true;
}
return false;
}
bool EyeEncryptedDB::Remove (const std::string& collection)
{
if (!_DEK) {
IONUDEBUG ("EyeEncryptedDB::Remove() - DB not open");
return false;
}
else if (collection.size() < 1) {
IONUDEBUG ("EyeEncryptedDB::Remove() - Invalid collection");
return false;
}
else {
std::stringstream sql;
sql << "DROP TABLE IF EXISTS " << collection;
statement stmt(*this, sql.str());
if (stmt.isPrepared()) {
int ec = stmt.step();
if (ec != SQLITE_DONE) {
IONUDEBUG ("EyeEncryptedDB::Remove() - step failed");
return false;
}
} else {
IONUDEBUG ("EyeEncryptedDB::Remove() - prepare failed");
return false;
}
}
return true;
}
bool EyeEncryptedDB::Remove (const std::string& collection, const std::string& key)
{
if (!_DEK) {
IONUDEBUG ("EyeEncryptedDB::Remove() - DB not open");
return false;
}
else if (collection.size() < 1 || key.size() < 1) {
IONUDEBUG ("EyeEncryptedDB::Remove() - Invalid collection and/or key");
return false;
}
else {
std::stringstream sql;
sql << "DELETE FROM " << collection << " WHERE id == ?";
statement stmt(*this, sql.str());
if (stmt.isPrepared() && stmt.bind(1, key)) {
int ec = stmt.step();
if (ec != SQLITE_DONE) {
IONUDEBUG ("EyeEncryptedDB::Remove() - step failed");
return false;
}
} else {
IONUDEBUG ("EyeEncryptedDB::Remove() - prepare failed");
return false;
}
}
return true;
}
void EyeEncryptedDB::Dump (const std::string& collection)
{
size_t bytes = 0;
std::string sql;
sqlite3_stmt* stmt = NULL;
sql = "SELECT * FROM Header;";
int ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB::Dump() - SELECT * FROM Header failed, prepare: %d", ec);
}
printf ("Header {\n");
while (sqlite3_step (stmt) == SQLITE_ROW) {
const unsigned char* key = sqlite3_column_text (stmt, 0);
unsigned char* val = (unsigned char*) sqlite3_column_blob (stmt, 1);
bytes = (size_t) sqlite3_column_bytes (stmt, 1);
if (!sequencelogic::IsStrAscii ((char*) val, bytes)) {
char b64[256];
sequencelogic::Base64Encode (val, bytes, b64);
printf (" %s: %s\n", key, b64);
}
else {
printf (" %s: %s\n", key, (char*)val);
}
}
ec = sqlite3_finalize (stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB::Dump() - SELECT * FROM Header failed, finalize: %d", ec);
}
printf ("}\n");
sql = "SELECT * FROM ";
sql += collection;
sql += ";";
ec = sqlite3_prepare_v2 (_db, sql.c_str(), (int)sql.length(), &stmt, 0);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB::Dump() - SELECT * FROM %s failed, prepare: %d", collection.c_str(), ec);
}
std::cout << collection << " {" << std::endl;
while (sqlite3_step (stmt) == SQLITE_ROW) {
const unsigned char* key = sqlite3_column_text (stmt, 0);
unsigned char* val = (unsigned char*) sqlite3_column_blob (stmt, 1);
bytes = (size_t) sqlite3_column_bytes (stmt, 1);
if (!sequencelogic::IsStrAscii ((char*) val, bytes)) {
char b64[256];
sequencelogic::Base64Encode (val, bytes, b64);
std::cout << " " << key << ": " << b64 << std::endl;
}
else {
printf (" %s: %s\n", key, (char*)val);
}
}
ec = sqlite3_finalize (stmt);
if (ec != SQLITE_OK) {
IONUDEBUG ("EyeEncryptedDB::Dump() - SELECT * FROM %s failed, finalize: %d", collection.c_str(), ec);
}
printf ("}\n");
}