// Copyright 2017 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 "net/ntlm/ntlm_buffer_reader.h"

#include <string.h>

#include "base/logging.h"

namespace net {
namespace ntlm {

NtlmBufferReader::NtlmBufferReader() {}

NtlmBufferReader::NtlmBufferReader(base::span<const uint8_t> buffer)
    : buffer_(buffer) {}

NtlmBufferReader::~NtlmBufferReader() = default;

bool NtlmBufferReader::CanRead(size_t len) const {
  return CanReadFrom(GetCursor(), len);
}

bool NtlmBufferReader::CanReadFrom(size_t offset, size_t len) const {
  if (len == 0)
    return true;

  return (len <= GetLength() && offset <= GetLength() - len);
}

bool NtlmBufferReader::ReadUInt16(uint16_t* value) {
  return ReadUInt<uint16_t>(value);
}

bool NtlmBufferReader::ReadUInt32(uint32_t* value) {
  return ReadUInt<uint32_t>(value);
}

bool NtlmBufferReader::ReadUInt64(uint64_t* value) {
  return ReadUInt<uint64_t>(value);
}

bool NtlmBufferReader::ReadFlags(NegotiateFlags* flags) {
  uint32_t raw;
  if (!ReadUInt32(&raw))
    return false;

  *flags = static_cast<NegotiateFlags>(raw);
  return true;
}

bool NtlmBufferReader::ReadBytes(base::span<uint8_t> buffer) {
  if (!CanRead(buffer.size()))
    return false;

  if (buffer.empty())
    return true;

  memcpy(buffer.data(), GetBufferAtCursor(), buffer.size());

  AdvanceCursor(buffer.size());
  return true;
}

bool NtlmBufferReader::ReadBytesFrom(const SecurityBuffer& sec_buf,
                                     base::span<uint8_t> buffer) {
  if (!CanReadFrom(sec_buf) || buffer.size() < sec_buf.length)
    return false;

  if (buffer.empty())
    return true;

  memcpy(buffer.data(), GetBufferPtr() + sec_buf.offset, sec_buf.length);

  return true;
}

bool NtlmBufferReader::ReadPayloadAsBufferReader(const SecurityBuffer& sec_buf,
                                                 NtlmBufferReader* reader) {
  if (!CanReadFrom(sec_buf))
    return false;

  *reader = NtlmBufferReader(
      base::make_span(GetBufferPtr() + sec_buf.offset, sec_buf.length));
  return true;
}

bool NtlmBufferReader::ReadSecurityBuffer(SecurityBuffer* sec_buf) {
  return ReadUInt16(&sec_buf->length) && SkipBytes(sizeof(uint16_t)) &&
         ReadUInt32(&sec_buf->offset);
}

bool NtlmBufferReader::ReadAvPairHeader(TargetInfoAvId* avid, uint16_t* avlen) {
  if (!CanRead(kAvPairHeaderLen))
    return false;

  uint16_t raw_avid;
  bool result = ReadUInt16(&raw_avid) && ReadUInt16(avlen);
  DCHECK(result);

  // Don't try and validate the avid because the code only cares about a few
  // specific ones and it is likely a future version might extend this field.
  // The implementation can ignore and skip over AV Pairs it doesn't
  // understand.
  *avid = static_cast<TargetInfoAvId>(raw_avid);

  return true;
}

bool NtlmBufferReader::ReadTargetInfo(size_t target_info_len,
                                      std::vector<AvPair>* av_pairs) {
  DCHECK(av_pairs->empty());

  // A completely empty target info is allowed.
  if (target_info_len == 0)
    return true;

  // If there is any content there has to be at least one terminating header.
  if (!CanRead(target_info_len) || target_info_len < kAvPairHeaderLen) {
    return false;
  }

  size_t target_info_end = GetCursor() + target_info_len;
  bool saw_eol = false;

  while ((GetCursor() < target_info_end)) {
    AvPair pair;
    if (!ReadAvPairHeader(&pair.avid, &pair.avlen))
      break;

    // Make sure the length wouldn't read outside the buffer.
    if (!CanRead(pair.avlen))
      return false;

    // Take a copy of the payload in the AVPair.
    pair.buffer.assign(GetBufferAtCursor(), GetBufferAtCursor() + pair.avlen);
    if (pair.avid == TargetInfoAvId::kEol) {
      // Terminator must have zero length.
      if (pair.avlen != 0)
        return false;

      // Break out of the loop once a valid terminator is found. After the
      // loop it will be validated that the whole target info was consumed.
      saw_eol = true;
      break;
    }

    switch (pair.avid) {
      case TargetInfoAvId::kFlags:
        // For flags also populate the flags field so it doesn't
        // have to be modified through the raw buffer later.
        if (pair.avlen != sizeof(uint32_t) ||
            !ReadUInt32(reinterpret_cast<uint32_t*>(&pair.flags)))
          return false;
        break;
      case TargetInfoAvId::kTimestamp:
        // Populate timestamp so it doesn't need to be read through the
        // raw buffer later.
        if (pair.avlen != sizeof(uint64_t) || !ReadUInt64(&pair.timestamp))
          return false;
        break;
      case TargetInfoAvId::kChannelBindings:
      case TargetInfoAvId::kTargetName:
        // The server should never send these, and with EPA enabled the client
        // will add these to the authenticate message. To avoid issues with
        // duplicates or only one being read, just don't allow them.
        return false;
      default:
        // For all other types, just jump over the payload to the next pair.
        // If there aren't enough bytes left, then fail.
        if (!SkipBytes(pair.avlen))
          return false;
        break;
    }

    av_pairs->push_back(std::move(pair));
  }

  // Fail if the buffer wasn't properly formed. The entire payload should have
  // been consumed and a terminator found.
  if ((GetCursor() != target_info_end) || !saw_eol)
    return false;

  return true;
}

bool NtlmBufferReader::ReadTargetInfoPayload(std::vector<AvPair>* av_pairs) {
  DCHECK(av_pairs->empty());

  SecurityBuffer sec_buf;

  // First read the security buffer.
  if (!ReadSecurityBuffer(&sec_buf))
    return false;

  NtlmBufferReader payload_reader;
  if (!ReadPayloadAsBufferReader(sec_buf, &payload_reader))
    return false;

  if (!payload_reader.ReadTargetInfo(sec_buf.length, av_pairs))
    return false;

  // |ReadTargetInfo| should have consumed the entire contents.
  return payload_reader.IsEndOfBuffer();
}

bool NtlmBufferReader::ReadMessageType(MessageType* message_type) {
  uint32_t raw_message_type;
  if (!ReadUInt32(&raw_message_type))
    return false;

  *message_type = static_cast<MessageType>(raw_message_type);

  if (*message_type != MessageType::kNegotiate &&
      *message_type != MessageType::kChallenge &&
      *message_type != MessageType::kAuthenticate)
    return false;

  return true;
}

bool NtlmBufferReader::SkipSecurityBuffer() {
  return SkipBytes(kSecurityBufferLen);
}

bool NtlmBufferReader::SkipSecurityBufferWithValidation() {
  SecurityBuffer sec_buf;
  return ReadSecurityBuffer(&sec_buf) && CanReadFrom(sec_buf);
}

bool NtlmBufferReader::SkipBytes(size_t count) {
  if (!CanRead(count))
    return false;

  AdvanceCursor(count);
  return true;
}

bool NtlmBufferReader::MatchSignature() {
  if (!CanRead(kSignatureLen))
    return false;

  if (memcmp(kSignature, GetBufferAtCursor(), kSignatureLen) != 0)
    return false;

  AdvanceCursor(kSignatureLen);
  return true;
}

bool NtlmBufferReader::MatchMessageType(MessageType message_type) {
  MessageType actual_message_type;
  return ReadMessageType(&actual_message_type) &&
         (actual_message_type == message_type);
}

bool NtlmBufferReader::MatchMessageHeader(MessageType message_type) {
  return MatchSignature() && MatchMessageType(message_type);
}

bool NtlmBufferReader::MatchZeros(size_t count) {
  if (!CanRead(count))
    return false;

  for (size_t i = 0; i < count; i++) {
    if (GetBufferAtCursor()[i] != 0)
      return false;
  }

  AdvanceCursor(count);
  return true;
}

bool NtlmBufferReader::MatchEmptySecurityBuffer() {
  SecurityBuffer sec_buf;
  return ReadSecurityBuffer(&sec_buf) && (sec_buf.offset <= GetLength()) &&
         (sec_buf.length == 0);
}

template <typename T>
bool NtlmBufferReader::ReadUInt(T* value) {
  size_t int_size = sizeof(T);
  if (!CanRead(int_size))
    return false;

  *value = 0;
  for (size_t i = 0; i < int_size; i++) {
    *value += static_cast<T>(GetByteAtCursor()) << (i * 8);
    AdvanceCursor(1);
  }

  return true;
}

void NtlmBufferReader::SetCursor(size_t cursor) {
  DCHECK_LE(cursor, GetLength());

  cursor_ = cursor;
}

}  // namespace ntlm
}  // namespace net
