File indexing completed on 2026-08-02 08:25:43
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/Io/Root/RootFileHasher.hpp"
0010
0011 #include "ActsExamples/Io/Root/detail/RootFileHasherHelpers.hpp"
0012
0013 #include <algorithm>
0014 #include <array>
0015 #include <cstddef>
0016 #include <cstdint>
0017 #include <format>
0018 #include <iostream>
0019 #include <memory>
0020 #include <set>
0021 #include <span>
0022 #include <stdexcept>
0023 #include <vector>
0024
0025 #include <TBranch.h>
0026 #include <TFile.h>
0027 #include <TKey.h>
0028 #include <TLeaf.h>
0029 #include <TObjArray.h>
0030 #include <TTree.h>
0031 #include <TTreeReader.h>
0032 #include <TTreeReaderArray.h>
0033 #include <TTreeReaderValue.h>
0034
0035 #if defined(ACTS_ROOT_FILE_HASHER_USE_BOOST_HASH2)
0036 #include <boost/hash2/sha2.hpp>
0037 #else
0038 #include <TMD5.h>
0039 #endif
0040
0041 namespace ActsExamples {
0042
0043 namespace {
0044
0045 template <typename H>
0046 concept HasBoostUpdate =
0047 requires(H h, const unsigned char* data, std::size_t size) {
0048 { h.update(data, size) } -> std::same_as<void>;
0049 };
0050
0051 template <typename H>
0052 concept HasFinalize = requires(H h, unsigned char* data) { h.Final(data); };
0053
0054
0055
0056
0057 template <typename H, std::size_t digestSize>
0058 class HasherT {
0059 public:
0060 using HasherType = H;
0061 static constexpr std::size_t kDigestSize = digestSize;
0062 using Digest = std::array<unsigned char, kDigestSize>;
0063
0064 void update(std::span<const std::byte> data) {
0065 if constexpr (HasBoostUpdate<H>) {
0066 m_hasher.update(reinterpret_cast<const unsigned char*>(data.data()),
0067 data.size());
0068 } else {
0069 m_hasher.Update(reinterpret_cast<const UChar_t*>(data.data()),
0070 data.size());
0071 }
0072 }
0073
0074 Digest finalize() {
0075 Digest digest{};
0076
0077 if constexpr (HasFinalize<H>) {
0078 m_hasher.Final(digest.data());
0079 } else {
0080 boost::hash2::digest<kDigestSize> result = m_hasher.result();
0081 std::copy(result.data(), result.data() + kDigestSize, digest.begin());
0082 }
0083 return digest;
0084 }
0085
0086 private:
0087 HasherType m_hasher;
0088 };
0089
0090 #if defined(ACTS_ROOT_FILE_HASHER_USE_BOOST_HASH2)
0091 using Hasher = HasherT<boost::hash2::sha2_256, 32>;
0092 #else
0093 using Hasher = HasherT<TMD5, 16>;
0094 #endif
0095
0096 using Digest = typename Hasher::Digest;
0097
0098
0099 Digest digestOf(std::span<const std::byte> data) {
0100 Hasher hasher;
0101 hasher.update(data);
0102 return hasher.finalize();
0103 }
0104
0105
0106 std::string toHex(const Digest& digest) {
0107 static const char* const hexDigits = "0123456789abcdef";
0108 std::string out;
0109 out.reserve(2 * digest.size());
0110 for (unsigned char byte : digest) {
0111 out.push_back(hexDigits[byte >> 4]);
0112 out.push_back(hexDigits[byte & 0x0f]);
0113 }
0114 return out;
0115 }
0116
0117
0118 template <typename T>
0119 void appendBytes(std::vector<std::byte>& out, const T& x) {
0120 const auto* p = reinterpret_cast<const std::byte*>(&x);
0121 out.insert(out.end(), p, p + sizeof(T));
0122 }
0123
0124
0125
0126 struct IBranchReader {
0127 virtual ~IBranchReader() = default;
0128
0129 virtual void append(std::vector<std::byte>& out) = 0;
0130
0131
0132 virtual bool valid() const = 0;
0133 };
0134
0135 template <typename T>
0136 bool setupOk(const T& proxy) {
0137 return proxy.GetSetupStatus() ==
0138 ROOT::Internal::TTreeReaderValueBase::kSetupMatch;
0139 }
0140
0141
0142 template <typename T>
0143 struct ScalarReader final : IBranchReader {
0144 TTreeReaderValue<T> value;
0145 ScalarReader(TTreeReader& reader, const char* name) : value(reader, name) {}
0146 bool valid() const override { return setupOk(value); }
0147 void append(std::vector<std::byte>& out) override {
0148 appendBytes(out, *value);
0149 }
0150 };
0151
0152
0153 template <typename T>
0154 struct ArrayReader final : IBranchReader {
0155 TTreeReaderArray<T> array;
0156 ArrayReader(TTreeReader& reader, const char* name) : array(reader, name) {}
0157 bool valid() const override { return setupOk(array); }
0158 void append(std::vector<std::byte>& out) override {
0159 for (T x : array) {
0160 appendBytes(out, x);
0161 }
0162 }
0163 };
0164
0165
0166 template <typename T>
0167 struct NestedReader final : IBranchReader {
0168 TTreeReaderValue<std::vector<std::vector<T>>> value;
0169 NestedReader(TTreeReader& reader, const char* name) : value(reader, name) {}
0170 bool valid() const override { return setupOk(value); }
0171 void append(std::vector<std::byte>& out) override {
0172 for (const auto& inner : *value) {
0173 for (T x : inner) {
0174 appendBytes(out, x);
0175 }
0176 }
0177 }
0178 };
0179
0180 enum class Kind { Scalar, Array, Nested };
0181
0182 template <typename T>
0183 std::unique_ptr<IBranchReader> makeReader(Kind kind, TTreeReader& reader,
0184 const char* name) {
0185 switch (kind) {
0186 case Kind::Scalar:
0187 return std::make_unique<ScalarReader<T>>(reader, name);
0188 case Kind::Array:
0189 return std::make_unique<ArrayReader<T>>(reader, name);
0190 case Kind::Nested:
0191 return std::make_unique<NestedReader<T>>(reader, name);
0192 }
0193 return nullptr;
0194 }
0195
0196
0197
0198
0199
0200 std::unique_ptr<IBranchReader> dispatch(const std::string& type, Kind kind,
0201 TTreeReader& reader, const char* name) {
0202 if (type == "Float_t" || type == "float") {
0203 return makeReader<float>(kind, reader, name);
0204 }
0205 if (type == "Double_t" || type == "double") {
0206 return makeReader<double>(kind, reader, name);
0207 }
0208 if (type == "Int_t" || type == "int") {
0209 return makeReader<int>(kind, reader, name);
0210 }
0211 if (type == "UInt_t" || type == "unsigned int" || type == "unsigned") {
0212 return makeReader<unsigned int>(kind, reader, name);
0213 }
0214 if (type == "Long64_t" || type == "long long") {
0215 return makeReader<long long>(kind, reader, name);
0216 }
0217 if (type == "ULong64_t" || type == "unsigned long long") {
0218 return makeReader<unsigned long long>(kind, reader, name);
0219 }
0220 if (type == "Long_t" || type == "long") {
0221 return makeReader<long>(kind, reader, name);
0222 }
0223 if (type == "ULong_t" || type == "unsigned long") {
0224 return makeReader<unsigned long>(kind, reader, name);
0225 }
0226 if (type == "Short_t" || type == "short") {
0227 return makeReader<short>(kind, reader, name);
0228 }
0229 if (type == "UShort_t" || type == "unsigned short") {
0230 return makeReader<unsigned short>(kind, reader, name);
0231 }
0232 if (type == "Char_t" || type == "char") {
0233 return makeReader<char>(kind, reader, name);
0234 }
0235 if (type == "UChar_t" || type == "unsigned char") {
0236 return makeReader<unsigned char>(kind, reader, name);
0237 }
0238 if (type == "Bool_t" || type == "bool") {
0239 return makeReader<bool>(kind, reader, name);
0240 }
0241 return nullptr;
0242 }
0243
0244 struct BranchInfo {
0245 std::string name;
0246 Kind kind = Kind::Scalar;
0247 std::string elementType;
0248 };
0249
0250
0251 std::vector<BranchInfo> describeBranches(TTree& tree) {
0252 std::vector<BranchInfo> infos;
0253 TObjArray* branches = tree.GetListOfBranches();
0254 if (branches == nullptr) {
0255 return infos;
0256 }
0257 for (int i = 0; i < branches->GetEntriesFast(); ++i) {
0258 auto* branch = dynamic_cast<TBranch*>(branches->At(i));
0259 if (branch == nullptr) {
0260 continue;
0261 }
0262 BranchInfo info;
0263 info.name = branch->GetName();
0264
0265 std::string className = branch->GetClassName();
0266 if (!className.empty()) {
0267
0268 std::string arg = detail::firstTemplateArg(className);
0269 if (arg.rfind("vector<", 0) == 0) {
0270 info.kind = Kind::Nested;
0271 info.elementType = detail::firstTemplateArg(arg);
0272 } else {
0273 info.kind = Kind::Array;
0274 info.elementType = arg;
0275 }
0276 } else {
0277
0278 auto* leaf = dynamic_cast<TLeaf*>(branch->GetListOfLeaves()->At(0));
0279 if (leaf == nullptr) {
0280 continue;
0281 }
0282 info.elementType = leaf->GetTypeName();
0283 bool isArray =
0284 (leaf->GetLeafCount() != nullptr) || (leaf->GetLenStatic() > 1);
0285 info.kind = isArray ? Kind::Array : Kind::Scalar;
0286 }
0287 infos.push_back(std::move(info));
0288 }
0289 std::sort(
0290 infos.begin(), infos.end(),
0291 [](const BranchInfo& a, const BranchInfo& b) { return a.name < b.name; });
0292 return infos;
0293 }
0294
0295 struct Reader {
0296 std::string name;
0297 std::unique_ptr<IBranchReader> reader;
0298 std::unique_ptr<Hasher> hasher;
0299 };
0300
0301
0302 Digest hashTree(TTree& tree, bool orderInvariant) {
0303 std::vector<BranchInfo> infos = describeBranches(tree);
0304
0305 TTreeReader treeReader(&tree);
0306 std::vector<Reader> readers;
0307 for (const BranchInfo& info : infos) {
0308 auto branchReader =
0309 dispatch(info.elementType, info.kind, treeReader, info.name.c_str());
0310 if (branchReader == nullptr) {
0311 std::cerr << "RootFileHasher: unsupported type '" << info.elementType
0312 << "' for branch '" << info.name << "' (skipped)" << std::endl;
0313 continue;
0314 }
0315 readers.push_back({info.name, std::move(branchReader),
0316 orderInvariant ? nullptr : std::make_unique<Hasher>()});
0317 }
0318
0319 std::vector<Digest> rowDigests;
0320 std::vector<std::byte> buffer;
0321 bool pruned = false;
0322 while (treeReader.Next()) {
0323 if (!pruned) {
0324
0325
0326 std::vector<Reader> kept;
0327 for (auto& r : readers) {
0328 if (r.reader->valid()) {
0329 kept.push_back(std::move(r));
0330 } else {
0331 std::cerr << "RootFileHasher: branch '" << r.name
0332 << "' could not be read (skipped)" << std::endl;
0333 }
0334 }
0335 readers = std::move(kept);
0336 pruned = true;
0337 }
0338
0339 if (orderInvariant) {
0340 buffer.clear();
0341 for (auto& r : readers) {
0342 r.reader->append(buffer);
0343 }
0344 rowDigests.push_back(digestOf(buffer));
0345 } else {
0346 for (auto& r : readers) {
0347 buffer.clear();
0348 r.reader->append(buffer);
0349 r.hasher->update(buffer);
0350 }
0351 }
0352 }
0353
0354 Hasher treeHash;
0355 std::string names;
0356 for (const auto& r : readers) {
0357 names += r.name;
0358 }
0359 treeHash.update(std::as_bytes(std::span(names)));
0360
0361 if (orderInvariant) {
0362 std::sort(rowDigests.begin(), rowDigests.end());
0363 for (const Digest& d : rowDigests) {
0364 treeHash.update(std::as_bytes(std::span(d)));
0365 }
0366 } else {
0367 for (auto& r : readers) {
0368 Digest d = r.hasher->finalize();
0369 treeHash.update(std::as_bytes(std::span(d)));
0370 }
0371 }
0372
0373 return treeHash.finalize();
0374 }
0375
0376 }
0377
0378 std::string hashRootFile(const std::filesystem::path& path,
0379 bool orderInvariant) {
0380 TFile file(path.c_str(), "READ");
0381 if (file.IsZombie()) {
0382 throw std::runtime_error(
0383 std::format("RootFileHasher: could not open '{}'", path.string()));
0384 }
0385
0386
0387
0388
0389
0390 std::set<std::string> keyNames;
0391 TList* keys = file.GetListOfKeys();
0392 if (keys != nullptr) {
0393 for (int i = 0; i < keys->GetEntries(); ++i) {
0394 auto* key = dynamic_cast<TKey*>(keys->At(i));
0395 if (key != nullptr) {
0396 keyNames.insert(key->GetName());
0397 }
0398 }
0399 }
0400
0401 Hasher global;
0402 for (const std::string& name : keyNames) {
0403 global.update(std::as_bytes(std::span(name)));
0404
0405 auto* tree = dynamic_cast<TTree*>(file.Get(name.c_str()));
0406 if (tree == nullptr) {
0407 continue;
0408 }
0409 Digest treeDigest = hashTree(*tree, orderInvariant);
0410 global.update(std::as_bytes(std::span(treeDigest)));
0411 }
0412
0413 return toHex(global.finalize());
0414 }
0415
0416 }