// Copyright 2022 The Pigweed Authors
//
// 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
//
//     https://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 "pw_hdlc/encoded_size.h"

#include <array>
#include <cstddef>
#include <cstdint>

#include "pw_bytes/array.h"
#include "pw_hdlc/decoder.h"
#include "pw_hdlc/encoder.h"
#include "pw_result/result.h"
#include "pw_stream/memory_stream.h"
#include "pw_unit_test/framework.h"
#include "pw_varint/varint.h"

namespace pw::hdlc {
namespace {

// The varint-encoded address that represents the value that will result in the
// largest on-the-wire address after HDLC escaping.
constexpr auto kWidestVarintAddress =
    bytes::String("\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x03");

// This is the decoded varint value of kWidestVarintAddress. This is
// pre-calculated as a constant to simplify tests.
constexpr uint64_t kWidestAddress = 0xbf7efdfbf7efdfbf;

// UI frames created by WriteUIFrame() will never be have an escaped control
// field, but it's technically possible for other HDLC frame types to produce
// control bytes that would need to be escaped.
constexpr size_t kEscapedControlCost = kControlSize;

// UI frames created by WriteUIFrame() will never have an escaped control
// field, but it's technically possible for other HDLC frame types to produce
// control bytes that would need to be escaped.
constexpr size_t kEscapedFcsCost = kMaxEscapedFcsSize - kFcsSize;

// Due to API limitations, the worst case buffer calculations used by the HDLC
// encoder/decoder can't be fully saturated. This constexpr value accounts for
// this by expressing the delta between the constant largest testable HDLC frame
// and the calculated worst-case-scenario.
constexpr size_t kTestLimitationsOverhead =
    kEscapedControlCost + kEscapedFcsCost;

// A payload only containing bytes that need to be escaped.
constexpr auto kFullyEscapedPayload =
    bytes::String("\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e");

constexpr uint8_t kEscapeAddress = static_cast<uint8_t>(kFlag);
constexpr uint8_t kNoEscapeAddress = 6;

TEST(EncodedSize, Constants_WidestAddress) {
  uint64_t address = 0;
  size_t address_size =
      varint::Decode(kWidestVarintAddress, &address, kAddressFormat);
  EXPECT_EQ(address_size, 10u);
  EXPECT_EQ(address_size, kMaxAddressSize);
  EXPECT_EQ(kMaxEscapedVarintAddressSize, 19u);
  EXPECT_EQ(EscapedSize(kWidestVarintAddress), kMaxEscapedVarintAddressSize);
  EXPECT_EQ(address, kWidestAddress);
  EXPECT_EQ(varint::EncodedSize(kWidestAddress), 10u);
}

TEST(EncodedSize, EscapedSize_AllEscapeBytes) {
  EXPECT_EQ(EscapedSize(kFullyEscapedPayload), kFullyEscapedPayload.size() * 2);
}

TEST(EncodedSize, EscapedSize_NoEscapeBytes) {
  constexpr auto kData = bytes::String("\x01\x23\x45\x67\x89\xab\xcd\xef");
  EXPECT_EQ(EscapedSize(kData), kData.size());
}

TEST(EncodedSize, EscapedSize_SomeEscapeBytes) {
  constexpr auto kData = bytes::String("\x7epabu\x7d");
  EXPECT_EQ(EscapedSize(kData), kData.size() + 2);
}

TEST(EncodedSize, EscapedSize_Address) {
  EXPECT_EQ(EscapedSize(kWidestVarintAddress),
            varint::EncodedSize(kWidestAddress) * 2 - 1);
}

TEST(EncodedSize, MaxEncodedSize_Overload) {
  EXPECT_EQ(MaxEncodedFrameSize(kFullyEscapedPayload.size()),
            MaxEncodedFrameSize(kWidestAddress, kFullyEscapedPayload));
}

TEST(EncodedSize, MaxEncodedSize_EmptyPayload) {
  EXPECT_EQ(14u, MaxEncodedFrameSize(kNoEscapeAddress, {}));
  EXPECT_EQ(14u, MaxEncodedFrameSize(kEscapeAddress, {}));
}

TEST(EncodedSize, MaxEncodedSize_PayloadWithoutEscapes) {
  constexpr auto data = bytes::Array<0x00, 0x01, 0x02, 0x03>();
  EXPECT_EQ(18u, MaxEncodedFrameSize(kNoEscapeAddress, data));
  EXPECT_EQ(18u, MaxEncodedFrameSize(kEscapeAddress, data));
}

TEST(EncodedSize, MaxEncodedSize_PayloadWithOneEscape) {
  constexpr auto data = bytes::Array<0x00, 0x01, 0x7e, 0x03>();
  EXPECT_EQ(19u, MaxEncodedFrameSize(kNoEscapeAddress, data));
  EXPECT_EQ(19u, MaxEncodedFrameSize(kEscapeAddress, data));
}

TEST(EncodedSize, MaxEncodedSize_PayloadWithAllEscapes) {
  constexpr auto data = bytes::Initialized<8>(0x7e);
  EXPECT_EQ(30u, MaxEncodedFrameSize(kNoEscapeAddress, data));
  EXPECT_EQ(30u, MaxEncodedFrameSize(kEscapeAddress, data));
}

TEST(EncodedSize, MaxPayload_UndersizedFrame) {
  EXPECT_EQ(MaxSafePayloadSize(4), 0u);
}

TEST(EncodedSize, MaxPayload_SmallFrame) {
  EXPECT_EQ(MaxSafePayloadSize(128), 48u);
}

TEST(EncodedSize, MaxPayload_MediumFrame) {
  EXPECT_EQ(MaxSafePayloadSize(512), 240u);
}

TEST(EncodedSize, FrameToPayloadInversion_Odd) {
  static constexpr size_t kIntendedPayloadSize = 1234567891;
  EXPECT_EQ(MaxSafePayloadSize(MaxEncodedFrameSize(kIntendedPayloadSize)),
            kIntendedPayloadSize);
}

TEST(EncodedSize, PayloadToFrameInversion_Odd) {
  static constexpr size_t kIntendedFrameSize = 1234567891;
  EXPECT_EQ(MaxEncodedFrameSize(MaxSafePayloadSize(kIntendedFrameSize)),
            kIntendedFrameSize);
}

TEST(EncodedSize, FrameToPayloadInversion_Even) {
  static constexpr size_t kIntendedPayloadSize = 42;
  EXPECT_EQ(MaxSafePayloadSize(MaxEncodedFrameSize(kIntendedPayloadSize)),
            kIntendedPayloadSize);
}

TEST(EncodedSize, PayloadToFrameInversion_Even) {
  static constexpr size_t kIntendedFrameSize = 42;
  // Because of HDLC encoding overhead requirements, the last byte of the
  // intended frame size is wasted because it doesn't allow sufficient space for
  // another byte since said additional byte could require escaping, therefore
  // requiring a second byte to increase the safe payload size by one.
  const size_t max_frame_usage =
      MaxEncodedFrameSize(MaxSafePayloadSize(kIntendedFrameSize));
  EXPECT_EQ(max_frame_usage, kIntendedFrameSize - 1);

  // There's no further change if the inversion is done again since the frame
  // size is aligned to the reduced bounds.
  EXPECT_EQ(MaxEncodedFrameSize(MaxSafePayloadSize(max_frame_usage)),
            kIntendedFrameSize - 1);
}

TEST(EncodedSize, MostlyEscaped) {
  constexpr auto kMostlyEscapedPayload =
      bytes::String(":)\x7e\x7e\x7e\x7e\x7e\x7e\x7e\x7e");
  constexpr size_t kUnescapedBytes =
      2 * kMostlyEscapedPayload.size() - EscapedSize(kMostlyEscapedPayload);
  // Subtracting 2 should still leave enough space since two bytes won't need
  // to be escaped.
  constexpr size_t kExpectedMaxFrameSize =
      MaxEncodedFrameSize(kMostlyEscapedPayload.size()) - kUnescapedBytes;
  std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
  stream::MemoryWriter writer(dest_buffer);
  EXPECT_EQ(kUnescapedBytes, 2u);
  EXPECT_EQ(OkStatus(),
            WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer));
  EXPECT_EQ(writer.size(),
            kExpectedMaxFrameSize - kTestLimitationsOverhead - kUnescapedBytes);
}

TEST(EncodedSize, BigAddress_SaturatedPayload) {
  constexpr size_t kExpectedMaxFrameSize =
      MaxEncodedFrameSize(kFullyEscapedPayload.size());
  std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
  stream::MemoryWriter writer(dest_buffer);
  EXPECT_EQ(OkStatus(),
            WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer));
  EXPECT_EQ(writer.size(), kExpectedMaxFrameSize - kTestLimitationsOverhead);
}

TEST(EncodedSize, BigAddress_OffByOne) {
  constexpr size_t kExpectedMaxFrameSize =
      MaxEncodedFrameSize(kFullyEscapedPayload.size()) - 1;
  std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
  stream::MemoryWriter writer(dest_buffer);
  EXPECT_EQ(Status::ResourceExhausted(),
            WriteUIFrame(kWidestAddress, kFullyEscapedPayload, writer));
}

TEST(EncodedSize, SmallAddress_SaturatedPayload) {
  constexpr auto kSmallerEscapedAddress = bytes::String("\x7e\x7d");
  // varint::Decode() is not constexpr, so this is a hard-coded and then runtime
  // validated.
  constexpr size_t kVarintDecodedAddress = 7999;
  constexpr size_t kExpectedMaxFrameSize =
      MaxEncodedFrameSize(kVarintDecodedAddress, kFullyEscapedPayload);
  std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
  stream::MemoryWriter writer(dest_buffer);

  uint64_t address = 0;
  size_t address_size =
      varint::Decode(kSmallerEscapedAddress, &address, kAddressFormat);
  EXPECT_EQ(address, kVarintDecodedAddress);
  EXPECT_EQ(address_size, 2u);

  EXPECT_EQ(OkStatus(), WriteUIFrame(address, kFullyEscapedPayload, writer));
  EXPECT_EQ(writer.size(), kExpectedMaxFrameSize - kTestLimitationsOverhead);
}

TEST(EncodedSize, SmallAddress_OffByOne) {
  constexpr auto kSmallerEscapedAddress = bytes::String("\x7e\x7d");
  // varint::Decode() is not constexpr, so this is a hard-coded and then runtime
  // validated.
  constexpr size_t kVarintDecodedAddress = 7999;
  constexpr size_t kExpectedMaxFrameSize =
      MaxEncodedFrameSize(kVarintDecodedAddress, kFullyEscapedPayload);
  std::array<std::byte, kExpectedMaxFrameSize - 1> dest_buffer;
  stream::MemoryWriter writer(dest_buffer);

  uint64_t address = 0;
  size_t address_size =
      varint::Decode(kSmallerEscapedAddress, &address, kAddressFormat);
  EXPECT_EQ(address, kVarintDecodedAddress);
  EXPECT_EQ(address_size, 2u);

  EXPECT_EQ(Status::ResourceExhausted(),
            WriteUIFrame(address, kFullyEscapedPayload, writer));
}

TEST(DecodedSize, BigAddress_SaturatedPayload) {
  constexpr auto kNoEscapePayload =
      bytes::String("The decoder needs the most space when there's no escapes");
  constexpr size_t kExpectedMaxFrameSize =
      MaxEncodedFrameSize(kNoEscapePayload.size());
  std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
  stream::MemoryWriter writer(dest_buffer);
  EXPECT_EQ(OkStatus(),
            WriteUIFrame(kNoEscapeAddress, kNoEscapePayload, writer));

  // Allocate at least enough real buffer space.
  constexpr size_t kDecoderBufferSize =
      Decoder::RequiredBufferSizeForFrameSize(kExpectedMaxFrameSize);
  std::array<std::byte, kDecoderBufferSize> buffer;

  // Pretend the supported frame size is whatever the final size of the encoded
  // frame was.
  const size_t max_frame_size =
      Decoder::RequiredBufferSizeForFrameSize(writer.size());

  Decoder decoder(ByteSpan(buffer).first(max_frame_size));
  for (const std::byte b : writer.WrittenData()) {
    Result<Frame> frame = decoder.Process(b);
    if (frame.ok()) {
      EXPECT_EQ(frame->address(), kNoEscapeAddress);
      EXPECT_EQ(frame->data().size(), kNoEscapePayload.size());
      EXPECT_TRUE(std::memcmp(frame->data().data(),
                              kNoEscapePayload.data(),
                              kNoEscapePayload.size()) == 0);
    }
  }
}

TEST(DecodedSize, BigAddress_OffByOne) {
  constexpr auto kNoEscapePayload =
      bytes::String("The decoder needs the most space when there's no escapes");
  constexpr size_t kExpectedMaxFrameSize =
      MaxEncodedFrameSize(kNoEscapePayload.size());
  std::array<std::byte, kExpectedMaxFrameSize> dest_buffer;
  stream::MemoryWriter writer(dest_buffer);
  EXPECT_EQ(OkStatus(),
            WriteUIFrame(kNoEscapeAddress, kNoEscapePayload, writer));

  // Allocate at least enough real buffer space.
  constexpr size_t kDecoderBufferSize =
      Decoder::RequiredBufferSizeForFrameSize(kExpectedMaxFrameSize);
  std::array<std::byte, kDecoderBufferSize> buffer;

  // Pretend the supported frame size is whatever the final size of the encoded
  // frame was.
  const size_t max_frame_size =
      Decoder::RequiredBufferSizeForFrameSize(writer.size());

  Decoder decoder(ByteSpan(buffer).first(max_frame_size - 1));
  for (size_t i = 0; i < writer.size(); i++) {
    Result<Frame> frame = decoder.Process(writer[i]);
    if (i < writer.size() - 1) {
      EXPECT_EQ(frame.status(), Status::Unavailable());
    } else {
      EXPECT_EQ(frame.status(), Status::ResourceExhausted());
    }
  }
}

}  // namespace
}  // namespace pw::hdlc
