about summary refs log tree commit diff
path: root/users/zseri
diff options
context:
space:
mode:
authorzseri <zseri.devel@ytrizja.de>2021-12-24T19·29+0100
committerzseri <zseri.devel@ytrizja.de>2021-12-25T01·22+0000
commit8cdda57580bb1b4fdb19091c45b437f6327d78c0 (patch)
tree388c06ee055193835fcab0a7791fbd6a40dc49d2 /users/zseri
parent979502335b8130599a89e2b3f91aaa974aa6dfe5 (diff)
feat(zseri): Add store-ref-scanner crate r/3383
This crate implements the scanner for finding references to store
paths in uncompressed binary blobs and text files.
It is currently a minimally working prototype and
it is probably a good idea to polish the interface further.

Change-Id: I8406f9d52d254fc3d660ea2b9bc9b7841cc815ec
Reviewed-on: https://cl.tvl.fyi/c/depot/+/4596
Tested-by: BuildkiteCI
Reviewed-by: zseri <zseri.devel@ytrizja.de>
Diffstat (limited to 'users/zseri')
-rw-r--r--users/zseri/.gitignore2
-rw-r--r--users/zseri/store-ref-scanner/Cargo.lock238
-rw-r--r--users/zseri/store-ref-scanner/Cargo.nix1336
-rw-r--r--users/zseri/store-ref-scanner/Cargo.toml16
-rw-r--r--users/zseri/store-ref-scanner/default.nix5
-rw-r--r--users/zseri/store-ref-scanner/src/hbm.rs150
-rw-r--r--users/zseri/store-ref-scanner/src/lib.rs225
-rw-r--r--users/zseri/store-ref-scanner/src/spec.rs46
8 files changed, 2018 insertions, 0 deletions
diff --git a/users/zseri/.gitignore b/users/zseri/.gitignore
new file mode 100644
index 0000000000..b8553ace55
--- /dev/null
+++ b/users/zseri/.gitignore
@@ -0,0 +1,2 @@
+.#*
+target/
diff --git a/users/zseri/store-ref-scanner/Cargo.lock b/users/zseri/store-ref-scanner/Cargo.lock
new file mode 100644
index 0000000000..85b9798b3a
--- /dev/null
+++ b/users/zseri/store-ref-scanner/Cargo.lock
@@ -0,0 +1,238 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "camino"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.112"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "proc_unroll"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ab97d993c96374333148bad5043d3c85a572c1ca81d13b9cf92f23f5ef72f54"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proptest"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5"
+dependencies = [
+ "bitflags",
+ "byteorder",
+ "lazy_static",
+ "num-traits",
+ "quick-error",
+ "rand",
+ "rand_chacha",
+ "rand_xorshift",
+ "regex-syntax",
+]
+
+[[package]]
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "store-ref-scanner"
+version = "0.1.0"
+dependencies = [
+ "camino",
+ "once_cell",
+ "proc_unroll",
+ "proptest",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23a1dfb999630e338648c83e91c59a4e9fb7620f520c3194b6b89e276f2f1959"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
diff --git a/users/zseri/store-ref-scanner/Cargo.nix b/users/zseri/store-ref-scanner/Cargo.nix
new file mode 100644
index 0000000000..d250065d12
--- /dev/null
+++ b/users/zseri/store-ref-scanner/Cargo.nix
@@ -0,0 +1,1336 @@
+
+# This file was @generated by crate2nix 0.10.0 with the command:
+#   "generate"
+# See https://github.com/kolloch/crate2nix for more info.
+
+{ nixpkgs ? <nixpkgs>
+, pkgs ? import nixpkgs { config = {}; }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+, buildRustCrateForPkgs ? if buildRustCrate != null
+    then lib.warn "crate2nix: Passing `buildRustCrate` as argument to Cargo.nix is deprecated. If you don't customize `buildRustCrate`, replace `callPackage ./Cargo.nix {}` by `import ./Cargo.nix { inherit pkgs; }`, and if you need to customize `buildRustCrate`, use `buildRustCrateForPkgs` instead." (_: buildRustCrate)
+    else pkgs: pkgs.buildRustCrate
+  # Deprecated
+, buildRustCrate ? null
+  # This is used as the `crateOverrides` argument for `buildRustCrate`.
+, defaultCrateOverrides ? pkgs.defaultCrateOverrides
+  # The features to enable for the root_crate or the workspace_members.
+, rootFeatures ? [ "default" ]
+  # If true, throw errors instead of issueing deprecation warnings.
+, strictDeprecation ? false
+  # Used for conditional compilation based on CPU feature detection.
+, targetFeatures ? []
+  # Whether to perform release builds: longer compile times, faster binaries.
+, release ? true
+  # Additional crate2nix configuration if it exists.
+, crateConfig
+  ? if builtins.pathExists ./crate-config.nix
+    then pkgs.callPackage ./crate-config.nix {}
+    else {}
+}:
+
+rec {
+  #
+  # "public" attributes that we attempt to keep stable with new versions of crate2nix.
+  #
+
+  rootCrate = rec {
+    packageId = "store-ref-scanner";
+
+    # Use this attribute to refer to the derivation building your root crate package.
+    # You can override the features with rootCrate.build.override { features = [ "default" "feature1" ... ]; }.
+    build = internal.buildRustCrateWithFeatures {
+      inherit packageId;
+    };
+
+    # Debug support which might change between releases.
+    # File a bug if you depend on any for non-debug work!
+    debug = internal.debugCrate { inherit packageId; };
+  };
+  # Refer your crate build derivation by name here.
+  # You can override the features with
+  # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }.
+  workspaceMembers = {
+    "store-ref-scanner" = rec {
+      packageId = "store-ref-scanner";
+      build = internal.buildRustCrateWithFeatures {
+        packageId = "store-ref-scanner";
+      };
+
+      # Debug support which might change between releases.
+      # File a bug if you depend on any for non-debug work!
+      debug = internal.debugCrate { inherit packageId; };
+    };
+  };
+
+  # A derivation that joins the outputs of all workspace members together.
+  allWorkspaceMembers = pkgs.symlinkJoin {
+      name = "all-workspace-members";
+      paths =
+        let members = builtins.attrValues workspaceMembers;
+        in builtins.map (m: m.build) members;
+  };
+
+  #
+  # "internal" ("private") attributes that may change in every new version of crate2nix.
+  #
+
+  internal = rec {
+    # Build and dependency information for crates.
+    # Many of the fields are passed one-to-one to buildRustCrate.
+    #
+    # Noteworthy:
+    # * `dependencies`/`buildDependencies`: similar to the corresponding fields for buildRustCrate.
+    #   but with additional information which is used during dependency/feature resolution.
+    # * `resolvedDependencies`: the selected default features reported by cargo - only included for debugging.
+    # * `devDependencies` as of now not used by `buildRustCrate` but used to
+    #   inject test dependencies into the build
+
+    crates = {
+      "autocfg" = rec {
+        crateName = "autocfg";
+        version = "1.0.1";
+        edition = "2015";
+        sha256 = "0jj6i9zn4gjl03kjvziqdji6rwx8ykz8zk2ngpc331z2g3fk3c6d";
+        authors = [
+          "Josh Stone <cuviper@gmail.com>"
+        ];
+
+      };
+      "bitflags" = rec {
+        crateName = "bitflags";
+        version = "1.3.2";
+        edition = "2018";
+        sha256 = "12ki6w8gn1ldq7yz9y680llwk5gmrhrzszaa17g1sbrw2r2qvwxy";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "byteorder" = rec {
+        crateName = "byteorder";
+        version = "1.4.3";
+        edition = "2018";
+        sha256 = "0456lv9xi1a5bcm32arknf33ikv76p3fr9yzki4lb2897p2qkh8l";
+        authors = [
+          "Andrew Gallant <jamslam@gmail.com>"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "camino" = rec {
+        crateName = "camino";
+        version = "1.0.5";
+        edition = "2018";
+        sha256 = "0avbz0vh6l9ggf1hd6l4q3zxgw5qni0ni94a43i48sdzv5h45msj";
+        authors = [
+          "Without Boats <saoirse@without.boats>"
+          "Ashley Williams <ashley666ashley@gmail.com>"
+          "Steve Klabnik <steve@steveklabnik.com>"
+          "Rain <rain@sunshowers.io>"
+        ];
+        features = {
+          "serde1" = [ "serde" ];
+        };
+      };
+      "cfg-if" = rec {
+        crateName = "cfg-if";
+        version = "1.0.0";
+        edition = "2018";
+        sha256 = "1za0vb97n4brpzpv8lsbnzmq5r8f2b0cpqqr0sy8h5bn751xxwds";
+        authors = [
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        features = {
+          "rustc-dep-of-std" = [ "core" "compiler_builtins" ];
+        };
+      };
+      "getrandom" = rec {
+        crateName = "getrandom";
+        version = "0.2.3";
+        edition = "2018";
+        sha256 = "0lr7mnkvnzdh1xxmwmhhbm4gwg29k3m2rzhpjmjm4k2jcfa9kkbz";
+        authors = [
+          "The Rand Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "cfg-if";
+            packageId = "cfg-if";
+          }
+          {
+            name = "libc";
+            packageId = "libc";
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "wasi";
+            packageId = "wasi";
+            target = { target, features }: (target."os" == "wasi");
+          }
+        ];
+        features = {
+          "js" = [ "wasm-bindgen" "js-sys" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "libc/rustc-dep-of-std" "wasi/rustc-dep-of-std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "lazy_static" = rec {
+        crateName = "lazy_static";
+        version = "1.4.0";
+        edition = "2015";
+        sha256 = "0in6ikhw8mgl33wjv6q6xfrb5b9jr16q8ygjy803fay4zcisvaz2";
+        authors = [
+          "Marvin Löbel <loebel.marvin@gmail.com>"
+        ];
+        features = {
+          "spin_no_std" = [ "spin" ];
+        };
+      };
+      "libc" = rec {
+        crateName = "libc";
+        version = "0.2.112";
+        edition = "2015";
+        sha256 = "09bik7pcck869kfr5i9hjhnck0mzpd9v0ijxbqnh8fja6rzx20qv";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "align" "rustc-std-workspace-core" ];
+          "use_std" = [ "std" ];
+        };
+      };
+      "num-traits" = rec {
+        crateName = "num-traits";
+        version = "0.2.14";
+        edition = "2015";
+        sha256 = "144j176s2p76azy2ngk2vkdzgwdc0bc8c93jhki8c9fsbknb2r4s";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        buildDependencies = [
+          {
+            name = "autocfg";
+            packageId = "autocfg";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "once_cell" = rec {
+        crateName = "once_cell";
+        version = "1.9.0";
+        edition = "2018";
+        sha256 = "1mfqhrsgi368x92bwnq3vi3p5nv0n1qlrn69gfflhvkfkxfm2cns";
+        authors = [
+          "Aleksey Kladov <aleksey.kladov@gmail.com>"
+        ];
+        features = {
+          "alloc" = [ "race" ];
+          "default" = [ "std" ];
+          "std" = [ "alloc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "default" "race" "std" ];
+      };
+      "ppv-lite86" = rec {
+        crateName = "ppv-lite86";
+        version = "0.2.15";
+        edition = "2018";
+        sha256 = "1fimwnyyh3wx33r5s77lw5g5vcxhw1p5j60pdvbbwr8l374gn37d";
+        authors = [
+          "The CryptoCorrosion Contributors"
+        ];
+        features = {
+          "default" = [ "std" ];
+        };
+        resolvedDefaultFeatures = [ "simd" "std" ];
+      };
+      "proc-macro-error" = rec {
+        crateName = "proc-macro-error";
+        version = "1.0.4";
+        edition = "2018";
+        sha256 = "1373bhxaf0pagd8zkyd03kkx6bchzf6g0dkwrwzsnal9z47lj9fs";
+        authors = [
+          "CreepySkeleton <creepy-skeleton@yandex.ru>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro-error-attr";
+            packageId = "proc-macro-error-attr";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+        features = {
+          "default" = [ "syn-error" ];
+          "syn-error" = [ "syn" ];
+        };
+        resolvedDefaultFeatures = [ "default" "syn" "syn-error" ];
+      };
+      "proc-macro-error-attr" = rec {
+        crateName = "proc-macro-error-attr";
+        version = "1.0.4";
+        edition = "2018";
+        sha256 = "0sgq6m5jfmasmwwy8x4mjygx5l7kp8s4j60bv25ckv2j1qc41gm1";
+        procMacro = true;
+        authors = [
+          "CreepySkeleton <creepy-skeleton@yandex.ru>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+        ];
+        buildDependencies = [
+          {
+            name = "version_check";
+            packageId = "version_check";
+          }
+        ];
+
+      };
+      "proc-macro2" = rec {
+        crateName = "proc-macro2";
+        version = "1.0.34";
+        edition = "2018";
+        sha256 = "1c93jhwl8lv3hiqqvdhd2d2xhjryh5bqb9w5icr5i7bw1wnfk11g";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+          "Alex Crichton <alex@alexcrichton.com>"
+        ];
+        dependencies = [
+          {
+            name = "unicode-xid";
+            packageId = "unicode-xid";
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "proc_unroll" = rec {
+        crateName = "proc_unroll";
+        version = "0.1.1";
+        edition = "2018";
+        sha256 = "0m1gyxg3zwljrywi67d83hn5fnn8sd1m1bcb2hrl6dwn7jcpvfca";
+        procMacro = true;
+        authors = [
+          "leo60228 <leo@60228.dev>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro-error";
+            packageId = "proc-macro-error";
+          }
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+          }
+          {
+            name = "syn";
+            packageId = "syn";
+            features = [ "full" "fold" ];
+          }
+        ];
+
+      };
+      "proptest" = rec {
+        crateName = "proptest";
+        version = "1.0.0";
+        edition = "2018";
+        sha256 = "1rdhjnf0xma5rmsq04d31n2vq1pgbm42pjc6jn3jsj8qgz09q38y";
+        authors = [
+          "Jason Lingle"
+        ];
+        dependencies = [
+          {
+            name = "bitflags";
+            packageId = "bitflags";
+          }
+          {
+            name = "byteorder";
+            packageId = "byteorder";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "lazy_static";
+            packageId = "lazy_static";
+            optional = true;
+          }
+          {
+            name = "num-traits";
+            packageId = "num-traits";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quick-error";
+            packageId = "quick-error";
+            optional = true;
+          }
+          {
+            name = "rand";
+            packageId = "rand";
+            usesDefaultFeatures = false;
+            features = [ "alloc" ];
+          }
+          {
+            name = "rand_chacha";
+            packageId = "rand_chacha";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "rand_xorshift";
+            packageId = "rand_xorshift";
+          }
+          {
+            name = "regex-syntax";
+            packageId = "regex-syntax";
+            optional = true;
+          }
+        ];
+        features = {
+          "default" = [ "std" "fork" "timeout" "bit-set" "break-dead-code" ];
+          "default-code-coverage" = [ "std" "fork" "timeout" "bit-set" ];
+          "fork" = [ "std" "rusty-fork" "tempfile" ];
+          "hardware-rng" = [ "x86" ];
+          "std" = [ "rand/std" "byteorder/std" "lazy_static" "quick-error" "regex-syntax" "num-traits/std" ];
+          "timeout" = [ "fork" "rusty-fork/timeout" ];
+        };
+        resolvedDefaultFeatures = [ "lazy_static" "quick-error" "regex-syntax" "std" ];
+      };
+      "quick-error" = rec {
+        crateName = "quick-error";
+        version = "2.0.1";
+        edition = "2018";
+        sha256 = "18z6r2rcjvvf8cn92xjhm2qc3jpd1ljvcbf12zv0k9p565gmb4x9";
+        authors = [
+          "Paul Colomiets <paul@colomiets.name>"
+          "Colin Kiegel <kiegel@gmx.de>"
+        ];
+
+      };
+      "quote" = rec {
+        crateName = "quote";
+        version = "1.0.10";
+        edition = "2018";
+        sha256 = "01ff7a76f871ggnby57iagw6499vci4bihcr11g6bqzjlp38rg1q";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+        ];
+        features = {
+          "default" = [ "proc-macro" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" ];
+        };
+        resolvedDefaultFeatures = [ "default" "proc-macro" ];
+      };
+      "rand" = rec {
+        crateName = "rand";
+        version = "0.8.4";
+        edition = "2018";
+        sha256 = "1n5wska2fbfj4dsfz8mc0pd0dgjlrb6c9anpk5mwym345rip6x9f";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "libc";
+            packageId = "libc";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (target."unix" or false);
+          }
+          {
+            name = "rand_chacha";
+            packageId = "rand_chacha";
+            optional = true;
+            usesDefaultFeatures = false;
+            target = { target, features }: (!(target."os" == "emscripten"));
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "alloc" = [ "rand_core/alloc" ];
+          "default" = [ "std" "std_rng" ];
+          "getrandom" = [ "rand_core/getrandom" ];
+          "serde1" = [ "serde" "rand_core/serde1" ];
+          "simd_support" = [ "packed_simd" ];
+          "std" = [ "rand_core/std" "rand_chacha/std" "alloc" "getrandom" "libc" ];
+          "std_rng" = [ "rand_chacha" "rand_hc" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "getrandom" "libc" "rand_chacha" "std" ];
+      };
+      "rand_chacha" = rec {
+        crateName = "rand_chacha";
+        version = "0.3.1";
+        edition = "2018";
+        sha256 = "123x2adin558xbhvqb8w4f6syjsdkmqff8cxwhmjacpsl1ihmhg6";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+          "The CryptoCorrosion Contributors"
+        ];
+        dependencies = [
+          {
+            name = "ppv-lite86";
+            packageId = "ppv-lite86";
+            usesDefaultFeatures = false;
+            features = [ "simd" ];
+          }
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "default" = [ "std" ];
+          "serde1" = [ "serde" ];
+          "std" = [ "ppv-lite86/std" ];
+        };
+        resolvedDefaultFeatures = [ "std" ];
+      };
+      "rand_core" = rec {
+        crateName = "rand_core";
+        version = "0.6.3";
+        edition = "2018";
+        sha256 = "1rxlxc3bpzgwphcg9c9yasvv9idipcg2z2y4j0vlb52jyl418kyk";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "getrandom";
+            packageId = "getrandom";
+            optional = true;
+          }
+        ];
+        features = {
+          "serde1" = [ "serde" ];
+          "std" = [ "alloc" "getrandom" "getrandom/std" ];
+        };
+        resolvedDefaultFeatures = [ "alloc" "getrandom" "std" ];
+      };
+      "rand_xorshift" = rec {
+        crateName = "rand_xorshift";
+        version = "0.3.0";
+        edition = "2018";
+        sha256 = "13vcag7gmqspzyabfl1gr9ykvxd2142q2agrj8dkyjmfqmgg4nyj";
+        authors = [
+          "The Rand Project Developers"
+          "The Rust Project Developers"
+        ];
+        dependencies = [
+          {
+            name = "rand_core";
+            packageId = "rand_core";
+          }
+        ];
+        features = {
+          "serde1" = [ "serde" ];
+        };
+      };
+      "regex-syntax" = rec {
+        crateName = "regex-syntax";
+        version = "0.6.25";
+        edition = "2018";
+        sha256 = "16y87hz1bxmmz6kk360cxwfm3jnbsxb3x4zw9x1gzz7khic2i5zl";
+        authors = [
+          "The Rust Project Developers"
+        ];
+        features = {
+          "default" = [ "unicode" ];
+          "unicode" = [ "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+        };
+        resolvedDefaultFeatures = [ "default" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" ];
+      };
+      "store-ref-scanner" = rec {
+        crateName = "store-ref-scanner";
+        version = "0.1.0";
+        edition = "2021";
+        src = lib.cleanSourceWith { filter = sourceFilter;  src = ./.; };
+        dependencies = [
+          {
+            name = "camino";
+            packageId = "camino";
+          }
+          {
+            name = "once_cell";
+            packageId = "once_cell";
+          }
+          {
+            name = "proc_unroll";
+            packageId = "proc_unroll";
+          }
+        ];
+        devDependencies = [
+          {
+            name = "proptest";
+            packageId = "proptest";
+            usesDefaultFeatures = false;
+            features = [ "std" ];
+          }
+        ];
+
+      };
+      "syn" = rec {
+        crateName = "syn";
+        version = "1.0.83";
+        edition = "2018";
+        sha256 = "0n8r5xpjg7mqnsa3232j1xibg7sfkb2r2gn8923363k3k6wxz893";
+        authors = [
+          "David Tolnay <dtolnay@gmail.com>"
+        ];
+        dependencies = [
+          {
+            name = "proc-macro2";
+            packageId = "proc-macro2";
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "quote";
+            packageId = "quote";
+            optional = true;
+            usesDefaultFeatures = false;
+          }
+          {
+            name = "unicode-xid";
+            packageId = "unicode-xid";
+          }
+        ];
+        features = {
+          "default" = [ "derive" "parsing" "printing" "clone-impls" "proc-macro" ];
+          "printing" = [ "quote" ];
+          "proc-macro" = [ "proc-macro2/proc-macro" "quote/proc-macro" ];
+          "test" = [ "syn-test-suite/all-features" ];
+        };
+        resolvedDefaultFeatures = [ "clone-impls" "default" "derive" "fold" "full" "parsing" "printing" "proc-macro" "quote" ];
+      };
+      "unicode-xid" = rec {
+        crateName = "unicode-xid";
+        version = "0.2.2";
+        edition = "2015";
+        sha256 = "1wrkgcw557v311dkdb6n2hrix9dm2qdsb1zpw7pn79l03zb85jwc";
+        authors = [
+          "erick.tryzelaar <erick.tryzelaar@gmail.com>"
+          "kwantam <kwantam@gmail.com>"
+          "Manish Goregaokar <manishsmail@gmail.com>"
+        ];
+        features = {
+        };
+        resolvedDefaultFeatures = [ "default" ];
+      };
+      "version_check" = rec {
+        crateName = "version_check";
+        version = "0.9.3";
+        edition = "2015";
+        sha256 = "1zmkcgj2m0pq0l4wnhrp1wl1lygf7x2h5p7pvjwc4719lnlxrv2z";
+        authors = [
+          "Sergio Benitez <sb@sergio.bz>"
+        ];
+
+      };
+      "wasi" = rec {
+        crateName = "wasi";
+        version = "0.10.2+wasi-snapshot-preview1";
+        edition = "2018";
+        sha256 = "1ii7nff4y1mpcrxzzvbpgxm7a1nn3szjf1n21jnx37c2g6dbsvzx";
+        authors = [
+          "The Cranelift Project Developers"
+        ];
+        features = {
+          "default" = [ "std" ];
+          "rustc-dep-of-std" = [ "compiler_builtins" "core" "rustc-std-workspace-alloc" ];
+        };
+        resolvedDefaultFeatures = [ "default" "std" ];
+      };
+    };
+
+    #
+# crate2nix/default.nix (excerpt start)
+#
+
+  /* Target (platform) data for conditional dependencies.
+    This corresponds roughly to what buildRustCrate is setting.
+  */
+  defaultTarget = {
+    unix = true;
+    windows = false;
+    fuchsia = true;
+    test = false;
+
+    # This doesn't appear to be officially documented anywhere yet.
+    # See https://github.com/rust-lang-nursery/rust-forge/issues/101.
+    os =
+      if stdenv.hostPlatform.isDarwin
+      then "macos"
+      else stdenv.hostPlatform.parsed.kernel.name;
+    arch = stdenv.hostPlatform.parsed.cpu.name;
+    family = "unix";
+    env = "gnu";
+    endian =
+      if stdenv.hostPlatform.parsed.cpu.significantByte.name == "littleEndian"
+      then "little" else "big";
+    pointer_width = toString stdenv.hostPlatform.parsed.cpu.bits;
+    vendor = stdenv.hostPlatform.parsed.vendor.name;
+    debug_assertions = false;
+  };
+
+  /* Filters common temp files and build files. */
+  # TODO(pkolloch): Substitute with gitignore filter
+  sourceFilter = name: type:
+    let
+      baseName = builtins.baseNameOf (builtins.toString name);
+    in
+      ! (
+        # Filter out git
+        baseName == ".gitignore"
+        || (type == "directory" && baseName == ".git")
+
+        # Filter out build results
+        || (
+          type == "directory" && (
+            baseName == "target"
+            || baseName == "_site"
+            || baseName == ".sass-cache"
+            || baseName == ".jekyll-metadata"
+            || baseName == "build-artifacts"
+          )
+        )
+
+        # Filter out nix-build result symlinks
+        || (
+          type == "symlink" && lib.hasPrefix "result" baseName
+        )
+
+        # Filter out IDE config
+        || (
+          type == "directory" && (
+            baseName == ".idea" || baseName == ".vscode"
+          )
+        ) || lib.hasSuffix ".iml" baseName
+
+        # Filter out nix build files
+        || baseName == "Cargo.nix"
+
+        # Filter out editor backup / swap files.
+        || lib.hasSuffix "~" baseName
+        || builtins.match "^\\.sw[a-z]$$" baseName != null
+        || builtins.match "^\\..*\\.sw[a-z]$$" baseName != null
+        || lib.hasSuffix ".tmp" baseName
+        || lib.hasSuffix ".bak" baseName
+        || baseName == "tests.nix"
+      );
+
+  /* Returns a crate which depends on successful test execution
+    of crate given as the second argument.
+
+    testCrateFlags: list of flags to pass to the test exectuable
+    testInputs: list of packages that should be available during test execution
+  */
+  crateWithTest = { crate, testCrate, testCrateFlags, testInputs, testPreRun, testPostRun }:
+    assert builtins.typeOf testCrateFlags == "list";
+    assert builtins.typeOf testInputs == "list";
+    assert builtins.typeOf testPreRun == "string";
+    assert builtins.typeOf testPostRun == "string";
+    let
+      # override the `crate` so that it will build and execute tests instead of
+      # building the actual lib and bin targets We just have to pass `--test`
+      # to rustc and it will do the right thing.  We execute the tests and copy
+      # their log and the test executables to $out for later inspection.
+      test =
+        let
+          drv = testCrate.override
+            (
+              _: {
+                buildTests = true;
+              }
+            );
+          # If the user hasn't set any pre/post commands, we don't want to
+          # insert empty lines. This means that any existing users of crate2nix
+          # don't get a spurious rebuild unless they set these explicitly.
+          testCommand = pkgs.lib.concatStringsSep "\n"
+            (pkgs.lib.filter (s: s != "") [
+              testPreRun
+              "$f $testCrateFlags 2>&1 | tee -a $out"
+              testPostRun
+            ]);
+        in
+        pkgs.runCommand "run-tests-${testCrate.name}"
+          {
+            inherit testCrateFlags;
+            buildInputs = testInputs;
+          } ''
+          set -ex
+
+          export RUST_BACKTRACE=1
+
+          # recreate a file hierarchy as when running tests with cargo
+
+          # the source for test data
+          ${pkgs.xorg.lndir}/bin/lndir ${crate.src}
+
+          # build outputs
+          testRoot=target/debug
+          mkdir -p $testRoot
+
+          # executables of the crate
+          # we copy to prevent std::env::current_exe() to resolve to a store location
+          for i in ${crate}/bin/*; do
+            cp "$i" "$testRoot"
+          done
+          chmod +w -R .
+
+          # test harness executables are suffixed with a hash, like cargo does
+          # this allows to prevent name collision with the main
+          # executables of the crate
+          hash=$(basename $out)
+          for file in ${drv}/tests/*; do
+            f=$testRoot/$(basename $file)-$hash
+            cp $file $f
+            ${testCommand}
+          done
+        '';
+    in
+    pkgs.runCommand "${crate.name}-linked"
+      {
+        inherit (crate) outputs crateName;
+        passthru = (crate.passthru or { }) // {
+          inherit test;
+        };
+      } ''
+      echo tested by ${test}
+      ${lib.concatMapStringsSep "\n" (output: "ln -s ${crate.${output}} ${"$"}${output}") crate.outputs}
+    '';
+
+  /* A restricted overridable version of builtRustCratesWithFeatures. */
+  buildRustCrateWithFeatures =
+    { packageId
+    , features ? rootFeatures
+    , crateOverrides ? defaultCrateOverrides
+    , buildRustCrateForPkgsFunc ? null
+    , runTests ? false
+    , testCrateFlags ? [ ]
+    , testInputs ? [ ]
+      # Any command to run immediatelly before a test is executed.
+    , testPreRun ? ""
+      # Any command run immediatelly after a test is executed.
+    , testPostRun ? ""
+    }:
+    lib.makeOverridable
+      (
+        { features
+        , crateOverrides
+        , runTests
+        , testCrateFlags
+        , testInputs
+        , testPreRun
+        , testPostRun
+        }:
+        let
+          buildRustCrateForPkgsFuncOverriden =
+            if buildRustCrateForPkgsFunc != null
+            then buildRustCrateForPkgsFunc
+            else
+              (
+                if crateOverrides == pkgs.defaultCrateOverrides
+                then buildRustCrateForPkgs
+                else
+                  pkgs: (buildRustCrateForPkgs pkgs).override {
+                    defaultCrateOverrides = crateOverrides;
+                  }
+              );
+          builtRustCrates = builtRustCratesWithFeatures {
+            inherit packageId features;
+            buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+            runTests = false;
+          };
+          builtTestRustCrates = builtRustCratesWithFeatures {
+            inherit packageId features;
+            buildRustCrateForPkgsFunc = buildRustCrateForPkgsFuncOverriden;
+            runTests = true;
+          };
+          drv = builtRustCrates.crates.${packageId};
+          testDrv = builtTestRustCrates.crates.${packageId};
+          derivation =
+            if runTests then
+              crateWithTest
+                {
+                  crate = drv;
+                  testCrate = testDrv;
+                  inherit testCrateFlags testInputs testPreRun testPostRun;
+                }
+            else drv;
+        in
+        derivation
+      )
+      { inherit features crateOverrides runTests testCrateFlags testInputs testPreRun testPostRun; };
+
+  /* Returns an attr set with packageId mapped to the result of buildRustCrateForPkgsFunc
+    for the corresponding crate.
+  */
+  builtRustCratesWithFeatures =
+    { packageId
+    , features
+    , crateConfigs ? crates
+    , buildRustCrateForPkgsFunc
+    , runTests
+    , target ? defaultTarget
+    } @ args:
+      assert (builtins.isAttrs crateConfigs);
+      assert (builtins.isString packageId);
+      assert (builtins.isList features);
+      assert (builtins.isAttrs target);
+      assert (builtins.isBool runTests);
+      let
+        rootPackageId = packageId;
+        mergedFeatures = mergePackageFeatures
+          (
+            args // {
+              inherit rootPackageId;
+              target = target // { test = runTests; };
+            }
+          );
+        # Memoize built packages so that reappearing packages are only built once.
+        builtByPackageIdByPkgs = mkBuiltByPackageIdByPkgs pkgs;
+        mkBuiltByPackageIdByPkgs = pkgs:
+          let
+            self = {
+              crates = lib.mapAttrs (packageId: value: buildByPackageIdForPkgsImpl self pkgs packageId) crateConfigs;
+              build = mkBuiltByPackageIdByPkgs pkgs.buildPackages;
+            };
+          in
+          self;
+        buildByPackageIdForPkgsImpl = self: pkgs: packageId:
+          let
+            features = mergedFeatures."${packageId}" or [ ];
+            crateConfig' = crateConfigs."${packageId}";
+            crateConfig =
+              builtins.removeAttrs crateConfig' [ "resolvedDefaultFeatures" "devDependencies" ];
+            devDependencies =
+              lib.optionals
+                (runTests && packageId == rootPackageId)
+                (crateConfig'.devDependencies or [ ]);
+            dependencies =
+              dependencyDerivations {
+                inherit features target;
+                buildByPackageId = depPackageId:
+                  # proc_macro crates must be compiled for the build architecture
+                  if crateConfigs.${depPackageId}.procMacro or false
+                  then self.build.crates.${depPackageId}
+                  else self.crates.${depPackageId};
+                dependencies =
+                  (crateConfig.dependencies or [ ])
+                  ++ devDependencies;
+              };
+            buildDependencies =
+              dependencyDerivations {
+                inherit features target;
+                buildByPackageId = depPackageId:
+                  self.build.crates.${depPackageId};
+                dependencies = crateConfig.buildDependencies or [ ];
+              };
+            filterEnabledDependenciesForThis = dependencies: filterEnabledDependencies {
+              inherit dependencies features target;
+            };
+            dependenciesWithRenames =
+              lib.filter (d: d ? "rename")
+                (
+                  filterEnabledDependenciesForThis
+                    (
+                      (crateConfig.buildDependencies or [ ])
+                      ++ (crateConfig.dependencies or [ ])
+                      ++ devDependencies
+                    )
+                );
+            # Crate renames have the form:
+            #
+            # {
+            #    crate_name = [
+            #       { version = "1.2.3"; rename = "crate_name01"; }
+            #    ];
+            #    # ...
+            # }
+            crateRenames =
+              let
+                grouped =
+                  lib.groupBy
+                    (dependency: dependency.name)
+                    dependenciesWithRenames;
+                versionAndRename = dep:
+                  let
+                    package = crateConfigs."${dep.packageId}";
+                  in
+                  { inherit (dep) rename; version = package.version; };
+              in
+              lib.mapAttrs (name: choices: builtins.map versionAndRename choices) grouped;
+          in
+          buildRustCrateForPkgsFunc pkgs
+            (
+              crateConfig // {
+                src = crateConfig.src or (
+                  pkgs.fetchurl rec {
+                    name = "${crateConfig.crateName}-${crateConfig.version}.tar.gz";
+                    # https://www.pietroalbini.org/blog/downloading-crates-io/
+                    # Not rate-limited, CDN URL.
+                    url = "https://static.crates.io/crates/${crateConfig.crateName}/${crateConfig.crateName}-${crateConfig.version}.crate";
+                    sha256 =
+                      assert (lib.assertMsg (crateConfig ? sha256) "Missing sha256 for ${name}");
+                      crateConfig.sha256;
+                  }
+                );
+                extraRustcOpts = lib.lists.optional (targetFeatures != [ ]) "-C target-feature=${lib.concatMapStringsSep "," (x: "+${x}") targetFeatures}";
+                inherit features dependencies buildDependencies crateRenames release;
+              }
+            );
+      in
+      builtByPackageIdByPkgs;
+
+  /* Returns the actual derivations for the given dependencies. */
+  dependencyDerivations =
+    { buildByPackageId
+    , features
+    , dependencies
+    , target
+    }:
+      assert (builtins.isList features);
+      assert (builtins.isList dependencies);
+      assert (builtins.isAttrs target);
+      let
+        enabledDependencies = filterEnabledDependencies {
+          inherit dependencies features target;
+        };
+        depDerivation = dependency: buildByPackageId dependency.packageId;
+      in
+      map depDerivation enabledDependencies;
+
+  /* Returns a sanitized version of val with all values substituted that cannot
+    be serialized as JSON.
+  */
+  sanitizeForJson = val:
+    if builtins.isAttrs val
+    then lib.mapAttrs (n: v: sanitizeForJson v) val
+    else if builtins.isList val
+    then builtins.map sanitizeForJson val
+    else if builtins.isFunction val
+    then "function"
+    else val;
+
+  /* Returns various tools to debug a crate. */
+  debugCrate = { packageId, target ? defaultTarget }:
+    assert (builtins.isString packageId);
+    let
+      debug = rec {
+        # The built tree as passed to buildRustCrate.
+        buildTree = buildRustCrateWithFeatures {
+          buildRustCrateForPkgsFunc = _: lib.id;
+          inherit packageId;
+        };
+        sanitizedBuildTree = sanitizeForJson buildTree;
+        dependencyTree = sanitizeForJson
+          (
+            buildRustCrateWithFeatures {
+              buildRustCrateForPkgsFunc = _: crate: {
+                "01_crateName" = crate.crateName or false;
+                "02_features" = crate.features or [ ];
+                "03_dependencies" = crate.dependencies or [ ];
+              };
+              inherit packageId;
+            }
+          );
+        mergedPackageFeatures = mergePackageFeatures {
+          features = rootFeatures;
+          inherit packageId target;
+        };
+        diffedDefaultPackageFeatures = diffDefaultPackageFeatures {
+          inherit packageId target;
+        };
+      };
+    in
+    { internal = debug; };
+
+  /* Returns differences between cargo default features and crate2nix default
+    features.
+
+    This is useful for verifying the feature resolution in crate2nix.
+  */
+  diffDefaultPackageFeatures =
+    { crateConfigs ? crates
+    , packageId
+    , target
+    }:
+      assert (builtins.isAttrs crateConfigs);
+      let
+        prefixValues = prefix: lib.mapAttrs (n: v: { "${prefix}" = v; });
+        mergedFeatures =
+          prefixValues
+            "crate2nix"
+            (mergePackageFeatures { inherit crateConfigs packageId target; features = [ "default" ]; });
+        configs = prefixValues "cargo" crateConfigs;
+        combined = lib.foldAttrs (a: b: a // b) { } [ mergedFeatures configs ];
+        onlyInCargo =
+          builtins.attrNames
+            (lib.filterAttrs (n: v: !(v ? "crate2nix") && (v ? "cargo")) combined);
+        onlyInCrate2Nix =
+          builtins.attrNames
+            (lib.filterAttrs (n: v: (v ? "crate2nix") && !(v ? "cargo")) combined);
+        differentFeatures = lib.filterAttrs
+          (
+            n: v:
+              (v ? "crate2nix")
+              && (v ? "cargo")
+              && (v.crate2nix.features or [ ]) != (v."cargo".resolved_default_features or [ ])
+          )
+          combined;
+      in
+      builtins.toJSON {
+        inherit onlyInCargo onlyInCrate2Nix differentFeatures;
+      };
+
+  /* Returns an attrset mapping packageId to the list of enabled features.
+
+    If multiple paths to a dependency enable different features, the
+    corresponding feature sets are merged. Features in rust are additive.
+  */
+  mergePackageFeatures =
+    { crateConfigs ? crates
+    , packageId
+    , rootPackageId ? packageId
+    , features ? rootFeatures
+    , dependencyPath ? [ crates.${packageId}.crateName ]
+    , featuresByPackageId ? { }
+    , target
+      # Adds devDependencies to the crate with rootPackageId.
+    , runTests ? false
+    , ...
+    } @ args:
+      assert (builtins.isAttrs crateConfigs);
+      assert (builtins.isString packageId);
+      assert (builtins.isString rootPackageId);
+      assert (builtins.isList features);
+      assert (builtins.isList dependencyPath);
+      assert (builtins.isAttrs featuresByPackageId);
+      assert (builtins.isAttrs target);
+      assert (builtins.isBool runTests);
+      let
+        crateConfig = crateConfigs."${packageId}" or (builtins.throw "Package not found: ${packageId}");
+        expandedFeatures = expandFeatures (crateConfig.features or { }) features;
+        enabledFeatures = enableFeatures (crateConfig.dependencies or [ ]) expandedFeatures;
+        depWithResolvedFeatures = dependency:
+          let
+            packageId = dependency.packageId;
+            features = dependencyFeatures enabledFeatures dependency;
+          in
+          { inherit packageId features; };
+        resolveDependencies = cache: path: dependencies:
+          assert (builtins.isAttrs cache);
+          assert (builtins.isList dependencies);
+          let
+            enabledDependencies = filterEnabledDependencies {
+              inherit dependencies target;
+              features = enabledFeatures;
+            };
+            directDependencies = map depWithResolvedFeatures enabledDependencies;
+            foldOverCache = op: lib.foldl op cache directDependencies;
+          in
+          foldOverCache
+            (
+              cache: { packageId, features }:
+                let
+                  cacheFeatures = cache.${packageId} or [ ];
+                  combinedFeatures = sortedUnique (cacheFeatures ++ features);
+                in
+                if cache ? ${packageId} && cache.${packageId} == combinedFeatures
+                then cache
+                else
+                  mergePackageFeatures {
+                    features = combinedFeatures;
+                    featuresByPackageId = cache;
+                    inherit crateConfigs packageId target runTests rootPackageId;
+                  }
+            );
+        cacheWithSelf =
+          let
+            cacheFeatures = featuresByPackageId.${packageId} or [ ];
+            combinedFeatures = sortedUnique (cacheFeatures ++ enabledFeatures);
+          in
+          featuresByPackageId // {
+            "${packageId}" = combinedFeatures;
+          };
+        cacheWithDependencies =
+          resolveDependencies cacheWithSelf "dep"
+            (
+              crateConfig.dependencies or [ ]
+              ++ lib.optionals
+                (runTests && packageId == rootPackageId)
+                (crateConfig.devDependencies or [ ])
+            );
+        cacheWithAll =
+          resolveDependencies
+            cacheWithDependencies "build"
+            (crateConfig.buildDependencies or [ ]);
+      in
+      cacheWithAll;
+
+  /* Returns the enabled dependencies given the enabled features. */
+  filterEnabledDependencies = { dependencies, features, target }:
+    assert (builtins.isList dependencies);
+    assert (builtins.isList features);
+    assert (builtins.isAttrs target);
+
+    lib.filter
+      (
+        dep:
+        let
+          targetFunc = dep.target or (features: true);
+        in
+        targetFunc { inherit features target; }
+        && (
+          !(dep.optional or false)
+          || builtins.any (doesFeatureEnableDependency dep) features
+        )
+      )
+      dependencies;
+
+  /* Returns whether the given feature should enable the given dependency. */
+  doesFeatureEnableDependency = { name, rename ? null, ... }: feature:
+    let
+      prefix = "${name}/";
+      len = builtins.stringLength prefix;
+      startsWithPrefix = builtins.substring 0 len feature == prefix;
+    in
+    (rename == null && feature == name)
+    || (rename != null && rename == feature)
+    || startsWithPrefix;
+
+  /* Returns the expanded features for the given inputFeatures by applying the
+    rules in featureMap.
+
+    featureMap is an attribute set which maps feature names to lists of further
+    feature names to enable in case this feature is selected.
+  */
+  expandFeatures = featureMap: inputFeatures:
+    assert (builtins.isAttrs featureMap);
+    assert (builtins.isList inputFeatures);
+    let
+      expandFeature = feature:
+        assert (builtins.isString feature);
+        [ feature ] ++ (expandFeatures featureMap (featureMap."${feature}" or [ ]));
+      outFeatures = lib.concatMap expandFeature inputFeatures;
+    in
+    sortedUnique outFeatures;
+
+  /* This function adds optional dependencies as features if they are enabled
+    indirectly by dependency features. This function mimics Cargo's behavior
+    described in a note at:
+    https://doc.rust-lang.org/nightly/cargo/reference/features.html#dependency-features
+  */
+  enableFeatures = dependencies: features:
+    assert (builtins.isList features);
+    assert (builtins.isList dependencies);
+    let
+      additionalFeatures = lib.concatMap
+        (
+          dependency:
+            assert (builtins.isAttrs dependency);
+            let
+              enabled = builtins.any (doesFeatureEnableDependency dependency) features;
+            in
+            if (dependency.optional or false) && enabled then [ dependency.name ] else [ ]
+        )
+        dependencies;
+    in
+    sortedUnique (features ++ additionalFeatures);
+
+  /*
+    Returns the actual features for the given dependency.
+
+    features: The features of the crate that refers this dependency.
+  */
+  dependencyFeatures = features: dependency:
+    assert (builtins.isList features);
+    assert (builtins.isAttrs dependency);
+    let
+      defaultOrNil =
+        if dependency.usesDefaultFeatures or true
+        then [ "default" ]
+        else [ ];
+      explicitFeatures = dependency.features or [ ];
+      additionalDependencyFeatures =
+        let
+          dependencyPrefix = (dependency.rename or dependency.name) + "/";
+          dependencyFeatures =
+            builtins.filter (f: lib.hasPrefix dependencyPrefix f) features;
+        in
+        builtins.map (lib.removePrefix dependencyPrefix) dependencyFeatures;
+    in
+    defaultOrNil ++ explicitFeatures ++ additionalDependencyFeatures;
+
+  /* Sorts and removes duplicates from a list of strings. */
+  sortedUnique = features:
+    assert (builtins.isList features);
+    assert (builtins.all builtins.isString features);
+    let
+      outFeaturesSet = lib.foldl (set: feature: set // { "${feature}" = 1; }) { } features;
+      outFeaturesUnique = builtins.attrNames outFeaturesSet;
+    in
+    builtins.sort (a: b: a < b) outFeaturesUnique;
+
+  deprecationWarning = message: value:
+    if strictDeprecation
+    then builtins.throw "strictDeprecation enabled, aborting: ${message}"
+    else builtins.trace message value;
+
+  #
+  # crate2nix/default.nix (excerpt end)
+  #
+  };
+}
+
diff --git a/users/zseri/store-ref-scanner/Cargo.toml b/users/zseri/store-ref-scanner/Cargo.toml
new file mode 100644
index 0000000000..c73b517374
--- /dev/null
+++ b/users/zseri/store-ref-scanner/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "store-ref-scanner"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+camino = "1.0"
+once_cell = "1.9.0"
+proc_unroll = "0.1"
+
+[dev-dependencies]
+
+[dev-dependencies.proptest]
+version = "1.0"
+default-features = false
+features = ["std"]
diff --git a/users/zseri/store-ref-scanner/default.nix b/users/zseri/store-ref-scanner/default.nix
new file mode 100644
index 0000000000..b78cbfbc65
--- /dev/null
+++ b/users/zseri/store-ref-scanner/default.nix
@@ -0,0 +1,5 @@
+{ depot, pkgs, ... }:
+
+(import ./Cargo.nix { inherit pkgs; }).rootCrate.build.override {
+  runTests = true;
+}
diff --git a/users/zseri/store-ref-scanner/src/hbm.rs b/users/zseri/store-ref-scanner/src/hbm.rs
new file mode 100644
index 0000000000..810a0c99cf
--- /dev/null
+++ b/users/zseri/store-ref-scanner/src/hbm.rs
@@ -0,0 +1,150 @@
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub struct HalfBytesMask(pub [u8; 16]);
+
+impl HalfBytesMask {
+    pub const B32_REVSHA256: HalfBytesMask =
+        HalfBytesMask([0, 0, 0, 0, 0, 0, 255, 3, 0, 0, 0, 0, 222, 127, 207, 7]);
+
+    pub const B64_BLAKE2B256: HalfBytesMask = HalfBytesMask([
+        0, 0, 0, 0, 0, 8, 255, 3, 254, 255, 255, 135, 254, 255, 255, 7,
+    ]);
+
+    #[inline]
+    #[proc_unroll::unroll]
+    pub const fn from_expanded(x: [bool; 128]) -> Self {
+        let mut ret = [0u8; 16];
+        for idx in 0..16 {
+            let mut tmp = 0;
+            let fin = idx * 8;
+            macro_rules! bitx {
+            ($($a:expr),+) => {{ $( if x[fin + $a] { tmp += (1 << $a) as u8; } )+ }}
+            }
+            bitx!(0, 1, 2, 3, 4, 5, 6, 7);
+            ret[idx] = tmp;
+        }
+        Self(ret)
+    }
+
+    /// create a mask by allowing all characters via the mask which are included in the given string
+    pub fn from_bytes(s: &[u8]) -> Self {
+        s.iter().fold(Self([0u8; 16]), |mut ret, &i| {
+            ret.set(i, true);
+            ret
+        })
+    }
+
+    #[proc_unroll::unroll]
+    pub const fn into_expanded(self) -> [bool; 128] {
+        let Self(ihbm) = self;
+        let mut ret = [false; 128];
+        for idx in 0..16 {
+            let fin = idx * 8;
+            let curi = ihbm[idx];
+            macro_rules! bitx {
+                ($($a:expr),+) => {{ $( ret[fin + $a] = (curi >> $a) & 0b1 != 0; )+ }}
+            }
+            bitx!(0, 1, 2, 3, 4, 5, 6, 7);
+        }
+        ret
+    }
+
+    pub fn contains(&self, byte: u8) -> bool {
+        (self.0[usize::from(byte / 8)] >> u32::from(byte % 8)) & 0b1 != 0
+    }
+
+    pub fn set(&mut self, byte: u8, allow: bool) {
+        if byte >= 0x80 {
+            if cfg!(debug_assertions) {
+                panic!(
+                    "tried to manipulate invalid byte {:?} in HalfBytesMask",
+                    byte
+                );
+            } else {
+                return;
+            }
+        }
+        let mut block = &mut self.0[usize::from(byte / 8)];
+        let bitpat = (1 << u32::from(byte % 8)) as u8;
+        if allow {
+            *block |= bitpat;
+        } else {
+            *block &= !bitpat;
+        }
+    }
+
+    #[cfg(test)]
+    fn count_ones(&self) -> u8 {
+        self.0
+            .iter()
+            .map(|i| i.count_ones())
+            .sum::<u32>()
+            .try_into()
+            .unwrap()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn maskbase() {
+        assert_eq!(HalfBytesMask::B32_REVSHA256.count_ones(), 32);
+        assert_eq!(HalfBytesMask::B64_BLAKE2B256.count_ones(), 64);
+    }
+
+    #[test]
+    fn dflmask() {
+        assert_eq!(
+            HalfBytesMask::from_expanded(
+                [
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                ]
+                .map(|i| i != 0)
+            ),
+            Default::default(),
+        );
+
+        assert_eq!(
+            HalfBytesMask::from_expanded(
+                [
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
+                    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1,
+                    1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+                ]
+                .map(|i| i != 0)
+            ),
+            HalfBytesMask::B32_REVSHA256,
+        );
+
+        assert_eq!(
+            HalfBytesMask::from_expanded(
+                [
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1,
+                    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1,
+                    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+                ]
+                .map(|i| i != 0)
+            ),
+            HalfBytesMask::B64_BLAKE2B256,
+        );
+    }
+
+    proptest::proptest! {
+        #[test]
+        fn hbm_roundtrip(s: [u8; 16]) {
+            let a = HalfBytesMask(s);
+            let b = a.into_expanded();
+            let c = HalfBytesMask::from_expanded(b);
+            assert_eq!(a, c);
+        }
+    }
+}
diff --git a/users/zseri/store-ref-scanner/src/lib.rs b/users/zseri/store-ref-scanner/src/lib.rs
new file mode 100644
index 0000000000..2cf94b42cc
--- /dev/null
+++ b/users/zseri/store-ref-scanner/src/lib.rs
@@ -0,0 +1,225 @@
+// TODO: make this no_std if possible
+
+use camino::Utf8PathBuf;
+
+mod hbm;
+pub use hbm::HalfBytesMask;
+
+mod spec;
+pub use spec::*;
+
+/// limit maximal length of store basename
+const BASENAME_MAXLEN: usize = 255;
+
+/// this is a trait which implements the interface of possible inputs
+/// (usually byte slices)
+pub trait ScannerInput: AsRef<[u8]> + Sized {
+    /// Splits the input into two at the given index.
+    /// Afterwards self contains elements [at, len), and the returned input part contains elements [0, at).
+    fn split_to(&mut self, at: usize) -> Self;
+    fn finish(&mut self);
+}
+
+impl ScannerInput for &[u8] {
+    fn split_to(&mut self, at: usize) -> Self {
+        let (a, b) = self.split_at(at);
+        *self = b;
+        a
+    }
+
+    fn finish(&mut self) {
+        *self = &[];
+    }
+}
+
+impl ScannerInput for &mut [u8] {
+    fn split_to(&mut self, at: usize) -> Self {
+        // Lifetime dance taken from `impl Write for &mut [u8]`.
+        // Taken from crate `std`.
+        let (a, b) = core::mem::replace(self, &mut []).split_at_mut(at);
+        *self = b;
+        a
+    }
+
+    fn finish(&mut self) {
+        *self = &mut [];
+    }
+}
+
+/// this is the primary structure of this crate
+///
+/// it represents a scanner which scans binary slices for store references,
+/// and implements an iterator interfaces which returns these as byte slices.
+pub struct StoreRefScanner<'x, Input: 'x> {
+    input: Input,
+    spec: &'x StoreSpec,
+}
+
+/// Taken from crate `yz-string-utils`.
+fn get_offset_of<T>(whole_buffer: &T, part: &T) -> usize
+where
+    T: AsRef<[u8]> + ?Sized,
+{
+    // NOTE: originally I wanted to use offset_from() here once it's stable,
+    // but according to https://github.com/rust-lang/rust/issues/41079#issuecomment-657163887
+    // this would be UB in cases where the code below isn't.
+    part.as_ref().as_ptr() as usize - whole_buffer.as_ref().as_ptr() as usize
+}
+
+impl<'x, Input> StoreRefScanner<'x, Input>
+where
+    Input: ScannerInput + 'x,
+{
+    pub fn new(input: Input, spec: &'x StoreSpec) -> Self {
+        for i in [&spec.valid_hashbytes, &spec.valid_restbytes] {
+            for j in [b'\0', b' ', b'\t', b'\n', b'/', b'\\'] {
+                assert!(!i.contains(j));
+            }
+        }
+        Self { input, spec }
+    }
+}
+
+impl<'x, Input: 'x> Iterator for StoreRefScanner<'x, Input>
+where
+    Input: ScannerInput + 'x,
+{
+    type Item = Input;
+
+    fn next(&mut self) -> Option<Input> {
+        let empty_path = camino::Utf8Path::new("");
+        let hbl: usize = self.spec.hashbytes_len.into();
+        while !self.input.as_ref().is_empty() {
+            if self.spec.path_to_store != empty_path {
+                let p2sas = self.spec.path_to_store.as_str();
+                if self.input.as_ref().starts_with(p2sas.as_bytes()) {
+                    self.input.split_to(p2sas.len());
+                } else {
+                    self.input.split_to(1);
+                    continue;
+                }
+            }
+            let hsep = matches!(self.input.as_ref().iter().next(), Some(b'/') | Some(b'\\'));
+            self.input.split_to(1);
+            if hsep && self.spec.check_rest(self.input.as_ref()) {
+                // we have found a valid hash
+                // rest contains the store basename and all following components
+                // now let's search for the end
+                // and then cut off possible following components after the basename
+                let rlen = self
+                    .input
+                    .as_ref()
+                    .iter()
+                    .enumerate()
+                    .take(BASENAME_MAXLEN)
+                    .skip(hbl)
+                    .find(|&(_, &i)| !self.spec.valid_restbytes.contains(i))
+                    .map(|(eosp, _)| eosp)
+                    .unwrap_or(core::cmp::min(BASENAME_MAXLEN, self.input.as_ref().len()));
+                return Some(self.input.split_to(rlen));
+            }
+        }
+        self.input.finish();
+        None
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn simple_nix2() {
+        let drv: &[u8] = br#"
+            Derive([("out","","r:sha256","")],[("/nix/store/2ax7bvjdfkzim69q957i0jlg0nvmapg0-util-linux-2.37.2.drv",["dev"]),("/nix/store/6b55ssmh8pzqsc4q4kw1yl3kqvr4fvqj-bash-5.1-p12.drv",["out"]),("/nix/store/fp2vx24kczlzv84avds28wyzsmrn8kyv-source.drv",["out"]),("/nix/store/s6c2lm5hpsvdwnxq9y1g3ngncghjzc3k-stdenv-linux.drv",["out"]),("/nix/store/xlnzpf4mzghi8vl0krabrgcbnqk5qjf3-pkg-config-wrapper-0.29.2.drv",["out"])],["/nix/store/03sl46khd8gmjpsad7223m32ma965vy9-fix-static.patch","/nix/store/2q3z7587yhlz0i2xvfvvap42zk5carlv-bcache-udev-modern.patch","/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh"],"x86_64-linux","/0g15yibzzi3rmw29gqlbms05x9dbghbvh61v1qggydvmzh3bginw/bin/bash",["-e","/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh"],[("buildInputs","/0sdk1r4l43yw4g6lmqdhd92vhdfhlwz3m76jxzvzsqsv63czw2km"),("builder","/0g15yibzzi3rmw29gqlbms05x9dbghbvh61v1qggydvmzh3bginw/bin/bash"),("configureFlags",""),("depsBuildBuild",""),("depsBuildBuildPropagated",""),("depsBuildTarget",""),("depsBuildTargetPropagated",""),("depsHostHost",""),("depsHostHostPropagated",""),("depsTargetTarget",""),("depsTargetTargetPropagated",""),("doCheck",""),("doInstallCheck",""),("makeFlags","PREFIX=/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9 UDEVLIBDIR=/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9/lib/udev/"),("name","bcache-tools-1.0.7"),("nativeBuildInputs","/1kw0rwgdyq9q69wmmsa5d2kap6p52b0yldbzi4w17bhcq5g5cp2f"),("out","/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"),("outputHashAlgo","sha256"),("outputHashMode","recursive"),("outputs","out"),("patches","/nix/store/2q3z7587yhlz0i2xvfvvap42zk5carlv-bcache-udev-modern.patch /nix/store/03sl46khd8gmjpsad7223m32ma965vy9-fix-static.patch"),("pname","bcache-tools"),("preBuild","sed -e \"s|/bin/sh|/0g15yibzzi3rmw29gqlbms05x9dbghbvh61v1qggydvmzh3bginw/bin/sh|\" -i *.rules\n"),("preInstall","mkdir -p \"$out/sbin\" \"$out/lib/udev/rules.d\" \"$out/share/man/man8\"\n"),("prePatch","sed -e \"/INSTALL.*initramfs\\/hook/d\" \\\n    -e \"/INSTALL.*initcpio\\/install/d\" \\\n    -e \"/INSTALL.*dracut\\/module-setup.sh/d\" \\\n    -e \"s/pkg-config/$PKG_CONFIG/\" \\\n    -i Makefile\n"),("propagatedBuildInputs",""),("propagatedNativeBuildInputs",""),("src","/nix/store/6izcafvfcbz19chi7hl20834g0fa043n-source"),("stdenv","/01ncyv8bxibj0imgfvmxgqy648n697bachil6aw6i46g1jk0bbds"),("strictDeps",""),("system","x86_64-linux"),("version","1.0.7")])
+        "#;
+        // we convert everything into strings because it is way easier to compare elements in error messages
+        let refs: Vec<&str> = StoreRefScanner::new(drv, &*SPEC_DFL_NIX2)
+            .map(|i| core::str::from_utf8(i).unwrap())
+            .collect();
+        let refs_expect: Vec<&[u8]> = vec![
+            b"2ax7bvjdfkzim69q957i0jlg0nvmapg0-util-linux-2.37.2.drv",
+            b"6b55ssmh8pzqsc4q4kw1yl3kqvr4fvqj-bash-5.1-p12.drv",
+            b"fp2vx24kczlzv84avds28wyzsmrn8kyv-source.drv",
+            b"s6c2lm5hpsvdwnxq9y1g3ngncghjzc3k-stdenv-linux.drv",
+            b"xlnzpf4mzghi8vl0krabrgcbnqk5qjf3-pkg-config-wrapper-0.29.2.drv",
+            b"03sl46khd8gmjpsad7223m32ma965vy9-fix-static.patch",
+            b"2q3z7587yhlz0i2xvfvvap42zk5carlv-bcache-udev-modern.patch",
+            b"9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh",
+            b"9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh",
+            b"2q3z7587yhlz0i2xvfvvap42zk5carlv-bcache-udev-modern.patch",
+            b"03sl46khd8gmjpsad7223m32ma965vy9-fix-static.patch",
+            b"6izcafvfcbz19chi7hl20834g0fa043n-source",
+        ];
+        let refs_expect: Vec<&str> = refs_expect
+            .into_iter()
+            .map(|i| core::str::from_utf8(i).unwrap())
+            .collect();
+        assert_eq!(refs, refs_expect);
+    }
+
+    #[test]
+    fn simple_yzix1() {
+        // I haven't yet produced any yzix derivation which included /yzixs absolute paths...
+        let fake: &[u8] = br#"
+            /yzixs/4Zx1PBoft1YyAuKdhjAY1seZFHloxQ+8voHQRkRMuys:         ASCII text
+            /yzixs/dNE3yogD4JHKHzNa2t3jQMZddT8wjqlMDB0naDIFo0A:         ASCII text
+            /yzixs/FMluSVOHLc4bxX7F4lBCXafNljBnDn+rAM5HzG7k8LI:         unified diff output, ASCII text
+            /yzixs/g2G3GRL87hGEdw9cq2BZWqDQP_HeHSPRLbJ9P9KH+HI:         unified diff output, ASCII text
+            /yzixs/H08Av1ZAONwFdzVLpFQm0Sc0dvyk0sbnk82waoBig7I:         ASCII text
+            /yzixs/IndARQp+gaGDLS3K+PeyXdaRqAcCyS3EIbRXkkYjC94:         unified diff output, ASCII text
+            /yzixs/IrLPnbkEolTAuWRxkXpuvVs6Imb1iB6wUJcI+fxWwkU:         POSIX shell script, ASCII text executable
+            /yzixs/JsS_H3n3TSh2R6fiIzgOPZdjSmRkV71vGxstJJKPmr4:         unified diff output, ASCII text
+            /yzixs/LZ6pQh1x8DRxZ2IYzetBRS4LuE__IXFjpOfQPxHVwpw:         unified diff output, ASCII text
+            /yzixs/mEi2RPep9daRs0JUvwt1JsDfgYSph5sH_+_ihwn8IGQ:         ASCII text
+            /yzixs/nd4DyljinP3auDMHL_LrpsRJkWQpSHQK2jqtyyzWcBA:         POSIX shell script, ASCII text executable
+            /yzixs/nzpaknF0_ONSHtd0i_e1E3pkLF1QPeJQhAB7x9Ogo_M:         unified diff output, ASCII text
+            /yzixs/UZ3uzVUUMC1gKGLw6tg_aLFwoFrJedXB3xbhEgQOaiY:         unified diff output, ASCII text
+            /yzixs/VKyXxKTXsDGxYJ24YgbvCc1bZkA5twp3TC+Gbi4Kwd8:         unified diff output, ASCII text
+            /yzixs/VPJMl8O1xkc1LsJznpoQrCrQO0Iy+ODCPsgoUBLiRZc:         unified diff output, ASCII text
+            /yzixs/W6r1ow001ASHRj+gtRfyj9Fb_gCO_pBztX8WhYXVdIc:         unified diff output, ASCII text
+            /yzixs/xvwEcXIob_rQynUEtQiQbwaDXEobTVKEGaBMir9oH9k:         unified diff output, ASCII text
+            /yzixs/ZPvQbRJrtyeSITvW3FUZvw99hhNOO3CFqGgmWgScxcg:         ASCII text
+        "#;
+        let refs: Vec<&str> = StoreRefScanner::new(fake, &*SPEC_DFL_YZIX1)
+            .map(|i| core::str::from_utf8(i).unwrap())
+            .collect();
+        let refs_expect: Vec<&[u8]> = vec![
+            b"4Zx1PBoft1YyAuKdhjAY1seZFHloxQ+8voHQRkRMuys",
+            b"dNE3yogD4JHKHzNa2t3jQMZddT8wjqlMDB0naDIFo0A",
+            b"FMluSVOHLc4bxX7F4lBCXafNljBnDn+rAM5HzG7k8LI",
+            b"g2G3GRL87hGEdw9cq2BZWqDQP_HeHSPRLbJ9P9KH+HI",
+            b"H08Av1ZAONwFdzVLpFQm0Sc0dvyk0sbnk82waoBig7I",
+            b"IndARQp+gaGDLS3K+PeyXdaRqAcCyS3EIbRXkkYjC94",
+            b"IrLPnbkEolTAuWRxkXpuvVs6Imb1iB6wUJcI+fxWwkU",
+            b"JsS_H3n3TSh2R6fiIzgOPZdjSmRkV71vGxstJJKPmr4",
+            b"LZ6pQh1x8DRxZ2IYzetBRS4LuE__IXFjpOfQPxHVwpw",
+            b"mEi2RPep9daRs0JUvwt1JsDfgYSph5sH_+_ihwn8IGQ",
+            b"nd4DyljinP3auDMHL_LrpsRJkWQpSHQK2jqtyyzWcBA",
+            b"nzpaknF0_ONSHtd0i_e1E3pkLF1QPeJQhAB7x9Ogo_M",
+            b"UZ3uzVUUMC1gKGLw6tg_aLFwoFrJedXB3xbhEgQOaiY",
+            b"VKyXxKTXsDGxYJ24YgbvCc1bZkA5twp3TC+Gbi4Kwd8",
+            b"VPJMl8O1xkc1LsJznpoQrCrQO0Iy+ODCPsgoUBLiRZc",
+            b"W6r1ow001ASHRj+gtRfyj9Fb_gCO_pBztX8WhYXVdIc",
+            b"xvwEcXIob_rQynUEtQiQbwaDXEobTVKEGaBMir9oH9k",
+            b"ZPvQbRJrtyeSITvW3FUZvw99hhNOO3CFqGgmWgScxcg",
+        ];
+        let refs_expect: Vec<&str> = refs_expect
+            .into_iter()
+            .map(|i| core::str::from_utf8(i).unwrap())
+            .collect();
+        assert_eq!(refs, refs_expect);
+    }
+
+    proptest::proptest! {
+        #[test]
+        fn nocrash_nix2(s: Vec<u8>) {
+            let _ = StoreRefScanner::new(&s[..], &*SPEC_DFL_NIX2).count();
+        }
+
+        #[test]
+        fn nocrash_yzix1(s: Vec<u8>) {
+            let _ = StoreRefScanner::new(&s[..], &*SPEC_DFL_YZIX1).count();
+        }
+    }
+}
diff --git a/users/zseri/store-ref-scanner/src/spec.rs b/users/zseri/store-ref-scanner/src/spec.rs
new file mode 100644
index 0000000000..034779e8e8
--- /dev/null
+++ b/users/zseri/store-ref-scanner/src/spec.rs
@@ -0,0 +1,46 @@
+use crate::hbm::HalfBytesMask;
+use camino::Utf8PathBuf;
+use once_cell::sync::Lazy;
+
+pub struct StoreSpec {
+    /// path to store without trailing slash
+    pub path_to_store: Utf8PathBuf,
+
+    /// compressed map of allowed ASCII characters in hash part
+    pub valid_hashbytes: HalfBytesMask,
+
+    /// compressed map of allowed ASCII characters in part after hash
+    pub valid_restbytes: HalfBytesMask,
+
+    /// exact length of hash part of store paths
+    pub hashbytes_len: u8,
+}
+
+impl StoreSpec {
+    pub(crate) fn check_rest(&self, rest: &[u8]) -> bool {
+        let hbl = self.hashbytes_len.into();
+        rest.iter()
+            .take(hbl)
+            .take_while(|&&i| self.valid_hashbytes.contains(i))
+            .count()
+            == hbl
+    }
+}
+
+pub static SPEC_DFL_NIX2: Lazy<StoreSpec> = Lazy::new(|| StoreSpec {
+    path_to_store: "/nix/store".into(),
+    valid_hashbytes: HalfBytesMask::B32_REVSHA256,
+    valid_restbytes: HalfBytesMask::from_bytes(
+        b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-._?=",
+    ),
+    hashbytes_len: 32,
+});
+
+pub static SPEC_DFL_YZIX1: Lazy<StoreSpec> = Lazy::new(|| StoreSpec {
+    path_to_store: "/yzixs".into(),
+    valid_hashbytes: HalfBytesMask::B64_BLAKE2B256,
+    valid_restbytes: HalfBytesMask::from_bytes(
+        b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-._?=",
+    ),
+    hashbytes_len: 43,
+});