// Copyright (c) 2019 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/quic/qbone/platform/ip_range.h"

#include "quiche/common/quiche_endian.h"

namespace quic {

namespace {

constexpr size_t kIPv4Size = 32;
constexpr size_t kIPv6Size = 128;

QuicIpAddress TruncateToLength(const QuicIpAddress& input,
                               size_t* prefix_length) {
  QuicIpAddress output;
  if (input.IsIPv4()) {
    if (*prefix_length > kIPv4Size) {
      *prefix_length = kIPv4Size;
      return input;
    }
    uint32_t raw_address =
        *reinterpret_cast<const uint32_t*>(input.ToPackedString().data());
    raw_address = quiche::QuicheEndian::NetToHost32(raw_address);
    raw_address &= ~0U << (kIPv4Size - *prefix_length);
    raw_address = quiche::QuicheEndian::HostToNet32(raw_address);
    output.FromPackedString(reinterpret_cast<const char*>(&raw_address),
                            sizeof(raw_address));
    return output;
  }
  if (input.IsIPv6()) {
    if (*prefix_length > kIPv6Size) {
      *prefix_length = kIPv6Size;
      return input;
    }
    uint64_t raw_address[2];
    memcpy(raw_address, input.ToPackedString().data(), sizeof(raw_address));
    // raw_address[0] holds higher 8 bytes in big endian and raw_address[1]
    // holds lower 8 bytes. Converting each to little endian for us to mask bits
    // out.
    // The endianess between raw_address[0] and raw_address[1] is handled
    // explicitly by handling lower and higher bytes separately.
    raw_address[0] = quiche::QuicheEndian::NetToHost64(raw_address[0]);
    raw_address[1] = quiche::QuicheEndian::NetToHost64(raw_address[1]);
    if (*prefix_length <= kIPv6Size / 2) {
      raw_address[0] &= ~uint64_t{0} << (kIPv6Size / 2 - *prefix_length);
      raw_address[1] = 0;
    } else {
      raw_address[1] &= ~uint64_t{0} << (kIPv6Size - *prefix_length);
    }
    raw_address[0] = quiche::QuicheEndian::HostToNet64(raw_address[0]);
    raw_address[1] = quiche::QuicheEndian::HostToNet64(raw_address[1]);
    output.FromPackedString(reinterpret_cast<const char*>(raw_address),
                            sizeof(raw_address));
    return output;
  }
  return output;
}

}  // namespace

IpRange::IpRange(const QuicIpAddress& prefix, size_t prefix_length)
    : prefix_(prefix), prefix_length_(prefix_length) {
  prefix_ = TruncateToLength(prefix_, &prefix_length_);
}

bool IpRange::operator==(IpRange other) const {
  return prefix_ == other.prefix_ && prefix_length_ == other.prefix_length_;
}

bool IpRange::operator!=(IpRange other) const { return !(*this == other); }

bool IpRange::FromString(const std::string& range) {
  size_t slash_pos = range.find('/');
  if (slash_pos == std::string::npos) {
    return false;
  }
  QuicIpAddress prefix;
  bool success = prefix.FromString(range.substr(0, slash_pos));
  if (!success) {
    return false;
  }
  uint64_t num_processed = 0;
  size_t prefix_length = std::stoi(range.substr(slash_pos + 1), &num_processed);
  if (num_processed + 1 + slash_pos != range.length()) {
    return false;
  }
  prefix_ = TruncateToLength(prefix, &prefix_length);
  prefix_length_ = prefix_length;
  return true;
}

QuicIpAddress IpRange::FirstAddressInRange() const { return prefix(); }

}  // namespace quic
