// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "quiche/http2/test_tools/http2_frame_builder.h"

#ifdef WIN32
#include <winsock2.h>  // for htonl() functions
#else
#include <arpa/inet.h>
#include <netinet/in.h>  // for htonl, htons
#endif

#include "absl/strings/str_cat.h"
#include "quiche/common/platform/api/quiche_test.h"

namespace http2 {
namespace test {

Http2FrameBuilder::Http2FrameBuilder(Http2FrameType type, uint8_t flags,
                                     uint32_t stream_id) {
  AppendUInt24(0);  // Frame payload length, unknown so far.
  Append(type);
  AppendUInt8(flags);
  AppendUInt31(stream_id);
}

Http2FrameBuilder::Http2FrameBuilder(const Http2FrameHeader& v) { Append(v); }

void Http2FrameBuilder::Append(absl::string_view s) {
  absl::StrAppend(&buffer_, s);
}

void Http2FrameBuilder::AppendBytes(const void* data, uint32_t num_bytes) {
  Append(absl::string_view(static_cast<const char*>(data), num_bytes));
}

void Http2FrameBuilder::AppendZeroes(size_t num_zero_bytes) {
  char zero = 0;
  buffer_.append(num_zero_bytes, zero);
}

void Http2FrameBuilder::AppendUInt8(uint8_t value) { AppendBytes(&value, 1); }

void Http2FrameBuilder::AppendUInt16(uint16_t value) {
  value = htons(value);
  AppendBytes(&value, 2);
}

void Http2FrameBuilder::AppendUInt24(uint32_t value) {
  // Doesn't make sense to try to append a larger value, as that doesn't
  // simulate something an encoder could do (i.e. the other 8 bits simply aren't
  // there to be occupied).
  EXPECT_EQ(value, value & 0xffffff);
  value = htonl(value);
  AppendBytes(reinterpret_cast<char*>(&value) + 1, 3);
}

void Http2FrameBuilder::AppendUInt31(uint32_t value) {
  // If you want to test the high-bit being set, call AppendUInt32 instead.
  uint32_t tmp = value & StreamIdMask();
  EXPECT_EQ(value, value & StreamIdMask())
      << "High-bit of uint32_t should be clear.";
  value = htonl(tmp);
  AppendBytes(&value, 4);
}

void Http2FrameBuilder::AppendUInt32(uint32_t value) {
  value = htonl(value);
  AppendBytes(&value, sizeof(value));
}

void Http2FrameBuilder::Append(Http2ErrorCode error_code) {
  AppendUInt32(static_cast<uint32_t>(error_code));
}

void Http2FrameBuilder::Append(Http2FrameType type) {
  AppendUInt8(static_cast<uint8_t>(type));
}

void Http2FrameBuilder::Append(Http2SettingsParameter parameter) {
  AppendUInt16(static_cast<uint16_t>(parameter));
}

void Http2FrameBuilder::Append(const Http2FrameHeader& v) {
  AppendUInt24(v.payload_length);
  Append(v.type);
  AppendUInt8(v.flags);
  AppendUInt31(v.stream_id);
}

void Http2FrameBuilder::Append(const Http2PriorityFields& v) {
  // The EXCLUSIVE flag is the high-bit of the 32-bit stream dependency field.
  uint32_t tmp = v.stream_dependency & StreamIdMask();
  EXPECT_EQ(tmp, v.stream_dependency);
  if (v.is_exclusive) {
    tmp |= 0x80000000;
  }
  AppendUInt32(tmp);

  // The PRIORITY frame's weight field is logically in the range [1, 256],
  // but is encoded as a byte in the range [0, 255].
  ASSERT_LE(1u, v.weight);
  ASSERT_LE(v.weight, 256u);
  AppendUInt8(v.weight - 1);
}

void Http2FrameBuilder::Append(const Http2RstStreamFields& v) {
  Append(v.error_code);
}

void Http2FrameBuilder::Append(const Http2SettingFields& v) {
  Append(v.parameter);
  AppendUInt32(v.value);
}

void Http2FrameBuilder::Append(const Http2PushPromiseFields& v) {
  AppendUInt31(v.promised_stream_id);
}

void Http2FrameBuilder::Append(const Http2PingFields& v) {
  AppendBytes(v.opaque_bytes, sizeof Http2PingFields::opaque_bytes);
}

void Http2FrameBuilder::Append(const Http2GoAwayFields& v) {
  AppendUInt31(v.last_stream_id);
  Append(v.error_code);
}

void Http2FrameBuilder::Append(const Http2WindowUpdateFields& v) {
  EXPECT_NE(0u, v.window_size_increment) << "Increment must be non-zero.";
  AppendUInt31(v.window_size_increment);
}

void Http2FrameBuilder::Append(const Http2AltSvcFields& v) {
  AppendUInt16(v.origin_length);
}

void Http2FrameBuilder::Append(const Http2PriorityUpdateFields& v) {
  AppendUInt31(v.prioritized_stream_id);
}

// Methods for changing existing buffer contents.

void Http2FrameBuilder::WriteAt(absl::string_view s, size_t offset) {
  ASSERT_LE(offset, buffer_.size());
  size_t len = offset + s.size();
  if (len > buffer_.size()) {
    buffer_.resize(len);
  }
  for (size_t ndx = 0; ndx < s.size(); ++ndx) {
    buffer_[offset + ndx] = s[ndx];
  }
}

void Http2FrameBuilder::WriteBytesAt(const void* data, uint32_t num_bytes,
                                     size_t offset) {
  WriteAt(absl::string_view(static_cast<const char*>(data), num_bytes), offset);
}

void Http2FrameBuilder::WriteUInt24At(uint32_t value, size_t offset) {
  ASSERT_LT(value, static_cast<uint32_t>(1 << 24));
  value = htonl(value);
  WriteBytesAt(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1, offset);
}

void Http2FrameBuilder::SetPayloadLength(uint32_t payload_length) {
  WriteUInt24At(payload_length, 0);
}

size_t Http2FrameBuilder::SetPayloadLength() {
  EXPECT_GE(size(), Http2FrameHeader::EncodedSize());
  uint32_t payload_length = size() - Http2FrameHeader::EncodedSize();
  SetPayloadLength(payload_length);
  return payload_length;
}

}  // namespace test
}  // namespace http2
