/*

  sshfastalloc.c

  Author: Antti Huima <huima@ssh.fi>

  Copyright (c) 1999, 2000 SSH Communications Security, Finland
  All rights reserved.

  Created Thu Aug 19 13:38:25 1999.

  */

#include "sshincludes.h"
#include "sshdebug.h"
#include "sshfastalloc.h"

#define SSH_DEBUG_MODULE "SshFastalloc"

static Boolean make_more_blobs(SshFastMemoryAllocator a)
{
  SshFastallocBlobs *newp;

  newp = ssh_xmalloc(sizeof(*newp));

  /* Check the return value. */
  if (newp == NULL)
    {
      return FALSE;
    }

  newp->blobs = ssh_xmalloc(a->blob_quant * a->blob_size);

  /* Check the return value. */
  if (newp->blobs == NULL)
    {
      ssh_xfree(newp);
      return FALSE;
    }
  
  newp->next = a->blobs;
  a->blobs = newp;
  a->total_size += a->blob_quant;

  /* Add the new blobs to the chain of free blobs. */
  {
    unsigned char *ptr = newp->blobs;
    unsigned char *end = ptr + a->blob_size * (a->blob_quant - 1);
    int step = a->blob_size;

    while (ptr < end)
      {
        ((SshFastallocProtoBlob *)ptr)->free_chain =
          (ptr + step);
        ptr += step;
      }
    ((SshFastallocProtoBlob *)ptr)->free_chain = a->free_chain;
    a->free_chain = newp->blobs;
  }

  return TRUE;
}

static void *get_blob(SshFastMemoryAllocator a)
{
  void *r;

  if (a->free_chain == NULL)
    {
      if (!make_more_blobs(a))
        {
          return NULL;
        }
    }

  r = a->free_chain;
  a->free_chain = a->free_chain->free_chain;
  a->allocated++;
  return r;      
}

static void release_blob(SshFastMemoryAllocator a, void *ptr)
{
  ((SshFastallocProtoBlob *)ptr)->free_chain = a->free_chain;
  a->free_chain = (SshFastallocProtoBlob *)ptr;
  a->allocated--;
  SSH_ASSERT(a->allocated >= 0);
}

SshFastMemoryAllocator ssh_fastalloc_initialize(int blob_size,
                                                int blob_quant)
{
  SshFastMemoryAllocator newp;

  SSH_VERIFY(blob_size > 0);
  SSH_VERIFY(blob_quant > 0);

  /* Ensure correct alignment: round the `blob_size' up to be a
     multiple of sizeof(void *). */
  if (blob_size % sizeof(void *))
    {
      blob_size += sizeof(void *) - (blob_size % sizeof(void *));
    }

  newp = ssh_xmalloc(sizeof(*newp));

  /* This allocation must not fail. */
  SSH_ASSERT(newp != NULL);

  newp->blob_size = blob_size;
  newp->blob_quant = blob_quant;
  newp->allocated = 0;
  newp->total_size = 0;
  newp->blobs = NULL;
  newp->free_chain = NULL;
  return newp;
}

void ssh_fastalloc_uninitialize(SshFastMemoryAllocator a)
{
  if (a->allocated > 0)
    {
      ssh_fatal("%d blobs not freed in ssh_fastalloc_uninitialize",
                a->allocated);
    }

  while (a->blobs != NULL)
    {
      SshFastallocBlobs *b = a->blobs;
      a->blobs = a->blobs->next;
      ssh_xfree(b->blobs);
      ssh_xfree(b);
    }

  ssh_xfree(a);
}

void *ssh_fastalloc_alloc(SshFastMemoryAllocator a)
{
  return get_blob(a);
}

void ssh_fastalloc_free(SshFastMemoryAllocator a, void *ptr)
{
  release_blob(a, ptr);
}
