/*
 * Copyright (c) 2018 Red Hat, Inc.
 *
 * 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.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * $Id: //eng/uds-releases/flanders/src/uds/bits.h#2 $
 */

#ifndef BITS_H
#define BITS_H 1

#include "compiler.h"
#include "typeDefs.h"

/*
 * These bit stream and bit field utility routines are used for the
 * non-byte aligned delta indices.
 *
 * Bits and bytes are numbered in little endian order.  For example: Within
 * a byte, bit 0 is the least significant bit (0x1), and bit 7 is the most
 * significant bit (0x80).  Within a bit stream, bit 7 is the most
 * signficant bit of byte 0, and bit 8 is the least significant bit of byte
 * 1.  Within a byte array, a byte's number corresponds to it's index in
 * the array.
 *
 * The implementation assumes that the native machine is little endian, and
 * that performance is very important.  These assumptions match our current
 * operating environment.
 */

/**
 * This is the largest field size supported by getField & setField.  Any
 * field that is larger is not guaranteed to fit in a single, byte aligned
 * uint32_t.
 **/
enum { MAX_FIELD_BITS = (sizeof(uint32_t) - 1) * CHAR_BIT + 1 };

/**
 * This is the number of guard bytes needed at the end of the memory byte
 * array when using the bit utilities.  3 bytes are needed when getField &
 * setField access a field, because they will access some "extra" bytes
 * past the end of the field.  And 7 bytes are needed when getBigField &
 * setBigField access a big field, for the same reason.  Note that moveBits
 * calls getBigField & setBigField.  7 is rewritten to make it clear how it
 * is derived.
 **/
enum { POST_FIELD_GUARD_BYTES = sizeof(uint64_t) - 1 };

/**
 * Get a bit field from a bit stream
 *
 * @param memory  The base memory byte address
 * @param offset  The bit offset into the memory for the start of the field
 * @param size    The number of bits in the field
 *
 * @return the bit field
 **/
static INLINE unsigned int getField(const byte *memory, uint64_t offset,
                                    int size)
{
  const uint32_t *addr = (const uint32_t *) (memory + offset / CHAR_BIT);
  return (*addr >> (offset % CHAR_BIT)) & ((1 << size) - 1);
}

/**
 * Set a bit field in a bit stream
 *
 * @param value   The value to put into the field
 * @param memory  The base memory byte address
 * @param offset  The bit offset into the memory for the start of the field
 * @param size    The number of bits in the field
 *
 * @return the bit field
 **/
static INLINE void setField(unsigned int value, byte *memory, uint64_t offset,
                            int size)
{
  uint32_t *addr = (uint32_t *) (memory + offset / CHAR_BIT);
  int shift = offset % CHAR_BIT;
  *addr &= ~(((1 << size) - 1) << shift);
  *addr |= value << shift;
}

/**
 * Set a bit field in a bit stream to all ones
 *
 * @param memory  The base memory byte address
 * @param offset  The bit offset into the memory for the start of the field
 * @param size    The number of bits in the field
 *
 * @return the bit field
 **/
static INLINE void setOne(byte *memory, uint64_t offset, int size)
{
  if (size > 0) {
    byte *addr = memory + offset / CHAR_BIT;
    int shift = offset % CHAR_BIT;
    int count = size + shift > CHAR_BIT ? CHAR_BIT - shift : size;
    *addr++ |= ((1 << count) - 1) << shift;
    for (size -= count; size > CHAR_BIT; size -= CHAR_BIT) {
      *addr++ = 0xFF;
    }
    if (size) {
      *addr |= ~(0xFF << size);
    }
  }
}

/**
 * Set a bit field in a bit stream to all zeros
 *
 * @param memory  The base memory byte address
 * @param offset  The bit offset into the memory for the start of the field
 * @param size    The number of bits in the field
 *
 * @return the bit field
 **/
static INLINE void setZero(byte *memory, uint64_t offset, int size)
{
  if (size > 0) {
    byte *addr = memory + offset / CHAR_BIT;
    int shift = offset % CHAR_BIT;
    int count = size + shift > CHAR_BIT ? CHAR_BIT - shift : size;
    *addr++ &= ~(((1 << count) - 1) << shift);
    for (size -= count; size > CHAR_BIT; size -= CHAR_BIT) {
      *addr++ = 0;
    }
    if (size) {
      *addr &= 0xFF << size;
    }
  }
}

/**
 * Get a byte stream from a bit stream, reading a whole number of bytes
 * from an arbitrary bit boundary.
 *
 * @param memory       The base memory byte address for the bit stream
 * @param offset       The bit offset of the start of the bit stream
 * @param destination  Where to store the bytes
 * @param size         The number of bytes
 **/
void getBytes(const byte *memory, uint64_t offset, byte *destination, int size);

/**
 * Store a byte stream into a bit stream, writing a whole number of bytes
 * to an arbitrary bit boundary.
 *
 * @param memory  The base memory byte address for the bit stream
 * @param offset  The bit offset of the start of the bit stream
 * @param source  Where to read the bytes
 * @param size    The number of bytes
 **/
void setBytes(byte *memory, uint64_t offset, const byte *source, int size);

/**
 * Move bits from one field to another.  When the fields overlap, behave as
 * if we first move all the bits from the source to a temporary value, and
 * then move all the bits from the temporary value to the destination.
 *
 * @param sMemory         The base source memory byte address
 * @param source          Bit offset into memory for the source start
 * @param dMemory         The base destination memory byte address
 * @param destination     Bit offset into memory for the destination start
 * @param size            The number of bits in the field
 **/
void moveBits(const byte *sMemory, uint64_t source, byte *dMemory,
              uint64_t destination, int size);

/**
 * Compare bits from one field to another, testing for sameness
 *
 * @param mem1     The base memory byte address (first field)
 * @param offset1  Bit offset into the memory for the start (first field)
 * @param mem2     The base memory byte address (second field)
 * @param offset2  Bit offset into the memory for the start (second field)
 * @param size     The number of bits in the field
 *
 * @return true if fields are the same, false if different
 **/
bool sameBits(const byte *mem1, uint64_t offset1, const byte *mem2,
              uint64_t offset2, int size)
  __attribute__((warn_unused_result));

#endif /* BITS_H */
