// Copyright 2024 The Pigweed Authors
//
// Licensed 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
//
//     https://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 "pw_grpc_private/hpack.h"

#include <array>

#include "pw_assert/check.h"
#include "pw_bytes/byte_builder.h"
#include "pw_log/log.h"
#include "pw_status/status.h"
#include "pw_status/try.h"
#include "pw_string/string_builder.h"
#include "pw_string/util.h"

namespace pw::grpc {

namespace {
#include "hpack.autogen.inc"
}

// RFC 7541 §5.1
Result<int> HpackIntegerDecode(ConstByteSpan& input, int bits_in_first_byte) {
  if (input.empty()) {
    return Status::InvalidArgument();
  }

  const int n = bits_in_first_byte;
  int i = static_cast<int>(input[0]) & ((1 << n) - 1);
  input = input.subspan(1);

  if (i < ((1 << n) - 1)) {
    return i;
  }

  int m = 0;
  while (true) {
    if (input.empty()) {
      return Status::InvalidArgument();
    }
    int b = static_cast<int>(input[0]);
    input = input.subspan(1);
    i += (b & 127) << m;
    m += 7;
    if ((b & 128) == 0) {
      return i;
    }
    if (m >= 31) {
      // Shift overflowed.
      return Status::InvalidArgument();
    }
  }
}

// RFC 7541 §5.2
Result<InlineString<kHpackMaxStringSize>> HpackStringDecode(
    ConstByteSpan& input) {
  if (input.empty()) {
    return Status::InvalidArgument();
  }

  int first = static_cast<int>(input[0]);
  bool is_huffman = (first & 0x80) != 0;

  PW_TRY_ASSIGN(size_t length, HpackIntegerDecode(input, 7));
  if (length > input.size()) {
    return Status::InvalidArgument();
  }
  if (length > kHpackMaxStringSize) {
    return Status::OutOfRange();
  }

  auto value = input.subspan(0, length);
  input = input.subspan(length);
  if (is_huffman) {
    return HpackHuffmanDecode(value);
  }
  return InlineString<kHpackMaxStringSize>(
      reinterpret_cast<const char*>(value.data()), value.size());
}

Result<InlineString<kHpackMaxStringSize>> HpackHuffmanDecode(
    ConstByteSpan input) {
  StringBuffer<kHpackMaxStringSize> buffer;
  int table_index = 0;

  // See definition of kHuffmanDecoderTable in hpack.autogen.h.
  for (std::byte byte : input) {
    for (int k = 7; k >= 0; k--) {
      auto bit = int(byte >> k) & 0x1;
      auto cmd = kHuffmanDecoderTable[table_index][bit];
      if ((cmd & 0b1000'0000) == 0) {
        table_index = cmd;
      } else if (cmd == 0b1111'1110 || cmd == 0b1111'1111) {
        // Error: unprintable character or the decoder entered an invalid state.
        return Status::InvalidArgument();
      } else {
        if (buffer.size() == buffer.max_size()) {
          return Status::OutOfRange();
        }
        buffer.push_back(32 + (cmd & 0b0111'1111));
        table_index = 0;
      }
    }
  }

  return InlineString<kHpackMaxStringSize>(buffer.view());
}

// RFC 7541 §6
Result<InlineString<kHpackMaxStringSize>> HpackParseRequestHeaders(
    ConstByteSpan input) {
  while (!input.empty()) {
    int first = static_cast<int>(input[0]);

    // RFC 7541 §6.1
    if ((first & 0b1000'0000) != 0) {
      PW_TRY_ASSIGN(int index, HpackIntegerDecode(input, 7));
      // RFC 7541 Appendix A: these are the only static table entries for :path.
      if (index == 4) {
        return "/";
      }
      if (index == 5) {
        return "/index.html";
      }
      continue;
    }

    // RFC 7541 §6.3: dynamic table size update
    if ((first & 0b1110'0000) == 0b0010'0000) {
      // Ignore: we don't use the dynamic table.
      PW_TRY(HpackIntegerDecode(input, 5));
      continue;
    }

    // RFC 7541 §6.2
    int index;
    if ((first & 0b1100'0000) == 0b0100'0000) {
      PW_TRY_ASSIGN(index, HpackIntegerDecode(input, 6));
    } else {
      PW_CHECK((first & 0b1111'0000) == 0b0000'0000 ||
               (first & 0b1111'0000) == 0b0001'0000);
      PW_TRY_ASSIGN(index, HpackIntegerDecode(input, 4));
    }

    // Check if the name is ":path".
    bool is_path;
    if (index == 0) {
      PW_TRY_ASSIGN(auto name, HpackStringDecode(input));
      is_path = (name == ":path");
    } else {
      // RFC 7541 Appendix A: these are the only static table entries for :path.
      is_path = (index == 4 || index == 5);
    }

    // Always extract the value to advance the `input` span.
    PW_TRY_ASSIGN(auto value, HpackStringDecode(input));
    if (is_path) {
      return value;
    }
  }

  return Status::NotFound();
}

ConstByteSpan ResponseHeadersPayload() {
  return as_bytes(span{kResponseHeaderFields});
}

ConstByteSpan ResponseTrailersPayload(Status response_code) {
  PW_CHECK_UINT_LT(response_code.code(), kResponseTrailerFields.size());
  auto* payload = &kResponseTrailerFields[response_code.code()];
  return as_bytes(span{payload->bytes}.subspan(0, payload->size));
}

}  // namespace pw::grpc
