/*
 * Copyright (c) 2019 Clementine Computing LLC.
 *
 * This file is part of PopuFare.
 * 
 * PopuFare is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * PopuFare is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with PopuFare.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include <sys/user.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#include "../common/common_defs.h"
#include "passdb.h"

static void free_rider_node_list(rider_node *head)
{
    rider_node *p = head;
    rider_node *q;
    
    while(p)
    {
        q = p;
        p = p->next;
        
        free(q);
    }
}

#define FIND_IDX_IN_BUCKET(b, idx, p, q)\
    {					\
        p = b;				\
        q = NULL;			\
                                        \
        while(p)			\
        {				\
            if(p->idx == idx)		\
            {				\
                break;			\
            }				\
                                        \
            q = p;			\
            p = p->next;		\
        }				\
    }					\
//---------------------------

#define ADD_TO_BUCKET(b, idx, p, q)	\
    {					\
        p = (rider_node *) malloc( sizeof(rider_node) );\
                                        \
        if(p == NULL) return FAIL_MEM;	\
                                        \
        p->next = NULL;			\
        p->idx = idx;			\
                                        \
        if(q)				\
        {				\
            q->next = p;		\
        }				\
        else				\
        {				\
            b = p;			\
        }				\
    }					\
//---------------------------

#define DEL_FROM_BUCKET(b, p, q)	\
    {					\
        if(q)				\
        {				\
            q->next = p->next;		\
        }				\
        else				\
        {				\
            b = p->next;		\
        }				\
                                        \
        free(p);			\
    }    				\
//---------------------------

int find_id_in_hash(passdb_context *ctx, logical_card_id_t id)
{
    rider_node *p;
    
    if(id <= 0)
        return FAIL_PARAM;
    
    p = ctx->logical_card_id_hash[id % STORED_PASS_HASH];
    
    while(p)
    {
        if(ctx->riders[p->idx].id == id)
            return p->idx;
        
        p = p->next;  
    }
    
    return WARN_NOTFOUND;
}
        
int add_to_id_hash(passdb_context *ctx, int idx)
{
    rider_node *p, *q;
    unsigned int bucket;
    
    if(idx < 0) return FAIL_PARAM;
    if(ctx->riders[idx].id == ID_INVALID) return FAIL_PARAM;
    
    bucket = ctx->riders[idx].id % STORED_PASS_HASH;
    
    FIND_IDX_IN_BUCKET( ctx->logical_card_id_hash[bucket], idx, p, q )
    
    if(p)
    {
        return FAIL_DUPKEY;	//already exists!
    }
    else
    {
        ADD_TO_BUCKET( ctx->logical_card_id_hash[bucket], idx, p, q );
        return 0;
    }
}

int delete_from_id_hash(passdb_context *ctx, int idx)
{
    rider_node *p, *q;
    unsigned int bucket;
    
    if(idx < 0) return FAIL_PARAM;
    
    bucket = ctx->riders[idx].id % STORED_PASS_HASH;
    
    FIND_IDX_IN_BUCKET( ctx->logical_card_id_hash[bucket], idx, p, q)
    
    if(p)
    {
        DEL_FROM_BUCKET( ctx->logical_card_id_hash[bucket], p, q )
        return 0;
    }
    else
    {
        return WARN_NOTFOUND;
    }
}
//##
int find_mag_in_hash(passdb_context *ctx, char *mag)
{
    rider_node *p;

    if(mag[0] == '\0')	return FAIL_PARAM;
    
    p = ctx->rider_mag_hash[stringhash(mag) % STORED_PASS_HASH];
    
    while(p)
    {
        if(!strncmp(ctx->riders[p->idx].magstripe_value, mag, CREDENTIAL_LEN))
            return p->idx;
        
        p = p->next;  
    }
    
    return WARN_NOTFOUND;
}
        
int add_to_mag_hash(passdb_context *ctx, int idx)
{
    rider_node *p, *q;
    unsigned int bucket;
    
    if(idx < 0) return 0;	//if we have a non index or a non-value, return silently
    if(ctx->riders[idx].magstripe_value[0] == '\0') return 0;

    bucket = stringhash(ctx->riders[idx].magstripe_value) % STORED_PASS_HASH;
    
    FIND_IDX_IN_BUCKET( ctx->rider_mag_hash[bucket], idx, p, q )
    
    #ifndef ALLOW_CREDENTIAL_COLLISIONS
    							//On allowing hash collisions among credentials see comment tagged **STUPID** later in this file.
    if(p)
    {
        return FAIL_DUPKEY;	//already exists!
    }
    else	
    #endif
    {
        ADD_TO_BUCKET( ctx->rider_mag_hash[bucket], idx, p, q );
        return 0;
    }
}

int delete_from_mag_hash(passdb_context *ctx,  int idx)
{
    rider_node *p, *q;
    unsigned int bucket;
    
    if(idx < 0) return FAIL_PARAM;
    
    bucket = stringhash(ctx->riders[idx].magstripe_value) % STORED_PASS_HASH;
    
    FIND_IDX_IN_BUCKET( ctx->rider_mag_hash[bucket], idx, p, q)
    
    if(p)
    {
        DEL_FROM_BUCKET( ctx->rider_mag_hash[bucket], p, q )
        return 0;
    }
    else
    {
        return WARN_NOTFOUND;
    }
}
//##
int find_rf_in_hash(passdb_context *ctx, char *rfid)
{
    rider_node *p;
    
    if(rfid[0] == '\0')	return WARN_NOTFOUND;

    p = ctx->rider_rf_hash[stringhash(rfid)% STORED_PASS_HASH];

    while(p)
    {
        if(!strncmp(ctx->riders[p->idx].rfid_value, rfid, CREDENTIAL_LEN))
            return p->idx;
        
        p = p->next;  
    }
    
    return WARN_NOTFOUND;
}
        
int add_to_rf_hash(passdb_context *ctx, int idx)
{
    rider_node *p, *q;
    unsigned int bucket;
    
    if(idx < 0) return 0;	//if we have a non index or a non-value, return silently
    if(ctx->riders[idx].rfid_value[0] == '\0') return 0;
    
    bucket = stringhash(ctx->riders[idx].rfid_value) % STORED_PASS_HASH;
    
    FIND_IDX_IN_BUCKET( ctx->rider_rf_hash[bucket], idx, p, q )

    #ifndef ALLOW_CREDENTIAL_COLLISIONS
    							//On allowing hash collisions among credentials see comment tagged **STUPID** later in this file.
    if(p)
    {
        return FAIL_DUPKEY;	//already exists!
    }
    else
    #endif
    {
        ADD_TO_BUCKET( ctx->rider_rf_hash[bucket], idx, p, q );
        return 0;
    }
}

int delete_from_rf_hash(passdb_context *ctx, int idx)
{
    rider_node *p, *q;
    unsigned int bucket; 
    
    if(idx < 0) return FAIL_PARAM;
    
    bucket = stringhash(ctx->riders[idx].rfid_value) % STORED_PASS_HASH;
    
    FIND_IDX_IN_BUCKET( ctx->rider_rf_hash[bucket], idx, p, q)
    
    if(p)
    {
        DEL_FROM_BUCKET( ctx->rider_rf_hash[bucket], p, q )
        return 0;
    }
    else
    {
        return WARN_NOTFOUND;
    }
}
//##

int build_hashes(passdb_context *ctx)
{
    rider_node *p = ctx->activelist;
    int retval;
    
    while(p)
    {
        retval = add_to_id_hash(ctx, p->idx);
        if( !DB_OKAY(retval) )
        {
            fprintf(stderr, "Error (%d) indexing rider ID %llu at index %d!\n", retval, ctx->riders[p->idx].id, p->idx);
            return retval;
        }
        
        retval = add_to_mag_hash(ctx, p->idx);
        if( !DB_OKAY(retval) )
        {
            fprintf(stderr, "Error (%d) indexing magstripe %s at index %d!\n", retval, ctx->riders[p->idx].magstripe_value, p->idx);
            return -1;
        }
        
        retval = add_to_rf_hash(ctx, p->idx);
        if( !DB_OKAY(retval) )
        {
            fprintf(stderr, "Error (%d) indexing RFID %s at index %d!\n", retval, ctx->riders[p->idx].rfid_value, p->idx);
            return -1;
        }
        
        p = p->next;
    }

    return 0;
}

int format_new_passdb()
{
    char blank[MEMORY_PAGE_SIZE] = {0};
      
    int i,n;
    int fd;
    
    fd = creat(PASSES_FILE, S_IRUSR | S_IWUSR);
    
    if( fd < 0 )
    {
        fprintf(stderr, "Cannot create pass file!\n");
        return FAIL_DATABASE;
    }
    
    n = PASS_MAP_SIZE / MEMORY_PAGE_SIZE;
    
    for(i = 0; i < n; i++)
    {
        if( write(fd, &blank, sizeof(blank)) != sizeof(blank) )
        {
            fprintf(stderr, "Cannot write blank data to passes file!\n");
            close(fd);
            return FAIL_DATABASE;
        }
    }
    
    close(fd);
    return 0;
}

int detach_from_passdb(passdb_context *ctx)
{
    int i;
  
    if(!ctx)
      return FAIL_PARAM;
    
    free_rider_node_list(ctx->freelist);
    free_rider_node_list(ctx->activelist);
    
    for(i=0; i < STORED_PASS_HASH; i++)
    {
        free_rider_node_list(ctx->logical_card_id_hash[i]);  
        free_rider_node_list(ctx->rider_mag_hash[i]);  
        free_rider_node_list(ctx->rider_rf_hash[i]);  
    }
    
    if(ctx->riders != NULL)
    {
        munmap(ctx->riders, PASS_MAP_SIZE);
    }
    
    if(ctx->mmap_broken)
    {
        close(ctx->passes_fd);
    }
    
    memset(ctx, 0, sizeof(passdb_context));

    return 0;
}

int attach_to_passdb(passdb_context *ctx)
{
    int n;
    int retval;
    struct stat st;
    int fd;
    rider_record *foo;
    int mmap_broken;
    
    seq_t maxseq = 0;

    rider_node *freehead, *acthead, *q;
    int numfree, numact;
    
    int i;
    
    //--------
    
    if(!ctx)		//fail if we get passed a null pointer
        return FAIL_PARAM;
    
    //We also want to fail if we get passed a pointer to an active/in-use context...
    if(ctx->riders || ctx->activelist || ctx->freelist)
    {
        return FAIL_PARAM;
    }
    
    mmap_broken = 0;
    
    //Go and stat the pass database file
    retval = stat(PASSES_FILE, &st);
    
    if(retval)
    {
        fprintf(stderr, "Cannot find passes file!\n");
        return FAIL_DATABASE;
    }
    
    //Make sure it is the right size...
    n = (st.st_size / sizeof(rider_record));
    
    if(n != NUM_STORED_PASSES)
    {
        fprintf(stderr, "Passes file contains %d records, expecting %d!\n", n, NUM_STORED_PASSES);
        return FAIL_DATABASE;
    }    

    //open the file 
    fd = open(PASSES_FILE, O_RDWR | O_SYNC);
    
    if(fd < 0)
    {
        fprintf(stderr, "Cannot open passes file!\n");
        return FAIL_DATABASE;
    }

    //mmap() the file into a pointer in our address space
    foo = (rider_record *) mmap(NULL, PASS_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    
    if( (foo == NULL) || (foo == MAP_FAILED) )	//if the MAP_SHARED option fails...
    {
        //try again with MAP_PRIVATE and see if it works...
        foo = (rider_record *) mmap(NULL, PASS_MAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);    
    
        if( (foo == NULL) || (foo == MAP_FAILED) )
        {
            close(fd);
            fprintf(stderr, "Cannot mmap passes file! Try checking sysctl settings kernel.shmall and kernel.shmmax (return == %p errno == %d)\n",foo,errno);
            return FAIL_MEM;
        }
        else
        {
            //set our mmap broken flag
            printf("mmap seems to be broken...  operating in braindead file IO mode...\n");
            mmap_broken = 1;
        }
    }
    else
    {
        //close the file (we no longer need it open once it is mmap()'d)
        close(fd);
    }
    //------
    
    freehead = acthead = q = NULL;
    numfree = numact = 0;
    maxseq = 0;
    
    //For all records in our flat file
    for(i=0; i < n; i++)
    {
        //check the sequence number and update our "latest" tally if it is newer.
        if(foo[i].seq > maxseq)
        {
            maxseq = foo[i].seq;
        }
        
        //if the record is not in use        
        if(foo[i].id == ID_INVALID)
        {
            //add it to the freelist
            q = (rider_node *) malloc( sizeof(rider_node) );
            
            if(!q)
            {
                free_rider_node_list(freehead);
                free_rider_node_list(acthead);
                fprintf(stderr, "Malloc returned NULL loading riders!\n");
                munmap(foo, n * sizeof(rider_record));
                return FAIL_MEM;
            }
            else
            {
                numfree++;
                
                q->next = freehead;
                q->idx = i;
                
                freehead = q;
            }
        }
        else
        {
            //add it to the active list
            q = (rider_node *) malloc( sizeof(rider_node) );
            
            if(!q)
            {
                free_rider_node_list(freehead);
                free_rider_node_list(acthead);
                fprintf(stderr, "Malloc returned NULL loading riders!\n");
                munmap(foo, n * sizeof(rider_record));
                return FAIL_MEM;
            }
            else
            {
                numact++;
                
                q->next = acthead;
                q->idx = i;
                
                acthead = q;
            }
        }  
    }    

    ctx->riders = foo;
    ctx->freelist = freehead;
    ctx->activelist = acthead;
    ctx->seq = maxseq;

    retval = build_hashes(ctx);
    
    if( DB_FAIL(retval) )
    {
        fprintf(stderr, "Building hashes failed.\n");
        detach_from_passdb(ctx);
        return retval;
    }

    if(mmap_broken)
    {
        ctx->mmap_broken = 1;
        ctx->passes_fd = fd;
    }
    else
    {
        ctx->mmap_broken = 0;
        ctx->passes_fd = 0;
    }

    printf("Loaded and indexed %d records (%d used, %d free); Newest seq = %llu\n", n, numact, numfree, maxseq);
    
    return n;
} 




static int copy_rider(rider_record *dst, rider_record *src)
{
    if(! (src && dst) )
        return FAIL_PARAM;
    
    memcpy(dst, src, sizeof(rider_record));
    
    dst->rfid_value[CREDENTIAL_LEN - 1] = '\0';
    dst->magstripe_value[CREDENTIAL_LEN - 1] = '\0';
    dst->rule_name[RULENAME_LEN - 1] = '\0';
    dst->rule_param[PARAM_LEN - 1] = '\0';
    
    return 0;
}

static int alloc_rider(passdb_context *ctx)
{
    rider_node *p;
    
    p = ctx->freelist;
    
    if(p)
    {
        ctx->freelist = ctx->freelist->next;
        
        p->next = ctx->activelist;
        ctx->activelist = p;
    
        return p->idx;
    }
    else
    {
        return FAIL_FULL;
    }
}

static int free_rider(passdb_context *ctx, int idx)
{
    rider_node *p, *q;
    
    q = NULL;
    p = ctx->activelist;

    while(p)
    {
        if( p->idx == idx )
            break;
    
        q = p;
        p = p->next;
    }
    
    if(p)
    {
        if(q)
        {
            q->next = p->next;  
        }
        else
        {
            ctx->activelist = p->next;
        }
        
        p->next = ctx->freelist;
        ctx->freelist = p;
        
        memset(&ctx->riders[idx], 0, sizeof(rider_record));
        
        ctx->riders[idx].id = ID_INVALID;
    
        return 0;
    }
    else
    {
        return WARN_NOTFOUND;
    }
}

static void sync_rider_change(passdb_context *ctx, int idx)
{
    int offset;
    int retval;
    
    if(idx < 0)
        return;
        
    if(idx >= NUM_STORED_PASSES)
        return;

    if(!ctx)
        return;

    if(ctx->mmap_broken)
    {
        offset = (idx * sizeof(rider_record)) / MEMORY_PAGE_SIZE;	//calculate the beginning page number
        offset *= MEMORY_PAGE_SIZE;					//multiply by page size
        
        retval = lseek(ctx->passes_fd, offset, SEEK_SET);
        
        if(retval != offset)
        {
            fprintf(stderr, "lseek() failed in sync_rider_change(). errno = %d\n", errno);
            return;  
        }
        
        retval = write(ctx->passes_fd, ((void *)ctx->riders) + offset, MEMORY_PAGE_SIZE);
    
        if(retval != MEMORY_PAGE_SIZE)
        {
            fprintf(stderr, "write() failed in sync_rider_change(). errno = %d\n", errno);
            return;
        }
    }
    else
    {
        retval = msync(ctx->riders, PASS_MAP_SIZE, MS_SYNC | MS_INVALIDATE);
        
        if(retval < 0)
        {
            fprintf(stderr, "msync() failed in sync_rider_change(). errno = %d\n", errno);
            return;
        }
    }
}

void sync_all_riders(passdb_context *ctx)
{
    int retval;

    if(!ctx)
        return;
        
    if(ctx->mmap_broken)
    {
        retval = lseek(ctx->passes_fd, 0, SEEK_SET);

        if(retval != 0)
        {
            fprintf(stderr, "lseek() failed in sync_all_riders(). errno = %d\n", errno);
            return;  
        }

        retval = write(ctx->passes_fd, ctx->riders, PASS_MAP_SIZE);

        if(retval != PASS_MAP_SIZE)
        {
            fprintf(stderr, "write() failed in sync_all_riders(). errno = %d\n", errno);
            return;
        }
    }
    else
    {
        retval = msync(ctx->riders, PASS_MAP_SIZE, MS_SYNC | MS_INVALIDATE);

        if(retval < 0)
        {
            fprintf(stderr, "msync() failed in sync_all_riders(). errno = %d\n", errno);
            return;
        }
    }
}

int delete_rider(passdb_context *ctx, rider_record *rec, int sync)
{
    int id_idx;

    if(!ctx)
    {
        return FAIL_PARAM;
    }

    if(!ctx->riders)
    {
        return FAIL_PARAM;
    }
    
    //If this record is older than out current database, ignore it as a duplicate.
    if( ctx->seq >= rec->seq )
    {
        return 0;
    }

    //find the record to be deleted in our ID hash
    id_idx = find_id_in_hash(ctx, rec->id);

    //If we didn't find it, it must have already been deleted...
    if(id_idx < 0)	
    {
        return 0;
    }

    //delete it from all hashes
    delete_from_id_hash(ctx, id_idx);        
    delete_from_mag_hash(ctx, id_idx);        
    delete_from_rf_hash(ctx, id_idx);        
    
    //free the record (this zeros out the entire block)
    free_rider(ctx, id_idx);

    //populate the seq number of this delete    
    ctx->riders[id_idx].seq = rec->seq;
    //and sync our SHM

    if(sync)
    {
        sync_rider_change(ctx, id_idx);
    }
    
    return 1;
}

int update_rider(passdb_context *ctx, rider_record *rec, int sync)
{
    int id_idx;
    int mag_idx;
    int rf_idx;

    int update_credentials = 0;
    int update_id_hash = 0;

    int retval;

    if(!ctx)
    {
        return FAIL_MEM;
    }

    if(!ctx->riders)
    {
        return FAIL_MEM;
    }
    
    //If this record is older than out current database, ignore it as a duplicate.
    if( ctx->seq >= rec->seq )
    {
        return 0;
    }

    id_idx = find_id_in_hash(ctx, rec->id);

    mag_idx = find_mag_in_hash(ctx, rec->magstripe_value);
    rf_idx = find_rf_in_hash(ctx, rec->rfid_value);

//	We want to allow a short period of magstrip or RFID collision as the lesser of two evils vs.		 	**STUPID**
//  possibly losing a record due to a degenerately stupid administrator doing the following:
//
//	1) Create user 1 with magstripe '1:foo'
//	2) Delete user 1
//	3) Create user 2 with magstripe '1:foo'
//	4) Create user 1 with magstripe '1:bar'	<----  THIS IS NOT ALLOWED (Creating user 1 is dissalowed after a delete of user 1.  This card should be created as user 3).
//
//	The issue here is that if the bus asks what's happensed since sequence number 1, it will get rows 
//  3 and 4.  
//
//	In reality, we'd hope that each bus would complete a sync at least once on a shorter interval
//  than the frequency at which credentials are recycled, but you never know...  And if somebody manually
//  fucks things up such that a user id (card id) is deleted, and then created again (this is a big no-no), we
//  can recover by allowing a hash collision to exist in the meantime.
#ifndef ALLOW_CREDENTIAL_COLLISIONS
    if( (mag_idx >= 0) && (mag_idx != id_idx) )
    {
        fprintf(stderr, "Refusing to accept change that would introduce duplicate magstripe \"%s\" for records %llu and %llu.\n", rec->magstripe_value, ctx->riders[mag_idx].id, rec->id);
        return FAIL_DUPKEY;
    }

    if( (rf_idx >= 0) && (rf_idx != id_idx) )
    {
        fprintf(stderr, "Refusing to accept change that would introduce duplicate RFID \"%s\" for records %llu and %llu.\n", rec->rfid_value, ctx->riders[rf_idx].id, rec->id);
        return FAIL_DUPKEY;
    }
#endif

    if(id_idx >= 0)	//if rec->id already exists, we're updating an existing record...
    {
        //if EITHER the RFID or MAGSTRIPE values have changed...
        if( strncmp(ctx->riders[id_idx].magstripe_value, rec->magstripe_value, CREDENTIAL_LEN) || strncmp(ctx->riders[id_idx].rfid_value, rec->rfid_value, CREDENTIAL_LEN) )
        {
            update_credentials = 1;
        }
    }
    else		//otherwise, we're creating a new record...
    {
        id_idx = alloc_rider(ctx);
        
        if(DB_FAIL(id_idx))
        {
            fprintf(stderr, "Error (%d) trying to allocate rider\n", id_idx);
            return id_idx;
        }
        
        update_credentials = update_id_hash = 1;
    }    
    
    if(update_credentials)
    {
        delete_from_mag_hash(ctx, id_idx);
        delete_from_rf_hash(ctx, id_idx);
    }

    if(update_id_hash)
    {
        delete_from_id_hash(ctx, id_idx);
    }
    
    copy_rider( &ctx->riders[id_idx], rec );

    if(sync)
    {
        sync_rider_change(ctx, id_idx);
    }
    
    if(update_id_hash)
    {    
        retval = add_to_id_hash(ctx, id_idx);
        
        if( !DB_OKAY(retval) )
        {
            fprintf(stderr, "Error (%d) updating id hash for record seq = %llu\n", retval, (unsigned long long)rec->seq);
        }
    }
    
    if(update_credentials)
    {
        retval = add_to_mag_hash(ctx, id_idx);
        
        if( !DB_OKAY(retval) )
        {
            fprintf(stderr, "Error (%d) updating magstripe hash for record seq = %llu\n", retval, (unsigned long long)rec->seq);
        }
        
        retval = add_to_rf_hash(ctx, id_idx);

        if( !DB_OKAY(retval) )
        {
            fprintf(stderr, "Error (%d) updating rf hash for record seq = %llu\n", retval, (unsigned long long)rec->seq);
        }
    }
    
    ctx->seq = rec->seq;
    
    return 1;
}

void dump_hashes(passdb_context *ctx)
{
    int i;
    rider_node *p;

    if(!ctx)
    {
        printf("NULL Context!\n");
        return;
    }

    if(!ctx->riders)
    {
        printf("NULL Riders, no database mmap()'d!\n");
        return;
    }

    printf("ID HASH:\n");
    for(i=0; i < STORED_PASS_HASH; i++)
    {
        if(!ctx->logical_card_id_hash[i]) continue;
        
        printf("\t%d:", i);
        
        p = ctx->logical_card_id_hash[i];
        while(p)
        {
            printf("\t[%d] %llu", p->idx, ctx->riders[p->idx].id);
            p = p -> next;
        }
        printf("\n");  
    }

    printf("RFID HASH:\n");
    for(i=0; i < STORED_PASS_HASH; i++)
    {
        if(!ctx->rider_rf_hash[i]) continue;
        
        printf("\t%d:", i);
        
        p = ctx->rider_rf_hash[i];
        while(p)
        {
            printf("\t[%d] \"%s\"", p->idx, ctx->riders[p->idx].rfid_value);
            p = p -> next;
        }
        printf("\n");  
    }

    printf("MAGSTRIPE HASH:\n");
    for(i=0; i < STORED_PASS_HASH; i++)
    {
        if(!ctx->rider_mag_hash[i]) continue;
        
        printf("\t%d:", i);
        
        p = ctx->rider_mag_hash[i];
        while(p)
        {
            printf("\t[%d] \"%s\"", p->idx, ctx->riders[p->idx].magstripe_value);
            p = p -> next;
        }
        printf("\n");  
    }

};

/*

int main(int argc, char **argv)
{
    passdb_context ctx = {0};
    int retval;

    rider_record foo = {12362, 6, "SILLYRF", "FOOBAR", "STUPIDRULE", "STUPIDPARAM"};
    rider_record bar = {12354, 7, "WAWAWA", "SILLYMAG", "STUPIDRULE", "STUPIDPARAM"};
    rider_record baz = {12361, 8, "", "BARFOLA_MAG", "STUPIDRULE2", "STUPIDPARAM2"};
    
    rider_record bat = {12363, 7, "", "", "", ""};
    
    retval = attach_to_passdb(&ctx);

    dump_hashes(&ctx);

    retval = update_rider(&ctx, &foo);
    retval = update_rider(&ctx, &bar);
    retval = update_rider(&ctx, &baz);

    retval = delete_rider(&ctx, &bat);

    dump_hashes(&ctx);

    return 0;
}

*/

