/*****************************************************************************
 *
 * ESP engine software implementation.
 * Copyright (C) 2001  Bart Trojanowski <bart@jukie.net>.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 * 
 * This program 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 General Public License
 * for more details.
 *
 *****************************************************************************/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include "engine.h"
#include "engine-debug.h"
#include "proto-esp.h"

/* processing functions used later */
static int proto_esp_sw_con_encapsulate( struct generic_job * gj );
static void proto_esp_sw_con_encapsulate_callback( struct generic_job * gj );
static int proto_esp_sw_con_decapsulate( struct generic_job * gj );
static void proto_esp_sw_con_decapsulate_callback( struct generic_job * gj );

/* -------------------------------------------------------------------------
 * STRUCTURE SUBCLASSES
 * ------------------------------------------------------------------------- */

struct esp_engine {
  struct generic_engine ge;
  
  
  /* engine statistics can go here... 
   * ex: # of contexts in use, time in system, etc */
};

struct esp_context {
  struct generic_context gc;

  struct generic_context *cipher; /* what to encrypt with */
  struct generic_context *digest; /* what to authenticate with */
  
  
  
  /* context statistics can go here...
   * ex: # of jobs in use, # of jobs total, time since creation, etc */
};

/* each ESP job should only need to detach twice (at maximum) */
#define ESP_MAX_ASYNC_TASKS 2

struct esp_job {
  struct generic_job gj;

  /* each ESP job can have up to ESP_MAX_ASYNC_TASKS async tasks for 
  * processing internal stages... */
  int task_idx;			/* 0 for first phase, 1 for second... */
  int task_cnt;			/* total count of tasks */
  struct generic_job *task_jobs[ESP_MAX_ASYNC_TASKS];
  enum { CIPHER, DIGEST } task_type[ESP_MAX_ASYNC_TASKS];
  
  /* other ESP specific variables */
};


/* -------------------------------------------------------------------------
 * ENGINE METHODS
 * ------------------------------------------------------------------------- */

static int
proto_esp_sw_eng_context_size( struct generic_engine * ge, void* vdefn )
{
  int ret;
  //struct proto_esp_context_definition *defn = (void*)vdefn;
  FIN();
  ret = sizeof( struct esp_context );
  FOUT();
  return ret;
}


/* -------------------------------------------------------------------------
 * CONTEXT METHODS
 * ------------------------------------------------------------------------- */

static int
proto_esp_sw_con_init( struct generic_context * gc, int gfp_flag )
{
  int ret;
  struct esp_context *esp_con = (void*)gc;
  struct proto_esp_context_definition *defn;
  struct generic_engine *cipher_eng = NULL, *digest_eng = NULL;
  struct generic_context *cipher_con = NULL, *digest_con = NULL;
  
  FIN();
  
  ret = -1;			/* XXX need a better return code */
  if( !esp_con->gc.defn )
    goto done;
  
  defn = (void*)esp_con->gc.defn;
  
  /* test cipher/digest names */
  if( strncmp(defn->cipher_name, "cipher-", 7) )
    goto done;
  
  if( strncmp(defn->digest_name, "digest-", 7) )
    goto done;
  
  /* set the appropriate processing function */
  ret = -ENOENT;
  switch( defn->operation ) {
   case ENCAPSULATE:
    gc->ops.process = proto_esp_sw_con_encapsulate;
    break;
   case DECAPSULATE:
    gc->ops.process = proto_esp_sw_con_decapsulate;
    break;
   default:
    goto done;
  }

  /* generate and initialize the cipher context */
  if( !defn->cipher_name )
    goto null_cipher;
  
  ret = -ENOENT;
  cipher_eng = get_engine_by_name( defn->cipher_name );
  if( !cipher_eng )
    goto done;
  
  ret = create_context( cipher_eng, defn->cipher_defn, 
			gfp_flag, &cipher_con );
  if( ret )
    goto bail_has_cipher_eng;
  
  put_engine( cipher_eng );
 null_cipher:
  
  /* generate and initialize the cipher context */
  if( !defn->digest_name )
    goto null_digest;
  
  ret = -ENOENT;
  digest_eng = get_engine_by_name(defn->digest_name);
  if( !cipher_eng )
    goto bail;
  
  ret = create_context( digest_eng, defn->digest_defn, 
			gfp_flag, &digest_con );
  if( ret )
    goto bail_has_digest_eng;
  
  put_engine( digest_eng );
 null_digest:
    
  /* success */
  esp_con->cipher = cipher_con;
  esp_con->digest = cipher_con;
  ret = 0;
  goto done;
  
  /* error conditions... */
 bail_has_digest_eng:
  if( digest_eng )
    put_engine( digest_eng );
  goto bail;
  
 bail_has_cipher_eng:
  if( cipher_eng )
    put_engine( cipher_eng );
  
 bail:
  if( cipher_con )
    release_context( &cipher_con ); 
  if( digest_con )
    release_context( &digest_con );
  
 done:
  FEXIT(ret==0);
  return ret;
}

static int
proto_esp_sw_con_cleanup( struct generic_context * gc )
{
  struct esp_context *esp_con = (void*)gc;
  FIN();
  if( esp_con->cipher )
    release_context( &esp_con->cipher );
  if( esp_con->digest )
    release_context( &esp_con->digest );
  FOUT();
  return 0;
}

static int
proto_esp_sw_con_job_size( struct generic_context * gc )
{
  int ret, clen, dlen;
  struct esp_context *esp_con = (void*)gc;
  FIN();
  clen = esp_con->cipher ? esp_con->cipher->ops.job_size( esp_con->cipher ) : 0;
  dlen = esp_con->digest ? esp_con->digest->ops.job_size( esp_con->digest ) : 0;
  ret =  sizeof( struct esp_job ) + clen + dlen;
  FOUT();
  return ret;
}

/* outbound processing */
static int
proto_esp_sw_con_encapsulate( struct generic_job * gj )
{
  int ret;
  struct esp_job *esp_job = (void*)gj;  
  struct esp_context *esp_con = (void*)gj->con;
  void *vdata;
  struct generic_job *tmp_job = NULL;
  
  FIN();
  
  ret = -EBUSY;
  if( get_context( &esp_con->gc ) ) 
    goto done;
 
  /* initialize task componets */
  esp_job->task_idx = 0;
  esp_job->task_cnt = 0;
 
  /* find out where the extra data can be stored in the generic job
   * it's right past the generic job structure */
  vdata = (void*)esp_job + sizeof( struct esp_job );

  /* INITIALIZE the cipher job if one is needed */
  if( esp_con->cipher ) {
    tmp_job = vdata;
    vdata += esp_con->cipher->ops.job_size( esp_con->cipher );
    
    /* XXX need to do actual initialization based on passed in packet */
    INIT_GENERIC_JOB( tmp_job, esp_con->cipher );
    tmp_job->data.in_ptr = NULL;
    tmp_job->data.in_len = 0;
    tmp_job->data.out_ptr = NULL;
    tmp_job->data.out_len = 0;
    tmp_job->callback = proto_esp_sw_con_encapsulate_callback;
    tmp_job->opaque = esp_job;
    
    esp_job->task_type[esp_job->task_cnt ] = CIPHER;
    esp_job->task_jobs[esp_job->task_cnt++] = tmp_job;
  }

  /* INITIALIZE the digest job if one is needed */
  if( esp_con->digest ) {
    tmp_job = vdata;
    /* vdata += esp_con->digest->ops.job_size( esp_con->digest ); */
    
    /* XXX need to do actual initialization based on passed in packet */
    INIT_GENERIC_JOB( tmp_job, esp_con->digest );
    tmp_job->data.in_ptr = NULL;
    tmp_job->data.in_len = 0;
    tmp_job->data.out_ptr = NULL;
    tmp_job->data.out_len = 0;
    tmp_job->callback = proto_esp_sw_con_encapsulate_callback;
    tmp_job->opaque = esp_job;

    esp_job->task_type[esp_job->task_cnt ] = DIGEST;
    esp_job->task_jobs[esp_job->task_cnt++] = tmp_job;
  }

  /* TODO: all actuall ESP processing */
  
  ret = 0;
  if( esp_job->task_jobs[0] )
    ret = execute_job( esp_job->task_jobs[0] );
  else
    proto_esp_sw_con_encapsulate_callback( gj );
  
  goto done;
  
  //bail_has_con:
  put_context( &esp_con->gc );

 done:
  FEXIT(ret==0);
  return ret;
}

/* inbound processing */
static int
proto_esp_sw_con_decapsulate( struct generic_job * gj )
{
  int ret;
  struct esp_job *esp_job = (void*)gj;  
  struct esp_context *esp_con = (void*)gj->con;
  void *vdata;
  struct generic_job *tmp_job = NULL;
  
  FIN();
  
  ret = -EBUSY;
  if( get_context( &esp_con->gc ) ) 
    goto done;
 
  /* initialize task componets */
  esp_job->task_idx = 0;
  esp_job->task_cnt = 0;
 
  /* find out where the extra data can be stored in the generic job
   * it's right past the generic job structure */
  vdata = (void*)esp_job + sizeof( struct esp_job );

  /* INITIALIZE the digest job if one is needed */
  if( esp_con->digest ) {
    tmp_job = vdata;
    vdata += esp_con->digest->ops.job_size( esp_con->digest );
    
    /* XXX need to do actual initialization based on passed in packet */
    INIT_GENERIC_JOB( tmp_job, esp_con->digest );
    tmp_job->data.in_ptr = NULL;
    tmp_job->data.in_len = 0;
    tmp_job->data.out_ptr = NULL;
    tmp_job->data.out_len = 0;
    tmp_job->callback = proto_esp_sw_con_decapsulate_callback;
    tmp_job->opaque = esp_job;
    
    esp_job->task_type[esp_job->task_cnt] = DIGEST;
    esp_job->task_jobs[esp_job->task_cnt++] = tmp_job;
  }

  /* INITIALIZE the cipher job if one is needed */
  if( esp_con->cipher ) {
    tmp_job = vdata;
    /* vdata += esp_con->cipher->ops.job_size( esp_con->cipher ); */
    
    /* XXX need to do actual initialization based on passed in packet */
    INIT_GENERIC_JOB( tmp_job, esp_con->cipher );
    tmp_job->data.in_ptr = NULL;
    tmp_job->data.in_len = 0;
    tmp_job->data.out_ptr = NULL;
    tmp_job->data.out_len = 0;
    tmp_job->callback = proto_esp_sw_con_decapsulate_callback;
    tmp_job->opaque = esp_job;

    esp_job->task_type[esp_job->task_cnt] = CIPHER;
    esp_job->task_jobs[esp_job->task_cnt++] = tmp_job;
  }

  /* TODO: all actuall ESP processing */
  
  ret = 0;
  if( esp_job->task_jobs[0] )
    ret = execute_job( esp_job->task_jobs[0] );
  else
    proto_esp_sw_con_decapsulate_callback( gj );
  
  goto done;
  
  //bail_has_con:
  put_context( &esp_con->gc );

 done:
  FEXIT(ret==0);
  return ret;
}


/* -------------------------------------------------------------------------
 * JOB METHODS
 * ------------------------------------------------------------------------- */

static void
proto_esp_sw_job_cancel_async ( struct generic_job * gj)
{
  /* for now it's not possible... but at least we can set the del_flag */
  FIN();
  atomic_set( &gj->del_flag, 1 );
  FOUT();
  return;
}


/* -------------------------------------------------------------------------
 * FORWARD (OUTBOUND) CALLBACKS
 * ------------------------------------------------------------------------- */

static void
proto_esp_sw_con_encapsulate_callback( struct generic_job * task_job )
{
  int ret;
  struct esp_job *esp_job = (void*)task_job->opaque;
  /* struct esp_context *esp_con = (void*)esp_job->gj.con; */
  
  FIN();
  
  /* TODO: check the result of task that just finished*/
  ret = task_job->result;
  if( ret<0 )
    goto errors;
  
  /* TODO: continue the ESP processing */
 
  switch ( esp_job->task_type[ esp_job->task_idx ] ) {
   case CIPHER:
     /* XXX */
     break;
   case DIGEST:
     /* XXX */
     break;
   default:
     ret = -1; /* XXX: better result code needed */
     goto errors;
  }

  /* proceed to the next task (if there is one) */
  esp_job->task_idx ++;
  if( esp_job->task_idx < esp_job->task_cnt ) {
    ret = execute_job( esp_job->task_jobs[ esp_job->task_idx ] );
    if( ret )
      goto errors;
    
  } else {
    /* this is the last task for the esp_job */
    esp_job->gj.result = 0;
    finalize_job( &esp_job->gj );
  }
    
  FOUT();
  
  return;
  
errors:
  esp_job->gj.result = ret;
  finalize_job( &esp_job->gj );
  FERR();
}


/* -------------------------------------------------------------------------
 * REVERSE (INBOUND) CALLBACKS
 * ------------------------------------------------------------------------- */

static void
proto_esp_sw_con_decapsulate_callback( struct generic_job * task_job )
{
  int ret;
  struct esp_job *esp_job = (void*)task_job->opaque;
  /* struct esp_context *esp_con = (void*)esp_job->gj.con; */
  
  FIN();
  
  /* TODO: check the result of the task just finished */
  ret = task_job->result;
  if( ret<0 )
    goto errors;
  
  /* TODO: continue the ESP processing */
 
  switch ( esp_job->task_type[ esp_job->task_idx ] ) {
   case DIGEST:
     /* XXX */
     break;
   case CIPHER:
     /* XXX */
     break;
   default:
     ret = -1; /* XXX: better result code needed */
     goto errors;
  }

  /* proceed to the next task (if there is one) */
  esp_job->task_idx ++;
  if( esp_job->task_idx < esp_job->task_cnt ) {
    ret = execute_job( esp_job->task_jobs[ esp_job->task_idx ] );
    if( ret )
      goto errors;
    
  } else {
    /* this is the last task for the esp_job */
    esp_job->gj.result = 0;
    finalize_job( &esp_job->gj );
  }
    
  FOUT();
  
  return;
  
errors:
  esp_job->gj.result = ret;
  finalize_job( &esp_job->gj );
  FERR();
}


/* -------------------------------------------------------------------------
 * ENGINE STRUCTURE
 * ------------------------------------------------------------------------- */

DEFINE_GENERIC_ENGINE("proto-esp_sw",proto_esp_sw);


/* -------------------------------------------------------------------------
 * MODULE FUNCTIONS
 * ------------------------------------------------------------------------- */

static int __init
engine_init(void)
{
  int ret = 0;
  
  FIN();
  
  printk(KERN_INFO "ESP engine init\n");
  
  ret = register_generic_engine( &proto_esp_sw );
  
  FEXIT(ret==0);
  
  return ret;
}

static void __init
engine_cleanup(void)
{
  FIN();
  unregister_generic_engine( &proto_esp_sw );
  FOUT();
}

module_init(engine_init);
module_exit(engine_cleanup);

/*****************************************************************************
 *
 * $Log: proto-esp-sw.c,v $
 * Revision 1.11  2001/06/16 19:56:35  bart
 * *** empty log message ***
 *
 * Revision 1.10  2001/06/04 02:14:02  bart
 * merged forward/reverse into one call 'process' which is a function pointer
 * to a specific processor decided in create_context (i.e. con_init)
 *
 * Revision 1.9  2001/06/03 13:15:23  bart
 * added FIN/FOUT functions, removed not-needed get_engine from con_init.
 *
 * Revision 1.8  2001/06/03 02:16:33  bart
 * extended the DEFINE_GENERIC_ENGINE macro to take different prefix and name
 *
 * Revision 1.7  2001/06/02 22:24:59  bart
 * added debug messages to generic engine.
 *
 * Revision 1.6  2001/06/01 01:28:21  bart
 * updates for context_size function - now incorporates the definition
 *
 * Revision 1.5  2001/05/28 01:01:32  bart
 * intermediate checkin - under development
 *
 * Revision 1.4  2001/05/27 21:50:38  bart
 * corrected where the context is appended-to/removed-from the eng->con_list
 *
 * Revision 1.3  2001/05/27 21:44:04  bart
 * cleaned up the activation and cleanup of contexts by using init/cleanup
 * functions.
 *
 * Revision 1.2  2001/05/27 00:47:48  bart
 * implementation of proto_esp_sw_con_activate finished.
 *
 * Revision 1.1  2001/05/26 17:26:06  bart
 * first cut of proto-esp-sw engine
 *
 *
 *****************************************************************************/

