aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.envrc1
-rw-r--r--.gitignore3
-rw-r--r--Cargo.lock1330
-rw-r--r--Cargo.toml3
-rw-r--r--README.org1
-rw-r--r--cmd/git-leaderboard/main.go128
-rw-r--r--cmd/goget/main.go165
-rw-r--r--cmd/pviz/main.go266
-rw-r--r--cmd/seq-stat/main.go115
-rw-r--r--flake.lock142
-rw-r--r--flake.nix135
-rw-r--r--go.mod20
-rw-r--r--go.sum38
-rw-r--r--nix/modules/goget.nix66
-rw-r--r--nix/overlay.nix5
-rw-r--r--nix/packages/default.nix5
-rw-r--r--nix/packages/goget.nix31
-rw-r--r--rust-toolchain.toml3
-rw-r--r--src/apple_silicon/Cargo.lock65
-rw-r--r--src/apple_silicon/Cargo.toml18
-rw-r--r--src/apple_silicon/README.md1
-rw-r--r--src/apple_silicon/src/bin/apple_silicon.rs12
-rw-r--r--src/apple_silicon/src/error.rs23
-rw-r--r--src/apple_silicon/src/lib.rs2
-rw-r--r--src/apple_silicon/src/soc.rs302
-rw-r--r--src/x509-info/Cargo.lock1135
-rw-r--r--src/x509-info/Cargo.toml17
-rw-r--r--src/x509-info/README.md72
-rw-r--r--src/x509-info/deny.toml46
-rw-r--r--src/x509-info/src/client.rs110
-rw-r--r--src/x509-info/src/main.rs41
-rw-r--r--src/x509-info/src/output.rs120
-rw-r--r--treefmt.nix35
33 files changed, 4456 insertions, 0 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2af821e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.pre-commit-config.yaml
+/result
+/target/
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..5c31fb2
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1330 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
+dependencies = [
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
+
+[[package]]
+name = "apple_silicon"
+version = "0.1.0"
+dependencies = [
+ "thiserror 2.0.16",
+]
+
+[[package]]
+name = "asn1-rs"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror 1.0.69",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "aws-lc-rs"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.30.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff"
+dependencies = [
+ "bindgen",
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+]
+
+[[package]]
+name = "bindgen"
+version = "0.69.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "cc"
+version = "1.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
+dependencies = [
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+[[package]]
+name = "chrono"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "windows-link",
+]
+
+[[package]]
+name = "clang-sys"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "cmake"
+version = "0.1.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "data-encoding"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
+
+[[package]]
+name = "der-parser"
+version = "9.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
+[[package]]
+name = "deranged"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.3+wasi-0.2.4",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "home"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.3",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.175"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+
+[[package]]
+name = "libloading"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
+dependencies = [
+ "cfg-if",
+ "windows-targets 0.53.3",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "oid-registry"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9"
+dependencies = [
+ "asn1-rs",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "regex"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.16",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
+dependencies = [
+ "aws-lc-rs",
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
+dependencies = [
+ "aws-lc-rs",
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
+dependencies = [
+ "thiserror-impl 2.0.16",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
+
+[[package]]
+name = "time-macros"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.3+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.2",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.3",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
+
+[[package]]
+name = "x509-info"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "chrono",
+ "clap",
+ "rustls",
+ "rustls-native-certs",
+ "webpki-roots 0.26.11",
+ "x509-parser",
+]
+
+[[package]]
+name = "x509-parser"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror 1.0.69",
+ "time",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..c1ce08a
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,3 @@
+[workspace]
+members = ["src/apple_silicon/", "src/x509-info/"]
+resolver = "3"
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..19764b6
--- /dev/null
+++ b/README.org
@@ -0,0 +1 @@
+My experimental monorepo for Go tools and packages.
diff --git a/cmd/git-leaderboard/main.go b/cmd/git-leaderboard/main.go
new file mode 100644
index 0000000..7c846a5
--- /dev/null
+++ b/cmd/git-leaderboard/main.go
@@ -0,0 +1,128 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "os/exec"
+ "sort"
+ "strings"
+)
+
+type Author struct {
+ Name string
+ Count int
+}
+
+func main() {
+ lineFlag := flag.Bool("line", false, "Count lines changed instead of commits")
+ flag.Parse()
+
+ if !isGitRepository() {
+ fmt.Println("Error: Not in a git repository")
+ os.Exit(1)
+ }
+
+ authors, err := getAuthors(*lineFlag)
+ if err != nil {
+ fmt.Printf("Error getting authors: %v\n", err)
+ os.Exit(1)
+ }
+
+ if len(authors) == 0 {
+ fmt.Println("No authors found. The repository might be empty or have no commits.")
+ os.Exit(0)
+ }
+
+ sort.Slice(authors, func(i, j int) bool {
+ return authors[i].Count > authors[j].Count
+ })
+
+ maxCount := authors[0].Count
+
+ fmt.Printf("%-30s %-10s %s\n", "Author", "Count", "Contribution")
+ fmt.Println(strings.Repeat("-", 80))
+
+ for _, author := range authors {
+ bar := generateBar(author.Count, maxCount)
+ fmt.Printf("%-30s %-10d %s\n", author.Name, author.Count, bar)
+ }
+}
+
+func isGitRepository() bool {
+ cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree")
+ err := cmd.Run()
+ return err == nil
+}
+
+func getAuthors(countLines bool) ([]Author, error) {
+ var cmd *exec.Cmd
+
+ if countLines {
+ cmd = exec.Command("git", "log", "--pretty=format:%aN", "--numstat")
+ } else {
+ cmd = exec.Command("git", "shortlog", "-sn", "--no-merges", "HEAD")
+ }
+
+ output, err := cmd.Output()
+ if err != nil {
+ if exitErr, ok := err.(*exec.ExitError); ok {
+ return nil, fmt.Errorf("git command failed: %s. Error output: %s", err, exitErr.Stderr)
+ }
+ return nil, fmt.Errorf("error executing git command: %v", err)
+ }
+
+ lines := strings.Split(string(output), "\n")
+ authors := make(map[string]int)
+
+ if countLines {
+ currentAuthor := ""
+ for _, line := range lines {
+ if line == "" {
+ currentAuthor = ""
+ continue
+ }
+ if currentAuthor == "" {
+ currentAuthor = line
+ continue
+ } else {
+ fields := strings.Fields(line)
+ if len(fields) >= 2 {
+ added, _ := parseInt(fields[0])
+ deleted, _ := parseInt(fields[1])
+ authors[currentAuthor] += added + deleted
+ }
+ }
+ }
+ } else {
+ for _, line := range lines {
+ fields := strings.Fields(line)
+ if len(fields) >= 2 {
+ count, _ := parseInt(fields[0])
+ name := strings.Join(fields[1:], " ")
+ authors[name] = count
+ }
+ }
+ }
+
+ var result []Author
+ for name, count := range authors {
+ result = append(result, Author{Name: name, Count: count})
+ }
+ return result, nil
+}
+
+func parseInt(s string) (int, error) {
+ if s == "-" {
+ return 0, nil
+ }
+ var result int
+ _, err := fmt.Sscanf(s, "%d", &result)
+ return result, err
+}
+
+func generateBar(count, maxCount int) string {
+ maxWidth := 38
+ width := int(float64(count) / float64(maxCount) * float64(maxWidth))
+ return strings.Repeat("▆", width)
+}
diff --git a/cmd/goget/main.go b/cmd/goget/main.go
new file mode 100644
index 0000000..3f17448
--- /dev/null
+++ b/cmd/goget/main.go
@@ -0,0 +1,165 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "net/http"
+ "net/url"
+ "os"
+ "os/signal"
+ "slices"
+ "strings"
+ "syscall"
+ "time"
+
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
+ "golang.org/x/mod/semver"
+)
+
+const (
+ forgeDomain = "code.fcuny.net"
+ forgeUser = "fcuny"
+ goPkgDomain = "go.fcuny.net"
+)
+
+var (
+ goGetReqs = promauto.NewCounterVec(prometheus.CounterOpts{
+ Name: "goget_requests_total",
+ Help: "go get requests processed, by repository name.",
+ }, []string{"name"})
+
+ modules = []string{"x"}
+)
+
+func main() {
+ ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+ defer cancel()
+
+ metricsMux := http.NewServeMux()
+ metricsMux.Handle("/metrics", promhttp.Handler())
+ metricsServer := &http.Server{
+ Addr: ":9091", Handler: metricsMux,
+ ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
+ }
+
+ go func() {
+ log.Println("Starting metrics server on :9091")
+ if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Printf("Metrics server error: %v", err)
+ }
+ }()
+
+ s := &http.Server{
+ Addr: ":8070",
+ Handler: handler(),
+ ReadTimeout: 10 * time.Second,
+ WriteTimeout: 10 * time.Second,
+ IdleTimeout: 1 * time.Minute,
+ }
+
+ go func() {
+ log.Println("Starting main server on :8080")
+ if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Printf("Main server error: %v", err)
+ }
+ }()
+
+ <-ctx.Done()
+ log.Println("Shutdown signal received, starting graceful shutdown...")
+
+ shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer shutdownCancel()
+
+ var shutdownErr error
+
+ log.Println("Shutting down main server...")
+ if err := s.Shutdown(shutdownCtx); err != nil {
+ log.Printf("Main server shutdown error: %v", err)
+ shutdownErr = err
+ }
+
+ log.Println("Shutting down metrics server...")
+ if err := metricsServer.Shutdown(shutdownCtx); err != nil {
+ log.Printf("Metrics server shutdown error: %v", err)
+ if shutdownErr == nil {
+ shutdownErr = err
+ }
+ }
+
+ if shutdownErr != nil {
+ log.Printf("Shutdown completed with errors")
+ os.Exit(1)
+ }
+
+ log.Println("Shutdown completed successfully")
+}
+
+func handler() http.Handler {
+ mux := http.NewServeMux()
+
+ mux.Handle(goPkgDomain+"/{name}", siteHandler(modules))
+ mux.Handle(goPkgDomain+"/{name}/", siteHandler(modules))
+
+ goGetMux := http.NewServeMux()
+ for _, name := range modules {
+ module := goPkgDomain + "/" + name
+ goGetMux.Handle(
+ module+"/",
+ goImportHandler(module, "https://"+forgeDomain+"/"+forgeUser+"/"+name),
+ )
+ }
+
+ return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ status := http.StatusMethodNotAllowed
+ http.Error(rw, http.StatusText(status), status)
+ return
+ }
+
+ if r.URL.Query().Get("go-get") == "1" {
+ goGetMux.ServeHTTP(rw, r)
+ return
+ }
+ mux.ServeHTTP(rw, r)
+ })
+}
+
+func goImportHandler(module, repo string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ goGetReqs.WithLabelValues(module).Inc()
+ w.Header().Set("Content-Type", "text/html; charset=UTF-8")
+ if _, err := fmt.Fprintf(w, `<head><meta name="go-import" content="%s git %s">`, module, repo); err != nil {
+ log.Printf("Error writing response: %v", err)
+ }
+ })
+}
+
+func siteHandler(names []string) http.Handler {
+ return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+ name := r.PathValue("name")
+ name, version, hasV := strings.Cut(name, "@")
+ if !hasV {
+ name, _, _ = strings.Cut(name, ".")
+ }
+ if hasV && !semver.IsValid(version) || !slices.Contains(names, name) {
+ http.NotFound(rw, r)
+ return
+ }
+ u := &url.URL{
+ Scheme: "https",
+ Host: forgeDomain,
+ Path: forgeUser + r.URL.Path,
+ }
+ if !hasV {
+ path, symbol, hasSymbol := strings.Cut(r.URL.Path, ".")
+ if hasSymbol {
+ u.Path = forgeUser + "/" + path
+ u.Fragment = symbol
+ }
+ }
+ http.Redirect(rw, r, u.String(), http.StatusFound)
+ })
+}
diff --git a/cmd/pviz/main.go b/cmd/pviz/main.go
new file mode 100644
index 0000000..89bd78d
--- /dev/null
+++ b/cmd/pviz/main.go
@@ -0,0 +1,266 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "text/tabwriter"
+
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+type Period string
+
+const (
+ PeriodDay Period = "day"
+ PeriodWeek Period = "week"
+ PeriodMonth Period = "month"
+ PeriodQuarter Period = "quarter"
+ PeriodYear Period = "year"
+)
+
+var periodMinutes = map[Period]float64{
+ PeriodDay: 24 * 60,
+ PeriodWeek: 7 * 24 * 60,
+ PeriodMonth: 30 * 24 * 60,
+ PeriodQuarter: 90 * 24 * 60,
+ PeriodYear: 365 * 24 * 60,
+}
+
+var periodOrder = []Period{PeriodDay, PeriodWeek, PeriodMonth, PeriodQuarter, PeriodYear}
+
+type TimeUnit struct {
+ Minutes float64
+ Formatted string
+}
+
+type AvailabilityCalculator struct {
+ availability float64
+}
+
+func NewAvailabilityCalculator(availabilityPercentage float64) (*AvailabilityCalculator, error) {
+ if availabilityPercentage < 0 || availabilityPercentage > 100 {
+ return nil, fmt.Errorf("availability must be between 0 and 100")
+ }
+ return &AvailabilityCalculator{
+ availability: availabilityPercentage / 100.0,
+ }, nil
+}
+
+func (ac *AvailabilityCalculator) CalculateDowntime(period Period) TimeUnit {
+ totalMinutes := periodMinutes[period]
+ downtimeMinutes := totalMinutes * (1 - ac.availability)
+ return TimeUnit{
+ Minutes: downtimeMinutes,
+ Formatted: formatDuration(downtimeMinutes),
+ }
+}
+
+func (ac *AvailabilityCalculator) CalculateAllPeriods() map[Period]TimeUnit {
+ result := make(map[Period]TimeUnit)
+ for _, period := range periodOrder {
+ result[period] = ac.CalculateDowntime(period)
+ }
+ return result
+}
+
+func formatDuration(minutes float64) string {
+ if minutes < 1 {
+ return fmt.Sprintf("%.1f seconds", minutes*60)
+ }
+
+ hours := int(minutes / 60)
+ remainingMinutes := minutes - float64(hours*60)
+
+ if hours == 0 {
+ return fmt.Sprintf("%.1f minutes", remainingMinutes)
+ } else if remainingMinutes == 0 {
+ return fmt.Sprintf("%d hours", hours)
+ } else {
+ return fmt.Sprintf("%d hours %.1f minutes", hours, remainingMinutes)
+ }
+}
+
+func printTable(headers []string, rows [][]string) {
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
+
+ for i, header := range headers {
+ if i > 0 {
+ _, _ = fmt.Fprint(w, "\t")
+ }
+ _, _ = fmt.Fprint(w, header)
+ }
+ _, _ = fmt.Fprintln(w)
+
+ for _, row := range rows {
+ for i, cell := range row {
+ if i > 0 {
+ _, _ = fmt.Fprint(w, "\t")
+ }
+ _, _ = fmt.Fprint(w, cell)
+ }
+ _, _ = fmt.Fprintln(w)
+ }
+
+ _ = w.Flush()
+}
+
+func analyzeSingle(availability float64, focusPeriod string) error {
+ calc, err := NewAvailabilityCalculator(availability)
+ if err != nil {
+ return err
+ }
+
+ var periods []Period
+ if focusPeriod != "" {
+ periods = []Period{Period(focusPeriod)}
+ } else {
+ periods = periodOrder
+ }
+
+ headers := []string{"Time Period", "Maximum Downtime"}
+ var rows [][]string
+
+ caser := cases.Title(language.English)
+ for _, period := range periods {
+ downtime := calc.CalculateDowntime(period)
+ rows = append(rows, []string{
+ caser.String(string(period)),
+ downtime.Formatted,
+ })
+ }
+
+ printTable(headers, rows)
+ return nil
+}
+
+func compareAvailabilities(availability1, availability2 float64) error {
+ if availability1 == availability2 {
+ return fmt.Errorf("cannot compare identical availability values")
+ }
+
+ calc1, err := NewAvailabilityCalculator(availability1)
+ if err != nil {
+ return err
+ }
+
+ calc2, err := NewAvailabilityCalculator(availability2)
+ if err != nil {
+ return err
+ }
+
+ improvement := ((100 - availability1) - (100 - availability2)) / (100 - availability1) * 100
+ fmt.Printf("Downtime reduction: %.1f%%\n\n", improvement)
+
+ headers := []string{
+ "Time Period",
+ fmt.Sprintf("%.2f%%", availability1),
+ fmt.Sprintf("%.2f%%", availability2),
+ }
+ var rows [][]string
+
+ caser := cases.Title(language.English)
+ for _, period := range periodOrder {
+ downtime1 := calc1.CalculateDowntime(period)
+ downtime2 := calc2.CalculateDowntime(period)
+ rows = append(rows, []string{
+ caser.String(string(period)),
+ downtime1.Formatted,
+ downtime2.Formatted,
+ })
+ }
+
+ printTable(headers, rows)
+ return nil
+}
+
+func printUsage() {
+ fmt.Println("Usage:")
+ fmt.Println(" pviz analyze <availability> - Analyze a single availability target")
+ fmt.Println(" pviz analyze <availability> --focus <period> - Focus on a specific time period")
+ fmt.Println(" pviz compare <availability1> <availability2> - Compare two availability targets")
+ fmt.Println()
+ fmt.Println("Examples:")
+ fmt.Println(" pviz analyze 99.99")
+ fmt.Println(" pviz analyze 99.99 --focus quarter")
+ fmt.Println(" pviz compare 99.9 99.95")
+ fmt.Println()
+ fmt.Println("Valid periods: day, week, month, quarter, year")
+}
+
+func main() {
+ if len(os.Args) < 2 {
+ printUsage()
+ os.Exit(1)
+ }
+
+ command := os.Args[1]
+
+ switch command {
+ case "analyze":
+ if len(os.Args) < 3 {
+ fmt.Fprintf(os.Stderr, "Error: analyze command requires availability argument\n")
+ os.Exit(1)
+ }
+
+ availability, err := strconv.ParseFloat(os.Args[2], 64)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: invalid availability value: %v\n", err)
+ os.Exit(1)
+ }
+
+ var focusPeriod string
+ if len(os.Args) >= 5 && os.Args[3] == "--focus" {
+ focusPeriod = os.Args[4]
+ validPeriod := false
+ for _, p := range periodOrder {
+ if string(p) == focusPeriod {
+ validPeriod = true
+ break
+ }
+ }
+ if !validPeriod {
+ fmt.Fprintf(
+ os.Stderr,
+ "Error: invalid period '%s'. Valid periods: day, week, month, quarter, year\n",
+ focusPeriod,
+ )
+ os.Exit(1)
+ }
+ }
+
+ if err := analyzeSingle(availability, focusPeriod); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+
+ case "compare":
+ if len(os.Args) < 4 {
+ fmt.Fprintf(os.Stderr, "Error: compare command requires two availability arguments\n")
+ os.Exit(1)
+ }
+
+ availability1, err := strconv.ParseFloat(os.Args[2], 64)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: invalid first availability value: %v\n", err)
+ os.Exit(1)
+ }
+
+ availability2, err := strconv.ParseFloat(os.Args[3], 64)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: invalid second availability value: %v\n", err)
+ os.Exit(1)
+ }
+
+ if err := compareAvailabilities(availability1, availability2); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+
+ default:
+ fmt.Fprintf(os.Stderr, "Error: unknown command '%s'\n", command)
+ printUsage()
+ os.Exit(1)
+ }
+}
diff --git a/cmd/seq-stat/main.go b/cmd/seq-stat/main.go
new file mode 100644
index 0000000..75bb0a3
--- /dev/null
+++ b/cmd/seq-stat/main.go
@@ -0,0 +1,115 @@
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+)
+
+var ticks = []rune{'▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
+
+func histogram(sequence []float64) []rune {
+ if len(sequence) == 0 {
+ return []rune{}
+ }
+
+ minVal := sequence[0]
+ maxVal := sequence[0]
+
+ for _, v := range sequence {
+ if v < minVal {
+ minVal = v
+ }
+ if v > maxVal {
+ maxVal = v
+ }
+ }
+
+ scale := (maxVal - minVal) * 256 / float64(len(ticks)-1)
+ if scale < 1 {
+ scale = 1
+ }
+
+ result := make([]rune, len(sequence))
+ for i, v := range sequence {
+ index := int(((v - minVal) * 256) / scale)
+ if index >= len(ticks) {
+ index = len(ticks) - 1
+ }
+ result[i] = ticks[index]
+ }
+
+ return result
+}
+
+func parseNumbers(input string) ([]float64, error) {
+ fields := strings.Fields(input)
+ numbers := make([]float64, 0, len(fields))
+
+ for _, field := range fields {
+ num, err := strconv.ParseFloat(field, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid number: %s", field)
+ }
+ numbers = append(numbers, num)
+ }
+
+ return numbers, nil
+}
+
+func readFromStdin() ([]float64, error) {
+ var numbers []float64
+ scanner := bufio.NewScanner(os.Stdin)
+
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" {
+ continue
+ }
+
+ lineNumbers, err := parseNumbers(line)
+ if err != nil {
+ return nil, err
+ }
+ numbers = append(numbers, lineNumbers...)
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("error reading from stdin: %v", err)
+ }
+
+ return numbers, nil
+}
+
+func main() {
+ var numbers []float64
+ var err error
+
+ if len(os.Args) > 1 {
+ numbers = make([]float64, 0, len(os.Args)-1)
+ for _, arg := range os.Args[1:] {
+ num, parseErr := strconv.ParseFloat(arg, 64)
+ if parseErr != nil {
+ fmt.Fprintf(os.Stderr, "Invalid number: %s\n", arg)
+ os.Exit(1)
+ }
+ numbers = append(numbers, num)
+ }
+ } else {
+ numbers, err = readFromStdin()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+
+ if len(numbers) == 0 {
+ fmt.Fprintln(os.Stderr, "No numbers provided")
+ os.Exit(1)
+ }
+ }
+
+ h := histogram(numbers)
+ fmt.Println(string(h))
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..6d1a15e
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,142 @@
+{
+ "nodes": {
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1747046372,
+ "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "gitignore": {
+ "inputs": {
+ "nixpkgs": [
+ "pre-commit-hooks",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1709087332,
+ "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "hercules-ci",
+ "repo": "gitignore.nix",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1755274400,
+ "narHash": "sha256-rTInmnp/xYrfcMZyFMH3kc8oko5zYfxsowaLv1LVobY=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "ad7196ae55c295f53a7d1ec39e4a06d922f3b899",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-25.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "pre-commit-hooks": {
+ "inputs": {
+ "flake-compat": "flake-compat",
+ "gitignore": "gitignore",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1755446520,
+ "narHash": "sha256-I0Ok1OGDwc1jPd8cs2VvAYZsHriUVFGIUqW+7uSsOUM=",
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "rev": "4b04db83821b819bbbe32ed0a025b31e7971f22e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs",
+ "pre-commit-hooks": "pre-commit-hooks",
+ "treefmt-nix": "treefmt-nix"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "treefmt-nix": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1754847726,
+ "narHash": "sha256-2vX8QjO5lRsDbNYvN9hVHXLU6oMl+V/PsmIiJREG4rE=",
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "rev": "7d81f6fb2e19bf84f1c65135d1060d829fae2408",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..0833885
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,135 @@
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
+ flake-utils.url = "github:numtide/flake-utils";
+ treefmt-nix = {
+ url = "github:numtide/treefmt-nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ pre-commit-hooks = {
+ url = "github:cachix/git-hooks.nix";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+ outputs =
+ {
+ self,
+ nixpkgs,
+ treefmt-nix,
+ flake-utils,
+ pre-commit-hooks,
+ }:
+ let
+ # Import our packages and overlay
+ overlay = import ./nix/overlay.nix;
+
+ # Import NixOS modules
+ nixosModules = {
+ goget = import ./nix/modules/goget.nix;
+ default = {
+ imports = [
+ ./nix/modules/goget.nix
+ ];
+ };
+ };
+ in
+ {
+ # Export the overlay for others to use
+ overlays.default = overlay;
+
+ # Export NixOS modules
+ inherit nixosModules;
+ nixosModule = nixosModules.default;
+ }
+ // flake-utils.lib.eachDefaultSystem (
+ system:
+ let
+ pkgs = import nixpkgs {
+ inherit system;
+ overlays = [ overlay ];
+ };
+
+ packages = import ./nix/packages { inherit pkgs; };
+
+ treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix;
+
+ ciPackages = with pkgs; [
+ go # Need that obviously
+ gofumpt # Go formatter
+ golangci-lint # Local/CI linter
+ gotestsum # Pretty tester
+ goperf # Go performance suite
+ ];
+
+ devPackages = with pkgs; [
+ gopls
+ gotools
+ ];
+ in
+ {
+ packages = packages // {
+ default = packages.goget;
+ };
+
+ formatter = treefmtEval.config.build.wrapper;
+
+ checks = {
+ # Throws an error if any of the source files are not correctly formatted
+ # when you run `nix flake check --print-build-logs`. Useful for CI
+ treefmt = treefmtEval.config.build.check self;
+ pre-commit-check = pre-commit-hooks.lib.${system}.run {
+ src = ./.;
+ hooks = {
+ format = {
+ enable = true;
+ name = "Format with treefmt";
+ pass_filenames = true;
+ entry = "${treefmtEval.config.build.wrapper}/bin/treefmt";
+ stages = [
+ "pre-commit"
+ "pre-push"
+ ];
+ };
+ golangci-lint = {
+ enable = true;
+ package = pkgs.golangci-lint;
+ pass_filenames = true;
+ stages = [ "pre-push" ];
+ };
+ unit-tests-go = {
+ enable = true;
+ name = "Run unit tests";
+ entry = "gotestsum --format testdox ./...";
+ pass_filenames = false;
+ stages = [ "pre-push" ];
+ types = [ "go" ];
+ };
+ };
+ };
+ };
+
+ devShells = {
+ default = pkgs.mkShell {
+ buildInputs =
+ [ ] ++ ciPackages ++ devPackages ++ self.checks.${system}.pre-commit-check.enabledPackages;
+
+ shellHook = ''
+ ${self.checks.${system}.pre-commit-check.shellHook}
+ export GOTOOLCHAIN=local
+ export GOPRIVATE=go.fcuny.net/*
+ '';
+ };
+
+ ci = pkgs.mkShell {
+ buildInputs = [ ] ++ ciPackages;
+ CI = true;
+ shellHook = ''
+ export GOTOOLCHAIN=local
+ export GOPRIVATE=go.fcuny.net/*
+ echo "Entering CI shell. Only essential CI tools available."
+ '';
+ };
+ };
+ }
+ );
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..e506b94
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,20 @@
+module go.fcuny.net/x
+
+go 1.24.5
+
+require (
+ github.com/prometheus/client_golang v1.23.0
+ golang.org/x/mod v0.27.0
+ golang.org/x/text v0.28.0
+)
+
+require (
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/prometheus/client_model v0.6.2 // indirect
+ github.com/prometheus/common v0.65.0 // indirect
+ github.com/prometheus/procfs v0.16.1 // indirect
+ golang.org/x/sys v0.33.0 // indirect
+ google.golang.org/protobuf v1.36.6 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..a74ab67
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,38 @@
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
+github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
+github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
+golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
+golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/nix/modules/goget.nix b/nix/modules/goget.nix
new file mode 100644
index 0000000..3ed5e04
--- /dev/null
+++ b/nix/modules/goget.nix
@@ -0,0 +1,66 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+
+with lib;
+
+let
+ cfg = config.services.goget;
+in
+{
+ options.services.goget = {
+ enable = mkEnableOption "goget service";
+
+ package = mkPackageOption pkgs "goget" { };
+
+ port = mkOption {
+ type = types.port;
+ default = 8070;
+ description = "Port to listen on";
+ };
+
+ openFirewall = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to open the firewall for the goget service";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.goget = {
+ description = "goget service";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ wants = [ "network.target" ];
+
+ serviceConfig = {
+ Type = "exec";
+ DynamicUser = true;
+ ExecStart = "${cfg.package}/bin/goget";
+ Restart = "always";
+ RestartSec = "5";
+
+ # Security settings
+ NoNewPrivileges = true;
+ ProtectSystem = "strict";
+ ProtectHome = true;
+ PrivateTmp = true;
+ ProtectKernelTunables = true;
+ ProtectKernelModules = true;
+ ProtectControlGroups = true;
+ RestrictSUIDSGID = true;
+ RestrictRealtime = true;
+ RestrictNamespaces = true;
+ LockPersonality = true;
+ MemoryDenyWriteExecute = true;
+ };
+ };
+
+ networking.firewall = mkIf cfg.openFirewall {
+ allowedTCPPorts = [ cfg.port ];
+ };
+ };
+}
diff --git a/nix/overlay.nix b/nix/overlay.nix
new file mode 100644
index 0000000..6789627
--- /dev/null
+++ b/nix/overlay.nix
@@ -0,0 +1,5 @@
+final: prev:
+let
+ packages = import ./packages { pkgs = final; };
+in
+packages
diff --git a/nix/packages/default.nix b/nix/packages/default.nix
new file mode 100644
index 0000000..e598fa1
--- /dev/null
+++ b/nix/packages/default.nix
@@ -0,0 +1,5 @@
+{ pkgs }:
+
+{
+ goget = pkgs.callPackage ./goget.nix { };
+}
diff --git a/nix/packages/goget.nix b/nix/packages/goget.nix
new file mode 100644
index 0000000..c767740
--- /dev/null
+++ b/nix/packages/goget.nix
@@ -0,0 +1,31 @@
+{
+ lib,
+ buildGoModule,
+}:
+
+buildGoModule rec {
+ pname = "goget";
+ version = "0.1.0"; # Consider deriving from git tags: version = builtins.substring 0 8 self.rev;
+
+ src = ../..;
+
+ vendorHash = "sha256-pStRgjhjjZdsYSnYMcWNbHSF7CJ3+7ZQradZgBfi5Gw=";
+
+ subPackages = [ "cmd/goget" ];
+
+ ldflags = [
+ "-s"
+ "-w"
+ ];
+
+ doCheck = false;
+
+ meta = with lib; {
+ description = "A Go tool for getting things"; # Update with actual description
+ homepage = "https://github.com/yourusername/yourrepo"; # Update with your repo
+ license = licenses.mit;
+ maintainers = with maintainers; [ fcuny ];
+ platforms = platforms.unix;
+ mainProgram = "goget";
+ };
+}
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..a978b3e
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,3 @@
+[toolchain]
+channel = "1.89"
+components = ["rustfmt", "clippy", "rust-analyzer"]
diff --git a/src/apple_silicon/Cargo.lock b/src/apple_silicon/Cargo.lock
new file mode 100644
index 0000000..1ee0f1d
--- /dev/null
+++ b/src/apple_silicon/Cargo.lock
@@ -0,0 +1,65 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "apple_silicon"
+version = "0.1.0"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
diff --git a/src/apple_silicon/Cargo.toml b/src/apple_silicon/Cargo.toml
new file mode 100644
index 0000000..65dcf5e
--- /dev/null
+++ b/src/apple_silicon/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "apple_silicon"
+version = "0.1.0"
+edition = "2021"
+description = "A common line tool for Apple Silicon"
+
+license = "MIT"
+authors = ["Franck Cuny <franck@fcuny.net>"]
+repository = "https://github.com/fcuny/apple_silicon"
+homepage = "https://github.com/fcuny/apple_silicon"
+categories = ["command-line-utilities"]
+
+[[bin]]
+name = "apple_silicon"
+path = "src/bin/apple_silicon.rs"
+
+[dependencies]
+thiserror = "2.0.11"
diff --git a/src/apple_silicon/README.md b/src/apple_silicon/README.md
new file mode 100644
index 0000000..7d83327
--- /dev/null
+++ b/src/apple_silicon/README.md
@@ -0,0 +1 @@
+A command line utility to report information about Apple Silicon (inspired by [`asitop`](https://github.com/tlkh/asitop/tree/74ebe2cbc23d5b1eec874aebb1b9bacfe0e670cd)).
diff --git a/src/apple_silicon/src/bin/apple_silicon.rs b/src/apple_silicon/src/bin/apple_silicon.rs
new file mode 100644
index 0000000..0f71eeb
--- /dev/null
+++ b/src/apple_silicon/src/bin/apple_silicon.rs
@@ -0,0 +1,12 @@
+use apple_silicon::soc::SocInfo;
+
+fn main() {
+ let cpu_info = SocInfo::new().unwrap();
+ println!(
+ "our CPU is an {}, and we have {} CPU cores, and {} GPU cores. The TDP is {}.",
+ cpu_info.cpu_brand_name,
+ cpu_info.num_cpu_cores,
+ cpu_info.num_gpu_cores,
+ cpu_info.cpu_max_power.unwrap(),
+ );
+}
diff --git a/src/apple_silicon/src/error.rs b/src/apple_silicon/src/error.rs
new file mode 100644
index 0000000..a70b9b5
--- /dev/null
+++ b/src/apple_silicon/src/error.rs
@@ -0,0 +1,23 @@
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+ #[error("socinfo parsing error: `{0}`")]
+ Parse(String),
+
+ #[error("I/O error: `{source}`")]
+ Io {
+ #[from]
+ source: std::io::Error,
+ },
+
+ #[error("utf8 conversion error: `{source}`")]
+ Utf8Conversion {
+ #[from]
+ source: std::string::FromUtf8Error,
+ },
+
+ #[error("integer parsing error: `{source}`")]
+ ParseInt {
+ #[from]
+ source: std::num::ParseIntError,
+ },
+}
diff --git a/src/apple_silicon/src/lib.rs b/src/apple_silicon/src/lib.rs
new file mode 100644
index 0000000..74bd62c
--- /dev/null
+++ b/src/apple_silicon/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod error;
+pub mod soc;
diff --git a/src/apple_silicon/src/soc.rs b/src/apple_silicon/src/soc.rs
new file mode 100644
index 0000000..0e2bf5c
--- /dev/null
+++ b/src/apple_silicon/src/soc.rs
@@ -0,0 +1,302 @@
+use crate::error::Error;
+
+use std::process::{Command, Output};
+
+pub type Result<T> = std::result::Result<T, Error>;
+pub type Watts = u32;
+pub type Bandwidth = u32;
+pub type CoreCount = u16;
+
+/// Information about the Silicon chip
+pub struct SocInfo {
+ /// The CPU brand name string
+ pub cpu_brand_name: String,
+ /// Number of CPU cores
+ pub num_cpu_cores: CoreCount,
+ /// Number of GPU cores
+ pub num_gpu_cores: CoreCount,
+ /// Maximum CPU power in watts (if available)
+ pub cpu_max_power: Option<Watts>,
+ /// Maximum GPU power in watts (if available)
+ pub gpu_max_power: Option<Watts>,
+ /// Maximum CPU bandwidth in GB/s (if available)
+ pub cpu_max_bw: Option<Bandwidth>,
+ /// Maximum GPU bandwidth in GB/s (if available)
+ pub gpu_max_bw: Option<Bandwidth>,
+ /// Number of efficiency cores
+ pub e_core_count: CoreCount,
+ /// Number of performance cores
+ pub p_core_count: CoreCount,
+}
+
+#[derive(Debug, PartialEq)]
+enum AppleChip {
+ M1,
+ M1Pro,
+ M1Max,
+ M1Ultra,
+ M2,
+ M2Pro,
+ M2Max,
+ M2Ultra,
+ M3,
+ M3Pro,
+ M3Max,
+ Unknown,
+}
+
+struct ChipSpecs {
+ cpu_tdp: Watts,
+ gpu_tdp: Watts,
+ cpu_bw: Bandwidth,
+ gpu_bw: Bandwidth,
+}
+
+impl AppleChip {
+ fn from_brand_string(brand: &str) -> Self {
+ match brand {
+ s if s.contains("M1 Pro") => AppleChip::M1Pro,
+ s if s.contains("M1 Max") => AppleChip::M1Max,
+ s if s.contains("M1 Ultra") => AppleChip::M1Ultra,
+ s if s.contains("M1") => AppleChip::M1,
+ s if s.contains("M2 Pro") => AppleChip::M2Pro,
+ s if s.contains("M2 Max") => AppleChip::M2Max,
+ s if s.contains("M2 Ultra") => AppleChip::M2Ultra,
+ s if s.contains("M2") => AppleChip::M2,
+ s if s.contains("M3 Pro") => AppleChip::M3Pro,
+ s if s.contains("M3 Max") => AppleChip::M3Max,
+ s if s.contains("M3") => AppleChip::M3,
+ _ => AppleChip::Unknown,
+ }
+ }
+
+ fn get_specs(&self) -> ChipSpecs {
+ match self {
+ AppleChip::M1 => ChipSpecs {
+ cpu_tdp: 20,
+ gpu_tdp: 20,
+ cpu_bw: 70,
+ gpu_bw: 70,
+ },
+ AppleChip::M1Pro => ChipSpecs {
+ cpu_tdp: 30,
+ gpu_tdp: 30,
+ cpu_bw: 200,
+ gpu_bw: 200,
+ },
+ AppleChip::M1Max => ChipSpecs {
+ cpu_tdp: 30,
+ gpu_tdp: 60,
+ cpu_bw: 250,
+ gpu_bw: 400,
+ },
+ AppleChip::M1Ultra => ChipSpecs {
+ cpu_tdp: 60,
+ gpu_tdp: 120,
+ cpu_bw: 500,
+ gpu_bw: 800,
+ },
+ AppleChip::M2 => ChipSpecs {
+ cpu_tdp: 25,
+ gpu_tdp: 15,
+ cpu_bw: 100,
+ gpu_bw: 100,
+ },
+ AppleChip::M2Pro => ChipSpecs {
+ cpu_tdp: 30,
+ gpu_tdp: 35,
+ cpu_bw: 0,
+ gpu_bw: 0,
+ },
+ AppleChip::M2Max => ChipSpecs {
+ cpu_tdp: 30,
+ gpu_tdp: 40,
+ cpu_bw: 0,
+ gpu_bw: 0,
+ },
+ // Add more variants as needed
+ _ => ChipSpecs {
+ cpu_tdp: 0,
+ gpu_tdp: 0,
+ cpu_bw: 0,
+ gpu_bw: 0,
+ },
+ }
+ }
+}
+
+impl SocInfo {
+ pub fn new() -> Result<SocInfo> {
+ let (cpu_brand_name, num_cpu_cores, e_core_count, p_core_count) = cpu_info(&RealCommand)?;
+ let num_gpu_cores = gpu_info(&RealCommand)?;
+
+ let chip = AppleChip::from_brand_string(&cpu_brand_name);
+ let specs = chip.get_specs();
+
+ Ok(SocInfo {
+ cpu_brand_name,
+ num_cpu_cores,
+ num_gpu_cores,
+ cpu_max_power: Some(specs.cpu_tdp),
+ gpu_max_power: Some(specs.gpu_tdp),
+ cpu_max_bw: Some(specs.cpu_bw),
+ gpu_max_bw: Some(specs.gpu_bw),
+ e_core_count,
+ p_core_count,
+ })
+ }
+}
+
+// https://github.com/tlkh/asitop/blob/74ebe2cbc23d5b1eec874aebb1b9bacfe0e670cd/asitop/utils.py#L94
+const SYSCTL_PATH: &str = "/usr/sbin/sysctl";
+
+fn cpu_info(cmd: &impl SystemCommand) -> Result<(String, u16, u16, u16)> {
+ let binary = SYSCTL_PATH;
+ let args = &[
+ // don't display the variable name
+ "-n",
+ "machdep.cpu.brand_string",
+ "machdep.cpu.core_count",
+ "hw.perflevel0.logicalcpu",
+ "hw.perflevel1.logicalcpu",
+ ];
+
+ let output = cmd.execute(binary, args)?;
+ let buffer = String::from_utf8(output.stdout)?;
+
+ let mut iter = buffer.split('\n');
+ let cpu_brand_name = match iter.next() {
+ Some(s) => s.to_string(),
+ None => return Err(Error::Parse(buffer.to_string())),
+ };
+
+ let num_cpu_cores = match iter.next() {
+ Some(s) => s.parse::<u16>()?,
+ None => return Err(Error::Parse(buffer.to_string())),
+ };
+
+ let num_performance_cores = match iter.next() {
+ Some(s) => s.parse::<u16>()?,
+ None => return Err(Error::Parse(buffer.to_string())),
+ };
+
+ let num_efficiency_cores = match iter.next() {
+ Some(s) => s.parse::<u16>()?,
+ None => return Err(Error::Parse(buffer.to_string())),
+ };
+
+ Ok((
+ cpu_brand_name,
+ num_cpu_cores,
+ num_performance_cores,
+ num_efficiency_cores,
+ ))
+}
+
+// https://github.com/tlkh/asitop/blob/74ebe2cbc23d5b1eec874aebb1b9bacfe0e670cd/asitop/utils.py#L120
+fn gpu_info(cmd: &impl SystemCommand) -> Result<u16> {
+ let binary = "/usr/sbin/system_profiler";
+ let args = &["-detailLevel", "basic", "SPDisplaysDataType"];
+
+ let output = cmd.execute(binary, args)?;
+ let buffer = String::from_utf8(output.stdout)?;
+
+ let num_gpu_cores_line = buffer
+ .lines()
+ .find(|&line| line.trim_start().starts_with("Total Number of Cores"));
+
+ let num_gpu_cores = match num_gpu_cores_line {
+ Some(s) => match s.split(": ").last() {
+ Some(s) => s.parse::<u16>()?,
+ None => return Err(Error::Parse(buffer.to_string())),
+ },
+ None => return Err(Error::Parse(buffer.to_string())),
+ };
+
+ Ok(num_gpu_cores)
+}
+
+/// Trait for system command execution
+pub trait SystemCommand {
+ fn execute(&self, binary: &str, args: &[&str]) -> Result<Output>;
+}
+
+/// Real command executor
+pub struct RealCommand;
+
+impl SystemCommand for RealCommand {
+ fn execute(&self, binary: &str, args: &[&str]) -> Result<Output> {
+ Ok(Command::new(binary).args(args).output()?)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::os::unix::process::ExitStatusExt;
+
+ struct MockCommand {
+ output: Vec<u8>,
+ }
+
+ impl MockCommand {
+ fn new(output: &str) -> Self {
+ Self {
+ output: output.as_bytes().to_vec(),
+ }
+ }
+ }
+
+ impl SystemCommand for MockCommand {
+ fn execute(&self, _binary: &str, _args: &[&str]) -> Result<Output> {
+ Ok(Output {
+ status: std::process::ExitStatus::from_raw(0),
+ stdout: self.output.clone(),
+ stderr: Vec::new(),
+ })
+ }
+ }
+
+ #[test]
+ fn test_gpu_info() {
+ let mock_output = r#"Graphics/Displays:
+ Apple M2:
+ Total Number of Cores: 10"#;
+ let cmd = MockCommand::new(mock_output);
+
+ let result = gpu_info(&cmd);
+ assert_eq!(result.unwrap(), 10);
+ }
+
+ #[test]
+ fn test_cpu_info_success() {
+ let mock_output = "Apple M2\n8\n4\n4\n";
+ let cmd = MockCommand::new(mock_output);
+
+ let result = cpu_info(&cmd);
+ assert!(result.is_ok());
+ let (brand, cores, p_cores, e_cores) = result.unwrap();
+ assert_eq!(brand, "Apple M2");
+ assert_eq!(cores, 8);
+ assert_eq!(p_cores, 4);
+ assert_eq!(e_cores, 4);
+ }
+
+ #[test]
+ fn test_cpu_info_missing_core_count() {
+ let mock_output = "Apple M2\n";
+ let cmd = MockCommand::new(mock_output);
+
+ let result = cpu_info(&cmd);
+ assert!(matches!(result, Err(Error::ParseInt { .. })));
+ }
+
+ #[test]
+ fn test_cpu_info_invalid_core_count() {
+ let mock_output = "Apple M2\ninvalid\n";
+ let cmd = MockCommand::new(mock_output);
+
+ let result = cpu_info(&cmd);
+ assert!(matches!(result, Err(Error::ParseInt { .. })));
+ }
+}
diff --git a/src/x509-info/Cargo.lock b/src/x509-info/Cargo.lock
new file mode 100644
index 0000000..6e8a026
--- /dev/null
+++ b/src/x509-info/Cargo.lock
@@ -0,0 +1,1135 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
+
+[[package]]
+name = "asn1-rs"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
+
+[[package]]
+name = "aws-lc-rs"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5509d663b2c00ee421bda8d6a24d6c42e15970957de1701b8df9f6fbe5707df1"
+dependencies = [
+ "aws-lc-sys",
+ "mirai-annotations",
+ "paste",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d5d317212c2a78d86ba6622e969413c38847b62f48111f8b763af3dac2f9840"
+dependencies = [
+ "bindgen",
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+ "libc",
+ "paste",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
+
+[[package]]
+name = "bindgen"
+version = "0.69.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
+dependencies = [
+ "bitflags 2.5.0",
+ "cexpr",
+ "clang-sys",
+ "itertools",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "syn",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "cc"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
+dependencies = [
+ "jobserver",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "cexpr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "windows-targets",
+]
+
+[[package]]
+name = "clang-sys"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "cmake"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "data-encoding"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
+
+[[package]]
+name = "der-parser"
+version = "9.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
+
+[[package]]
+name = "either"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
+
+[[package]]
+name = "errno"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
+name = "getrandom"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "home"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "jobserver"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.153"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+
+[[package]]
+name = "libloading"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
+dependencies = [
+ "cfg-if",
+ "windows-targets",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "mirai-annotations"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "oid-registry"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d"
+dependencies = [
+ "asn1-rs",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "paste"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550"
+dependencies = [
+ "proc-macro2",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags 2.5.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79adb16721f56eb2d843e67676896a61ce7a0fa622dc18d3e372477a029d2740"
+dependencies = [
+ "aws-lc-rs",
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-native-certs"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
+dependencies = [
+ "openssl-probe",
+ "rustls-pemfile",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
+dependencies = [
+ "base64",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e"
+dependencies = [
+ "aws-lc-rs",
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.199"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.199"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+
+[[package]]
+name = "syn"
+version = "2.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "which"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
+dependencies = [
+ "either",
+ "home",
+ "once_cell",
+ "rustix",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+
+[[package]]
+name = "x509-info"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "chrono",
+ "clap",
+ "rustls",
+ "rustls-native-certs",
+ "webpki-roots",
+ "x509-parser",
+]
+
+[[package]]
+name = "x509-parser"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror",
+ "time",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
diff --git a/src/x509-info/Cargo.toml b/src/x509-info/Cargo.toml
new file mode 100644
index 0000000..24f0478
--- /dev/null
+++ b/src/x509-info/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "x509-info"
+version = "0.1.0"
+edition = "2021"
+authors = ["Franck Cuny <franck@fcuny.net>"]
+description = "Print summary of x509 certificates."
+homepage = "https://github.com/fcuny/x509-info/"
+readme = "README.md"
+
+[dependencies]
+anyhow = "1.0.82"
+chrono = {version = "0.4.38", features = ["clock"], default-features = false }
+clap = {version = "4.5.8", features = ["derive", "cargo"]}
+rustls = {version = "0.23.8", features = ["ring"]}
+rustls-native-certs = "0.7.0"
+webpki-roots = "0.26.3"
+x509-parser = "0.16"
diff --git a/src/x509-info/README.md b/src/x509-info/README.md
new file mode 100644
index 0000000..0369ba2
--- /dev/null
+++ b/src/x509-info/README.md
@@ -0,0 +1,72 @@
+# x509-info
+
+At this point it's pretty clear that I'll never remember the syntax for `openssl` to show various information about a certificate. At last I will not have to google for that syntax ever again.
+
+## Usage
+
+```shell
+$ Usage: x509-info [OPTIONS] <DOMAIN>
+
+Arguments:
+ <DOMAIN>
+ Domain to check
+
+Options:
+ -p, --port <PORT>
+ Port to check
+
+ [default: 443]
+
+ -i, --insecure
+ Accept invalid certificate
+
+ -f, --format <FORMAT>
+ [default: short]
+
+ Possible values:
+ - short: Format the output as one line of plain text
+ - long: Format the output as plain text
+
+ -h, --help
+ Print help information (use `-h` for a summary)
+
+ -V, --version
+ Print version information
+```
+
+The default format will print a short message:
+
+```shell
+$ x509-info twitter.com
+twitter.com: Mon, 11 Dec 2023 15:59:59 -0800 (257 days left)
+```
+
+It's possible to get more details:
+
+```shell
+$ x509-info --format=long twitter.com
+certificate
+ version: V3
+ serial: 0a:2c:01:b8:2b:5d:47:73:9a:5a:01:1a:6f:dc:1a:20
+ subject: C=US, ST=California, L=San Francisco, O=Twitter, Inc., CN=twitter.com
+ issuer: C=US, O=DigiCert Inc, CN=DigiCert TLS RSA SHA256 2020 CA1
+ validity
+ not before : Sat, 10 Dec 2022 16:00:00 -0800
+ not after : Mon, 11 Dec 2023 15:59:59 -0800
+ validity days : 365
+ remaining days: 257
+ SANs:
+ DNS:twitter.com
+ DNS:www.twitter.com
+```
+
+You can also check expired certificates:
+
+```shell
+$ x509-info --insecure expired.badssl.com
+<no name>: Sun, 12 Apr 2015 16:59:59 -0700 (it expired -2907 days ago)
+```
+
+## Notes
+
+Could the same be achieved with a wrapper around `openssl` ? yes.
diff --git a/src/x509-info/deny.toml b/src/x509-info/deny.toml
new file mode 100644
index 0000000..fd95cdb
--- /dev/null
+++ b/src/x509-info/deny.toml
@@ -0,0 +1,46 @@
+[advisories]
+db-path = "~/.cargo/advisory-db"
+db-urls = ["https://github.com/rustsec/advisory-db"]
+vulnerability = "deny"
+unmaintained = "warn"
+yanked = "warn"
+notice = "warn"
+ignore = []
+
+[licenses]
+unlicensed = "deny"
+allow = ["MIT", "Apache-2.0", "ISC", "Unicode-DFS-2016", "OpenSSL"]
+deny = []
+copyleft = "allow"
+default = "deny"
+confidence-threshold = 0.8
+exceptions = []
+
+[licenses.private]
+ignore = false
+registries = []
+
+# see https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html#example
+[[licenses.clarify]]
+name = "ring"
+expression = "MIT AND ISC AND OpenSSL"
+license-files = [
+ { path = "LICENSE", hash = 0xbd0eed23 }
+]
+
+[bans]
+multiple-versions = "warn"
+wildcards = "allow"
+highlight = "all"
+allow = []
+deny = []
+skip = []
+skip-tree = []
+
+[sources]
+unknown-registry = "warn"
+unknown-git = "warn"
+allow-registry = ["https://github.com/rust-lang/crates.io-index"]
+allow-git = []
+
+[sources.allow-org]
diff --git a/src/x509-info/src/client.rs b/src/x509-info/src/client.rs
new file mode 100644
index 0000000..83e010c
--- /dev/null
+++ b/src/x509-info/src/client.rs
@@ -0,0 +1,110 @@
+use anyhow::{anyhow, Error};
+use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
+use rustls::crypto::{ring, verify_tls12_signature, verify_tls13_signature, CryptoProvider};
+use rustls::pki_types::{self, CertificateDer, ServerName, UnixTime};
+use rustls::{ClientConfig, ClientConnection};
+use rustls::{DigitallySignedStruct, SignatureScheme};
+
+use std::io::Write;
+use std::sync::Arc;
+
+#[derive(Debug)]
+struct NoCertificateVerification {}
+
+impl ServerCertVerifier for NoCertificateVerification {
+ fn verify_server_cert(
+ &self,
+ _end_entity: &CertificateDer<'_>,
+ _intermediates: &[CertificateDer<'_>],
+ _server_name: &pki_types::ServerName<'_>,
+ _ocsp_response: &[u8],
+ _now: UnixTime,
+ ) -> Result<ServerCertVerified, rustls::Error> {
+ Ok(ServerCertVerified::assertion())
+ }
+
+ fn verify_tls12_signature(
+ &self,
+ message: &[u8],
+ cert: &CertificateDer<'_>,
+ dss: &DigitallySignedStruct,
+ ) -> Result<HandshakeSignatureValid, rustls::Error> {
+ verify_tls12_signature(
+ message,
+ cert,
+ dss,
+ &ring::default_provider().signature_verification_algorithms,
+ )
+ }
+
+ fn verify_tls13_signature(
+ &self,
+ message: &[u8],
+ cert: &CertificateDer<'_>,
+ dss: &DigitallySignedStruct,
+ ) -> Result<HandshakeSignatureValid, rustls::Error> {
+ verify_tls13_signature(
+ message,
+ cert,
+ dss,
+ &ring::default_provider().signature_verification_algorithms,
+ )
+ }
+
+ fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
+ ring::default_provider()
+ .signature_verification_algorithms
+ .supported_schemes()
+ }
+}
+
+pub fn get_certificates(
+ domain: String,
+ port: u16,
+ insecure: bool,
+) -> Result<Vec<CertificateDer<'static>>, Error> {
+ let mut tcp_stream = std::net::TcpStream::connect(format!("{}:{}", domain, port))?;
+
+ let config = if insecure {
+ ClientConfig::builder()
+ .dangerous()
+ .with_custom_certificate_verifier(Arc::new(NoCertificateVerification {}))
+ .with_no_client_auth()
+ } else {
+ let root_store = rustls::RootCertStore {
+ roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
+ };
+ ClientConfig::builder_with_provider(
+ CryptoProvider {
+ cipher_suites: vec![ring::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256],
+ kx_groups: vec![ring::kx_group::X25519],
+ ..ring::default_provider()
+ }
+ .into(),
+ )
+ .with_protocol_versions(&[&rustls::version::TLS13])
+ .unwrap()
+ .with_root_certificates(root_store)
+ .with_no_client_auth()
+ };
+
+ let server_name = ServerName::try_from(domain.clone())
+ .expect("invalid DNS name")
+ .to_owned();
+
+ let mut conn = ClientConnection::new(Arc::new(config), server_name)?;
+ while conn.wants_write() {
+ conn.write_tls(&mut tcp_stream)?;
+ }
+
+ tcp_stream.flush()?;
+ while conn.is_handshaking() && conn.peer_certificates().is_none() {
+ conn.read_tls(&mut tcp_stream)?;
+ conn.process_new_packets()?;
+ }
+
+ match conn.peer_certificates() {
+ Some(c) => Ok(c.to_vec()),
+ None => Err(anyhow!("no certificate found for {}", domain))?,
+ }
+}
diff --git a/src/x509-info/src/main.rs b/src/x509-info/src/main.rs
new file mode 100644
index 0000000..d37ce0c
--- /dev/null
+++ b/src/x509-info/src/main.rs
@@ -0,0 +1,41 @@
+extern crate webpki_roots;
+
+mod client;
+mod output;
+
+use clap::Parser;
+use x509_parser::prelude::*;
+
+#[derive(Parser, Debug)]
+#[clap(author, version, about, long_about = None)]
+struct Args {
+ /// Domain to check
+ domain: String,
+
+ /// Port to check
+ #[clap(short, long, default_value_t = 443)]
+ port: u16,
+
+ /// Accept invalid certificate
+ #[clap(short, long, default_value_t = false)]
+ insecure: bool,
+
+ #[clap(short, long,value_enum, default_value_t = output::OutputFormat::Short)]
+ format: output::OutputFormat,
+}
+
+fn main() {
+ let args = Args::parse();
+
+ match client::get_certificates(args.domain, args.port, args.insecure) {
+ Ok(certs) => {
+ let (_, cert) =
+ x509_parser::certificate::X509Certificate::from_der(certs[0].as_ref()).unwrap();
+ output::OutputFormat::print(args.format, cert);
+ }
+ Err(e) => {
+ println!("error: {}", e);
+ std::process::exit(1);
+ }
+ };
+}
diff --git a/src/x509-info/src/output.rs b/src/x509-info/src/output.rs
new file mode 100644
index 0000000..963ef20
--- /dev/null
+++ b/src/x509-info/src/output.rs
@@ -0,0 +1,120 @@
+use chrono::prelude::*;
+use std::net::{Ipv4Addr, Ipv6Addr};
+use x509_parser::prelude::*;
+
+/// How to format the output data.
+#[derive(PartialEq, Eq, Debug, Copy, Clone, clap::ValueEnum)]
+pub enum OutputFormat {
+ /// Format the output as one line of plain text.
+ Short,
+
+ /// Format the output as plain text.
+ Long,
+}
+
+impl OutputFormat {
+ pub fn print(self, cert: X509Certificate) {
+ match self {
+ Self::Short => {
+ self.short(cert);
+ }
+ Self::Long => {
+ self.to_text(cert);
+ }
+ }
+ }
+
+ fn short(self, cert: X509Certificate) {
+ let not_after = chrono::Local
+ .timestamp_opt(cert.validity().not_after.timestamp(), 0)
+ .unwrap();
+ let now: DateTime<Local> = Local::now();
+ let remaining = not_after - now;
+
+ if remaining >= chrono::Duration::zero() {
+ println!(
+ "{}: {} ({} days left)",
+ cert.subject()
+ .iter_common_name()
+ .next()
+ .and_then(|cn| cn.as_str().ok())
+ .unwrap_or("<no name>"),
+ not_after.to_rfc2822(),
+ remaining.num_days(),
+ );
+ } else {
+ println!(
+ "{}: {} (it expired {} days ago)",
+ cert.subject()
+ .iter_common_name()
+ .next()
+ .and_then(|cn| cn.as_str().ok())
+ .unwrap_or("<no name>"),
+ not_after.to_rfc2822(),
+ remaining.num_days(),
+ );
+ }
+ }
+
+ fn to_text(self, cert: X509Certificate) {
+ let not_before = chrono::Local
+ .timestamp_opt(cert.validity().not_before.timestamp(), 0)
+ .unwrap();
+ let not_after = chrono::Local
+ .timestamp_opt(cert.validity().not_after.timestamp(), 0)
+ .unwrap();
+ let now: DateTime<Local> = Local::now();
+ let remaining = not_after - now;
+ let validity_duration = not_after - not_before;
+
+ println!("certificate");
+ println!(" version: {}", cert.version);
+ println!(" serial: {}", cert.tbs_certificate.raw_serial_as_string());
+ println!(" subject: {}", cert.subject());
+ println!(" issuer: {}", cert.issuer());
+
+ println!(" validity");
+ println!(" not before : {}", not_before.to_rfc2822());
+ println!(" not after : {}", not_after.to_rfc2822());
+ println!(" validity days : {}", validity_duration.num_days());
+ println!(" remaining days: {}", remaining.num_days());
+
+ println!(" SANs:");
+ if let Some(subnames) = subject_alternative_name(cert) {
+ for name in subnames {
+ println!(" {}", name);
+ }
+ }
+ }
+}
+
+fn subject_alternative_name(cert: X509Certificate) -> Option<Vec<String>> {
+ let mut subnames = Vec::new();
+ if let Ok(Some(san)) = cert.subject_alternative_name() {
+ let san = san.value;
+ for name in &san.general_names {
+ let s = match name {
+ GeneralName::DNSName(s) => format!("DNS:{}", s),
+ GeneralName::IPAddress(b) => {
+ let ip = match b.len() {
+ 4 => {
+ let b = <[u8; 4]>::try_from(*b).unwrap();
+ let ip = Ipv4Addr::from(b);
+ format!("{}", ip)
+ }
+ 16 => {
+ let b = <[u8; 16]>::try_from(*b).unwrap();
+ let ip = Ipv6Addr::from(b);
+ format!("{}", ip)
+ }
+ l => format!("invalid (len={})", l),
+ };
+ format!("IP address:{}", ip)
+ }
+ _ => format!("{:?}", name),
+ };
+ subnames.push(s);
+ }
+ }
+ Some(subnames)
+}
diff --git a/treefmt.nix b/treefmt.nix
new file mode 100644
index 0000000..b2e04da
--- /dev/null
+++ b/treefmt.nix
@@ -0,0 +1,35 @@
+{ pkgs, ... }:
+{
+ # See https://github.com/numtide/treefmt-nix#supported-programs
+ projectRootFile = ".git/config";
+ settings.global.includes = [
+ "*.go"
+ "*.md"
+ "*.nix"
+ "*.rs"
+ "*.toml"
+ "*.yaml"
+ "*.yml"
+ ];
+ settings.global.fail-on-change = true;
+ settings.global.no-cache = true;
+ programs.gofumpt = {
+ enable = true;
+ package = pkgs.gofumpt;
+ };
+ programs.goimports.enable = true;
+ programs.golines.enable = true;
+ # GitHub Actions
+ programs.yamlfmt.enable = true;
+ programs.actionlint.enable = true;
+ # Markdown
+ programs.mdformat.enable = true;
+ # Rust
+ programs.rustfmt.enable = true;
+ programs.taplo.enable = true;
+ # Nix
+ programs.nixfmt = {
+ enable = true;
+ package = pkgs.nixfmt-rfc-style;
+ };
+}