/*
 * 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.
 */

#include <folly/MacAddress.h>

#include <cassert>
#include <ostream>

#include <folly/Exception.h>
#include <folly/Format.h>
#include <folly/IPAddressV6.h>
#include <folly/String.h>

using std::invalid_argument;
using std::string;

namespace folly {

const MacAddress MacAddress::BROADCAST{Endian::big(uint64_t(0xffffffffffffU))};
const MacAddress MacAddress::ZERO;

MacAddress::MacAddress(StringPiece str) {
  memset(&bytes_, 0, 8);
  parse(str);
}

MacAddress MacAddress::createMulticast(IPAddressV6 v6addr) {
  // This method should only be used for multicast addresses.
  assert(v6addr.isMulticast());

  uint8_t bytes[SIZE];
  bytes[0] = 0x33;
  bytes[1] = 0x33;
  memcpy(bytes + 2, v6addr.bytes() + 12, 4);
  return fromBinary(ByteRange(bytes, SIZE));
}

string MacAddress::toString() const {
  static const char hexValues[] = "0123456789abcdef";
  string result;
  result.resize(17);
  result[0] = hexValues[getByte(0) >> 4];
  result[1] = hexValues[getByte(0) & 0xf];
  result[2] = ':';
  result[3] = hexValues[getByte(1) >> 4];
  result[4] = hexValues[getByte(1) & 0xf];
  result[5] = ':';
  result[6] = hexValues[getByte(2) >> 4];
  result[7] = hexValues[getByte(2) & 0xf];
  result[8] = ':';
  result[9] = hexValues[getByte(3) >> 4];
  result[10] = hexValues[getByte(3) & 0xf];
  result[11] = ':';
  result[12] = hexValues[getByte(4) >> 4];
  result[13] = hexValues[getByte(4) & 0xf];
  result[14] = ':';
  result[15] = hexValues[getByte(5) >> 4];
  result[16] = hexValues[getByte(5) & 0xf];
  return result;
}

Expected<Unit, MacAddressFormatError> MacAddress::trySetFromString(
    StringPiece value) {
  return setFromString(value, [](auto _, auto) { return makeUnexpected(_); });
}

void MacAddress::setFromString(StringPiece value) {
  setFromString(value, [](auto, auto _) { return _(), unit; });
}

Expected<Unit, MacAddressFormatError> MacAddress::trySetFromBinary(
    ByteRange value) {
  return setFromBinary(value, [](auto _, auto) { return makeUnexpected(_); });
}

void MacAddress::setFromBinary(ByteRange value) {
  setFromBinary(value, [](auto, auto _) { return _(), unit; });
}

template <typename OnError>
Expected<Unit, MacAddressFormatError> MacAddress::setFromString(
    StringPiece str,
    OnError err) {
  // Helper function to convert a single hex char into an integer
  auto isSeparatorChar = [](char c) { return c == ':' || c == '-'; };

  uint8_t parsed[SIZE];
  auto p = str.begin();
  for (unsigned int byteIndex = 0; byteIndex < SIZE; ++byteIndex) {
    if (p == str.end()) {
      return err(MacAddressFormatError::Invalid, [&] {
        throw invalid_argument(
            sformat("invalid MAC address '{}': not enough digits", str));
      });
    }

    // Skip over ':' or '-' separators between bytes
    if (byteIndex != 0 && isSeparatorChar(*p)) {
      ++p;
      if (p == str.end()) {
        return err(MacAddressFormatError::Invalid, [&] {
          throw invalid_argument(
              sformat("invalid MAC address '{}': not enough digits", str));
        });
      }
    }

    // Parse the upper nibble
    uint8_t upper = detail::hexTable[static_cast<uint8_t>(*p)];
    if (upper & 0x10) {
      return err(MacAddressFormatError::Invalid, [&] {
        throw invalid_argument(
            sformat("invalid MAC address '{}': contains non-hex digit", str));
      });
    }
    ++p;

    // Parse the lower nibble
    uint8_t lower;
    if (p == str.end()) {
      lower = upper;
      upper = 0;
    } else {
      lower = detail::hexTable[static_cast<uint8_t>(*p)];
      if (lower & 0x10) {
        // Also accept ':', '-', or '\0', to handle the case where one
        // of the bytes was represented by just a single digit.
        if (isSeparatorChar(*p)) {
          lower = upper;
          upper = 0;
        } else {
          return err(MacAddressFormatError::Invalid, [&] {
            throw invalid_argument(sformat(
                "invalid MAC address '{}': contains non-hex digit", str));
          });
        }
      }
      ++p;
    }

    // Update parsed with the newly parsed byte
    parsed[byteIndex] = (upper << 4) | lower;
  }

  if (p != str.end()) {
    // String is too long to be a MAC address
    return err(MacAddressFormatError::Invalid, [&] {
      throw invalid_argument(
          sformat("invalid MAC address '{}': found trailing characters", str));
    });
  }

  // Only update now that we have successfully parsed the entire
  // string.  This way we remain unchanged on error.
  return setFromBinary(ByteRange(parsed, SIZE), err);
}

template <typename OnError>
Expected<Unit, MacAddressFormatError> MacAddress::setFromBinary(
    ByteRange value,
    OnError err) {
  if (value.size() != SIZE) {
    return err(MacAddressFormatError::Invalid, [&] {
      throw invalid_argument(
          sformat("MAC address must be 6 bytes long, got ", value.size()));
    });
  }
  memcpy(bytes_ + 2, value.begin(), SIZE);
  return unit;
}

std::ostream& operator<<(std::ostream& os, MacAddress address) {
  os << address.toString();
  return os;
}

} // namespace folly
