/*
 * Copyright Red Hat
 *
 * 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/vdo-releases/aluminum/src/c++/vdo/base/fixedLayout.c#3 $
 */

#include "fixedLayout.h"

#include "buffer.h"
#include "logger.h"
#include "memoryAlloc.h"

#include "header.h"
#include "statusCodes.h"

const BlockCount ALL_FREE_BLOCKS = (uint64_t) -1;

struct fixedLayout {
  PhysicalBlockNumber  firstFree;
  PhysicalBlockNumber  lastFree;
  size_t               numPartitions;
  Partition           *head;
};

struct partition {
  PartitionID          id;     // The id of this partition
  FixedLayout         *layout; // The layout to which this partition belongs
  PhysicalBlockNumber  offset; // The offset into the layout of this partition
  PhysicalBlockNumber  base;   // The untranslated number of the first block
  BlockCount           count;  // The number of blocks in the partition
  Partition           *next;   // A pointer to the next partition in the layout
};

typedef struct {
  PhysicalBlockNumber firstFree;
  PhysicalBlockNumber lastFree;
  byte                partitionCount;
} __attribute__((packed)) Layout3_0;

typedef struct {
  PartitionID         id;
  PhysicalBlockNumber offset;
  PhysicalBlockNumber base;
  BlockCount          count;
} __attribute__((packed)) Partition3_0;

static const Header LAYOUT_HEADER_3_0 = {
  .id = FIXED_LAYOUT,
  .version = {
    .majorVersion = 3,
    .minorVersion = 0,
  },
  .size = sizeof(Layout3_0),   // Minimum size (contains no partitions)
};

/**********************************************************************/
int makeFixedLayout(BlockCount            totalBlocks,
                    PhysicalBlockNumber   startOffset,
                    FixedLayout         **layoutPtr)
{
  FixedLayout *layout;
  int result = ALLOCATE(1, FixedLayout, "fixed layout", &layout);
  if (result != UDS_SUCCESS) {
    return result;
  }

  layout->firstFree     = startOffset;
  layout->lastFree      = startOffset + totalBlocks;
  layout->numPartitions = 0;
  layout->head          = NULL;

  *layoutPtr = layout;
  return VDO_SUCCESS;
}

/**********************************************************************/
void freeFixedLayout(FixedLayout **layoutPtr)
{
  FixedLayout *layout = *layoutPtr;
  if (layout == NULL) {
    return;
  }

  while (layout->head != NULL) {
    Partition *part = layout->head;
    layout->head = part->next;
    FREE(part);
  }

  FREE(layout);
  *layoutPtr = NULL;
}

/**********************************************************************/
BlockCount getTotalFixedLayoutSize(const FixedLayout *layout)
{
  BlockCount size = getFixedLayoutBlocksAvailable(layout);
  for (Partition *partition = layout->head; partition != NULL;
       partition = partition->next) {
    size += partition->count;
  }

  return size;
}

/**********************************************************************/
int getPartition(FixedLayout *layout, PartitionID id, Partition **partitionPtr)
{
  for (Partition *partition = layout->head; partition != NULL;
       partition = partition->next) {
    if (partition->id == id) {
      if (partitionPtr != NULL) {
        *partitionPtr = partition;
      }
      return VDO_SUCCESS;
    }
  }

  return VDO_UNKNOWN_PARTITION;
}

/**********************************************************************/
int translateToPBN(const Partition     *partition,
                   PhysicalBlockNumber  partitionBlockNumber,
                   PhysicalBlockNumber *layerBlockNumber)
{
  if (partition == NULL) {
    *layerBlockNumber = partitionBlockNumber;
    return VDO_SUCCESS;
  }

  if (partitionBlockNumber < partition->base) {
    return VDO_OUT_OF_RANGE;
  }

  PhysicalBlockNumber offsetFromBase = partitionBlockNumber - partition->base;
  if (offsetFromBase >= partition->count) {
    return VDO_OUT_OF_RANGE;
  }

  *layerBlockNumber = partition->offset + offsetFromBase;
  return VDO_SUCCESS;
}

/**********************************************************************/
int translateFromPBN(const Partition     *partition,
                     PhysicalBlockNumber  layerBlockNumber,
                     PhysicalBlockNumber *partitionBlockNumberPtr)
{
  if (partition == NULL) {
    *partitionBlockNumberPtr = layerBlockNumber;
    return VDO_SUCCESS;
  }

  if (layerBlockNumber < partition->offset) {
    return VDO_OUT_OF_RANGE;
  }

  PhysicalBlockNumber partitionBlockNumber
    = layerBlockNumber - partition->offset;
  if (partitionBlockNumber >= partition->count) {
    return VDO_OUT_OF_RANGE;
  }

  *partitionBlockNumberPtr = partitionBlockNumber + partition->base;
  return VDO_SUCCESS;
}

/**********************************************************************/
BlockCount getFixedLayoutBlocksAvailable(const FixedLayout *layout)
{
  return layout->lastFree - layout->firstFree;
}

/**
 * Allocate a partition. The partition will be attached to the partition
 * list in the layout.
 *
 * @param layout     The layout containing the partition
 * @param id         The id of the partition
 * @param offset     The offset into the layout at which the partition begins
 * @param base       The number of the first block for users of the partition
 * @param blockCount The number of blocks in the partition
 *
 * @return VDO_SUCCESS or an error
 **/
static int allocatePartition(FixedLayout         *layout,
                             byte                 id,
                             PhysicalBlockNumber  offset,
                             PhysicalBlockNumber  base,
                             BlockCount           blockCount)
{
  Partition *partition;
  int result = ALLOCATE(1, Partition, "fixed layout partition", &partition);
  if (result != UDS_SUCCESS) {
    return result;
  }

  partition->id                  = id;
  partition->layout              = layout;
  partition->offset              = offset;
  partition->base                = base;
  partition->count               = blockCount;
  partition->next                = layout->head;
  layout->head                   = partition;

  return VDO_SUCCESS;
}

/**********************************************************************/
int makeFixedLayoutPartition(FixedLayout         *layout,
                             PartitionID          id,
                             BlockCount           blockCount,
                             PartitionDirection   direction,
                             PhysicalBlockNumber  base)
{
  BlockCount freeBlocks = layout->lastFree - layout->firstFree;
  if (blockCount == ALL_FREE_BLOCKS) {
    if (freeBlocks == 0) {
      return VDO_NO_SPACE;
    } else {
      blockCount = freeBlocks;
    }
  } else if (blockCount > freeBlocks) {
    return VDO_NO_SPACE;
  }

  int result = getPartition(layout, id, NULL);
  if (result != VDO_UNKNOWN_PARTITION) {
    return VDO_PARTITION_EXISTS;
  }

  PhysicalBlockNumber offset = ((direction == FROM_END)
                                ? (layout->lastFree - blockCount)
                                : layout->firstFree);
  result = allocatePartition(layout, id, offset, base, blockCount);
  if (result != VDO_SUCCESS) {
    return result;
  }

  layout->numPartitions++;
  if (direction == FROM_END) {
    layout->lastFree = layout->lastFree - blockCount;
  } else {
    layout->firstFree += blockCount;
  }

  return VDO_SUCCESS;
}

/**********************************************************************/
BlockCount getFixedLayoutPartitionSize(const Partition *partition)
{
  return partition->count;
}

/**********************************************************************/
PhysicalBlockNumber getFixedLayoutPartitionOffset(const Partition *partition)
{
  return partition->offset;
}

/**********************************************************************/
PhysicalBlockNumber getFixedLayoutPartitionBase(const Partition *partition)
{
  return partition->base;
}

/**********************************************************************/
static inline size_t getEncodedSize(const FixedLayout *layout)
{
  return sizeof(Layout3_0) + (sizeof(Partition3_0) * layout->numPartitions);
}

/**********************************************************************/
size_t getFixedLayoutEncodedSize(const FixedLayout *layout)
{
  return ENCODED_HEADER_SIZE + getEncodedSize(layout);
}

/**
 * Encode a null-terminated list of fixed layout partitions into a buffer
 * using partition format 3.0.
 *
 * @param layout  The layout containing the list of partitions to encode
 * @param buffer  A buffer positioned at the start of the encoding
 *
 * @return UDS_SUCCESS or an error code
 **/
static int encodePartitions_3_0(const FixedLayout *layout, Buffer *buffer)
{
  for (const Partition *partition = layout->head;
       partition != NULL;
       partition = partition->next) {
    STATIC_ASSERT_SIZEOF(PartitionID, sizeof(byte));
    int result = putByte(buffer, partition->id);
    if (result != UDS_SUCCESS) {
      return result;
    }

    result = putUInt64LEIntoBuffer(buffer, partition->offset);
    if (result != UDS_SUCCESS) {
      return result;
    }

    result = putUInt64LEIntoBuffer(buffer, partition->base);
    if (result != UDS_SUCCESS) {
      return result;
    }

    result = putUInt64LEIntoBuffer(buffer, partition->count);
    if (result != UDS_SUCCESS) {
      return result;
    }
  }

  return UDS_SUCCESS;
}

/**
 * Encode the header fields of a fixed layout into a buffer using layout
 * format 3.0.
 *
 * @param layout  The layout to encode
 * @param buffer  A buffer positioned at the start of the encoding
 *
 * @return UDS_SUCCESS or an error code
 **/
static int encodeLayout_3_0(const FixedLayout *layout, Buffer *buffer)
{
  int result = ASSERT(layout->numPartitions <= UINT8_MAX,
                      "fixed layout partition count must fit in a byte");
  if (result != UDS_SUCCESS) {
    return result;
  }

  result = putUInt64LEIntoBuffer(buffer, layout->firstFree);
  if (result != UDS_SUCCESS) {
    return result;
  }

  result = putUInt64LEIntoBuffer(buffer, layout->lastFree);
  if (result != UDS_SUCCESS) {
    return result;
  }

  return putByte(buffer, layout->numPartitions);
}

/**********************************************************************/
int encodeFixedLayout(const FixedLayout *layout, Buffer *buffer)
{
  if (!ensureAvailableSpace(buffer, getFixedLayoutEncodedSize(layout))) {
    return UDS_BUFFER_ERROR;
  }

  Header header = LAYOUT_HEADER_3_0;
  header.size = getEncodedSize(layout);
  int result = encodeHeader(&header, buffer);
  if (result != UDS_SUCCESS) {
    return result;
  }

  size_t initialLength = contentLength(buffer);

  result = encodeLayout_3_0(layout, buffer);
  if (result != UDS_SUCCESS) {
    return result;
  }

  size_t encodedSize = contentLength(buffer) - initialLength;
  result = ASSERT(encodedSize == sizeof(Layout3_0),
                "encoded size of fixed layout header must match structure");
  if (result != UDS_SUCCESS) {
    return result;
  }

  result = encodePartitions_3_0(layout, buffer);
  if (result != UDS_SUCCESS) {
    return result;
  }

  encodedSize = contentLength(buffer) - initialLength;
  return ASSERT(encodedSize == header.size,
                "encoded size of fixed layout must match header size");
}

/**
 * Decode a sequence of fixed layout partitions from a buffer
 * using partition format 3.0.
 *
 * @param buffer  A buffer positioned at the start of the encoding
 * @param layout  The layout in which to allocate the decoded partitions
 *
 * @return UDS_SUCCESS or an error code
 **/
static int decodePartitions_3_0(Buffer *buffer, FixedLayout *layout)
{
  for (size_t i = 0; i < layout->numPartitions; i++) {
    byte id;
    int result = getByte(buffer, &id);
    if (result != UDS_SUCCESS) {
      return result;
    }

    uint64_t offset;
    result = getUInt64LEFromBuffer(buffer, &offset);
    if (result != UDS_SUCCESS) {
      return result;
    }

    uint64_t base;
    result = getUInt64LEFromBuffer(buffer, &base);
    if (result != UDS_SUCCESS) {
      return result;
    }

    uint64_t count;
    result = getUInt64LEFromBuffer(buffer, &count);
    if (result != UDS_SUCCESS) {
      return result;
    }

    result = allocatePartition(layout, id, offset, base, count);
    if (result != VDO_SUCCESS) {
      return result;
    }
  }

  return UDS_SUCCESS;
}

/**
 * Decode the header fields of a fixed layout from a buffer using layout
 * format 3.0.
 *
 * @param buffer  A buffer positioned at the start of the encoding
 * @param layout  The structure to receive the decoded fields
 *
 * @return UDS_SUCCESS or an error code
 **/
static int decodeLayout_3_0(Buffer *buffer, Layout3_0 *layout)
{
  size_t initialLength = contentLength(buffer);

  PhysicalBlockNumber firstFree;
  int result = getUInt64LEFromBuffer(buffer, &firstFree);
  if (result != UDS_SUCCESS) {
    return result;
  }

  PhysicalBlockNumber lastFree;
  result = getUInt64LEFromBuffer(buffer, &lastFree);
  if (result != UDS_SUCCESS) {
    return result;
  }

  byte partitionCount;
  result = getByte(buffer, &partitionCount);
  if (result != UDS_SUCCESS) {
    return result;
  }

  *layout = (Layout3_0) {
    .firstFree      = firstFree,
    .lastFree       = lastFree,
    .partitionCount = partitionCount,
  };

  size_t decodedSize = initialLength - contentLength(buffer);
  return ASSERT(decodedSize == sizeof(Layout3_0),
                "decoded size of fixed layout header must match structure");
}

/**********************************************************************/
int decodeFixedLayout(Buffer *buffer, FixedLayout **layoutPtr)
{
  Header header;
  int result = decodeHeader(buffer, &header);
  if (result != UDS_SUCCESS) {
    return result;
  }

  // Layout is variable size, so only do a minimum size check here.
  result = validateHeader(&LAYOUT_HEADER_3_0, &header, false, __func__);
  if (result != VDO_SUCCESS) {
    return result;
  }

  Layout3_0 layoutHeader;
  result = decodeLayout_3_0(buffer, &layoutHeader);
  if (result != UDS_SUCCESS) {
    return result;
  }

  if (contentLength(buffer)
      < (sizeof(Partition3_0) * layoutHeader.partitionCount)) {
    return VDO_UNSUPPORTED_VERSION;
  }

  FixedLayout *layout;
  result = ALLOCATE(1, FixedLayout, "fixed layout", &layout);
  if (result != UDS_SUCCESS) {
    return result;
  }

  layout->firstFree     = layoutHeader.firstFree;
  layout->lastFree      = layoutHeader.lastFree;
  layout->numPartitions = layoutHeader.partitionCount;

  result = decodePartitions_3_0(buffer, layout);
  if (result != VDO_SUCCESS) {
    freeFixedLayout(&layout);
    return result;
  }

  *layoutPtr = layout;
  return VDO_SUCCESS;
}
