/*
 * nasd_pipe.c
 *
 * Generic support for data piping.
 *
 * Authors: Jim Zelenka, Howard Gobioff
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1997,1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_pipe.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_security.h>
#include <nasd/nasd_freelist.h>

#if defined(DEC_OSF) && defined(KERNEL)
#include <mach/vm_param.h>
#include <nasd/dux/nasd_dux_opt.h>
#endif /* DEC_OSF && KERNEL */

#define DEBUG_PUSH    0
#define DEBUG_PULL    0
#define DEBUG_READ    0
#define DEBUG_WRITE   0

#define NASD_MAX_FREE_FO_STATE 16
#define NASD_FO_STATE_INC      2
#define NASD_FREE_FO_STATE_INITIAL 8

#define NASD_MAX_FREE_FO_LIST_STATE 16
#define NASD_FO_LIST_STATE_INC      2
#define NASD_FREE_FO_LIST_STATE_INITIAL 8

#define NASD_MAX_FREE_DG        16
#define NASD_DG_INC             2
#define NASD_FREE_DG_INITIAL    8


#define NASD_CL_P_BUCKET_LEN 8192
static char nasd_cl_p_bitbucket[NASD_CL_P_BUCKET_LEN];
nasd_freelist_t *nasd_cl_fo_state_freelist = NULL;
nasd_freelist_t *nasd_cl_fo_list_state_freelist = NULL;
nasd_freelist_t *nasd_cl_dg_freelist = NULL;

int nasd_pipe_initialized = 0;

void
nasd_cl_shutdown_fo_state_fl(
  void  *ignored)
{
  NASD_FREELIST_DESTROY(nasd_cl_fo_state_freelist,buf,(nasd_cl_fo_state_t *));
}

void
nasd_cl_shutdown_fo_state_list_fl(
  void  *ignored)
{
  NASD_FREELIST_DESTROY(nasd_cl_fo_list_state_freelist,push_cur,
    (nasd_cl_fo_list_state_t *));
}

void
nasd_cl_shutdown_dg_fl(void *ignored)
{
  NASD_FREELIST_DESTROY(nasd_cl_dg_freelist, next, (nasd_cl_dg_t *));
}

nasd_status_t
nasd_cl_pipe_init(
  nasd_shutdown_list_t  *sl)
{
  nasd_status_t rc;

  NASD_FREELIST_CREATE(nasd_cl_fo_state_freelist, NASD_MAX_FREE_FO_STATE,
                     NASD_FO_STATE_INC, sizeof(nasd_cl_fo_state_t));
  if (nasd_cl_fo_state_freelist == NULL)
    return(NASD_NO_MEM);
  rc = nasd_shutdown_proc(sl, nasd_cl_shutdown_fo_state_fl, NULL);
  if (rc) {
    nasd_cl_shutdown_fo_state_fl(NULL);
    return(rc);
  }
  NASD_FREELIST_PRIME(nasd_cl_fo_state_freelist, NASD_FREE_FO_STATE_INITIAL,
                          buf, (nasd_cl_fo_state_t *));

  NASD_FREELIST_CREATE(nasd_cl_fo_list_state_freelist, 
                       NASD_MAX_FREE_FO_LIST_STATE,
                       NASD_FO_LIST_STATE_INC,
                       sizeof(nasd_cl_fo_list_state_t));
  if (nasd_cl_fo_list_state_freelist == NULL)
    return(NASD_NO_MEM);
  rc = nasd_shutdown_proc(sl, nasd_cl_shutdown_fo_state_list_fl, NULL);
  if (rc) {
    nasd_cl_shutdown_fo_state_list_fl(NULL);
    return(rc);
  }
  NASD_FREELIST_PRIME(nasd_cl_fo_list_state_freelist,
                      NASD_FREE_FO_LIST_STATE_INITIAL,
                      push_cur,
                      (nasd_cl_fo_list_state_t *));

  NASD_FREELIST_CREATE(nasd_cl_dg_freelist,
                       NASD_MAX_FREE_DG,
                       NASD_DG_INC,
                       sizeof(nasd_cl_dg_t));
  if(nasd_cl_dg_freelist == NULL)
    return NASD_NO_MEM;
  rc = nasd_shutdown_proc(sl, nasd_cl_shutdown_dg_fl, NULL);
  if(rc) {
    nasd_cl_shutdown_dg_fl(NULL);
    return rc;
  }
  NASD_FREELIST_PRIME(nasd_cl_dg_freelist,
                      NASD_FREE_DG_INITIAL,
                      next,
                      (nasd_cl_dg_t *));

  return(NASD_SUCCESS);
}


nasd_status_t
nasd_pipe_init(nasd_shutdown_list_t *sl)
{
  nasd_status_t rc;


  nasd_pipe_initialized = 1;
  return NASD_SUCCESS;
}

/* Send bytes using the low-level RPC pipe, perform necessary security
   magic. */
nasd_status_t
nasd_pipe_push(void *state_arg,
               void *buf,
               nasd_len_t len,
               nasd_byte_t *in_digest,
               nasd_byte_t *out_digest,
               int *digest_valid)
{
  nasd_net_pipe_state_t *s;
  nasd_status_t rc;
  nasd_security_context_t *ctx;
  nasd_byte_t *b;
#if NASD_SECURE_RPCS_ENABLE > 0
  nasd_digest_t tdigest;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */

  if(!nasd_pipe_initialized)
    return NASD_SYS_NOT_INITIALIZED;

  s = (nasd_net_pipe_state_t *) state_arg;
  ctx = s->context;

  NASD_ASSERT(ctx != NULL);

  if(ctx->protection != NASD_NO_PROTECTION && len > 0) {
#if NASD_SECURE_RPCS_ENABLE > 0
      b = buf;

    /* update digest */
    if(ctx->protection & NASD_INTEGRITY_DATA) {
      if(digest_valid)
        *digest_valid = 0;

      if(in_digest) {
        /* if we have a precomputed digest, use that */
        ctx->remain -= len;
        ctx->cur_off += len;

        NASD_ASSERT(((ctx->cur_off % NASD_SEC_DIGEST_BLOCKSIZE) == 0) ||
                    ctx->remain == 0);
        NASD_ASSERT(len == NASD_SEC_DIGEST_BLOCKSIZE);
        HMAC_SHA1_Update(&ctx->HMAC_context, in_digest, sizeof(nasd_digest_t));
      } else {
        int to_go;
        int blk_len;
        unsigned char *p;

        to_go = len;
        p = buf;
        while(to_go > 0) {
          blk_len = NASD_SEC_DIGEST_BLOCKSIZE -
            (ctx->cur_off % NASD_SEC_DIGEST_BLOCKSIZE);
          blk_len = NASD_MIN(blk_len, to_go);

#if DEBUG_PUSH > 1
          nasd_printf("push: digest %d bytes at 0x%x\n", blk_len,
                      (unsigned int)p);
#endif /* DEBUG_PUSH > 1 */
          SHA1_Update(0, &ctx->SHA1_ctx, p, blk_len);

          to_go -= blk_len;
          ctx->cur_off += blk_len;
          ctx->remain -= blk_len;
          p += blk_len;

          if(ctx->cur_off % NASD_SEC_DIGEST_BLOCKSIZE == 0) {
#if DEBUG_PUSH > 1
            nasd_printf("push: adding SHA1 to HMAC at off %d\n", ctx->cur_off);
#endif /* DEBUG_PUSH > 1 */
            /* finish the SHA1 and add it to the HMAC */
            ctx->pending_hmac = 0;
            SHA1_Final(0, tdigest, &ctx->SHA1_ctx);
            SHA1_Init(0, &ctx->SHA1_ctx);
            HMAC_SHA1_Update(&ctx->HMAC_context, tdigest, sizeof(nasd_digest_t));

            if(blk_len == NASD_SEC_DIGEST_BLOCKSIZE && out_digest &&
               digest_valid) {
              /* send back precomputed digest */
              bcopy(tdigest, out_digest, sizeof(nasd_digest_t));
              *digest_valid = 1;
            }
          } else {
            ctx->pending_hmac = 1;
          }
        }
      }
    }
#else /* NASD_SECURE_RPCS_ENABLE > 0 */
#if NASD_SECURITY_DEBUG_ERRORS > 0
    nasd_printf("WARNING: drive: unsupported security protection mask 0x%x\n",
                ctx->protection);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
    /* should never get here, but be fault-tolerant about it */
    return NASD_UNSUPPORTED_PROTECTION;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
  } else {
    b = buf;
  }

  /* push data */
  rc = s->push(s->rpc_state, b, len);

#if DEBUG_PUSH > 0
  nasd_printf("push data: %d bytes rc=%d\n", len, rc);
#endif /* DEBUG_PUSH > 0 */


  /* saved return from s->push */
  return rc;
}

/* Receive bytes off the wire using the low-level RPC pipe, perform
   necessary security magic.  Note that some of this code assumes
   that len <= NASD_SEC_DIGEST_BLOCKSIZE. */
nasd_status_t
nasd_pipe_pull(void *state_arg,
               void *buf,
               nasd_len_t len,
               nasd_len_t *out_len,
               nasd_byte_t *out_digest,
               int *digest_valid,
               void (*commit)(void *, nasd_offset_t, nasd_len_t),
               void *commit_rock)
{
  nasd_net_pipe_state_t *s;
  nasd_len_t count;
  nasd_status_t rc;
  nasd_security_context_t *ctx;
#if NASD_SECURE_RPCS_ENABLE > 0
  int r, g;
  nasd_digest_t ld;
  nasd_byte_t *dp;
  nasd_digest_t wire_hmac;
  HMAC_SHA1_CTX temp_hmac_ctx;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */

  s = (nasd_net_pipe_state_t *) state_arg;
  ctx = s->context;

  NASD_ASSERT(ctx != NULL);

  /* XXX timer */
  rc = s->pull(s->rpc_state, buf, len, &count);
  *out_len = count;

#if DEBUG_PULL > 0
  nasd_printf("pull data: want %d bytes got %d bytes rc=%d\n", len, count, rc);
#endif /* DEBUG_PULL > 0 */

  if(rc && count == 0)
    /* used to return here on error regardless of count; however if we
       had a partial pull, I think we should process that. -mju */
    return rc;

  NASD_ASSERT(count <= len);

  if(count == 0)
    /* don't need to decrypt or digest */
    return rc;

  if(ctx->protection != NASD_NO_PROTECTION) {
#if NASD_SECURE_RPCS_ENABLE > 0

    /* update message digest */
    if(ctx->protection & NASD_INTEGRITY_DATA) {
      if(digest_valid)
        *digest_valid = 0;
      if(out_digest)
        dp = out_digest;
      else
        dp = ld;

#if DEBUG_PULL > 1
      nasd_printf("pull update digest %d bytes at 0x%x\n", count,
                  (unsigned int) buf);
#endif /* DEBUG_PULL > 1 */
      SHA1_Update(0, &ctx->SHA1_ctx, buf, count);

      ctx->cur_off += count;
      ctx->remain -= count;

      if((ctx->cur_off % NASD_SEC_DIGEST_BLOCKSIZE) == 0 ||
         ctx->remain == 0) {
        /* finish the SHA1 & add it to the HMAC */
#if DEBUG_PULL > 1
        nasd_printf("pull adding SHA1 to HMAC at off %" NASD_64s_FMT "\n",
                    ctx->cur_off);
#endif /* DEBUG_PULL > 1 */
        SHA1_Final(0, dp, &ctx->SHA1_ctx);
        if(digest_valid)
          *digest_valid = 1;
        SHA1_Init(0, &ctx->SHA1_ctx);
        HMAC_SHA1_Update(&ctx->HMAC_context, dp, sizeof(nasd_digest_t));
      }

      if((ctx->cur_off % NASD_SEC_HMAC_BLOCKSIZE) == 0 ||
         ctx->remain == 0) {
        /* do an intermediate HMAC */
        bcopy(&ctx->HMAC_context, &temp_hmac_ctx, sizeof(HMAC_SHA1_CTX));
        HMAC_SHA1_Final(ld, &temp_hmac_ctx);

        /* get digest from the wire */
        r = sizeof(nasd_digest_t);
        g = 0;
        while(r) {
          /* XXX timer */
          rc = s->pull(s->rpc_state, &((char *)wire_hmac)[g], r, &count);
#if DEBUG_PULL > 0
          nasd_printf("pull digest: want %d bytes got %d bytes rc=%d\n",
                      r, count, rc);
#endif /* DEBUG_PULL > 0 */
          if(rc)
            return rc;
          r -= count;
          g += count;
        }

        if(bcmp(wire_hmac, ld, sizeof(nasd_digest_t)) != 0) {
#if DEBUG_PULL > 0
          nasd_printf("pull: bad digest\n");
#endif /* DEBUG_PULL > 0 */
          return NASD_BAD_DIGEST;
        } else if(commit) {
#if DEBUG_PULL > 0
          nasd_printf("pull: good digest %" NASD_64s_FMT "-%" NASD_64s_FMT "\n",
                      ctx->last_commit_off, ctx->cur_off);
#endif /* DEBUG_PULL > 0 */
          /* MAC so far is good, so execute commit callback to tell the
             cache it doesn't have to keep those bytes "in flight"
             anymore */
          commit(commit_rock, ctx->last_commit_off,
                 ctx->cur_off - ctx->last_commit_off);
          ctx->last_commit_off = ctx->cur_off;
        }
      }
    }
#else /* NASD_SECURE_RPCS_ENABLE > 0 */
#if NASD_SECURITY_DEBUG_ERRORS > 0
    nasd_printf("WARNING: drive: unsupported security protection mask 0x%x\n",
                ctx->protection);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
    /* should never get here, but be fault-tolerant about it */
    return NASD_UNSUPPORTED_PROTECTION;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
  }
  return NASD_SUCCESS;
}

/* non-memlist versions of the callbacks.  not yet implemented because
   no RPC layer uses them. */
void
nasd_pipe_cb_read_alloc(
  nasd_cl_fo_state_t   *s,
  nasd_uint32           bsize,
  nasd_byte_t         **buf,
  nasd_uint32          *bcount)
{
  NASD_PANIC();
}

void
nasd_pipe_cb_write_alloc(
  nasd_cl_fo_state_t   *s,
  nasd_uint32           bsize,
  nasd_byte_t         **buf,
  nasd_uint32          *bcount)
{
  NASD_PANIC();
}

void
nasd_pipe_cb_read_push(
  nasd_cl_fo_state_t  *s,
  nasd_byte_t         *buf,
  nasd_uint32          count)
{
  NASD_PANIC();
}

void
nasd_pipe_cb_write_pull(
  nasd_cl_fo_state_t  *s,
  nasd_byte_t         *buf,
  nasd_uint32          in_count,
  nasd_uint32         *out_countp)
{
  NASD_PANIC();
}

void
nasd_pipe_cb_read_alloc_list(
  nasd_cl_fo_list_state_t   *s,
  nasd_uint32                bsize,
  nasd_byte_t              **buf,
  nasd_uint32               *bcount)
{
  nasd_len_t out_len;
  int abytes;

  out_len = *s->out_lenp;
  if (s->badstate) {
    *buf = (nasd_byte_t *)nasd_cl_p_bitbucket;
    *bcount = NASD_MIN(bsize,NASD_CL_P_BUCKET_LEN);
    return;
  }
  if (out_len > s->targetlen) {
    s->badstate = 1;
    s->status = NASD_REMOTE_OVERRUN;
    *buf = (nasd_byte_t *)nasd_cl_p_bitbucket;
    *bcount = NASD_MIN(bsize,NASD_CL_P_BUCKET_LEN);
    return;
  }

  *buf = (nasd_byte_t *)(s->alloc_buf + s->alloc_off);
  abytes = NASD_MIN(bsize, s->alloc_len - s->alloc_off);
  NASD_ASSERT(abytes >= 0);
  if(abytes == 0) {
    if (s->dce_bug) {
      /*
       * A DCE pipe bug prevents us from being allowed to
       * indicate EOT here by having abytes go to zero.
       */
      *bcount = NASD_MIN(bsize,NASD_CL_P_BUCKET_LEN);
      *buf = (nasd_byte_t *)nasd_cl_p_bitbucket;
      return;
    } else {
      *bcount = 0;
      *buf = NULL;
    }
    return;
  }

  *bcount = abytes;
  s->alloc_off += abytes;

  if (s->alloc_off >= s->alloc_len) {
    /* next chunk */
    NASD_ASSERT(s->alloc_off == s->alloc_len);
    s->alloc_elem++;
    if (s->alloc_elem >= s->alloc_cur->nelem) {
      NASD_ASSERT(s->alloc_elem == s->alloc_cur->nelem);
      if ((out_len + abytes) >= s->targetlen) {
        if ((out_len + abytes) > s->targetlen) {
          s->badstate = 1;
          *bcount = 0;
          *buf = NULL;
          return;
        }
        s->alloc_cur = NULL;
        s->alloc_buf = NULL;
        s->alloc_off = 0;
        s->alloc_len = 0;
      }
      else {
        /* still more to send; get the next entry */
        s->alloc_cur = s->alloc_cur->next;
        s->alloc_elem = 0;
        if(s->alloc_cur) {
          s->status = nasd_check_mem_list(s->alloc_cur);
          if (s->status) {
            s->badstate = 1;
            *bcount = 0;
            *buf = NULL;
            return;
          }
          s->alloc_buf = s->alloc_cur->addr;
          s->alloc_off = 0;
          s->alloc_len = s->alloc_cur->len;
        } else {
          /* end of memlist */
          s->alloc_buf = NULL;
          s->alloc_off = 0;
          s->alloc_len = 0;
        }
      }
    }
    else {
      s->alloc_buf = s->alloc_buf + s->alloc_cur->stride;
      s->alloc_off = 0;
      s->alloc_len = s->alloc_cur->len;
    }
  }
#if DEBUG_READ > 0
  nasd_printf("alloc %d bytes at 0x%x (data)\n", *bcount, (unsigned int) *buf);
#endif /* DEBUG_READ > 0 */
}

/* write_alloc_list: We are operating from a memlist.  RPC layer wants
   a pointer to a buffer with no more than bsize bytes; buffer should
   be ready to be sent (wired in the cache, etc.) when we return.
   Return pointer to the buf and actual size (must be <= bsize) in
   buf/bcount.  If we are encrypting, this function takes care
   of duplicating the memlist buffer, so it doesn't get trashed when
   we encrypt it.  Our RPC wrapper is responsible for walking this
   duplicate list and freeing the memory when the RPC is complete. */
void
nasd_pipe_cb_write_alloc_list(
  nasd_cl_fo_list_state_t   *s,
  nasd_uint32                bsize,
  nasd_byte_t              **buf,
  nasd_uint32               *bcount)
{
  nasd_len_t out_len;
  int abytes;
  nasd_digest_t tdigest;
  HMAC_SHA1_CTX temp_hmac_ctx;
  nasd_cl_dg_t *d;

  out_len = *s->out_lenp;

  /* if we've already hit an error, return bit bucket */
  if (s->badstate) {
    *buf = (nasd_byte_t *)nasd_cl_p_bitbucket;
    *bcount = NASD_MIN(bsize,NASD_CL_P_BUCKET_LEN);
    return;
  }

  /* did we send more than we were supposed to?  don't see how this
     could happen. */
  if (out_len > s->targetlen) {
    s->badstate = 1;
    s->status = NASD_REMOTE_OVERRUN;
    *buf = (nasd_byte_t *)nasd_cl_p_bitbucket;
    *bcount = NASD_MIN(bsize,NASD_CL_P_BUCKET_LEN);
    return;
  }

  /* if we're sending a digest, take care of that now */
  if(s->alloc_digest) {
    if(s->digest_alloc_off == 0) {
      /* pull in & attach a new digest buf */
      NASD_FREELIST_GET(nasd_cl_dg_freelist, d, next, (nasd_cl_dg_t *));
      d->next = NULL;
      if(s->digest_alloc_cur) {
        s->digest_alloc_cur->next = d;
      }
      s->digest_alloc_cur = d;
      if(!s->digest_pull_cur) {
        s->digest_pull_cur = d;
      }
      s->digest_alloc_buf = d->digest;

      /* need to calculate the digest */
      if(s->context.pending_hmac) {
        /* still have a last hash chunk to incorporate */
        SHA1_Final(0, tdigest, &s->context.SHA1_ctx);
        HMAC_SHA1_Update(&s->context.HMAC_context, tdigest,
                         sizeof(nasd_digest_t));
        s->context.pending_hmac = 0;
      }

      /* fill in digest */
      bcopy(&s->context.HMAC_context, &temp_hmac_ctx, sizeof(HMAC_SHA1_CTX));
      HMAC_SHA1_Final(d->digest, &temp_hmac_ctx);
    }
    *buf = (nasd_byte_t *)s->digest_alloc_buf + s->digest_alloc_off;
    abytes = NASD_MIN(bsize, sizeof(nasd_digest_t) - s->digest_alloc_off);
    *bcount = abytes;

    s->digest_alloc_off += abytes;
    if(s->digest_alloc_off >= sizeof(nasd_digest_t))
      /* end of digest -- switch back to data mode on next alloc */
      s->alloc_digest = 0;

    /* we're done */
#if DEBUG_WRITE > 0
    nasd_printf("alloc %d bytes at 0x%x (digest)\n", *bcount,
                (unsigned int) *buf);
#endif /* DEBUG_WRITE > 0 */
    return;
  }

  /* find our place and figure out how many bytes we're going to
     return */
  *buf = (nasd_byte_t *)(s->alloc_buf + s->alloc_off);
  abytes = NASD_MIN(bsize,s->alloc_len-s->alloc_off);

  NASD_ASSERT(abytes >= 0);
  if (abytes == 0) {
    if (s->dce_bug) {
      /*
       * A DCE pipe bug prevents us from being allowed to
       * indicate EOT here by having abytes go to zero.
       */
      *bcount = NASD_MIN(bsize,NASD_CL_P_BUCKET_LEN);
      *buf = (nasd_byte_t *)nasd_cl_p_bitbucket;
    }
    else {
      *bcount = 0;
      *buf = NULL;
    }
  }
  else { /* abytes != 0 */
    if(s->context.protection != NASD_NO_PROTECTION) {
#if NASD_SECURE_RPCS_ENABLE > 0
      if(s->context.protection & NASD_INTEGRITY_DATA) {
        unsigned int real_off;
        unsigned int blk_len;
        unsigned int to_go;
        nasd_byte_t *p;

        real_off = s->alloc_off + s->alloc_adj;

        if(real_off + abytes >= s->next_alloc_digest_off) {
          /* if we are going to fall on or past the next digest boundary,
             time to send a digest. */

          /* truncate alloc if it would fall past next digest boundary */
          if(real_off + abytes > s->next_alloc_digest_off)
            abytes = s->next_alloc_digest_off - real_off;

          NASD_ASSERT(real_off + abytes == s->next_alloc_digest_off);

          /* set flag so we send the digest next time through */
          s->alloc_digest = 1;
          s->digest_alloc_off = 0;
          s->next_alloc_digest_off += NASD_SEC_HMAC_BLOCKSIZE;
        }

        /* walk through the buffer, SHA1-ing the data.  every time we
           get to a disk block boundary in the object, add the SHA1 hash
           to the HMAC digest and restart SHA1. */
        to_go = abytes;
        p = *buf;
        do {
          blk_len = NASD_SEC_DIGEST_BLOCKSIZE -
            (real_off % NASD_SEC_DIGEST_BLOCKSIZE);
          blk_len = NASD_MIN(blk_len, to_go);
          SHA1_Update(0, &s->context.SHA1_ctx, p, blk_len);
          to_go -= blk_len;
          real_off += blk_len;
          p += blk_len;
          if(real_off % NASD_SEC_DIGEST_BLOCKSIZE == 0) {
            s->context.pending_hmac = 0;
            SHA1_Final(0, tdigest, &s->context.SHA1_ctx);
            HMAC_SHA1_Update(&s->context.HMAC_context, tdigest,
                             sizeof(nasd_digest_t));
            SHA1_Init(0, &s->context.SHA1_ctx);
          } else {
            s->context.pending_hmac = 1;
          }
        } while(to_go > 0);
      }

#else /* NASD_SECURE_RPCS_ENABLE > 0 */
#if NASD_SECURITY_DEBUG_ERRORS > 0
      nasd_printf("WARNING: client: unsupported security protection mask 0x%x\n",
                  s->context.protection);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
      /* should never get here, but be fault-tolerant about it */
      s->badstate = 1;
      return;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
    }

    *bcount = abytes;

    s->alloc_off += abytes;

    if (s->alloc_off >= s->alloc_len) {
      /*
       * Next chunk
       */
      NASD_ASSERT(s->alloc_off == s->alloc_len);
      s->alloc_elem++;
      if (s->alloc_elem >= s->alloc_cur->nelem) {
        /* done with this memlist entry */
        NASD_ASSERT(s->alloc_elem == s->alloc_cur->nelem);
        if ((out_len + abytes) >= s->targetlen) {
          if ((out_len + abytes) > s->targetlen) {
            /* uh-oh. */
            s->badstate = 1;
            *bcount = 0;
            *buf = NULL;
            return;
          }
          /* we've sent the number of bytes we're supposed to; now need
             to send the last digest. */
          if(s->context.protection & NASD_INTEGRITY_DATA) {
            s->alloc_digest = 1;
            s->digest_alloc_off = 0;
          }
          s->alloc_cur = NULL;
          s->alloc_buf = NULL;
          s->alloc_off = 0;
          s->alloc_len = 0;
        }
        else {
          /* still more to send; get the next entry */
          s->alloc_cur = s->alloc_cur->next;
          s->alloc_elem = 0;
          if (s->alloc_cur) {
            s->status = nasd_check_mem_list(s->alloc_cur);
            if (s->status) {
              s->badstate = 1;
              *bcount = 0;
              *buf = NULL;
              return;
            }
            s->alloc_adj += s->alloc_off;
            s->alloc_buf = s->alloc_cur->addr;
            s->alloc_off = 0;
            s->alloc_len = s->alloc_cur->len;
          }
          else {
          /* we've reached the end of the mem_list; now we need to
             send the last digest. */
            if(s->context.protection & NASD_INTEGRITY_DATA) {
              s->alloc_digest = 1;
              s->digest_alloc_off = 0;
            }
            s->alloc_buf = NULL;
            s->alloc_off = 0;
            s->alloc_len = 0;
          }
        }
      }
      else {
        s->alloc_buf += s->alloc_cur->stride;
        s->alloc_adj += s->alloc_off;
        s->alloc_off = 0;
        s->alloc_len = s->alloc_cur->len;
      }
    }
  }
#if DEBUG_WRITE > 0
  nasd_printf("alloc %d bytes (max %d) at 0x%x (data)\n", *bcount, bsize,
              (unsigned int) *buf);
#endif /* DEBUG_WRITE > 0 */
}

void
nasd_pipe_cb_read_push_list(
  nasd_cl_fo_list_state_t  *s,
  nasd_byte_t              *buf,
  nasd_uint32               count)
{
  nasd_len_t out_len;
  char *b, *t;
  int a;
#if NASD_SECURE_RPCS_ENABLE > 0
  nasd_digest_t tdigest;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */

#if DEBUG_READ > 0
  nasd_printf("push  %d bytes at 0x%x\n", count, (unsigned int) buf);
#endif /* DEBUG_READ > 0 */

  if (count == 0)
    return;
  if (s->badstate)
    return;
  out_len = *s->out_lenp;
  t = (char *)buf;

  if (out_len > s->targetlen) {
    s->badstate = 1;
    s->status = NASD_REMOTE_OVERRUN;
    return;
  }

  b = (char *)(s->push_buf + s->push_off);
  NASD_ASSERT(t == b);
  a = NASD_MIN(count, s->push_len - s->push_off);

  if(s->context.protection != NASD_NO_PROTECTION) {
#if NASD_SECURE_RPCS_ENABLE > 0
 
    if(s->context.protection & NASD_INTEGRITY_DATA) {
      unsigned int real_off;
      unsigned int blk_len;
      unsigned int to_go;
      nasd_byte_t *p;

      real_off = s->push_off + s->push_adj;

      /* walk through the buffer, SHA1-ing the data.  every time we
         get to a disk block boundary in the object, add the SHA1 hash
         to the HMAC digest and restart SHA1. */
      to_go = a;
      p = buf;
      while(to_go > 0) {
        blk_len = NASD_SEC_DIGEST_BLOCKSIZE -
          (real_off % NASD_SEC_DIGEST_BLOCKSIZE);
        blk_len = NASD_MIN(blk_len, to_go);
#if DEBUG_READ > 1
        nasd_printf("push  digest %d bytes at 0x%x\n", blk_len, (unsigned int)p);
#endif /* DEBUG_READ > 1 */
        SHA1_Update(0, &s->context.SHA1_ctx, p, blk_len);
        to_go -= blk_len;
        real_off += blk_len;
        p += blk_len;
        if(real_off % NASD_SEC_DIGEST_BLOCKSIZE == 0) {
          s->context.pending_hmac = 0;
#if DEBUG_READ > 1
          nasd_printf("push  adding SHA1 to HMAC at off %d\n", real_off);
#endif /* DEBUG_READ > 1 */
          SHA1_Final(0, tdigest, &s->context.SHA1_ctx);
          HMAC_SHA1_Update(&s->context.HMAC_context, tdigest,
                           sizeof(nasd_digest_t));
          SHA1_Init(0, &s->context.SHA1_ctx);
        } else {
          s->context.pending_hmac = 1;
        }
      }
    }
#else /* NASD_SECURE_RPCS_ENABLE > 0 */
#if NASD_SECURITY_DEBUG_ERRORS > 0
    nasd_printf("WARNING: client: unsupported security protection mask 0x%x\n",
                s->context.protection);
#endif /* NASD_SECURITY_DEBUG_ERRORS > 0 */
    /* should never get here, but be fault-tolerant about it */
    s->badstate = 1;
    return;
#endif /* NASD_SECURE_RPCS_ENABLE > 0 */
  }
  /* update pointers and bytes-transferred count */
  s->push_off += a;
  out_len += a;
  *s->out_lenp = out_len;

  if (s->push_off >= s->push_len) {
    /* next chunk */
    NASD_ASSERT(s->push_off == s->push_len);
    s->push_elem++;
    if (s->push_elem >= s->push_cur->nelem) {
      NASD_ASSERT(s->push_elem == s->push_cur->nelem);
      if ((out_len) >= s->targetlen) {    
        if ((out_len) > s->targetlen) {   
          s->badstate = 1;
          return;
        }
        s->push_cur = NULL;
        s->push_buf = NULL;
        s->push_off = 0;
        s->push_len = 0;
      }
      else {
        /* still more to receive; get the next entry */
        s->push_cur = s->push_cur->next;
        s->push_elem = 0;
        if(s->push_cur) {
          s->status = nasd_check_mem_list(s->push_cur);
          if (s->status) {
            s->badstate = 1;
            return;
          }
          s->push_adj += s->push_off;
          s->push_buf = s->push_cur->addr;
          s->push_off = 0;
          s->push_len = s->push_cur->len;
        } else {
          /* we've reached the end of the mem_list */
          s->push_buf = NULL;
          s->push_off = 0;
          s->push_len = 0;
        }
      }
    }
    else {
      s->push_adj += s->push_off;
      s->push_buf = s->push_buf + s->push_cur->stride;
      s->push_off = 0;
      s->push_len = s->push_cur->len;
    }
  }
}

void
nasd_pipe_cb_write_pull_list(
  nasd_cl_fo_list_state_t  *s,
  nasd_byte_t              *buf,
  nasd_uint32               in_count,
  nasd_uint32              *out_countp)
{
  nasd_len_t out_len;
  nasd_byte_t *b, *t;
  int a;
  nasd_offset_t real_off;
  nasd_cl_dg_t *d;

  if (in_count == 0) {
    *out_countp = 0;
    return;
  }

  out_len = *s->out_lenp;

  if (s->badstate) {
    return;
  }

  t = buf;

  if (out_len >= s->targetlen && !s->pull_digest) {
    /* Is this legal? */
    *out_countp = 0;
    return;
  }

  if(s->pull_digest) {
    /* if we're pulling a digest, update pointers and possibly switch
       out of digest mode. */
    b = (nasd_byte_t *)s->digest_pull_buf + s->digest_pull_off;
    a = NASD_MIN(in_count, sizeof(nasd_digest_t) - s->digest_pull_off);
    s->digest_pull_off += a;
    if(s->digest_pull_off >= sizeof(nasd_digest_t)) {
      /* we're done sending this digest.  reset state back into data
         mode and put this digest buffer back on the freelist. */
      NASD_ASSERT(s->digest_pull_off == sizeof(nasd_digest_t));
      s->pull_digest = 0;
      s->digest_pull_off = 0;
      d = s->digest_pull_cur;
      s->digest_pull_cur = d->next;
      if(s->digest_pull_cur == NULL) {
        NASD_ASSERT(s->digest_alloc_cur == d);
        s->digest_alloc_cur = NULL;
      }
      NASD_FREELIST_FREE(nasd_cl_dg_freelist, d, next);
    }

#if DEBUG_WRITE > 0
    nasd_printf("pull %d bytes at 0x%x (digest)\n", a, (unsigned int) buf);
#endif /* DEBUG_WRITE > 0 */
  } else {
    /* if we're pulling data, update pointers and possibly switch into
       digest mode. */
    b = s->push_buf + s->push_off;
      NASD_ASSERT(t == b);
    
    a = NASD_MIN(in_count, s->push_len - s->push_off);

    if(s->context.protection & NASD_INTEGRITY_DATA) {
      real_off = s->push_off + s->push_adj;
      if(real_off + a >= s->next_pull_digest_off) {
        /* truncate pull if it falls past digest boundary */
        if(real_off + a > s->next_pull_digest_off)
          a = s->next_pull_digest_off - real_off;

        NASD_ASSERT(real_off + a == s->next_pull_digest_off);

        /* switch into digest mode for next pull */
        s->pull_digest = 1;
        s->digest_pull_off = 0;
        s->next_pull_digest_off += NASD_SEC_HMAC_BLOCKSIZE;
      }
    }

    s->push_off += a;
    out_len += a;
    *s->out_lenp = out_len;

    if (s->push_off >= s->push_len) {
      /* next chunk */
      NASD_ASSERT(s->push_off == s->push_len);
      s->push_elem++;
      if (s->push_elem >= s->push_cur->nelem) {
        NASD_ASSERT(s->push_elem == s->push_cur->nelem);
        if ((out_len) >= s->targetlen) {    
          if ((out_len) > s->targetlen) {   
            s->badstate = 1;
            *out_countp = 0;
            return;
          }
          if(s->context.protection & NASD_INTEGRITY_DATA) {
            /* prepare to pull final digest */
            s->pull_digest = 1;
            s->digest_pull_off = 0;
          }
          s->push_cur = NULL;
          s->push_buf = NULL;
          s->push_off = 0;
          s->push_len = 0;
        }
        else {
          s->push_cur = s->push_cur->next;
          s->push_elem = 0;
          s->status = nasd_check_mem_list(s->push_cur);
          if (s->status) {
            s->badstate = 1;
            *out_countp = 0;
            return;
          }
          s->push_buf = s->push_cur->addr;
          s->push_off = 0;
          s->push_len = s->push_cur->len;
        }
      }
      else {
        s->push_buf = s->push_buf + s->push_cur->stride;
        s->push_off = 0;
        s->push_len = s->push_cur->len;
      }
    }

#if DEBUG_WRITE > 0
    nasd_printf("pull %d bytes at 0x%x (data)\n", a, (unsigned int) buf);
#endif /* DEBUG_WRITE > 0 */
  }

  *out_countp = a;
}



#ifdef KERNEL

/*
 * Pipe routines for colocated drive
 */

nasd_status_t
nasd_co_pull(
  void        *state_arg,
  void        *buf,
  nasd_len_t   in_len,
  nasd_len_t  *out_lenp,
  nasd_byte_t *ign1,
  int         *ign2,
  void       (*ign3)(),
  void        *ign4)
{
  nasd_co_pipe_state_t *state;
  nasd_len_t len;

  state = (nasd_co_pipe_state_t *)state_arg;
  len = NASD_MIN(in_len, state->buf_len_remain);
  if (len) {
#if defined(DEC_OSF) && defined(KERNEL) && 1
    register vm_offset_t s0, s1, partial, d0, d1;
    /*
     * Maintain this ordering of checks- client buffer more
     * likely to be unaligned, and AXP predicts branch-taken
     */
    if ((!page_aligned(state->cur_buf)) || (!page_aligned(buf))) {
      bcopy((char *)state->cur_buf, (char *)buf, len);
    }
    else {
      s0 = ((vm_offset_t)state->cur_buf) + (vm_offset_t)len;
      s1 = trunc_page(s0);
      partial = s0 - s1;
      if (partial) {
        d0 = ((vm_offset_t)buf) + (vm_offset_t)len;
        d1 = trunc_page(d0);
        NASD_ASSERT(((int)partial) >= 0);
        bcopy((char *)s1, (char *)d1, (int)partial);
      }
      nasd_dux_pgs_cpy((vm_offset_t)state->cur_buf, (vm_offset_t)buf, s1);
    }
#else /* DEC_OSF && KERNEL */
    bcopy((char *)state->cur_buf, (char *)buf, len);
#endif /* DEC_OSF && KERNEL */
    state->buf_len_remain -= len;
    state->cur_buf += len;
  }
  *out_lenp = len;
  return(NASD_SUCCESS);
}

nasd_status_t
nasd_co_push(
  void        *state_arg,
  void        *buf,
  nasd_len_t   in_len,
  nasd_byte_t *ign1,
  nasd_byte_t *ign2,
  int         *ign3)
{
  nasd_co_pipe_state_t *state;
  int len;

  state = (nasd_co_pipe_state_t *)state_arg;
  len = NASD_MIN(in_len, state->buf_len_remain);
  if (len) {
#if defined(DEC_OSF) && defined(KERNEL)
    register vm_offset_t s0, s1, partial, d0, d1;
    /*
     * Maintain this ordering of checks- client buffer more
     * likely to be unaligned, and AXP predicts branch-taken
     */
    if ((!page_aligned(state->cur_buf)) || (!page_aligned(buf))) {
      bcopy((char *)buf, (char *)state->cur_buf, len);
    }
    else {
      s0 = ((vm_offset_t)buf) + (vm_offset_t)len;
      s1 = trunc_page(s0);
      partial = s0 - s1;
      if (partial) {
        d0 = ((vm_offset_t)state->cur_buf) + (vm_offset_t)len;
        d1 = trunc_page(d0);
        NASD_ASSERT(((int)partial) >= 0);
        bcopy((char *)s1, (char *)d1, (int)partial);
      }
      nasd_dux_pgs_cpy((vm_offset_t)buf, (vm_offset_t)state->cur_buf, s1);
    }
#else /* DEC_OSF && KERNEL */
    bcopy((char *)buf, (char *)state->cur_buf, len);
#endif /* DEC_OSF && KERNEL */
    state->buf_len_remain -= len;
    state->cur_buf += len;
  }
  return(NASD_SUCCESS);
}

#endif /* KERNEL */

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
