#include #include #include #include #include #include #include #include namespace { using Bytes = std::vector; static_assert(Io::AlignUp(std::uint64_t{17}, std::uint64_t{16}) == 32); void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } template void requireOutOfRange(const TOperation& operation, const std::string& name, std::size_t offset) { try { operation(); } catch (const std::out_of_range& error) { require(std::string(error.what()) == name + " out of bounds", name + " changed its out-of-range diagnostic"); return; } throw std::runtime_error(name + " accepted invalid offset " + std::to_string(offset)); } template void invalidRanges(Bytes& bytes, const TOperation& operation, const std::string& name) { const auto original = bytes; constexpr auto maxOffset = std::numeric_limits::max(); for (const std::size_t offset : {bytes.size() - sizeof(TValue) + 1, bytes.size(), bytes.size() + 1, maxOffset - sizeof(TValue), maxOffset - sizeof(TValue) + 1, maxOffset}) { requireOutOfRange([&] { operation(bytes, offset); }, name, offset); require(bytes == original, name + " modified the buffer on failure"); } for (std::size_t size = 0; size < sizeof(TValue); ++size) { Bytes shortBuffer(size, 0xCC); requireOutOfRange([&] { operation(shortBuffer, 0); }, name, 0); require(shortBuffer == Bytes(size, 0xCC), name + " modified a short buffer on failure"); requireOutOfRange([&] { operation(shortBuffer, maxOffset); }, name, maxOffset); require(shortBuffer == Bytes(size, 0xCC), name + " modified a short buffer on failure"); } } template void readBounds(const TRead& read, const std::string& name) { const auto value = static_cast(0x8877665544332211ull); Bytes bytes(sizeof(value) + 2, 0xCC); std::memcpy(bytes.data() + 1, &value, sizeof(value)); require(read(bytes, 1) == value, name + " failed an unaligned read"); bytes.pop_back(); require(read(bytes, 1) == value, name + " rejected a read ending at the buffer boundary"); invalidRanges(bytes, read, name); } template void writeBounds(const TWrite& write, const std::string& name) { const auto value = static_cast(0x8877665544332211ull); Bytes bytes(sizeof(value) + 2, 0xCC); auto expected = bytes; std::memcpy(expected.data() + 1, &value, sizeof(value)); write(bytes, 1, value); require(bytes == expected, name + " failed an unaligned write"); bytes.pop_back(); expected.pop_back(); bytes.assign(expected.size(), 0xCC); write(bytes, 1, value); require(bytes == expected, name + " rejected a write ending at the buffer boundary"); invalidRanges(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name); } template void requireThrows(const TOperation& operation, const std::string& message) { try { operation(); } catch (const TError&) { return; } throw std::runtime_error(message); } template void alignmentBounds() { constexpr auto maximum = std::numeric_limits::max(); require(Io::AlignUp(TValue{0}, TValue{16}) == 0, "AlignUp changed zero"); require(Io::AlignUp(TValue{17}, TValue{16}) == 32, "AlignUp failed to round up"); require(Io::AlignUp(TValue{7}, TValue{3}) == 9, "AlignUp requires a power-of-two alignment"); require(Io::AlignUp(maximum, TValue{1}) == maximum, "AlignUp changed an aligned maximum"); require(Io::AlignUp(static_cast(maximum - 15), TValue{16}) == maximum - 15, "AlignUp changed an aligned value near the maximum"); require(Io::AlignUp(static_cast(maximum - 1), maximum) == maximum, "AlignUp overflowed for a representable result"); requireThrows([&] { Io::AlignUp(maximum, TValue{16}); }, "AlignUp accepted an overflowing result"); requireThrows([] { Io::AlignUp(TValue{1}, TValue{0}); }, "AlignUp accepted zero alignment"); } } int main() { try { const Io::ByteReader reader; const Io::ByteWriter writer; alignmentBounds(); alignmentBounds(); alignmentBounds(); alignmentBounds(); requireThrows([] { Io::AlignUp(-1, 16); }, "AlignUp accepted a negative value"); requireThrows([] { Io::AlignUp(1, -1); }, "AlignUp accepted negative alignment"); require(Io::AlignUp64(std::numeric_limits::max() - 1, std::numeric_limits::max()) == std::numeric_limits::max(), "AlignUp64 overflowed for a representable result"); requireThrows([] { Io::AlignUp64(std::numeric_limits::max(), 16); }, "AlignUp64 accepted an overflowing result"); Bytes aligned{1, 2, 3}; const auto original = aligned; requireThrows([&] { Io::AlignBuffer(aligned, 0); }, "AlignBuffer accepted zero alignment"); require(aligned == original, "AlignBuffer modified the buffer on failure"); Io::AlignBuffer(aligned, 4); require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer failed to preserve bytes and zero-fill padding"); Io::AlignBuffer(aligned, 4); require(aligned == Bytes({1, 2, 3, 0}), "AlignBuffer changed an aligned buffer"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32"); readBounds([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64"); writeBounds([&](Bytes& bytes, std::size_t offset, std::uint8_t value) { writer.WriteU8(bytes, offset, value); }, "ByteWriter::WriteU8"); writeBounds([&](Bytes& bytes, std::size_t offset, std::uint16_t value) { writer.WriteU16(bytes, offset, value); }, "ByteWriter::WriteU16"); writeBounds([&](Bytes& bytes, std::size_t offset, std::uint32_t value) { writer.WriteU32(bytes, offset, value); }, "ByteWriter::WriteU32"); writeBounds([&](Bytes& bytes, std::size_t offset, std::uint64_t value) { writer.WriteU64(bytes, offset, value); }, "ByteWriter::WriteU64"); readBounds(Io::ReadU16, "ReadU16"); readBounds(Io::ReadU32, "ReadU32"); readBounds(Io::ReadU64, "ReadU64"); writeBounds(Io::WriteU8, "WriteU8"); writeBounds(Io::WriteU16, "WriteU16"); writeBounds(Io::WriteU32, "WriteU32"); writeBounds(Io::WriteU64, "WriteU64"); } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } return 0; }