/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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
 *
 *     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.
 */

#pragma once

#include <iosfwd>

#include <folly/Conv.h>
#include <folly/Expected.h>
#include <folly/Range.h>
#include <folly/Unit.h>
#include <folly/lang/Bits.h>

namespace folly {

class IPAddressV6;

enum class MacAddressFormatError {
  Invalid,
};

/*
 * MacAddress represents an IEEE 802 MAC address.
 */
class MacAddress {
 public:
  static constexpr size_t SIZE = 6;
  static const MacAddress BROADCAST;
  static const MacAddress ZERO;

  /*
   * Construct a zero-initialized MacAddress.
   */
  MacAddress() { memset(&bytes_, 0, 8); }

  /*
   * Parse a MacAddress from a human-readable string.
   * The string must contain 6 one- or two-digit hexadecimal
   * numbers, separated by dashes or colons.
   * Examples: 00:02:C9:C8:F9:68 or 0-2-c9-c8-f9-68
   */
  explicit MacAddress(StringPiece str);

  static Expected<MacAddress, MacAddressFormatError> tryFromString(
      StringPiece value) {
    MacAddress ret;
    auto ok = ret.trySetFromString(value);
    if (!ok) {
      return makeUnexpected(ok.error());
    }
    return ret;
  }
  static MacAddress fromString(StringPiece value) {
    MacAddress ret;
    ret.setFromString(value);
    return ret;
  }

  /*
   * Construct a MAC address from its 6-byte binary value
   */
  static Expected<MacAddress, MacAddressFormatError> tryFromBinary(
      ByteRange value) {
    MacAddress ret;
    auto ok = ret.trySetFromBinary(value);
    if (!ok) {
      return makeUnexpected(ok.error());
    }
    return ret;
  }
  static MacAddress fromBinary(ByteRange value) {
    MacAddress ret;
    ret.setFromBinary(value);
    return ret;
  }

  /*
   * Construct a MacAddress from a uint64_t in network byte order.
   *
   * The first two bytes are ignored, and the MAC address is taken from the
   * latter 6 bytes.
   *
   * This is a static method rather than a constructor to avoid confusion
   * between host and network byte order constructors.
   */
  static MacAddress fromNBO(uint64_t value) { return MacAddress(value); }

  /*
   * Construct a MacAddress from a uint64_t in host byte order.
   *
   * The most significant two bytes are ignored, and the MAC address is taken
   * from the least significant 6 bytes.
   *
   * This is a static method rather than a constructor to avoid confusion
   * between host and network byte order constructors.
   */
  static MacAddress fromHBO(uint64_t value) {
    return MacAddress(Endian::big(value));
  }

  /*
   * Construct the multicast MacAddress for the specified multicast IPv6
   * address.
   */
  static MacAddress createMulticast(IPAddressV6 addr);

  /*
   * Get a pointer to the MAC address' binary value.
   *
   * The returned value points to internal storage inside the MacAddress
   * object.  It is only valid as long as the MacAddress, and its contents may
   * change if the MacAddress is updated.
   */
  const uint8_t* bytes() const { return bytes_ + 2; }

  /*
   * Return the address as a uint64_t, in network byte order.
   *
   * The first two bytes will be 0, and the subsequent 6 bytes will contain
   * the address in network byte order.
   */
  uint64_t u64NBO() const { return packedBytes(); }

  /*
   * Return the address as a uint64_t, in host byte order.
   *
   * The two most significant bytes will be 0, and the remaining 6 bytes will
   * contain the address.  The most significant of these 6 bytes will contain
   * the first byte that appear on the wire, and the least significant byte
   * will contain the last byte.
   */
  uint64_t u64HBO() const {
    // Endian::big() does what we want here, even though we are converting
    // from big-endian to host byte order.  This swaps if and only if
    // the host byte order is little endian.
    return Endian::big(packedBytes());
  }

  /*
   * Return a human-readable representation of the MAC address.
   */
  std::string toString() const;

  /*
   * Update the current MacAddress object from a human-readable string.
   */
  Expected<Unit, MacAddressFormatError> trySetFromString(StringPiece value);
  void setFromString(StringPiece value);
  void parse(StringPiece str) { setFromString(str); }

  /*
   * Update the current MacAddress object from a 6-byte binary representation.
   */
  Expected<Unit, MacAddressFormatError> trySetFromBinary(ByteRange value);
  void setFromBinary(ByteRange value);

  bool isBroadcast() const { return *this == BROADCAST; }
  bool isMulticast() const { return getByte(0) & 0x1; }
  bool isUnicast() const { return !isMulticast(); }

  /*
   * Return true if this MAC address is locally administered.
   *
   * Locally administered addresses are assigned by the local network
   * administrator, and are not guaranteed to be globally unique.  (It is
   * similar to IPv4's private address space.)
   *
   * Note that isLocallyAdministered() will return true for the broadcast
   * address, since it has the locally administered bit set.
   */
  bool isLocallyAdministered() const { return getByte(0) & 0x2; }

  // Comparison operators.

  bool operator==(const MacAddress& other) const {
    // All constructors and modifying methods make sure padding is 0,
    // so we don't need to mask these bytes out when comparing here.
    return packedBytes() == other.packedBytes();
  }

  bool operator<(const MacAddress& other) const {
    return u64HBO() < other.u64HBO();
  }

  bool operator!=(const MacAddress& other) const { return !(*this == other); }

  bool operator>(const MacAddress& other) const { return other < *this; }

  bool operator>=(const MacAddress& other) const { return !(*this < other); }

  bool operator<=(const MacAddress& other) const { return !(*this > other); }

 private:
  explicit MacAddress(uint64_t valueNBO) {
    memcpy(&bytes_, &valueNBO, 8);
    // Set the pad bytes to 0.
    // This allows us to easily compare two MacAddresses,
    // without having to worry about differences in the padding.
    bytes_[0] = 0;
    bytes_[1] = 0;
  }

  template <typename OnError>
  Expected<Unit, MacAddressFormatError> setFromString(
      StringPiece value, OnError err);

  template <typename OnError>
  Expected<Unit, MacAddressFormatError> setFromBinary(
      ByteRange value, OnError err);

  /* We store the 6 bytes starting at bytes_[2] (most significant)
     through bytes_[7] (least).
     bytes_[0] and bytes_[1] are always equal to 0 to simplify comparisons.
  */
  unsigned char bytes_[8];

  inline uint64_t getByte(size_t index) const { return bytes_[index + 2]; }

  uint64_t packedBytes() const {
    uint64_t u64;
    memcpy(&u64, bytes_, 8);
    return u64;
  }
};

/* Define toAppend() so to<string> will work */
template <class Tgt>
typename std::enable_if<IsSomeString<Tgt>::value>::type toAppend(
    MacAddress address, Tgt* result) {
  toAppend(address.toString(), result);
}

std::ostream& operator<<(std::ostream& os, MacAddress address);

} // namespace folly

namespace std {

// Provide an implementation for std::hash<MacAddress>
template <>
struct hash<folly::MacAddress> {
  size_t operator()(const folly::MacAddress& address) const {
    return std::hash<uint64_t>()(address.u64HBO());
  }
};

} // namespace std
