/*
 *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#include "net/dcsctp/packet/sctp_packet.h"

#include <stddef.h>

#include <cstdint>
#include <string>
#include <utility>
#include <vector>

#include "absl/memory/memory.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "net/dcsctp/common/math.h"
#include "net/dcsctp/packet/bounded_byte_reader.h"
#include "net/dcsctp/packet/bounded_byte_writer.h"
#include "net/dcsctp/packet/chunk/chunk.h"
#include "net/dcsctp/packet/crc32c.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_format.h"

namespace dcsctp {
namespace {
constexpr size_t kMaxUdpPacketSize = 65535;
constexpr size_t kChunkTlvHeaderSize = 4;
constexpr size_t kExpectedDescriptorCount = 4;
}  // namespace

/*
  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |     Source Port Number        |     Destination Port Number   |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                      Verification Tag                         |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                           Checksum                            |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

SctpPacket::Builder::Builder(VerificationTag verification_tag,
                             const DcSctpOptions& options)
    : verification_tag_(verification_tag),
      source_port_(options.local_port),
      dest_port_(options.remote_port),
      max_packet_size_(RoundDownTo4(options.mtu)) {}

SctpPacket::Builder& SctpPacket::Builder::Add(const Chunk& chunk) {
  if (out_.empty()) {
    out_.reserve(max_packet_size_);
    out_.resize(SctpPacket::kHeaderSize);
    BoundedByteWriter<kHeaderSize> buffer(out_);
    buffer.Store16<0>(source_port_);
    buffer.Store16<2>(dest_port_);
    buffer.Store32<4>(*verification_tag_);
    // Checksum is at offset 8 - written when calling Build(), if configured.
  }
  RTC_DCHECK(IsDivisibleBy4(out_.size()));

  chunk.SerializeTo(out_);
  if (out_.size() % 4 != 0) {
    out_.resize(RoundUpTo4(out_.size()));
  }

  RTC_DCHECK(out_.size() <= max_packet_size_)
      << "Exceeded max size, data=" << out_.size()
      << ", max_size=" << max_packet_size_;
  return *this;
}

size_t SctpPacket::Builder::bytes_remaining() const {
  if (out_.empty()) {
    // The packet header (CommonHeader) hasn't been written yet:
    return max_packet_size_ - kHeaderSize;
  } else if (out_.size() > max_packet_size_) {
    RTC_DCHECK_NOTREACHED() << "Exceeded max size, data=" << out_.size()
                            << ", max_size=" << max_packet_size_;
    return 0;
  }
  return max_packet_size_ - out_.size();
}

std::vector<uint8_t> SctpPacket::Builder::Build(bool write_checksum) {
  std::vector<uint8_t> out;
  out_.swap(out);

  if (!out.empty() && write_checksum) {
    uint32_t crc = GenerateCrc32C(out);
    BoundedByteWriter<kHeaderSize>(out).Store32<8>(crc);
  }

  RTC_DCHECK(out.size() <= max_packet_size_)
      << "Exceeded max size, data=" << out.size()
      << ", max_size=" << max_packet_size_;

  return out;
}

absl::optional<SctpPacket> SctpPacket::Parse(rtc::ArrayView<const uint8_t> data,
                                             const DcSctpOptions& options) {
  if (data.size() < kHeaderSize + kChunkTlvHeaderSize ||
      data.size() > kMaxUdpPacketSize) {
    RTC_DLOG(LS_WARNING) << "Invalid packet size";
    return absl::nullopt;
  }

  BoundedByteReader<kHeaderSize> reader(data);

  CommonHeader common_header;
  common_header.source_port = reader.Load16<0>();
  common_header.destination_port = reader.Load16<2>();
  common_header.verification_tag = VerificationTag(reader.Load32<4>());
  common_header.checksum = reader.Load32<8>();

  // Create a copy of the packet, which will be held by this object.
  std::vector<uint8_t> data_copy =
      std::vector<uint8_t>(data.begin(), data.end());

  if (options.disable_checksum_verification ||
      (options.zero_checksum_alternate_error_detection_method !=
           ZeroChecksumAlternateErrorDetectionMethod::None() &&
       common_header.checksum == 0u)) {
    // https://www.ietf.org/archive/id/draft-tuexen-tsvwg-sctp-zero-checksum-01.html#section-4.3:
    // If an end point has sent the Zero Checksum Acceptable Chunk Parameter in
    // an INIT or INIT ACK chunk, it MUST accept SCTP packets using an incorrect
    // checksum value of zero in addition to SCTP packets containing the correct
    // CRC32c checksum value for this association.
  } else {
    // Verify the checksum. The checksum field must be zero when that's done.
    BoundedByteWriter<kHeaderSize>(data_copy).Store32<8>(0);
    uint32_t calculated_checksum = GenerateCrc32C(data_copy);
    if (calculated_checksum != common_header.checksum) {
      RTC_DLOG(LS_WARNING) << rtc::StringFormat(
          "Invalid packet checksum, packet_checksum=0x%08x, "
          "calculated_checksum=0x%08x",
          common_header.checksum, calculated_checksum);
      return absl::nullopt;
    }
    // Restore the checksum in the header.
    BoundedByteWriter<kHeaderSize>(data_copy).Store32<8>(
        common_header.checksum);
  }

  // Validate and parse the chunk headers in the message.
  /*
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Chunk Type  | Chunk  Flags  |        Chunk Length           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  */

  std::vector<ChunkDescriptor> descriptors;
  descriptors.reserve(kExpectedDescriptorCount);
  rtc::ArrayView<const uint8_t> descriptor_data =
      rtc::ArrayView<const uint8_t>(data_copy).subview(kHeaderSize);
  while (!descriptor_data.empty()) {
    if (descriptor_data.size() < kChunkTlvHeaderSize) {
      RTC_DLOG(LS_WARNING) << "Too small chunk";
      return absl::nullopt;
    }
    BoundedByteReader<kChunkTlvHeaderSize> chunk_header(descriptor_data);
    uint8_t type = chunk_header.Load8<0>();
    uint8_t flags = chunk_header.Load8<1>();
    uint16_t length = chunk_header.Load16<2>();
    uint16_t padded_length = RoundUpTo4(length);
    if (padded_length > descriptor_data.size()) {
      RTC_DLOG(LS_WARNING) << "Too large chunk. length=" << length
                           << ", remaining=" << descriptor_data.size();
      return absl::nullopt;
    } else if (padded_length < kChunkTlvHeaderSize) {
      RTC_DLOG(LS_WARNING) << "Too small chunk. length=" << length;
      return absl::nullopt;
    }
    descriptors.emplace_back(type, flags,
                             descriptor_data.subview(0, padded_length));
    descriptor_data = descriptor_data.subview(padded_length);
  }

  // Note that iterators (and pointer) are guaranteed to be stable when moving a
  // std::vector, and `descriptors` have pointers to within `data_copy`.
  return SctpPacket(common_header, std::move(data_copy),
                    std::move(descriptors));
}
}  // namespace dcsctp
