/* The Vault of the swiss numeral accounts.
 * Only clerks should access it!
 */

#include <string.h>
#include <sys/types.h>
#include <limits.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <db.h>

#include "swissvault.h"
#include "swissdef.h"
#include "swisscrypt.h"

//internal macros
#define VAULT_FILE_NAME "swissvault.db"
#define TTL_DEFAULT  10
#define MAX_VAULT_SIZE INT_MAX



DB* vault;
char initiated = 0;
int lastkey = 0; //last key that has been issued


inline void checkinit(){
      if(!initiated){
	    initiate_vault();
      }
}

int swiss_keycmp(DB *db, const DBT *key1, const DBT *key2){
      return (*(int*)(key1->data)-*(int*)(key2->data));
      
}


#define STEPKEY(k) ((0==(k=((k+1)%MAX_VAULT_SIZE)))?k+1:k)
int newkey(){
      int new, ret; 
      DBT key, data;

      checkinit();

      memset(&key, 0, sizeof(key));
      memset(&data, 0, sizeof(data));

      key.size = sizeof(ID_TYPE);
      key.data = &(new);

      new = lastkey;
      
      for(STEPKEY(new); new != lastkey; STEPKEY(new)){
	    ret = vault->get(vault, 0, &key, &data, 0);
	    if(ret == DB_NOTFOUND){
		  lastkey = new;
		  return new;
	    }
      }
      return -2;
}

// >0 if check is success
int check_auth(char *storedauth, int storedsize, char *testauth, int testsize){
      char correct[SWISS_PWDLEN] = {0};
      
      if((testsize != SWISS_PWDLEN) || (storedsize != SWISS_PWDLEN)){
	    return 0;
      }
            
      swiss_pwd_hash(correct, testauth);
      
      if(0 != memcmp(storedauth, correct, SWISS_PWDLEN)){
	    return 0;
      } else return 1;
	 
      return 0;
}

inline void free_account(struct account *acc){
      free(acc->data);
      free(acc->auth);
      free(acc);
}



// marshall into order: acc_id,dsize,asize,ttl,data,auth
int marshall_acc(char **buf, const struct account acc){
      int bufsize;
      char *b;
      
      bufsize = 4*sizeof(int)+acc.asize+acc.dsize;
      *buf = b = (char*)malloc(bufsize);
      if(!b){
	    perror("Couldn't get memory");
	    exit(errno);
      }
      
      b = memcpy(b, &(acc.acc_id), sizeof(int)) + sizeof(int);
      b = memcpy(b, &(acc.dsize), sizeof(int)) + sizeof(int);
      b = memcpy(b, &(acc.asize), sizeof(int)) + sizeof(int);
      b = memcpy(b, &(acc.ttl), sizeof(int)) + sizeof(int);
      b = memcpy(b, acc.data, acc.dsize) + acc.dsize;
      b = memcpy(b, acc.auth, acc.asize) + acc.asize;

      return bufsize;
}


// unmarshall from order: acc_id,dsize,asize,ttl,data,auth
struct account *unmarshall_acc(const char *const buf){
      struct account *acc;
      const char *b = buf;
      
      acc = (struct account*)malloc(sizeof(struct account));
      if(!acc){
	    perror("Couldn't get memory");
	    exit(errno);
      }
      
      memcpy(&(acc->acc_id), b, sizeof(int));
      b += sizeof(int);
      memcpy(&(acc->dsize), b, sizeof(int));
      b += sizeof(int);
      memcpy(&(acc->asize), b, sizeof(int));
      b += sizeof(int);
      memcpy(&(acc->ttl), b, sizeof(int));
      b += sizeof(int);

      acc->data = (char*)malloc(acc->dsize);
      if(!acc){
	    perror("Couldn't get memory");
	    exit(errno);
      }
      acc->auth = (char*)malloc(acc->asize);
      if(!acc){
	    perror("Couldn't get memory");
	    exit(errno);
      }

      memcpy(acc->data, b, acc->dsize);
      b += acc->dsize;

      memcpy(acc->auth, b, acc->asize);
      b += acc->asize;
      
      return acc;
}


inline int hasauth(struct account *acc){
      return acc->auth || acc->asize;
}
inline int hasdata(struct account *acc){
      return acc->data || acc->dsize;
}
inline int hasid(struct account *acc){
      return acc->acc_id > 0;
}


// store valuables in a new account
// @return number of account (negative or null on error)
int store(struct account *acc){
      struct account stored;
      DBT key, data;
      int ret;
      
      checkinit();
      
      if(!hasauth(acc) || !hasdata(acc)){
	    //request incomlete
	    return -1;
      }

      memset(&key, 0, sizeof(key));
      memset(&data, 0, sizeof(data));


      if(hasid(acc)){
	    stored.data = 0;
	    stored.dsize = 0;
	    stored.acc_id = acc->acc_id;
	    stored.auth = acc->auth;
	    stored.asize = acc->asize;
	    ret = retrieve(&stored);
	    if(ret < 0){
		  return ret;
	    } else if(ret == 0) {
		  return -3;
	    }
	    
	    key.data = &(acc->acc_id);
	    key.size = sizeof(ID_TYPE);
    
      } else {
	    // create new account    
      	    acc->acc_id = newkey();
	    key.data = &(acc->acc_id);
	    key.size = sizeof(ID_TYPE);
	    if(acc->ttl <= 0) acc->ttl = TTL_DEFAULT;
      }

      memset(&data, 0, sizeof(data));
      data.size = (size_t)marshall_acc((char**)&(data.data), *acc);

      ret = vault->put(vault, NULL, &key, &data, 0);
      if(ret != 0) {
	    vault->err(vault, ret, "vault->put");
	    return -3;
      }
      
      vault->sync(vault,0);
      return acc->acc_id;
}

// retrieve something from an account (auth,acc_id MUST be empty and have no data)
// @return size of the thing (negative or null on error)
int retrieve(struct account *const acc){
      struct account *stored;
      DBT key, data;
      int ret;

      checkinit();
      
      if(!hasauth(acc) || !hasid(acc) || hasdata(acc)){
	    return -1;
      }

      memset(&key, 0, sizeof(key));
      memset(&data, 0, sizeof(data));
      
      key.data = &(acc->acc_id);
      key.size = sizeof(ID_TYPE);
     
      ret = vault->get(vault, 0, &key, &data, 0);
      if(ret !=0){
	    fprintf(stderr, "retrieve: returned error %d\n",ret);
	    return -2;
      }

      
      stored = unmarshall_acc(data.data);

      ret = check_auth(stored->auth, stored->asize, acc->auth, acc->asize);
      if(ret <= 0){
	    // authorization failed
	    fprintf(stderr,"retrieve: authorisation failed\n");
	    return -3;
      }
     
      
      free(stored->auth);
      stored->auth=acc->auth;
      stored->asize=acc->asize;
      stored->ttl -= 1;

      memset(&data, 0, sizeof(data));      
      data.size = marshall_acc((char**)&(data.data), *stored);

      ret = vault->put(vault, NULL, &key, &data, 0);
      if(0!=ret){
	    vault->err(vault, ret, "vault->put");
	    free(stored);
	    return -2;
      }
            
      *acc = *stored;
      free(stored);
      return acc->dsize;
}

// renew the key of an account
// set ttl to ttl-1 if acc->ttl<=0
// @return success (negative or null on error)
int refresh(struct account *acc){
      int ret;
      struct account *stored;
      DBT key, data;
      checkinit();

      if(!hasauth(acc) || !hasid(acc) || hasdata(acc)){
	    return -1;
      }
      
      memset(&key, 0, sizeof(key));
      memset(&data, 0, sizeof(data));

      key.data = &(acc->acc_id);
      key.size = sizeof(ID_TYPE);
     
           
      ret = vault->get(vault, 0, &key, &data, 0);

      if(ret <= 0)
	    return -2;
      stored = unmarshall_acc(data.data);
      ret = check_auth(stored->auth, stored->asize, acc->auth, acc->asize);
      if(ret <= 0){
	    // authorization failed
	    return -3;
      }

      free(stored->auth);
      stored->auth=acc->auth;
      stored->asize=acc->asize;
      if(acc->ttl <= 0)
	    stored->ttl -= 1;
      else
	    stored->ttl = acc->ttl;
      
      //free(data.data);
      data.size = marshall_acc((char**)&(data.data), *stored);
      
      *acc = *stored;
      free(stored);
      return 1;
}


// delete an account (auth, id MUST not be empty/==0)
int delete(struct account *acc){
      int ret;
      struct account *stored;
      DBT key, data;
      checkinit();
      

      if(!hasauth(acc) || !hasid(acc) || hasdata(acc)){
	    return -1;
      }

      memset(&key, 0, sizeof(key));
      memset(&data, 0, sizeof(data));
      
      key.data = &(acc->acc_id);
      key.size = sizeof(ID_TYPE);
     
           
      ret = vault->get(vault, 0, &key, &data, 0);

      if(ret <= 0)
	    return -2;
      stored = unmarshall_acc(data.data);
      ret = check_auth(stored->auth, stored->asize, acc->auth, acc->asize);
      if(ret <= 0){
	    // authorization failed
	    return -3;
      }

      // now delete
      vault->del(vault, 0, &key, 0);
      free_account(stored);
      return 1;
}


// initiate a specific vault
// @param vault file name of the vault (if vault does not exists it will be opened
// @return <= 0 on error
int initiate_vault(char* vault_file){
      u_int32_t flags; /* database open flags */
      int ret;         /* function return value */

      // FIXME: verify vault
      ret = db_create(&vault, 0, 0);
      if (ret != 0) {
	    perror("Couldn't create the Vault.");
	    exit(errno);	    
      }
      /* Database open flags */
      flags = DB_CREATE;     /* If the database does not exist,
			      * create it.*/
      /* open the database */
      ret = vault->open(vault,         /* DB structure pointer */
		      NULL,        /* Transaction pointer */
		      VAULT_FILE_NAME,  /* On-disk file that holds the database. */
		      "SwissVault",        /* Optional logical database name */
		      DB_BTREE,    /* Database access method */
		      flags,       /* Open flags */
		      DB_FILE_PERMS);         /* File mode (using defaults) */
      if (ret != 0) {
	    perror("This vault could not be opened for you!");
	    exit(errno);
      }
      vault->set_bt_compare(vault, swiss_keycmp);
      initiated = 1;
      return initiated;
}

// always call before closing else valuables might be lost
int close_vault(){
      if(vault){
	    initiated = 0;
	    return vault->close(vault, 0);
      } else {
	    perror("nothing to close here. Stupid!\n");
	    return 0;
      }
}


