sots-engine/tests/mars_vfs/zip_builder.h

144 lines
5.4 KiB
C++

// Minimal in-memory ZIP writer for tests: stored entries, directory
// placeholders, and "raw" entries whose bytes are supplied pre-packed so a
// test can fabricate deflate / encrypted / odd-method records without a
// compressor. Written independently of the reader under test.
#pragma once
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
namespace ziptest {
inline std::uint32_t crc32(const std::uint8_t* p, std::size_t n, std::uint32_t crc = 0) {
static std::uint32_t table[256];
static bool init = false;
if (!init) {
for (std::uint32_t i = 0; i < 256; ++i) {
std::uint32_t c = i;
for (int k = 0; k < 8; ++k) c = (c & 1) ? 0xEDB88320u ^ (c >> 1) : c >> 1;
table[i] = c;
}
init = true;
}
crc = ~crc;
for (std::size_t i = 0; i < n; ++i) crc = table[(crc ^ p[i]) & 0xFF] ^ (crc >> 8);
return ~crc;
}
inline std::uint32_t crc32(std::string_view s) {
return crc32(reinterpret_cast<const std::uint8_t*>(s.data()), s.size());
}
struct Item {
std::string name;
std::vector<std::uint8_t> packed; // bytes as they sit in the file
std::uint32_t uncompressed_size = 0;
std::uint32_t crc = 0;
std::uint16_t method = 0;
std::uint16_t flags = 0;
std::vector<std::uint8_t> local_extra; // extra field on the local header only
std::vector<std::uint8_t> central_extra;
std::uint32_t local_header_offset = 0; // filled by build()
};
class ZipBuilder {
public:
Item& add(std::string name, std::string_view data) {
Item it;
it.name = std::move(name);
it.packed.assign(data.begin(), data.end());
it.uncompressed_size = static_cast<std::uint32_t>(data.size());
it.crc = crc32(data);
items_.push_back(std::move(it));
return items_.back();
}
Item& add_dir(std::string name) {
if (name.empty() || name.back() != '/') name.push_back('/');
return add(std::move(name), "");
}
// Pre-packed bytes with explicit method / sizes / crc.
Item& add_raw(std::string name, std::vector<std::uint8_t> packed, std::uint16_t method,
std::uint32_t uncompressed_size, std::uint32_t crc) {
Item it;
it.name = std::move(name);
it.packed = std::move(packed);
it.method = method;
it.uncompressed_size = uncompressed_size;
it.crc = crc;
items_.push_back(std::move(it));
return items_.back();
}
std::vector<Item>& items() { return items_; }
std::vector<std::uint8_t> build(std::string_view comment = {}) {
std::vector<std::uint8_t> out;
for (Item& it : items_) {
it.local_header_offset = static_cast<std::uint32_t>(out.size());
put32(out, 0x04034b50);
put16(out, 20);
put16(out, it.flags);
put16(out, it.method);
put16(out, 0); // time
put16(out, 0); // date
put32(out, it.crc);
put32(out, static_cast<std::uint32_t>(it.packed.size()));
put32(out, it.uncompressed_size);
put16(out, static_cast<std::uint16_t>(it.name.size()));
put16(out, static_cast<std::uint16_t>(it.local_extra.size()));
out.insert(out.end(), it.name.begin(), it.name.end());
out.insert(out.end(), it.local_extra.begin(), it.local_extra.end());
out.insert(out.end(), it.packed.begin(), it.packed.end());
}
const std::uint32_t cd_offset = static_cast<std::uint32_t>(out.size());
for (const Item& it : items_) {
put32(out, 0x02014b50);
put16(out, 20); // made by
put16(out, 20); // needed
put16(out, it.flags);
put16(out, it.method);
put16(out, 0);
put16(out, 0);
put32(out, it.crc);
put32(out, static_cast<std::uint32_t>(it.packed.size()));
put32(out, it.uncompressed_size);
put16(out, static_cast<std::uint16_t>(it.name.size()));
put16(out, static_cast<std::uint16_t>(it.central_extra.size()));
put16(out, 0); // comment len
put16(out, 0); // disk
put16(out, 0); // internal attrs
put32(out, 0); // external attrs
put32(out, it.local_header_offset);
out.insert(out.end(), it.name.begin(), it.name.end());
out.insert(out.end(), it.central_extra.begin(), it.central_extra.end());
}
const std::uint32_t cd_size = static_cast<std::uint32_t>(out.size()) - cd_offset;
put32(out, 0x06054b50);
put16(out, 0);
put16(out, 0);
put16(out, static_cast<std::uint16_t>(items_.size()));
put16(out, static_cast<std::uint16_t>(items_.size()));
put32(out, cd_size);
put32(out, cd_offset);
put16(out, static_cast<std::uint16_t>(comment.size()));
out.insert(out.end(), comment.begin(), comment.end());
return out;
}
private:
static void put16(std::vector<std::uint8_t>& v, std::uint16_t x) {
v.push_back(static_cast<std::uint8_t>(x & 0xFF));
v.push_back(static_cast<std::uint8_t>(x >> 8));
}
static void put32(std::vector<std::uint8_t>& v, std::uint32_t x) {
for (int i = 0; i < 4; ++i) v.push_back(static_cast<std::uint8_t>((x >> (8 * i)) & 0xFF));
}
std::vector<Item> items_;
};
} // namespace ziptest