100 lines
4.7 KiB
C++
100 lines
4.7 KiB
C++
#include <io/BufferUtils.hpp>
|
|
#include <io/ByteReader.hpp>
|
|
#include <io/ByteWriter.hpp>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
using Bytes = std::vector<std::uint8_t>;
|
|
|
|
void require(bool condition, const std::string& message) {
|
|
if (!condition) throw std::runtime_error(message);
|
|
}
|
|
|
|
template<typename TOperation>
|
|
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<typename TValue, typename TOperation>
|
|
void invalidRanges(Bytes& bytes, const TOperation& operation, const std::string& name) {
|
|
const auto original = bytes;
|
|
constexpr auto maxOffset = std::numeric_limits<std::size_t>::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<typename TValue, typename TRead>
|
|
void readBounds(const TRead& read, const std::string& name) {
|
|
const auto value = static_cast<TValue>(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<TValue>(bytes, read, name);
|
|
}
|
|
|
|
template<typename TValue, typename TWrite>
|
|
void writeBounds(const TWrite& write, const std::string& name) {
|
|
const auto value = static_cast<TValue>(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<TValue>(bytes, [&](Bytes& buffer, std::size_t offset) { write(buffer, offset, value); }, name);
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
const Io::ByteReader reader;
|
|
const Io::ByteWriter writer;
|
|
readBounds<std::uint16_t>([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU16(bytes, offset); }, "ByteReader::ReadU16");
|
|
readBounds<std::uint32_t>([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU32(bytes, offset); }, "ByteReader::ReadU32");
|
|
readBounds<std::uint64_t>([&](const Bytes& bytes, std::size_t offset) { return reader.ReadU64(bytes, offset); }, "ByteReader::ReadU64");
|
|
writeBounds<std::uint8_t>([&](Bytes& bytes, std::size_t offset, std::uint8_t value) { writer.WriteU8(bytes, offset, value); }, "ByteWriter::WriteU8");
|
|
writeBounds<std::uint16_t>([&](Bytes& bytes, std::size_t offset, std::uint16_t value) { writer.WriteU16(bytes, offset, value); }, "ByteWriter::WriteU16");
|
|
writeBounds<std::uint32_t>([&](Bytes& bytes, std::size_t offset, std::uint32_t value) { writer.WriteU32(bytes, offset, value); }, "ByteWriter::WriteU32");
|
|
writeBounds<std::uint64_t>([&](Bytes& bytes, std::size_t offset, std::uint64_t value) { writer.WriteU64(bytes, offset, value); }, "ByteWriter::WriteU64");
|
|
readBounds<std::uint16_t>(Io::ReadU16, "ReadU16");
|
|
readBounds<std::uint32_t>(Io::ReadU32, "ReadU32");
|
|
readBounds<std::uint64_t>(Io::ReadU64, "ReadU64");
|
|
writeBounds<std::uint8_t>(Io::WriteU8, "WriteU8");
|
|
writeBounds<std::uint16_t>(Io::WriteU16, "WriteU16");
|
|
writeBounds<std::uint32_t>(Io::WriteU32, "WriteU32");
|
|
writeBounds<std::uint64_t>(Io::WriteU64, "WriteU64");
|
|
} catch (const std::exception& error) {
|
|
std::cerr << error.what() << '\n';
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|