Skip to content

Commit fa9325a

Browse files
author
MarcoFalke
committed
streams: Add XorFile
1 parent fa22763 commit fa9325a

File tree

3 files changed

+161
-3
lines changed

3 files changed

+161
-3
lines changed

src/streams.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,54 @@
55
#include <span.h>
66
#include <streams.h>
77

8+
#include <array>
9+
10+
std::size_t XorFile::detail_fread(Span<std::byte> dst)
11+
{
12+
if (!m_file) throw std::ios_base::failure("XorFile::read: file handle is nullptr");
13+
const auto init_pos{std::ftell(m_file)};
14+
if (init_pos < 0) return 0;
15+
std::size_t ret{std::fread(dst.data(), 1, dst.size(), m_file)};
16+
util::Xor(dst.subspan(0, ret), m_xor, init_pos);
17+
return ret;
18+
}
19+
20+
void XorFile::read(Span<std::byte> dst)
21+
{
22+
if (detail_fread(dst) != dst.size()) {
23+
throw std::ios_base::failure{feof() ? "XorFile::read: end of file" : "XorFile::read: failed"};
24+
}
25+
}
26+
27+
void XorFile::ignore(size_t num_bytes)
28+
{
29+
if (!m_file) throw std::ios_base::failure("XorFile::ignore: file handle is nullptr");
30+
std::array<std::byte, 4096> buf;
31+
while (num_bytes > 0) {
32+
auto buf_now{Span{buf}.first(std::min<size_t>(num_bytes, buf.size()))};
33+
if (std::fread(buf_now.data(), 1, buf_now.size(), m_file) != buf_now.size()) {
34+
throw std::ios_base::failure{feof() ? "XorFile::ignore: end of file" : "XorFile::ignore: failed"};
35+
}
36+
num_bytes -= buf_now.size();
37+
}
38+
}
39+
40+
void XorFile::write(Span<const std::byte> src)
41+
{
42+
if (!m_file) throw std::ios_base::failure("XorFile::write: file handle is nullptr");
43+
std::array<std::byte, 4096> buf;
44+
while (src.size() > 0) {
45+
auto buf_now{Span{buf}.first(std::min<size_t>(src.size(), buf.size()))};
46+
std::copy(src.begin(), src.begin() + buf_now.size(), buf_now.begin());
47+
const auto current_pos{std::ftell(m_file)};
48+
util::Xor(buf_now, m_xor, current_pos);
49+
if (current_pos < 0 || std::fwrite(buf_now.data(), 1, buf_now.size(), m_file) != buf_now.size()) {
50+
throw std::ios_base::failure{"XorFile::write: failed"};
51+
}
52+
src = src.subspan(buf_now.size());
53+
}
54+
}
55+
856
std::size_t AutoFile::detail_fread(Span<std::byte> dst)
957
{
1058
if (!file) throw std::ios_base::failure("AutoFile::read: file handle is nullptr");

src/streams.h

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
#include <util/overflow.h>
1313

1414
#include <algorithm>
15-
#include <assert.h>
15+
#include <cassert>
16+
#include <cstdint>
1617
#include <cstdio>
18+
#include <cstring>
1719
#include <ios>
1820
#include <limits>
1921
#include <optional>
20-
#include <stdint.h>
21-
#include <string.h>
2222
#include <string>
2323
#include <utility>
2424
#include <vector>
@@ -477,6 +477,66 @@ class BitStreamWriter
477477
}
478478
};
479479

480+
/**
481+
* Like an AutoFile whose data is XOR'd.
482+
*/
483+
class XorFile
484+
{
485+
private:
486+
std::FILE* m_file;
487+
const int m_version;
488+
const std::vector<std::byte> m_xor;
489+
490+
public:
491+
//
492+
// AutoFile subset
493+
//
494+
explicit XorFile(std::FILE* file, int ver, std::vector<std::byte> data_xor)
495+
: m_file{file},
496+
m_version{ver},
497+
m_xor{std::move(data_xor)} {}
498+
~XorFile() { fclose(); }
499+
XorFile(const XorFile&) = delete;
500+
XorFile& operator=(const XorFile&) = delete;
501+
int GetVersion() const { return m_version; }
502+
bool feof() const { return std::feof(m_file); }
503+
int fclose()
504+
{
505+
if (std::FILE * file{release()}) return std::fclose(file);
506+
return 0;
507+
}
508+
std::FILE* release()
509+
{
510+
std::FILE* ret = m_file;
511+
m_file = nullptr;
512+
return ret;
513+
}
514+
std::FILE* Get() const { return m_file; }
515+
bool IsNull() const { return m_file == nullptr; }
516+
std::size_t detail_fread(Span<std::byte> dst);
517+
518+
//
519+
// Stream subset
520+
//
521+
void read(Span<std::byte> dst);
522+
void ignore(size_t num_bytes);
523+
void write(Span<const std::byte> src);
524+
525+
template <typename T>
526+
XorFile& operator<<(const T& obj)
527+
{
528+
::Serialize(*this, obj);
529+
return *this;
530+
}
531+
532+
template <typename T>
533+
XorFile& operator>>(T&& obj)
534+
{
535+
::Unserialize(*this, obj);
536+
return *this;
537+
}
538+
};
539+
480540
/** Non-refcounted RAII wrapper for FILE*
481541
*
482542
* Will automatically close the file when it goes out of scope if not null.

src/test/streams_tests.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,63 @@
66
#include <test/util/random.h>
77
#include <test/util/setup_common.h>
88
#include <util/fs.h>
9+
#include <util/strencodings.h>
910

1011
#include <boost/test/unit_test.hpp>
1112

1213
using namespace std::string_literals;
1314

1415
BOOST_FIXTURE_TEST_SUITE(streams_tests, BasicTestingSetup)
1516

17+
BOOST_AUTO_TEST_CASE(xor_file)
18+
{
19+
fs::path xor_path{m_args.GetDataDirBase() / "test_xor.bin"};
20+
auto raw_file{[&](const auto& mode) { return fsbridge::fopen(xor_path, mode); }};
21+
const std::vector<uint8_t> test1{1, 2, 3};
22+
const std::vector<uint8_t> test2{4, 5};
23+
const std::vector<std::byte> xor_pat{std::byte{0xff}, std::byte{0x00}};
24+
{
25+
// Check errors for missing file
26+
XorFile xor_file{raw_file("rb"), {}, xor_pat};
27+
BOOST_CHECK_EXCEPTION(xor_file << std::byte{}, std::ios_base::failure, HasReason{"XorFile::write: file handle is nullpt"});
28+
BOOST_CHECK_EXCEPTION(xor_file >> std::byte{}, std::ios_base::failure, HasReason{"XorFile::read: file handle is nullpt"});
29+
BOOST_CHECK_EXCEPTION(xor_file.ignore(1), std::ios_base::failure, HasReason{"XorFile::ignore: file handle is nullpt"});
30+
}
31+
{
32+
XorFile xor_file{raw_file("wbx"), {}, xor_pat};
33+
xor_file << test1 << test2;
34+
}
35+
{
36+
// Read raw from disk
37+
AutoFile non_xor_file{raw_file("rb")};
38+
std::vector<std::byte> raw(7);
39+
non_xor_file >> Span{raw};
40+
BOOST_CHECK_EQUAL(HexStr(raw), "fc01fd03fd04fa");
41+
// Check that no padding exists
42+
BOOST_CHECK_EXCEPTION(non_xor_file.ignore(1), std::ios_base::failure, HasReason{"AutoFile::ignore: end of file"});
43+
}
44+
{
45+
XorFile xor_file{raw_file("rb"), {}, xor_pat};
46+
std::vector<std::byte> read1, read2;
47+
xor_file >> read1 >> read2;
48+
BOOST_CHECK_EQUAL(HexStr(read1), HexStr(test1));
49+
BOOST_CHECK_EQUAL(HexStr(read2), HexStr(test2));
50+
// Check that eof was reached
51+
BOOST_CHECK_EXCEPTION(xor_file >> std::byte{}, std::ios_base::failure, HasReason{"XorFile::read: end of file"});
52+
}
53+
{
54+
XorFile xor_file{raw_file("rb"), {}, xor_pat};
55+
std::vector<std::byte> read2;
56+
// Check that ignore works
57+
xor_file.ignore(4);
58+
xor_file >> read2;
59+
BOOST_CHECK_EQUAL(HexStr(read2), HexStr(test2));
60+
// Check that ignore and read fail now
61+
BOOST_CHECK_EXCEPTION(xor_file.ignore(1), std::ios_base::failure, HasReason{"XorFile::ignore: end of file"});
62+
BOOST_CHECK_EXCEPTION(xor_file >> std::byte{}, std::ios_base::failure, HasReason{"XorFile::read: end of file"});
63+
}
64+
}
65+
1666
BOOST_AUTO_TEST_CASE(streams_vector_writer)
1767
{
1868
unsigned char a(1);

0 commit comments

Comments
 (0)