/** @file

  Common functions for HPACK and QPACK

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 */

#include "tscore/Arena.h"
#include "tscore/ink_memory.h"
#include "XPACK.h"
#include "HuffmanCodec.h"

//
// [RFC 7541] 5.1. Integer representation
//
int64_t
xpack_decode_integer(uint64_t &dst, const uint8_t *buf_start, const uint8_t *buf_end, uint8_t n)
{
  if (buf_start >= buf_end) {
    return XPACK_ERROR_COMPRESSION_ERROR;
  }

  const uint8_t *p = buf_start;

  dst = (*p & ((1 << n) - 1));
  if (dst == static_cast<uint64_t>(1 << n) - 1) {
    int m = 0;
    do {
      if (++p >= buf_end) {
        return XPACK_ERROR_COMPRESSION_ERROR;
      }

      uint64_t added_value = *p & 0x7f;
      if ((UINT64_MAX >> m) < added_value) {
        // Excessively large integer encodings - in value or octet
        // length - MUST be treated as a decoding error.
        return XPACK_ERROR_COMPRESSION_ERROR;
      }
      dst += added_value << m;
      m += 7;
    } while (*p & 0x80);
  }

  return p - buf_start + 1;
}

//
// [RFC 7541] 5.2. String Literal Representation
// return content from String Data (Length octets) with huffman decoding if it is encoded
//
int64_t
xpack_decode_string(Arena &arena, char **str, uint64_t &str_length, const uint8_t *buf_start, const uint8_t *buf_end, uint8_t n)
{
  if (buf_start >= buf_end) {
    return XPACK_ERROR_COMPRESSION_ERROR;
  }

  const uint8_t *p            = buf_start;
  bool isHuffman              = *p & (0x01 << n);
  uint64_t encoded_string_len = 0;
  int64_t len                 = 0;

  len = xpack_decode_integer(encoded_string_len, p, buf_end, n);
  if (len == XPACK_ERROR_COMPRESSION_ERROR) {
    return XPACK_ERROR_COMPRESSION_ERROR;
  }
  p += len;

  if (buf_end < p || static_cast<uint64_t>(buf_end - p) < encoded_string_len) {
    return XPACK_ERROR_COMPRESSION_ERROR;
  }

  if (isHuffman) {
    // Allocate temporary area twice the size of before decoded data
    *str = arena.str_alloc(encoded_string_len * 2);

    len = huffman_decode(*str, p, encoded_string_len);
    if (len < 0) {
      return XPACK_ERROR_COMPRESSION_ERROR;
    }
    str_length = len;
  } else {
    *str = arena.str_alloc(encoded_string_len);

    memcpy(*str, reinterpret_cast<const char *>(p), encoded_string_len);

    str_length = encoded_string_len;
  }

  return p + encoded_string_len - buf_start;
}

//
// [RFC 7541] 5.1. Integer representation
//
int64_t
xpack_encode_integer(uint8_t *buf_start, const uint8_t *buf_end, uint64_t value, uint8_t n)
{
  if (buf_start >= buf_end) {
    return -1;
  }

  uint8_t *p = buf_start;

  // Preserve the first n bits
  uint8_t prefix = *buf_start & (0xFF << n);

  if (value < (static_cast<uint64_t>(1 << n) - 1)) {
    *(p++) = value;
  } else {
    *(p++) = (1 << n) - 1;
    value -= (1 << n) - 1;
    while (value >= 128) {
      if (p >= buf_end) {
        return -1;
      }
      *(p++) = (value & 0x7F) | 0x80;
      value  = value >> 7;
    }
    if (p + 1 >= buf_end) {
      return -1;
    }
    *(p++) = value;
  }

  // Restore the prefix
  *buf_start |= prefix;

  return p - buf_start;
}

int64_t
xpack_encode_string(uint8_t *buf_start, const uint8_t *buf_end, const char *value, uint64_t value_len, uint8_t n)
{
  uint8_t *p       = buf_start;
  bool use_huffman = true;
  char *data       = nullptr;
  int64_t data_len = 0;

  // TODO Choose whether to use Huffman encoding wisely
  // cppcheck-suppress knownConditionTrueFalse; leaving "use_huffman" for wise huffman usage in the future
  if (use_huffman && value_len) {
    data = static_cast<char *>(ats_malloc(value_len * 4));
    if (data == nullptr) {
      return -1;
    }
    data_len = huffman_encode(reinterpret_cast<uint8_t *>(data), reinterpret_cast<const uint8_t *>(value), value_len);
  }

  // Length
  const int64_t len = xpack_encode_integer(p, buf_end, data_len, n);
  if (len == -1) {
    if (use_huffman) {
      ats_free(data);
    }

    return -1;
  }

  if (use_huffman) {
    *p |= 0x01 << n;
  } else {
    *p &= ~(0x01 << n);
  }
  p += len;

  if (buf_end < p || buf_end - p < data_len) {
    if (use_huffman) {
      ats_free(data);
    }

    return -1;
  }

  // Value
  if (data_len) {
    memcpy(p, data, data_len);
    p += data_len;
  }

  if (use_huffman) {
    ats_free(data);
  }

  return p - buf_start;
}
