using System;
using System.Text;
using Pgno = System.UInt32;
using u16 = System.UInt16;

namespace Community.CsharpSqlite
{
#if !SQLITE_WINRT

	using System.IO;
	using System.Security.Cryptography;

#endif

	public partial class Sqlite3
	{
		/*
		*************************************************************************
		**  Included in SQLite3 port to C#-SQLite;  2010 Noah B Hart, Diego Torres
		**  C#-SQLite is an independent reimplementation of the SQLite software library
		**
		*************************************************************************
		*/

		/*
		** SQLCipher
		** crypto.c developed by Stephen Lombardo (Zetetic LLC)
		** sjlombardo at zetetic dot net
		** http://zetetic.net
		**
		** Copyright (c) 2009, ZETETIC LLC
		** All rights reserved.
		**
		** Redistribution and use in source and binary forms, with or without
		** modification, are permitted provided that the following conditions are met:
		** * Redistributions of source code must retain the above copyright
		** notice, this list of conditions and the following disclaimer.
		** * Redistributions in binary form must reproduce the above copyright
		** notice, this list of conditions and the following disclaimer in the
		** documentation and/or other materials provided with the distribution.
		** * Neither the name of the ZETETIC LLC nor the
		** names of its contributors may be used to endorse or promote products
		** derived from this software without specific prior written permission.
		**
		** THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
		** EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
		** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
		** DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
		** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
		** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
		** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
		** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
		** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
		** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
		**
		*/
		/* BEGIN CRYPTO */
#if SQLITE_HAS_CODEC

		//#include <assert.h>
		//#include <openssl/evp.h>
		//#include <openssl/rand.h>
		//#include <openssl/hmac.h>
		//#include "sqliteInt.h"
		//#include "btreeInt.h"
		//#include "crypto.h"

#if CODEC_DEBUG || TRACE
//#define CODEC_TRACE(X) {printf X;fflush(stdout);}
static void CODEC_TRACE( string T, params object[] ap ) { if ( sqlite3PagerTrace )sqlite3DebugPrintf( T, ap ); }
#else

		//#define CODEC_TRACE(X)
		private static void CODEC_TRACE(string T, params object[] ap)
		{
		}

#endif

		//void sqlite3FreeCodecArg(void *pCodecArg);

		public class cipher_ctx
		{//typedef struct {
			public string pass;
			public int pass_sz;
			public bool derive_key;
			public byte[] key;
			public int key_sz;
			public byte[] iv;
			public int iv_sz;
			public ICryptoTransform encryptor;
			public ICryptoTransform decryptor;

			public cipher_ctx Copy()
			{
				cipher_ctx c = new cipher_ctx();
				c.derive_key = derive_key;
				c.pass = pass;
				c.pass_sz = pass_sz;
				if (key != null)
				{
					c.key = new byte[key.Length];
					key.CopyTo(c.key, 0);
				}
				c.key_sz = key_sz;
				if (iv != null)
				{
					c.iv = new byte[iv.Length];
					iv.CopyTo(c.iv, 0);
				}
				c.iv_sz = iv_sz;
				c.encryptor = encryptor;
				c.decryptor = decryptor;
				return c;
			}

			public void CopyTo(cipher_ctx ct)
			{
				ct.derive_key = derive_key;
				ct.pass = pass;
				ct.pass_sz = pass_sz;
				if (key != null)
				{
					ct.key = new byte[key.Length];
					key.CopyTo(ct.key, 0);
				}
				ct.key_sz = key_sz;
				if (iv != null)
				{
					ct.iv = new byte[iv.Length];
					iv.CopyTo(ct.iv, 0);
				}
				ct.iv_sz = iv_sz;
				ct.encryptor = encryptor;
				ct.decryptor = decryptor;
			}
		}

		public class codec_ctx
		{//typedef struct {
			public int mode_rekey;
			public byte[] buffer;
			public Btree pBt;
			public cipher_ctx read_ctx;
			public cipher_ctx write_ctx;

			public codec_ctx Copy()
			{
				codec_ctx c = new codec_ctx();
				c.mode_rekey = mode_rekey;
				c.buffer = sqlite3MemMalloc(buffer.Length);
				c.pBt = pBt;
				if (read_ctx != null)
					c.read_ctx = read_ctx.Copy();
				if (write_ctx != null)
					c.write_ctx = write_ctx.Copy();
				return c;
			}
		}

		private const int FILE_HEADER_SZ = 16;      //#define FILE_HEADER_SZ 16
		private const string CIPHER = "aes-256-cbc";  //#define CIPHER "aes-256-cbc"
		private const int CIPHER_DECRYPT = 0;        //#define CIPHER_DECRYPT 0
		private const int CIPHER_ENCRYPT = 1;        //#define CIPHER_ENCRYPT 1

#if NET_2_0
    static RijndaelManaged Aes = new RijndaelManaged();
#else
		private static AesManaged Aes = new AesManaged();
#endif

		/* BEGIN CRYPTO */

		private static void sqlite3pager_get_codec(Pager pPager, ref codec_ctx ctx)
		{
			ctx = pPager.pCodec;
		}

		private static int sqlite3pager_is_mj_pgno(Pager pPager, Pgno pgno)
		{
			return (PAGER_MJ_PGNO(pPager) == pgno) ? 1 : 0;
		}

		private static sqlite3_file sqlite3Pager_get_fd(Pager pPager)
		{
			return (isOpen(pPager.fd)) ? pPager.fd : null;
		}

		private static void sqlite3pager_sqlite3PagerSetCodec(
		Pager pPager,
		dxCodec xCodec,
		dxCodecSizeChng xCodecSizeChng,
		dxCodecFree xCodecFree,
		codec_ctx pCodec
		)
		{
			sqlite3PagerSetCodec(pPager, xCodec, xCodecSizeChng, xCodecFree, pCodec);
		}

		/* END CRYPTO */

		//static void activate_openssl() {
		//  if(EVP_get_cipherbyname(CIPHER) == null) {
		//    OpenSSL_add_all_algorithms();
		//  }
		//}

		/**
		* Free and wipe memory
		* If ptr is not null memory will be freed.
		* If sz is greater than zero, the memory will be overwritten with zero before it is freed
		*/

		private static void codec_free(ref byte[] ptr, int sz)
		{
			if (ptr != null)
			{
				if (sz > 0)
					Array.Clear(ptr, 0, sz);//memset( ptr, 0, sz );
				sqlite3_free(ref ptr);
			}
		}

		/**
		* Set the raw password / key data for a cipher context
		*
		* returns SQLITE_OK if assignment was successfull
		* returns SQLITE_NOMEM if an error occured allocating memory
		* returns SQLITE_ERROR if the key couldn't be set because the pass was null or size was zero
		*/

		private static int cipher_ctx_set_pass(cipher_ctx ctx, string zKey, int nKey)
		{
			ctx.pass = null; // codec_free( ctx.pass, ctx.pass_sz );
			ctx.pass_sz = nKey;
			if (!String.IsNullOrEmpty(zKey) && nKey > 0)
			{
				//ctx.pass = sqlite3Malloc(nKey);
				//if(ctx.pass == null) return SQLITE_NOMEM;
				ctx.pass = zKey;//memcpy(ctx.pass, zKey, nKey);
				return SQLITE_OK;
			}
			return SQLITE_ERROR;
		}

		/**
		* Initialize a new cipher_ctx struct. This function will allocate memory
		* for the cipher context and for the key
		*
		* returns SQLITE_OK if initialization was successful
		* returns SQLITE_NOMEM if an error occured allocating memory
		*/

		private static int cipher_ctx_init(ref cipher_ctx iCtx)
		{
			iCtx = new cipher_ctx();
			//iCtx = sqlite3Malloc( sizeof( cipher_ctx ) );
			//ctx = *iCtx;
			//if ( ctx == null ) return SQLITE_NOMEM;
			//memset( ctx, 0, sizeof( cipher_ctx ) );
			//ctx.key =  sqlite3Malloc( EVP_MAX_KEY_LENGTH );
			//if ( ctx.key == null ) return SQLITE_NOMEM;
			return SQLITE_OK;
		}

		/**
		* free and wipe memory associated with a cipher_ctx
		*/

		private static void cipher_ctx_free(ref cipher_ctx ictx)
		{
			cipher_ctx ctx = ictx;
			CODEC_TRACE("cipher_ctx_free: entered ictx=%d\n", ictx);
			ctx.pass = null;//codec_free(ctx.pass, ctx.pass_sz);
			if (ctx.key != null)
				Array.Clear(ctx.key, 0, ctx.key.Length);//codec_free(ctx.key, ctx.key_sz);
			if (ctx.iv != null)
				Array.Clear(ctx.iv, 0, ctx.iv.Length);
			ictx = new cipher_ctx();// codec_free( ref ctx, sizeof( cipher_ctx ) );
		}

		/**
		* Copy one cipher_ctx to another. For instance, assuming that read_ctx is a
		* fully initialized context, you could copy it to write_ctx and all yet data
		* and pass information across
		*
		* returns SQLITE_OK if initialization was successful
		* returns SQLITE_NOMEM if an error occured allocating memory
		*/

		private static int cipher_ctx_copy(cipher_ctx target, cipher_ctx source)
		{
			//byte[] key = target.key;
			CODEC_TRACE("cipher_ctx_copy: entered target=%d, source=%d\n", target, source);
			//codec_free(target.pass, target.pass_sz);
			source.CopyTo(target);//memcpy(target, source, sizeof(cipher_ctx);

			//target.key = key; //restore pointer to previously allocated key data
			//memcpy(target.key, source.key, EVP_MAX_KEY_LENGTH);
			//target.pass = sqlite3Malloc(source.pass_sz);
			//if(target.pass == null) return SQLITE_NOMEM;
			//memcpy(target.pass, source.pass, source.pass_sz);
			return SQLITE_OK;
		}

		/**
		* Compare one cipher_ctx to another.
		*
		* returns 0 if all the parameters (except the derived key data) are the same
		* returns 1 otherwise
		*/

		private static int cipher_ctx_cmp(cipher_ctx c1, cipher_ctx c2)
		{
			CODEC_TRACE("cipher_ctx_cmp: entered c1=%d c2=%d\n", c1, c2);

			if (c1.key_sz == c2.key_sz
			&& c1.pass_sz == c2.pass_sz
			&& c1.pass == c2.pass
			)
				return 0;
			return 1;
		}

		/**
		* Free and wipe memory associated with a cipher_ctx, including the allocated
		* read_ctx and write_ctx.
		*/

		private static void codec_ctx_free(ref codec_ctx iCtx)
		{
			codec_ctx ctx = iCtx;
			CODEC_TRACE("codec_ctx_free: entered iCtx=%d\n", iCtx);
			cipher_ctx_free(ref ctx.read_ctx);
			cipher_ctx_free(ref ctx.write_ctx);
			iCtx = new codec_ctx();//codec_free(ctx, sizeof(codec_ctx);
		}

		/**
		* Derive an encryption key for a cipher contex key based on the raw password.
		*
		* If the raw key data is formated as x'hex' and there are exactly enough hex chars to fill
		* the key space (i.e 64 hex chars for a 256 bit key) then the key data will be used directly.
		*
		* Otherwise, a key data will be derived using PBKDF2
		*
		* returns SQLITE_OK if initialization was successful
		* returns SQLITE_NOMEM if the key could't be derived (for instance if pass is null or pass_sz is 0)
		*/

		private static int codec_key_derive(codec_ctx ctx, cipher_ctx c_ctx)
		{
			CODEC_TRACE("codec_key_derive: entered c_ctx.pass=%s, c_ctx.pass_sz=%d ctx.iv=%d ctx.iv_sz=%d c_ctx.kdf_iter=%d c_ctx.key_sz=%d\n",
			c_ctx.pass, c_ctx.pass_sz, c_ctx.iv, c_ctx.iv_sz, c_ctx.key_sz);

			if (c_ctx.pass != null && c_ctx.pass_sz > 0)
			{ // if pass is not null
				if ((c_ctx.pass_sz == (c_ctx.key_sz * 2) + 3) && c_ctx.pass.StartsWith("x'", StringComparison.InvariantCultureIgnoreCase))
				{
					int n = c_ctx.pass_sz - 3; /* adjust for leading x' and tailing ' */
					string z = c_ctx.pass.Substring(2);// + 2; /* adjust lead offset of x' */
					CODEC_TRACE("codec_key_derive: deriving key from hex\n");
					c_ctx.key = sqlite3HexToBlob(null, z, n);
				}
				else
				{
					CODEC_TRACE("codec_key_derive: deriving key using AES256\n");

					Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(c_ctx.pass, c_ctx.iv, 2010);
					c_ctx.key_sz = 32;
					c_ctx.key = k1.GetBytes(c_ctx.key_sz);
				}
#if NET_2_0
        Aes.BlockSize = 0x80;
        Aes.FeedbackSize = 8;
        Aes.KeySize = 0x100;
        Aes.Mode = CipherMode.CBC;
#endif
				c_ctx.encryptor = Aes.CreateEncryptor(c_ctx.key, c_ctx.iv);
				c_ctx.decryptor = Aes.CreateDecryptor(c_ctx.key, c_ctx.iv);
				return SQLITE_OK;
			};
			return SQLITE_ERROR;
		}

		/*
		* ctx - codec context
		* pgno - page number in database
		* size - size in bytes of input and output buffers
		* mode - 1 to encrypt, 0 to decrypt
		* in - pointer to input bytes
		* out - pouter to output bytes
		*/

		private static int codec_cipher(cipher_ctx ctx, Pgno pgno, int mode, int size, byte[] bIn, byte[] bOut)
		{
			int iv;
			int tmp_csz, csz;

			CODEC_TRACE("codec_cipher:entered pgno=%d, mode=%d, size=%d\n", pgno, mode, size);

			/* just copy raw data from in to out when key size is 0
			* i.e. during a rekey of a plaintext database */
			if (ctx.key_sz == 0)
			{
				Array.Copy(bIn, bOut, bIn.Length);//memcpy(out, in, size);
				return SQLITE_OK;
			}

			MemoryStream dataStream = new MemoryStream();
			CryptoStream encryptionStream;
			if (mode == CIPHER_ENCRYPT)
			{
				encryptionStream = new CryptoStream(dataStream, ctx.encryptor, CryptoStreamMode.Write);
			}
			else
			{
				encryptionStream = new CryptoStream(dataStream, ctx.decryptor, CryptoStreamMode.Write);
			}
			encryptionStream.Write(bIn, 0, size);
			encryptionStream.FlushFinalBlock();
			dataStream.Position = 0;

			dataStream.Read(bOut, 0, (int)dataStream.Length);
			encryptionStream.Close();
			dataStream.Close();

			return SQLITE_OK;
		}

		/**
		*
		* when for_ctx == 0 then it will change for read
		* when for_ctx == 1 then it will change for write
		* when for_ctx == 2 then it will change for both
		*/

		private static int codec_set_cipher_name(sqlite3 db, int nDb, string cipher_name, int for_ctx)
		{
			Db pDb = db.aDb[nDb];
			CODEC_TRACE("codec_set_cipher_name: entered db=%d nDb=%d cipher_name=%s for_ctx=%d\n", db, nDb, cipher_name, for_ctx);

			if (pDb.pBt != null)
			{
				codec_ctx ctx = null;
				cipher_ctx c_ctx;
				sqlite3pager_get_codec(pDb.pBt.pBt.pPager, ref ctx);
				c_ctx = for_ctx != 0 ? ctx.write_ctx : ctx.read_ctx;

				c_ctx.derive_key = true;

				if (for_ctx == 2)
					cipher_ctx_copy(for_ctx != 0 ? ctx.read_ctx : ctx.write_ctx, c_ctx);
				return SQLITE_OK;
			}
			return SQLITE_ERROR;
		}

		private static int codec_set_pass_key(sqlite3 db, int nDb, string zKey, int nKey, int for_ctx)
		{
			Db pDb = db.aDb[nDb];
			CODEC_TRACE("codec_set_pass_key: entered db=%d nDb=%d cipher_name=%s nKey=%d for_ctx=%d\n", db, nDb, zKey, nKey, for_ctx);
			if (pDb.pBt != null)
			{
				codec_ctx ctx = null;
				cipher_ctx c_ctx;
				sqlite3pager_get_codec(pDb.pBt.pBt.pPager, ref ctx);
				c_ctx = for_ctx != 0 ? ctx.write_ctx : ctx.read_ctx;

				cipher_ctx_set_pass(c_ctx, zKey, nKey);
				c_ctx.derive_key = true;

				if (for_ctx == 2)
					cipher_ctx_copy(for_ctx != 0 ? ctx.read_ctx : ctx.write_ctx, c_ctx);
				return SQLITE_OK;
			}
			return SQLITE_ERROR;
		}

		/*
		* sqlite3Codec can be called in multiple modes.
		* encrypt mode - expected to return a pointer to the
		* encrypted data without altering pData.
		* decrypt mode - expected to return a pointer to pData, with
		* the data decrypted in the input buffer
		*/

		private static byte[] sqlite3Codec(codec_ctx iCtx, byte[] data, Pgno pgno, int mode)
		{
			codec_ctx ctx = (codec_ctx)iCtx;
			int pg_sz = sqlite3BtreeGetPageSize(ctx.pBt);
			int offset = 0;
			byte[] pData = data;

			CODEC_TRACE("sqlite3Codec: entered pgno=%d, mode=%d, ctx.mode_rekey=%d, pg_sz=%d\n", pgno, mode, ctx.mode_rekey, pg_sz);

			/* derive key on first use if necessary */
			if (ctx.read_ctx.derive_key)
			{
				codec_key_derive(ctx, ctx.read_ctx);
				ctx.read_ctx.derive_key = false;
			}

			if (ctx.write_ctx.derive_key)
			{
				if (cipher_ctx_cmp(ctx.write_ctx, ctx.read_ctx) == 0)
				{
					cipher_ctx_copy(ctx.write_ctx, ctx.read_ctx); // the relevant parameters are the same, just copy read key
				}
				else
				{
					codec_key_derive(ctx, ctx.write_ctx);
					ctx.write_ctx.derive_key = false;
				}
			}

			CODEC_TRACE("sqlite3Codec: switch mode=%d offset=%d\n", mode, offset);
			if (ctx.buffer.Length != pg_sz)
				ctx.buffer = sqlite3MemMalloc(pg_sz);
			switch (mode)
			{
				case SQLITE_DECRYPT:
					codec_cipher(ctx.read_ctx, pgno, CIPHER_DECRYPT, pg_sz, pData, ctx.buffer);
					if (pgno == 1)
						Buffer.BlockCopy(Encoding.UTF8.GetBytes(SQLITE_FILE_HEADER), 0, ctx.buffer, 0, FILE_HEADER_SZ);// memcpy( ctx.buffer, SQLITE_FILE_HEADER, FILE_HEADER_SZ ); /* copy file header to the first 16 bytes of the page */
					Buffer.BlockCopy(ctx.buffer, 0, pData, 0, pg_sz); //memcpy( pData, ctx.buffer, pg_sz ); /* copy buffer data back to pData and return */
					return pData;

				case SQLITE_ENCRYPT_WRITE_CTX: /* encrypt */
					if (pgno == 1)
						Buffer.BlockCopy(ctx.write_ctx.iv, 0, ctx.buffer, 0, FILE_HEADER_SZ);//memcpy( ctx.buffer, ctx.iv, FILE_HEADER_SZ ); /* copy salt to output buffer */
					codec_cipher(ctx.write_ctx, pgno, CIPHER_ENCRYPT, pg_sz, pData, ctx.buffer);
					return ctx.buffer; /* return persistent buffer data, pData remains intact */
				case SQLITE_ENCRYPT_READ_CTX:
					if (pgno == 1)
						Buffer.BlockCopy(ctx.read_ctx.iv, 0, ctx.buffer, 0, FILE_HEADER_SZ);//memcpy( ctx.buffer, ctx.iv, FILE_HEADER_SZ ); /* copy salt to output buffer */
					codec_cipher(ctx.read_ctx, pgno, CIPHER_ENCRYPT, pg_sz, pData, ctx.buffer);
					return ctx.buffer; /* return persistent buffer data, pData remains intact */
				default:
					return pData;
			}
		}

		private static int sqlite3CodecAttach(sqlite3 db, int nDb, string zKey, int nKey)
		{
			Db pDb = db.aDb[nDb];

			CODEC_TRACE("sqlite3CodecAttach: entered nDb=%d zKey=%s, nKey=%d\n", nDb, zKey, nKey);
			//activate_openssl();

			if (zKey != null && pDb.pBt != null)
			{
				Aes.KeySize = 256;
#if !SQLITE_SILVERLIGHT
				Aes.Padding = PaddingMode.None;
#endif
				codec_ctx ctx;
				int rc;
				////Pager pPager = pDb.pBt.pBt.pPager;
				sqlite3_file fd;

				ctx = new codec_ctx();//sqlite3Malloc(sizeof(codec_ctx);
				//if(ctx == null) return SQLITE_NOMEM;
				//memset(ctx, 0, sizeof(codec_ctx); /* initialize all pointers and values to 0 */

				ctx.pBt = pDb.pBt; /* assign pointer to database btree structure */

				if ((rc = cipher_ctx_init(ref ctx.read_ctx)) != SQLITE_OK)
					return rc;
				if ((rc = cipher_ctx_init(ref ctx.write_ctx)) != SQLITE_OK)
					return rc;

				/* pre-allocate a page buffer of PageSize bytes. This will
				be used as a persistent buffer for encryption and decryption
				operations to avoid overhead of multiple memory allocations*/
				ctx.buffer = sqlite3MemMalloc(sqlite3BtreeGetPageSize(ctx.pBt));//sqlite3Malloc(sqlite3BtreeGetPageSize(ctx.pBt);
				//if(ctx.buffer == null) return SQLITE_NOMEM;

				/* allocate space for salt data. Then read the first 16 bytes header as the salt for the key derivation */
				ctx.read_ctx.iv_sz = FILE_HEADER_SZ;
				ctx.read_ctx.iv = new byte[ctx.read_ctx.iv_sz];//sqlite3Malloc( ctx.iv_sz );

				Buffer.BlockCopy(Encoding.UTF8.GetBytes(SQLITE_FILE_HEADER), 0, ctx.read_ctx.iv, 0, FILE_HEADER_SZ);

				sqlite3pager_sqlite3PagerSetCodec(sqlite3BtreePager(pDb.pBt), sqlite3Codec, null, sqlite3FreeCodecArg, ctx);

				codec_set_cipher_name(db, nDb, CIPHER, 0);
				codec_set_pass_key(db, nDb, zKey, nKey, 0);
				cipher_ctx_copy(ctx.write_ctx, ctx.read_ctx);

				//sqlite3BtreeSetPageSize( ctx.pBt, sqlite3BtreeGetPageSize( ctx.pBt ), MAX_IV_LENGTH, 0 );
			}
			return SQLITE_OK;
		}

		private static void sqlite3FreeCodecArg(ref codec_ctx pCodecArg)
		{
			if (pCodecArg == null)
				return;
			codec_ctx_free(ref pCodecArg); // wipe and free allocated memory for the context
		}

		private static void sqlite3_activate_see(string zPassword)
		{
			/* do nothing, security enhancements are always active */
		}

		static public int sqlite3_key(sqlite3 db, string pKey, int nKey)
		{
			CODEC_TRACE("sqlite3_key: entered db=%d pKey=%s nKey=%d\n", db, pKey, nKey);
			/* attach key if db and pKey are not null and nKey is > 0 */
			if (db != null && pKey != null)
			{
				sqlite3CodecAttach(db, 0, pKey, nKey); // operate only on the main db
				//
				// If we are reopening an existing database, redo the header information setup
				//
				BtShared pBt = db.aDb[0].pBt.pBt;
				byte[] zDbHeader = sqlite3MemMalloc((int)pBt.pageSize);// pBt.pPager.pCodec.buffer;
				sqlite3PagerReadFileheader(pBt.pPager, zDbHeader.Length, zDbHeader);
				if (sqlite3Get4byte(zDbHeader) > 0) // Existing Database, need to reset some values
				{
					CODEC2(pBt.pPager, zDbHeader, 2, SQLITE_DECRYPT, ref zDbHeader);
					byte nReserve = zDbHeader[20];
					pBt.pageSize = (uint)((zDbHeader[16] << 8) | (zDbHeader[17] << 16));
					if (pBt.pageSize < 512 || pBt.pageSize > SQLITE_MAX_PAGE_SIZE
					|| ((pBt.pageSize - 1) & pBt.pageSize) != 0)
						pBt.pageSize = 0;
					pBt.pageSizeFixed = true;
#if !SQLITE_OMIT_AUTOVACUUM
					pBt.autoVacuum = sqlite3Get4byte(zDbHeader, 36 + 4 * 4) != 0;
					pBt.incrVacuum = sqlite3Get4byte(zDbHeader, 36 + 7 * 4) != 0;
#endif
					sqlite3PagerSetPagesize(pBt.pPager, ref pBt.pageSize, nReserve);
					pBt.usableSize = (u16)(pBt.pageSize - nReserve);
				}

				return SQLITE_OK;
			}
			return SQLITE_ERROR;
		}

		/* sqlite3_rekey
		** Given a database, this will reencrypt the database using a new key.
		** There are two possible modes of operation. The first is rekeying
		** an existing database that was not previously encrypted. The second
		** is to change the key on an existing database.
		**
		** The proposed logic for this function follows:
		** 1. Determine if there is already a key present
		** 2. If there is NOT already a key present, create one and attach a codec (key would be null)
		** 3. Initialize a ctx.rekey parameter of the codec
		**
		** Note: this will require modifications to the sqlite3Codec to support rekey
		**
		*/

		public static int sqlite3_rekey(sqlite3 db, string pKey, int nKey)
		{
			CODEC_TRACE("sqlite3_rekey: entered db=%d pKey=%s, nKey=%d\n", db, pKey, nKey);
			//activate_openssl();
			if (db != null && pKey != null)
			{
				Db pDb = db.aDb[0];
				CODEC_TRACE("sqlite3_rekey: database pDb=%d\n", pDb);
				if (pDb.pBt != null)
				{
					codec_ctx ctx = null;
					int rc;
					Pgno page_count = 0;
					Pgno pgno;
					PgHdr page = null;
					Pager pPager = pDb.pBt.pBt.pPager;

					sqlite3pager_get_codec(pDb.pBt.pBt.pPager, ref ctx);

					if (ctx == null)
					{
						CODEC_TRACE("sqlite3_rekey: no codec attached to db, attaching now\n");
						/* there was no codec attached to this database,so attach one now with a null password */
						sqlite3CodecAttach(db, 0, pKey, nKey);
						sqlite3pager_get_codec(pDb.pBt.pBt.pPager, ref ctx);

						/* prepare this setup as if it had already been initialized */
						Buffer.BlockCopy(Encoding.UTF8.GetBytes(SQLITE_FILE_HEADER), 0, ctx.read_ctx.iv, 0, FILE_HEADER_SZ);
						ctx.read_ctx.key_sz = ctx.read_ctx.iv_sz = ctx.read_ctx.pass_sz = 0;
					}

					//if ( ctx.read_ctx.iv_sz != ctx.write_ctx.iv_sz )
					//{
					//  string error = "";
					//  CODEC_TRACE( "sqlite3_rekey: updating page size for iv_sz change from %d to %d\n", ctx.read_ctx.iv_sz, ctx.write_ctx.iv_sz );
					//  db.nextPagesize = sqlite3BtreeGetPageSize( pDb.pBt );
					//  pDb.pBt.pBt.pageSizeFixed = false; /* required for sqlite3BtreeSetPageSize to modify pagesize setting */
					//  sqlite3BtreeSetPageSize( pDb.pBt, db.nextPagesize, MAX_IV_LENGTH, 0 );
					//  sqlite3RunVacuum( ref error, db );
					//}

					codec_set_pass_key(db, 0, pKey, nKey, 1);
					ctx.mode_rekey = 1;
					/* do stuff here to rewrite the database
					** 1. Create a transaction on the database
					** 2. Iterate through each page, reading it and then writing it.
					** 3. If that goes ok then commit and put ctx.rekey into ctx.key
					** note: don't deallocate rekey since it may be used in a subsequent iteration
					*/
					rc = sqlite3BtreeBeginTrans(pDb.pBt, 1); /* begin write transaction */
					sqlite3PagerPagecount(pPager, out page_count);
					for (pgno = 1; rc == SQLITE_OK && pgno <= page_count; pgno++)
					{ /* pgno's start at 1 see pager.c:pagerAcquire */
						if (0 == sqlite3pager_is_mj_pgno(pPager, pgno))
						{ /* skip this page (see pager.c:pagerAcquire for reasoning) */
							rc = sqlite3PagerGet(pPager, pgno, ref page);
							if (rc == SQLITE_OK)
							{ /* write page see pager_incr_changecounter for example */
								rc = sqlite3PagerWrite(page);
								//printf("sqlite3PagerWrite(%d)\n", pgno);
								if (rc == SQLITE_OK)
								{
									sqlite3PagerUnref(page);
								}
							}
						}
					}

					/* if commit was successful commit and copy the rekey data to current key, else rollback to release locks */
					if (rc == SQLITE_OK)
					{
						CODEC_TRACE("sqlite3_rekey: committing\n");
						db.nextPagesize = sqlite3BtreeGetPageSize(pDb.pBt);
						rc = sqlite3BtreeCommit(pDb.pBt);
						if (ctx != null)
							cipher_ctx_copy(ctx.read_ctx, ctx.write_ctx);
					}
					else
					{
						CODEC_TRACE("sqlite3_rekey: rollback\n");
						sqlite3BtreeRollback(pDb.pBt);
					}

					ctx.mode_rekey = 0;
				}
				return SQLITE_OK;
			}
			return SQLITE_ERROR;
		}

		private static void sqlite3CodecGetKey(sqlite3 db, int nDb, out string zKey, out int nKey)
		{
			Db pDb = db.aDb[nDb];
			CODEC_TRACE("sqlite3CodecGetKey: entered db=%d, nDb=%d\n", db, nDb);

			if (pDb.pBt != null)
			{
				codec_ctx ctx = null;
				sqlite3pager_get_codec(pDb.pBt.pBt.pPager, ref ctx);

				if (ctx != null)
				{ /* if the codec has an attached codec_context user the raw key data */
					zKey = ctx.read_ctx.pass;
					nKey = ctx.read_ctx.pass_sz;
					return;
				}
			}
			zKey = null;
			nKey = 0;
		}

		/* END CRYPTO */
#endif
		private const int SQLITE_ENCRYPT_WRITE_CTX = 6; /* Encode page */
		private const int SQLITE_ENCRYPT_READ_CTX = 7; /* Encode page */
		private const int SQLITE_DECRYPT = 3; /* Decode page */
	}
}