/* 

  sshbitvector.c

  Author: Jukka Aittokallio <jai@ssh.fi>

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

  This file contains functions for creating and 
  manipulating bit vectors. Bit vector is an arbitrary
  sized "boolean" array. You can set/get/query bit status on
  the array.
    
*/

#include "sshbitvector.h"

struct SshBitVectorRec {

  Boolean fixed_size;     /* is vector fixed_size */
  SshUInt32 size,         /* allocated size in bytes */
            bit_count;    /* current size in bits */
  unsigned char *bytes;   /* bit data in byte array. 8 bits per byte */

};

SshBitVector ssh_bitvector_create(SshUInt32 initial_size)
{
  int i;
  SshBitVector v;

  if (!(v = (SshBitVector)ssh_xmalloc(sizeof(*v))))
    {
      return NULL;
    }

  if (initial_size > 0)
    {
      v->fixed_size = 1;
      v->bit_count = initial_size;
      v->size = (initial_size + 7) / 8;
      v->bytes = (unsigned char *)ssh_xmalloc(v->size);
      if (!v->bytes)
        {
          ssh_xfree(v);
          return NULL;
        }
      for (i=0; i<v->size; i++) v->bytes[i] = 0;
    }
  else
    {
      v->bit_count = 0;
      v->size = 0;
      v->fixed_size = 0;
      v->bytes = NULL;
    }
  return v;
}

static unsigned char ssh_swap_bits(unsigned char b)
{
  unsigned char r = 0, m1 = 0x01, m2 = 0x80;
  while(m2)
    {
      if (b & m2) r |= m1;    
      m1<<=1;
      m2>>=1;
    }
  return r;
}

SshBitVector ssh_bitvector_import(unsigned char *byte_array, 
                                                                                                                                        SshUInt32 bit_count, 
                                  Boolean fixed_size, 
                                                                                                                                        Boolean swap_bits)
{
  SshUInt32 i;
  SshBitVector v;

  if (!(v = (SshBitVector)ssh_xcalloc(1, sizeof(*v))))
    {
      return NULL;
    }
  v->bit_count = bit_count;
  v->size = (bit_count + 7) / 8;
  v->fixed_size = fixed_size;

        if (v->size)
                {
                        v->bytes = (unsigned char *)ssh_xmalloc(v->size);
                        if (!v->bytes)
                                {
                                        ssh_xfree(v);
                                        return NULL;
                                }
                        for (i=0; i<v->size; i++) 
                                v->bytes[i] = (swap_bits ? 
                                        ssh_swap_bits(byte_array[i]) : byte_array[i]);
                }
  return v;
}

SshBitStatus ssh_bitvector_export(SshBitVector v, 
                                                                                                                                        unsigned char **byte_array, 
                                  SshUInt32 *bit_count)
{
  SshUInt32 i;

  *bit_count = v->bit_count;
  *byte_array = (unsigned char *)ssh_xmalloc(v->size);

  if (!*byte_array)
    {
      return SSH_BITVEC_ALLOC_ERROR;
    }

  for (i=0; i<v->size; i++) (*byte_array)[i] = v->bytes[i];

  return SSH_BITVEC_OK;
}

void ssh_bitvector_destroy(SshBitVector v)
{
  if (v)
    {
      ssh_xfree(v->bytes);
      ssh_xfree(v);
    }
}

SshBitStatus ssh_bitvector_resize(SshBitVector v, SshUInt32 bit_count)
{
  unsigned char *t;
  SshUInt32 size = bit_count / 8 + 1;

  if (v->fixed_size)
    {
      return SSH_BITVEC_FIXED_SIZE;
    }

  if (size != v->size)
    {
      t = (unsigned char *)ssh_xrealloc(v->bytes, size);
      if (!t)
        {
          return SSH_BITVEC_ALLOC_ERROR;
        }
      v->bytes = t;
      v->size = size;
      v->bit_count = bit_count;
    }
  return SSH_BITVEC_OK;
}

/*
 * Internally used function to check bit indexes and also to allocate
 * more space for bits.
 *
 */
static SshBitStatus handle_bit_index(SshBitVector v, 
                                                                                                                                                 SshUInt32 bit_num, 
                                     Boolean can_alloc)
{
  SshUInt32 size;
  unsigned char *t;

  if (bit_num > v->bit_count - 1)
    {
      if (!can_alloc)
        {
          return SSH_BITVEC_INVALID_INDEX;
        }
      size = bit_num / 8 + 1;
      if (size > v->size)
        {
          t = ssh_xrealloc(v->bytes, size);
          if (!t)
            {
              return SSH_BITVEC_ALLOC_ERROR;
            }
          v->bytes = t;
          v->size = size;
          v->bit_count = bit_num;
        }
        
    }
  return SSH_BITVEC_OK;
}

SshBitStatus ssh_bitvector_set_bit(SshBitVector v, SshUInt32 bit_num)
{
  SshBitStatus r = handle_bit_index(v, bit_num, !v->fixed_size);
  if (r != SSH_BITVEC_OK)
    {
      return r;
    }
  v->bytes[bit_num / 8] |= (1 << (bit_num % 8));
  return SSH_BITVEC_OK;    
}


SshBitStatus ssh_bitvector_clear_bit(SshBitVector v, SshUInt32 bit_num)
{
  SshBitStatus r = handle_bit_index(v, bit_num, 0);

  if (r != SSH_BITVEC_OK)
    {
      if (v->fixed_size)
        return SSH_BITVEC_INVALID_INDEX;
    }
  else
    {
      v->bytes[bit_num / 8] &= ~(1 << (bit_num % 8));
    }
  return SSH_BITVEC_OK;    
}

SshBitStatus ssh_bitvector_query_bit(SshBitVector v, SshUInt32 bit_num)
{
  SshBitStatus r = handle_bit_index(v, bit_num, 0);

  if (r != SSH_BITVEC_OK)
    {
      if (v->fixed_size)
        return SSH_BITVEC_INVALID_INDEX;
      else
        return SSH_BITVEC_BIT_OFF;
    }
  else
    {
      if (v->bytes[bit_num / 8] & (1 << (bit_num % 8)))
        return SSH_BITVEC_BIT_ON;
      else
        return SSH_BITVEC_BIT_OFF;
    }
}

static int find_clear_bit_in_byte(unsigned char b)
{
  unsigned char mask = 0x01;
  int p = 0;

  if (b == 0) return 0;
  do
    { 
      if (!(b & mask))
        return p;
      mask<<=1;
      p++;
    }
  while (mask);
  return -1;
}

static void dump_byte_bits(unsigned char b, int *bits_left)
{
  unsigned char mask = 0x01;
  char s[9];
  int p = 0;

  do
    {
      if (b & mask)
        s[p++] = '1';
      else
        {
           s[p++] = '0';
        }
      (*bits_left)--;
      mask<<=1;
    }      
  while (mask && *bits_left);
  s[p] = '\0';
  printf("%s", s);
}

SshBitStatus ssh_bitvector_dump(SshBitVector v)
{
  int i, bits_left = v->bit_count;

  for (i=0; i<v->size; i++)
    dump_byte_bits(v->bytes[i], &bits_left);
  return SSH_BITVEC_OK;
}

SshBitStatus ssh_bitvector_find_first_clear_bit(SshBitVector v, SshUInt32 *bit_pos)
{
  SshUInt32 i;
  int p;

  for (i=0; i<v->size; i++)
    {
      p = find_clear_bit_in_byte(v->bytes[i]);
      if (p != -1) 
        {
          *bit_pos = i*8+p;
          return SSH_BITVEC_OK;
        }
    }
  if (v->fixed_size)
    return SSH_BITVEC_NOT_FOUND;
  *bit_pos = v->bit_count;
  return SSH_BITVEC_OK;  
}

SshUInt32 ssh_bitvector_get_bit_count(SshBitVector v)
{
  return v->bit_count;
}

