Unravel Engine C++ Reference
Loading...
Searching...
No Matches
file_fingerprint.cpp
Go to the documentation of this file.
1#include "file_fingerprint.h"
2#include "file_istream.h"
3
4#define XXH_INLINE_ALL
5#include "xxhash.h"
6
7#include <array>
8#include <istream>
9#include <vector>
10
11namespace fs
12{
13namespace
14{
15
16auto xxh128_to_hex(const XXH128_hash_t& hash) -> std::string
17{
18 static constexpr char alphabet[] = "0123456789abcdef";
19 std::string hex(32, '\0');
20 const std::array<uint64_t, 2> parts = {hash.high64, hash.low64};
21 size_t offset = 0;
22 for(const auto part : parts)
23 {
24 for(int shift = 60; shift >= 0; shift -= 4)
25 {
26 hex[offset++] = alphabet[(part >> shift) & 0xF];
27 }
28 }
29 return hex;
30}
31
32auto hex_to_xxh128(const std::string& hex) -> XXH128_hash_t
33{
34 XXH128_hash_t hash{};
35 if(hex.size() != 32)
36 {
37 return hash;
38 }
39 auto parse_nibble = [](char c) -> uint64_t
40 {
41 if(c >= '0' && c <= '9')
42 {
43 return static_cast<uint64_t>(c - '0');
44 }
45 if(c >= 'a' && c <= 'f')
46 {
47 return static_cast<uint64_t>(10 + c - 'a');
48 }
49 if(c >= 'A' && c <= 'F')
50 {
51 return static_cast<uint64_t>(10 + c - 'A');
52 }
53 return 0;
54 };
55 for(size_t i = 0; i < 16; ++i)
56 {
57 hash.high64 = (hash.high64 << 4) | parse_nibble(hex[i]);
58 }
59 for(size_t i = 16; i < 32; ++i)
60 {
61 hash.low64 = (hash.low64 << 4) | parse_nibble(hex[i]);
62 }
63 return hash;
64}
65
66void xxh3_update_text_normalized(XXH3_state_t& state,
67 const unsigned char* data,
68 size_t size,
69 bool& last_was_cr)
70{
71 auto feed = [&](const unsigned char* ptr, size_t len)
72 {
73 if(len > 0)
74 {
75 XXH3_128bits_update(&state, ptr, len);
76 }
77 };
78 auto feed_lf = [&]()
79 {
80 static const unsigned char lf = '\n';
81 feed(&lf, 1);
82 };
83 size_t index = 0;
84 while(index < size)
85 {
86 if(last_was_cr)
87 {
88 const unsigned char c = data[index++];
89 if(c == '\n')
90 {
91 feed_lf();
92 last_was_cr = false;
93 }
94 else
95 {
96 feed_lf();
97 last_was_cr = false;
98 if(c == '\r')
99 {
100 last_was_cr = true;
101 }
102 else if(c == '\n')
103 {
104 feed_lf();
105 }
106 else
107 {
108 feed(&c, 1);
109 }
110 }
111 continue;
112 }
113 const size_t start = index;
114 while(index < size && data[index] != '\r' && data[index] != '\n')
115 {
116 ++index;
117 }
118 if(index > start)
119 {
120 feed(data + start, index - start);
121 }
122 if(index >= size)
123 {
124 break;
125 }
126 const unsigned char c = data[index++];
127 if(c == '\r')
128 {
129 last_was_cr = true;
130 }
131 else
132 {
133 feed_lf();
134 }
135 }
136}
137
138auto hash_mapped_fingerprint(const char* data, size_t size, bool normalize_text_line_endings) -> std::string
139{
140 if(normalize_text_line_endings)
141 {
142 XXH3_state_t state;
143 XXH3_128bits_reset(&state);
144 bool last_was_cr = false;
145 xxh3_update_text_normalized(state, reinterpret_cast<const unsigned char*>(data), size, last_was_cr);
146 if(last_was_cr)
147 {
148 static const unsigned char lf = '\n';
149 XXH3_128bits_update(&state, &lf, 1);
150 }
151 return xxh128_to_hex(XXH3_128bits_digest(&state));
152 }
153 return xxh128_to_hex(XXH3_128bits(data, size));
154}
155
156auto hash_stream_fingerprint(std::istream& file, bool normalize_text_line_endings) -> std::string
157{
158 std::vector<char> buffer(64 * 1024);
159 if(normalize_text_line_endings)
160 {
161 XXH3_state_t state;
162 XXH3_128bits_reset(&state);
163 bool last_was_cr = false;
164 while(file.good())
165 {
166 file.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
167 const std::streamsize bytes_read = file.gcount();
168 if(bytes_read <= 0)
169 {
170 break;
171 }
172 xxh3_update_text_normalized(state,
173 reinterpret_cast<const unsigned char*>(buffer.data()),
174 static_cast<size_t>(bytes_read),
175 last_was_cr);
176 }
177 if(last_was_cr)
178 {
179 static const unsigned char lf = '\n';
180 XXH3_128bits_update(&state, &lf, 1);
181 }
182 return xxh128_to_hex(XXH3_128bits_digest(&state));
183 }
184 XXH3_state_t state;
185 XXH3_128bits_reset(&state);
186 while(file.good())
187 {
188 file.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
189 const std::streamsize bytes_read = file.gcount();
190 if(bytes_read <= 0)
191 {
192 break;
193 }
194 XXH3_128bits_update(&state, buffer.data(), static_cast<size_t>(bytes_read));
195 }
196 return xxh128_to_hex(XXH3_128bits_digest(&state));
197}
198
199} // namespace
200
201auto hash_file_fingerprint(const path& file_path, bool normalize_text_line_endings) -> std::string
202{
203 file_istream input(file_path, normalize_text_line_endings ? std::ios::in : std::ios::binary);
204 if(!input.is_open())
205 {
206 return {};
207 }
208 return hash_stream_fingerprint(input, normalize_text_line_endings);
209}
210
211auto combine_file_fingerprints(const std::string& first_fingerprint,
212 const std::string* additional_fingerprints,
213 size_t additional_count) -> std::string
214{
215 XXH3_state_t state;
216 XXH3_128bits_reset(&state);
217 if(!first_fingerprint.empty())
218 {
219 const XXH128_hash_t first_hash = hex_to_xxh128(first_fingerprint);
220 XXH3_128bits_update(&state, &first_hash, sizeof(first_hash));
221 }
222 for(size_t i = 0; i < additional_count; ++i)
223 {
224 const XXH128_hash_t hash = hex_to_xxh128(additional_fingerprints[i]);
225 XXH3_128bits_update(&state, &hash, sizeof(hash));
226 }
227 return xxh128_to_hex(XXH3_128bits_digest(&state));
228}
229
230} // namespace fs
stdio-backed input stream. Accepts std::ios open flags (e.g. std::ios::binary) when opening a
uint16_t index
XXH_PUBLIC_API XXH_errorcode XXH3_128bits_reset(XXH_NOESCAPE XXH3_state_t *statePtr)
Resets an XXH3_state_t to begin a new hash.
XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits_digest(XXH_NOESCAPE const XXH3_state_t *statePtr)
Returns the calculated XXH3 128-bit hash value from an XXH3_state_t.
struct XXH3_state_s XXH3_state_t
The opaque state struct for the XXH3 streaming API.
Definition xxhash.h:1236
XXH_PUBLIC_API XXH_PUREF XXH128_hash_t XXH3_128bits(XXH_NOESCAPE const void *data, size_t len)
Calculates 128-bit unseeded variant of XXH3 of data.
XXH_PUBLIC_API XXH_errorcode XXH3_128bits_update(XXH_NOESCAPE XXH3_state_t *statePtr, XXH_NOESCAPE const void *input, size_t length)
Consumes a block of input to an XXH3_state_t.
Definition cache.hpp:11
auto hash_file_fingerprint(const path &file_path, bool normalize_text_line_endings) -> std::string
Hash a file's contents. Text assets normalize CRLF to LF before hashing.
auto combine_file_fingerprints(const std::string &first_fingerprint, const std::string *additional_fingerprints, size_t additional_count) -> std::string
Combine per-file fingerprints (raw 128-bit digests) into one hex fingerprint.
std::vector< math::vec3 > start
The return value from 128-bit hashes.
Definition xxhash.h:1375
XXH64_hash_t low64
Definition xxhash.h:1376
XXH64_hash_t high64
Definition xxhash.h:1377