diff --git a/Cargo.lock b/Cargo.lock index 5f965b7..73f82fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,9 +47,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", @@ -74,7 +74,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -91,9 +91,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-fips-sys" -version = "0.13.10" +version = "0.13.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57900537c00a0565a35b63c4c281b372edfc9744b072fd4a3b414350a8f5ed48" +checksum = "df6ea8e07e2df15b9f09f2ac5ee2977369b06d116f0c4eb5fa4ad443b73c7f53" dependencies = [ "bindgen", "cc", @@ -105,9 +105,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.15.2" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", @@ -116,9 +116,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.35.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" dependencies = [ "cc", "cmake", @@ -149,7 +149,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -237,9 +237,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -473,7 +473,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "unicode-xid", ] @@ -495,7 +495,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -592,9 +592,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -604,9 +604,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", "miniz_oxide", @@ -698,7 +698,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -749,9 +749,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -782,9 +782,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -941,7 +941,7 @@ dependencies = [ "similar", "stringmetrics", "tabwriter", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "url", @@ -1005,19 +1005,18 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", @@ -1136,9 +1135,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1152,9 +1151,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -1180,9 +1179,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -1196,9 +1195,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1212,9 +1211,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libdd-common" @@ -1424,9 +1423,9 @@ dependencies = [ [[package]] name = "mockito" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0603425789b4a70fcc4ac4f5a46a566c116ee3e2a6b768dc623f7719c611de" +checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0" dependencies = [ "assert-json-diff", "bytes", @@ -1500,9 +1499,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "ordered-float" @@ -1542,12 +1541,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "path-tree" version = "0.8.3" @@ -1590,7 +1583,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1636,7 +1629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1665,9 +1658,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1717,7 +1710,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.111", + "syn 2.0.114", "tempfile", ] @@ -1731,7 +1724,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1815,7 +1808,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -1836,7 +1829,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -1858,9 +1851,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1889,7 +1882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1909,7 +1902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1918,14 +1911,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -1936,7 +1929,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -1979,9 +1972,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.26" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64", "bytes", @@ -2013,7 +2006,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] @@ -2024,7 +2017,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -2032,33 +2025,29 @@ dependencies = [ [[package]] name = "rmp" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" dependencies = [ - "byteorder", "num-traits", - "paste", ] [[package]] name = "rmp-serde" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" dependencies = [ - "byteorder", "rmp", "serde", ] [[package]] name = "rmpv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58450723cd9ee93273ce44a20b6ec4efe17f8ed2e3631474387bfdecf18bb2a9" +checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417" dependencies = [ - "num-traits", "rmp", ] @@ -2083,9 +2072,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", @@ -2096,9 +2085,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "once_cell", @@ -2111,9 +2100,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2132,9 +2121,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -2142,9 +2131,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -2172,9 +2161,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "schannel" @@ -2261,20 +2250,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -2321,7 +2310,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2352,10 +2341,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -2373,9 +2363,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -2385,9 +2375,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -2429,9 +2419,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -2455,7 +2445,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2469,14 +2459,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.2", + "rustix 1.1.3", "windows-sys 0.61.2", ] @@ -2491,11 +2481,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -2506,18 +2496,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2556,9 +2546,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -2578,7 +2568,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2593,9 +2583,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -2604,9 +2594,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -2647,14 +2637,14 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -2715,7 +2705,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2775,7 +2765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2822,9 +2812,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -2894,18 +2884,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -2916,11 +2906,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -2929,9 +2920,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2939,31 +2930,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -2985,14 +2976,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -3182,9 +3173,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "writeable" @@ -3211,28 +3202,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3252,7 +3243,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure", ] @@ -3292,9 +3283,15 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" + [[package]] name = "zstd" version = "0.13.3" diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index f535588..6b22109 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -120,12 +120,11 @@ nix,https://github.com/nix-rust/nix,MIT,The nix-rust Project Developers nu-ansi-term,https://github.com/nushell/nu-ansi-term,MIT,"ogham@bsago.me, Ryan Scheel (Havvy) , Josh Triplett , The Nushell Project Developers" num-traits,https://github.com/rust-num/num-traits,MIT OR Apache-2.0,The Rust Project Developers once_cell,https://github.com/matklad/once_cell,MIT OR Apache-2.0,Aleksey Kladov -openssl-probe,https://github.com/alexcrichton/openssl-probe,MIT OR Apache-2.0,Alex Crichton +openssl-probe,https://github.com/rustls/openssl-probe,MIT OR Apache-2.0,Alex Crichton ordered-float,https://github.com/reem/rust-ordered-float,MIT,"Jonathan Reem , Matt Brubeck " parking,https://github.com/smol-rs/parking,Apache-2.0 OR MIT,"Stjepan Glavina , The Rust Project Developers" parking_lot,https://github.com/Amanieu/parking_lot,MIT OR Apache-2.0,Amanieu d'Antras parking_lot_core,https://github.com/Amanieu/parking_lot,MIT OR Apache-2.0,Amanieu d'Antras -paste,https://github.com/dtolnay/paste,MIT OR Apache-2.0,David Tolnay path-tree,https://github.com/viz-rs/path-tree,MIT OR Apache-2.0,Fangdun Tsai percent-encoding,https://github.com/servo/rust-url,MIT OR Apache-2.0,The rust-url developers petgraph,https://github.com/petgraph/petgraph,MIT OR Apache-2.0,"bluss, mitchmindtree" @@ -162,7 +161,7 @@ regex-automata,https://github.com/rust-lang/regex,MIT OR Apache-2.0,"The Rust Pr regex-syntax,https://github.com/rust-lang/regex,MIT OR Apache-2.0,"The Rust Project Developers, Andrew Gallant " reqwest,https://github.com/seanmonstar/reqwest,MIT OR Apache-2.0,Sean McArthur ring,https://github.com/briansmith/ring,Apache-2.0 AND ISC,The ring Authors -rmp,https://github.com/3Hren/msgpack-rust,MIT,Evgeny Safronov +rmp,https://github.com/3Hren/msgpack-rust,MIT,"Evgeny Safronov , Kornel " rmp-serde,https://github.com/3Hren/msgpack-rust,MIT,Evgeny Safronov rmpv,https://github.com/3Hren/msgpack-rust,MIT,Evgeny Safronov rustc-hash,https://github.com/rust-lang/rustc-hash,Apache-2.0 OR MIT,The Rust Project Developers @@ -285,6 +284,7 @@ zeroize,https://github.com/RustCrypto/utils,Apache-2.0 OR MIT,The RustCrypto Pro zerotrie,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers zerovec,https://github.com/unicode-org/icu4x,Unicode-3.0,The ICU4X Project Developers zerovec-derive,https://github.com/unicode-org/icu4x,Unicode-3.0,Manish Goregaokar +zmij,https://github.com/dtolnay/zmij,MIT,David Tolnay zstd,https://github.com/gyscos/zstd-rs,MIT,Alexandre Bury zstd-safe,https://github.com/gyscos/zstd-rs,MIT OR Apache-2.0,Alexandre Bury zstd-sys,https://github.com/gyscos/zstd-rs,MIT OR Apache-2.0,Alexandre Bury diff --git a/crates/datadog-serverless-compat/src/main.rs b/crates/datadog-serverless-compat/src/main.rs index f763627..5d7a6db 100644 --- a/crates/datadog-serverless-compat/src/main.rs +++ b/crates/datadog-serverless-compat/src/main.rs @@ -65,10 +65,36 @@ pub async fn main() { }; let dd_api_key: Option = env::var("DD_API_KEY").ok(); - let dd_dogstatsd_port: u16 = env::var("DD_DOGSTATSD_PORT") - .ok() - .and_then(|port| port.parse::().ok()) - .unwrap_or(DEFAULT_DOGSTATSD_PORT); + + // Windows named pipe name for DogStatsD. + // Normalize by adding \\.\pipe\ prefix if not present + let dd_dogstatsd_windows_pipe_name: Option = { + #[cfg(windows)] + { + env::var("DD_DOGSTATSD_WINDOWS_PIPE_NAME") + .ok() + .map(|pipe_name| { + if pipe_name.starts_with("\\\\.\\pipe\\") || pipe_name.starts_with(r"\\.\pipe\") + { + pipe_name + } else { + format!(r"\\.\pipe\{}", pipe_name) + } + }) + } + #[cfg(not(windows))] + { + None + } + }; + let dd_dogstatsd_port: u16 = if dd_dogstatsd_windows_pipe_name.is_some() { + 0 // Override to 0 when using Windows named pipe + } else { + env::var("DD_DOGSTATSD_PORT") + .ok() + .and_then(|port| port.parse::().ok()) + .unwrap_or(DEFAULT_DOGSTATSD_PORT) + }; let dd_site = env::var("DD_SITE").unwrap_or_else(|_| "datadoghq.com".to_string()); let dd_use_dogstatsd = env::var("DD_USE_DOGSTATSD") .map(|val| val.to_lowercase() != "false") @@ -152,9 +178,14 @@ pub async fn main() { https_proxy, dogstatsd_tags, dd_statsd_metric_namespace, + dd_dogstatsd_windows_pipe_name.clone(), ) .await; - info!("dogstatsd-udp: starting to listen on port {dd_dogstatsd_port}"); + if let Some(ref windows_pipe_name) = dd_dogstatsd_windows_pipe_name { + info!("dogstatsd-pipe: starting to listen on pipe {windows_pipe_name}"); + } else { + info!("dogstatsd-udp: starting to listen on port {dd_dogstatsd_port}"); + } (metrics_flusher, Some(aggregator_handle)) } else { info!("dogstatsd disabled"); @@ -181,6 +212,7 @@ async fn start_dogstatsd( https_proxy: Option, dogstatsd_tags: &str, metric_namespace: Option, + windows_pipe_name: Option, ) -> (CancellationToken, Option, AggregatorHandle) { // 1. Create the aggregator service #[allow(clippy::expect_used)] @@ -197,6 +229,7 @@ async fn start_dogstatsd( host: AGENT_HOST.to_string(), port, metric_namespace, + windows_pipe_name, }; let dogstatsd_cancel_token = tokio_util::sync::CancellationToken::new(); diff --git a/crates/datadog-trace-agent/src/config.rs b/crates/datadog-trace-agent/src/config.rs index 574e373..db40522 100644 --- a/crates/datadog-trace-agent/src/config.rs +++ b/crates/datadog-trace-agent/src/config.rs @@ -83,6 +83,7 @@ pub struct Config { pub dd_apm_receiver_port: u16, pub dd_apm_windows_pipe_name: Option, pub dd_dogstatsd_port: u16, + pub dd_dogstatsd_windows_pipe_name: Option, pub env_type: trace_utils::EnvironmentType, pub app_name: Option, pub max_request_content_length: usize, @@ -118,11 +119,25 @@ impl Config { anyhow::anyhow!("Unable to identify environment. Shutting down Mini Agent.") })?; - let dd_apm_windows_pipe_name: Option = - env::var("DD_APM_WINDOWS_PIPE_NAME").ok().map(|pipe_name| { - // Prepend \\.\pipe\ prefix to match datadog-agent behavior - format!(r"\\.\pipe\{}", pipe_name) - }); + // Windows named pipe name for APM receiver. + // Normalize by adding \\.\pipe\ prefix if not present + let dd_apm_windows_pipe_name: Option = { + #[cfg(any(windows, test))] + { + env::var("DD_APM_WINDOWS_PIPE_NAME").ok().map(|pipe_name| { + if pipe_name.starts_with("\\\\.\\pipe\\") || pipe_name.starts_with(r"\\.\pipe\") + { + pipe_name + } else { + format!(r"\\.\pipe\{}", pipe_name) + } + }) + } + #[cfg(not(any(windows, test)))] + { + None + } + }; let dd_apm_receiver_port: u16 = if dd_apm_windows_pipe_name.is_some() { 0 // Override to 0 when using Windows named pipe } else { @@ -132,10 +147,35 @@ impl Config { .unwrap_or(DEFAULT_APM_RECEIVER_PORT) }; - let dd_dogstatsd_port: u16 = env::var("DD_DOGSTATSD_PORT") - .ok() - .and_then(|port| port.parse::().ok()) - .unwrap_or(DEFAULT_DOGSTATSD_PORT); + let dd_dogstatsd_windows_pipe_name: Option = { + #[cfg(any(windows, test))] + { + env::var("DD_DOGSTATSD_WINDOWS_PIPE_NAME") + .ok() + .map(|pipe_name| { + if pipe_name.starts_with("\\\\.\\pipe\\") + || pipe_name.starts_with(r"\\.\pipe\") + { + pipe_name + } else { + format!(r"\\.\pipe\{}", pipe_name) + } + }) + } + #[cfg(not(any(windows, test)))] + { + None + } + }; + let dd_dogstatsd_port: u16 = if dd_dogstatsd_windows_pipe_name.is_some() { + 0 // Override to 0 when using Windows named pipe + } else { + env::var("DD_DOGSTATSD_PORT") + .ok() + .and_then(|port| port.parse::().ok()) + .unwrap_or(DEFAULT_DOGSTATSD_PORT) + }; + let dd_site = env::var("DD_SITE").unwrap_or_else(|_| "datadoghq.com".to_string()); // construct the trace & trace stats intake urls based on DD_SITE env var (to flush traces & @@ -185,6 +225,7 @@ impl Config { dd_apm_receiver_port, dd_apm_windows_pipe_name, dd_dogstatsd_port, + dd_dogstatsd_windows_pipe_name, dd_site, trace_intake: Endpoint { url: hyper::Uri::from_str(&trace_intake_url).unwrap(), @@ -337,51 +378,73 @@ mod tests { #[test] #[serial] - fn test_default_dogstatsd_port() { + fn test_apm_windows_pipe_name() { env::set_var("DD_API_KEY", "_not_a_real_key_"); env::set_var("ASCSVCRT_SPRING__APPLICATION__NAME", "test-spring-app"); + env::set_var("DD_APM_WINDOWS_PIPE_NAME", r"test_pipe"); let config_res = config::Config::new(); assert!(config_res.is_ok()); let config = config_res.unwrap(); - assert_eq!(config.dd_dogstatsd_port, 8125); + assert_eq!( + config.dd_apm_windows_pipe_name, + Some(r"\\.\pipe\test_pipe".to_string()) + ); + + // Port should be overridden to 0 when pipe is set + assert_eq!(config.dd_apm_receiver_port, 0); env::remove_var("DD_API_KEY"); env::remove_var("ASCSVCRT_SPRING__APPLICATION__NAME"); + env::remove_var("DD_APM_WINDOWS_PIPE_NAME"); } #[test] #[serial] - fn test_custom_dogstatsd_port() { + fn test_dogstatsd_windows_pipe_name() { env::set_var("DD_API_KEY", "_not_a_real_key_"); env::set_var("ASCSVCRT_SPRING__APPLICATION__NAME", "test-spring-app"); - env::set_var("DD_DOGSTATSD_PORT", "18125"); + env::set_var("DD_DOGSTATSD_WINDOWS_PIPE_NAME", r"test_pipe"); let config_res = config::Config::new(); - println!("{:?}", config_res); assert!(config_res.is_ok()); let config = config_res.unwrap(); - assert_eq!(config.dd_dogstatsd_port, 18125); + assert_eq!( + config.dd_dogstatsd_windows_pipe_name, + Some(r"\\.\pipe\test_pipe".to_string()) + ); + + // Port should be overridden to 0 when pipe is set + assert_eq!(config.dd_dogstatsd_port, 0); env::remove_var("DD_API_KEY"); env::remove_var("ASCSVCRT_SPRING__APPLICATION__NAME"); - env::remove_var("DD_DOGSTATSD_PORT"); + env::remove_var("DD_DOGSTATSD_WINDOWS_PIPE_NAME"); } #[test] #[serial] - fn test_apm_windows_pipe_name() { + fn test_default_dogstatsd_port() { env::set_var("DD_API_KEY", "_not_a_real_key_"); env::set_var("ASCSVCRT_SPRING__APPLICATION__NAME", "test-spring-app"); - env::set_var("DD_APM_WINDOWS_PIPE_NAME", r"test_pipe"); let config_res = config::Config::new(); assert!(config_res.is_ok()); let config = config_res.unwrap(); - assert_eq!( - config.dd_apm_windows_pipe_name, - Some(r"\\.\pipe\test_pipe".to_string()) - ); - // Port should be overridden to 0 when pipe is set - assert_eq!(config.dd_apm_receiver_port, 0); + assert_eq!(config.dd_dogstatsd_port, 8125); env::remove_var("DD_API_KEY"); env::remove_var("ASCSVCRT_SPRING__APPLICATION__NAME"); - env::remove_var("DD_APM_WINDOWS_PIPE_NAME"); + } + + #[test] + #[serial] + fn test_custom_dogstatsd_port() { + env::set_var("DD_API_KEY", "_not_a_real_key_"); + env::set_var("ASCSVCRT_SPRING__APPLICATION__NAME", "test-spring-app"); + env::set_var("DD_DOGSTATSD_PORT", "18125"); + let config_res = config::Config::new(); + println!("{:?}", config_res); + assert!(config_res.is_ok()); + let config = config_res.unwrap(); + assert_eq!(config.dd_dogstatsd_port, 18125); + env::remove_var("DD_API_KEY"); + env::remove_var("ASCSVCRT_SPRING__APPLICATION__NAME"); + env::remove_var("DD_DOGSTATSD_PORT"); } #[test] diff --git a/crates/datadog-trace-agent/src/trace_processor.rs b/crates/datadog-trace-agent/src/trace_processor.rs index 87550fc..f9d796c 100644 --- a/crates/datadog-trace-agent/src/trace_processor.rs +++ b/crates/datadog-trace-agent/src/trace_processor.rs @@ -206,6 +206,7 @@ mod tests { dd_apm_receiver_port: 8126, dd_apm_windows_pipe_name: None, dd_dogstatsd_port: 8125, + dd_dogstatsd_windows_pipe_name: None, env_type: trace_utils::EnvironmentType::CloudFunction, os: "linux".to_string(), obfuscation_config: ObfuscationConfig::new().unwrap(), diff --git a/crates/datadog-trace-agent/tests/integration_test.rs b/crates/datadog-trace-agent/tests/integration_test.rs index eacb64d..62d632e 100644 --- a/crates/datadog-trace-agent/tests/integration_test.rs +++ b/crates/datadog-trace-agent/tests/integration_test.rs @@ -26,6 +26,7 @@ pub fn create_tcp_test_config() -> Config { dd_apm_receiver_port: 8126, dd_apm_windows_pipe_name: None, dd_dogstatsd_port: 8125, + dd_dogstatsd_windows_pipe_name: None, env_type: trace_utils::EnvironmentType::AzureFunction, app_name: Some("test-app".to_string()), max_request_content_length: 10_000_000, diff --git a/crates/dogstatsd/Cargo.toml b/crates/dogstatsd/Cargo.toml index 2b36710..4ec6e09 100644 --- a/crates/dogstatsd/Cargo.toml +++ b/crates/dogstatsd/Cargo.toml @@ -19,7 +19,7 @@ reqwest = { version = "0.12.4", features = ["json", "http2"], default-features = serde = { version = "1.0.197", default-features = false, features = ["derive"] } serde_json = { version = "1.0.116", default-features = false, features = ["alloc"] } thiserror = { version = "1.0.58", default-features = false } -tokio = { version = "1.37.0", default-features = false, features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.37.0", default-features = false, features = ["macros", "rt-multi-thread", "net", "io-util"] } tokio-util = { version = "0.7.11", default-features = false } tracing = { version = "0.1.40", default-features = false } regex = { version = "1.10.6", default-features = false } diff --git a/crates/dogstatsd/src/dogstatsd.rs b/crates/dogstatsd/src/dogstatsd.rs index 6f81479..a7faa2c 100644 --- a/crates/dogstatsd/src/dogstatsd.rs +++ b/crates/dogstatsd/src/dogstatsd.rs @@ -1,6 +1,12 @@ // Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 +//! DogStatsD server implementation for receiving and processing metrics. +//! +//! This module implements a DogStatsD-compatible server that receives metric data from multiple +//! transport mechanisms (UDP sockets, Windows named pipes), parses the metrics, applies optional +//! namespacing, and forwards them to an aggregator for batching and shipping to Datadog. + use std::net::SocketAddr; use std::str::Split; @@ -9,68 +15,173 @@ use crate::errors::ParseError::UnsupportedType; use crate::metric::{id, parse, Metric}; use tracing::{debug, error, trace}; -pub struct DogStatsD { - cancel_token: tokio_util::sync::CancellationToken, - aggregator_handle: AggregatorHandle, - buffer_reader: BufferReader, - metric_namespace: Option, -} - +// Windows-specific imports +#[cfg(windows)] +use { + std::sync::Arc, + tokio::io::AsyncReadExt, + tokio::net::windows::named_pipe::{ClientOptions, ServerOptions}, +}; + +// DogStatsD buffer size for receiving metrics +// TODO(astuyve) buf should be dynamic +// Max buffer size is configurable in Go Agent with a default of 8KB +// https://github.com/DataDog/datadog-agent/blob/85939a62b5580b2a15549f6936f257e61c5aa153/pkg/config/config_template.yaml#L2154-L2158 +const BUFFER_SIZE: usize = 8192; + +/// Configuration for the DogStatsD server pub struct DogStatsDConfig { + /// Host to bind UDP socket to (e.g., "127.0.0.1") pub host: String, + /// Port to bind UDP socket to (e.g., 8125), will be 0 if we're using a Named Pipe pub port: u16, + /// Optional namespace to prepend to all metric names (e.g., "myapp") pub metric_namespace: Option, + /// Optional Windows named pipe name. (e.g., "\\\\.\\pipe\\my_pipe"). + pub windows_pipe_name: Option, +} + +/// Represents the source of a DogStatsD message. Varies by transport method. +#[derive(Debug, Clone)] +pub enum MessageSource { + /// Message received from a network socket (UDP) + Network(SocketAddr), + /// Message received from a Windows named pipe (Arc for efficient cloning) + #[cfg(windows)] + NamedPipe(Arc), +} + +impl std::fmt::Display for MessageSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Network(addr) => write!(f, "{}", addr), + #[cfg(windows)] + Self::NamedPipe(name) => write!(f, "{}", name), + } + } } +// BufferReader abstracts transport methods for metric data. enum BufferReader { - UdpSocketReader(tokio::net::UdpSocket), + /// UDP socket reader (cross-platform, default transport) + UdpSocket(tokio::net::UdpSocket), + + /// Mirror reader for testing - replays a fixed buffer #[allow(dead_code)] - MirrorReader(Vec, SocketAddr), + MirrorTest(Vec, SocketAddr), + + /// Windows named pipe reader (Windows-only transport) + #[cfg(windows)] + NamedPipe { + pipe_name: Arc, + receiver: Arc>>>, + }, } impl BufferReader { - async fn read(&self) -> std::io::Result<(Vec, SocketAddr)> { + /// This is the main entry point for receiving metric data. + /// Note: Different transports have different blocking behaviors. + async fn read(&self) -> std::io::Result<(Vec, MessageSource)> { match self { - BufferReader::UdpSocketReader(socket) => { - // TODO(astuyve) this should be dynamic - // Max buffer size is configurable in Go Agent and the default is 8KB - // https://github.com/DataDog/datadog-agent/blob/85939a62b5580b2a15549f6936f257e61c5aa153/pkg/config/config_template.yaml#L2154-L2158 - let mut buf = [0; 8192]; + BufferReader::UdpSocket(socket) => { + // UDP socket: blocks until a packet arrives + let mut buf = [0; BUFFER_SIZE]; #[allow(clippy::expect_used)] let (amt, src) = socket .recv_from(&mut buf) .await .expect("didn't receive data"); - Ok((buf[..amt].to_owned(), src)) + Ok((buf[..amt].to_owned(), MessageSource::Network(src))) + } + BufferReader::MirrorTest(data, socket) => { + // Mirror Reader: returns immediately with stored data + Ok((data.clone(), MessageSource::Network(*socket))) + } + #[cfg(windows)] + BufferReader::NamedPipe { + pipe_name, + receiver, + } => { + // Named Pipe Reader: receives data from client handler tasks + match receiver.lock().await.recv().await { + Some(data) => Ok((data, MessageSource::NamedPipe(pipe_name.clone()))), + None => { + // Channel closed - server exited, already triggered cancellation + Ok((Vec::new(), MessageSource::NamedPipe(pipe_name.clone()))) + } + } } - BufferReader::MirrorReader(data, socket) => Ok((data.clone(), *socket)), } } } +/// DogStatsD server to receive, parse, and forward metrics. +pub struct DogStatsD { + cancel_token: tokio_util::sync::CancellationToken, + aggregator_handle: AggregatorHandle, + buffer_reader: BufferReader, + metric_namespace: Option, +} + impl DogStatsD { + /// Creates a new DogStatsD server instance. + /// + /// The server will bind to either a UDP socket or Windows named pipe based on the config. + /// Metrics received will be forwarded to the provided aggregator_handle. #[must_use] pub async fn new( config: &DogStatsDConfig, aggregator_handle: AggregatorHandle, cancel_token: tokio_util::sync::CancellationToken, ) -> DogStatsD { - let addr = format!("{}:{}", config.host, config.port); + #[allow(unused_variables)] // pipe_name unused on non-Windows + let buffer_reader = if let Some(ref pipe_name) = config.windows_pipe_name { + #[cfg(windows)] + { + let pipe_name = Arc::new(pipe_name.clone()); + + // Create channel for receiving data from client handlers + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + let receiver = Arc::new(tokio::sync::Mutex::new(receiver)); + + // Spawn server accept loop + let server_pipe_name = pipe_name.clone(); + let server_cancel = cancel_token.clone(); + tokio::spawn(async move { + run_named_pipe_server(server_pipe_name, sender, server_cancel).await; + }); + + BufferReader::NamedPipe { + pipe_name, + receiver, + } + } + #[cfg(not(windows))] + #[allow(clippy::panic)] + { + panic!("Named pipes are only supported on Windows.") + } + } else { + // UDP socket for all platforms + let addr = format!("{}:{}", config.host, config.port); + // TODO (UDS socket) + #[allow(clippy::expect_used)] + let socket = tokio::net::UdpSocket::bind(addr) + .await + .expect("couldn't bind to address"); + BufferReader::UdpSocket(socket) + }; - // TODO (UDS socket) - #[allow(clippy::expect_used)] - let socket = tokio::net::UdpSocket::bind(addr) - .await - .expect("couldn't bind to address"); DogStatsD { cancel_token, aggregator_handle, - buffer_reader: BufferReader::UdpSocketReader(socket), + buffer_reader, metric_namespace: config.metric_namespace.clone(), } } + /// Main event loop that continuously receives and processes metrics. pub async fn spin(self) { let mut spin_cancelled = false; while !spin_cancelled { @@ -79,6 +190,7 @@ impl DogStatsD { } } + /// Receive one batch of metrics from the transport layer and process them. async fn consume_statsd(&self) { #[allow(clippy::expect_used)] let (buf, src) = self @@ -87,6 +199,12 @@ impl DogStatsD { .await .expect("didn't receive data"); + // Skip empty buffers (e.g., from channel close) + if buf.is_empty() { + debug!("Received empty buffer from {}, skipping", src); + return; + } + #[allow(clippy::expect_used)] let msgs = std::str::from_utf8(&buf).expect("couldn't parse as string"); trace!("Received message: {} from {}", msgs, src); @@ -142,6 +260,122 @@ impl DogStatsD { } } +/// Named Pipe server - accepts client connections and forwards metrics. +/// +/// Uses a multi-instance approach (like winio in the main agent): +/// - Creates new server instance for each client +/// - Spawns task to handle each client +#[cfg(windows)] +async fn run_named_pipe_server( + pipe_name: Arc, + sender: tokio::sync::mpsc::UnboundedSender>, + cancel_token: tokio_util::sync::CancellationToken, +) { + loop { + if cancel_token.is_cancelled() { + break; + } + + // Create new named pipe server + let server = match ServerOptions::new().create(&*pipe_name) { + Ok(s) => { + debug!("Created pipe server instance '{}' in byte mode", pipe_name); + s + } + Err(e) => { + error!("Failed to create pipe '{}': {}", pipe_name, e); + tokio::select! { + _ = tokio::time::sleep(tokio::time::Duration::from_secs(1)) => continue, + _ = cancel_token.cancelled() => break, + } + } + }; + + // Wait for client connection (cancellable) + let connect_result = tokio::select! { + result = server.connect() => result, + _ = cancel_token.cancelled() => break, + }; + + if let Err(e) = connect_result { + error!("Connection failed on '{}': {}", pipe_name, e); + continue; + } + + debug!("Client connected to '{}'", pipe_name); + + // Spawn task to handle this client + let sender_clone = sender.clone(); + let cancel_clone = cancel_token.clone(); + let pipe_name_clone = pipe_name.clone(); + tokio::spawn(async move { + let mut buf = [0u8; BUFFER_SIZE]; + let mut server = server; + // Byte mode requires manual buffering to handle messages split across reads + // so let's track where we're writing each read in the buffer + let mut start_write_index = 0; + + loop { + let read_result = tokio::select! { + result = server.read(&mut buf[start_write_index..]) => result, + _ = cancel_clone.cancelled() => break, + }; + + let bytes_read = match read_result { + Err(e) => { + error!("Read error on '{}': {}", pipe_name_clone, e); + break; + } + Ok(0) => { + debug!("Client disconnected from '{}'", pipe_name_clone); + break; + } + Ok(n) => n, + }; + + let end_index = start_write_index + bytes_read; + + // From the start of the buffer to the end of the last complete message + let complete_message_size = buf[..end_index] + .iter() + .rposition(|&b| b == b'\n') + .map(|pos| pos + 1) // \n is part of that last message, so +1 + .unwrap_or(0); + + if complete_message_size > 0 { + // Send complete messages + match sender_clone.send(buf[..complete_message_size].to_vec()) { + Err(e) => { + error!("Failed to send data from '{}': {}", pipe_name_clone, e); + break; + } + Ok(_) => { + if let Ok(_) = std::str::from_utf8(&buf[..complete_message_size]) { + debug!( + "Sent {} bytes from '{}'", + complete_message_size, pipe_name_clone + ); + } + } + } + } + + // Complete message has been sent, so we can write over it. + start_write_index = end_index - complete_message_size; + + // If the message is bigger than the buffer size, drop it and go on. + if start_write_index >= BUFFER_SIZE { + start_write_index = 0; + } else if start_write_index > 0 { + // Keep incomplete data in the buffer + buf.copy_within(complete_message_size..end_index, 0); + } + } + // Server instance is dropped here, automatically cleaned up + }); + } +} + #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { @@ -249,7 +483,7 @@ single_machine_performance.rouster.metrics_max_timestamp_latency:1376.90870216|d let dogstatsd = DogStatsD { cancel_token, aggregator_handle: handle.clone(), - buffer_reader: BufferReader::MirrorReader( + buffer_reader: BufferReader::MirrorTest( statsd_string.as_bytes().to_vec(), SocketAddr::new(IpAddr::V4(Ipv4Addr::new(111, 112, 113, 114)), 0), ), diff --git a/crates/dogstatsd/tests/integration_test.rs b/crates/dogstatsd/tests/integration_test.rs index 65d919c..443c72e 100644 --- a/crates/dogstatsd/tests/integration_test.rs +++ b/crates/dogstatsd/tests/integration_test.rs @@ -20,6 +20,9 @@ use tokio::{ use tokio_util::sync::CancellationToken; use zstd::zstd_safe::CompressionLevel; +#[cfg(windows)] +use tokio::{io::AsyncWriteExt, net::windows::named_pipe::ClientOptions}; + #[cfg(test)] #[tokio::test] async fn dogstatsd_server_ships_series() { @@ -97,6 +100,7 @@ async fn start_dogstatsd(aggregator_handle: AggregatorHandle) -> CancellationTok host: "127.0.0.1".to_string(), port: 18125, metric_namespace: None, + windows_pipe_name: None, }; let dogstatsd_cancel_token = tokio_util::sync::CancellationToken::new(); let dogstatsd_client = DogStatsD::new( @@ -280,3 +284,240 @@ async fn test_send_with_retry_immediate_failure_after_one_attempt() { // Verify that the mock was called exactly once mock.assert_async().await; } + +#[cfg(test)] +#[cfg(windows)] +#[tokio::test] +async fn test_named_pipe_basic_communication() { + let pipe_name = r"\\.\pipe\test_dogstatsd_basic"; + let (service, handle) = AggregatorService::new(SortedTags::parse("test:value").unwrap(), 1_024) + .expect("aggregator service creation failed"); + tokio::spawn(service.run()); + + let cancel_token = CancellationToken::new(); + + // Start DogStatsD server + let dogstatsd_task = { + let handle = handle.clone(); + let cancel_token = cancel_token.clone(); + tokio::spawn(async move { + let dogstatsd = DogStatsD::new( + &DogStatsDConfig { + host: String::new(), + port: 0, + metric_namespace: None, + windows_pipe_name: Some(pipe_name.to_string()), + }, + handle, + cancel_token, + ) + .await; + dogstatsd.spin().await; + }) + }; + + sleep(Duration::from_millis(100)).await; + + // Connect client and send metric + let mut client = ClientOptions::new().open(pipe_name).expect("client open"); + client + .write_all(b"test.metric:42|c\n") + .await + .expect("write failed"); + client.flush().await.expect("flush failed"); + + sleep(Duration::from_millis(100)).await; + + // Verify metric was received + let response = handle.flush().await.expect("flush failed"); + assert_eq!(response.series.len(), 1); + + // Cleanup + cancel_token.cancel(); + let result = timeout(Duration::from_millis(500), dogstatsd_task).await; + assert!(result.is_ok(), "task should complete after cancellation"); + handle.shutdown().expect("shutdown failed"); +} + +#[cfg(test)] +#[cfg(windows)] +#[tokio::test] +async fn test_named_pipe_disconnect_reconnect() { + let pipe_name = r"\\.\pipe\test_dogstatsd_reconnect"; + let (service, handle) = AggregatorService::new(SortedTags::parse("test:value").unwrap(), 1_024) + .expect("aggregator service creation failed"); + tokio::spawn(service.run()); + + let cancel_token = CancellationToken::new(); + + // Start DogStatsD server + let dogstatsd_task = { + let handle = handle.clone(); + let cancel_token_clone = cancel_token.clone(); + tokio::spawn(async move { + let dogstatsd = DogStatsD::new( + &DogStatsDConfig { + host: String::new(), + port: 0, + metric_namespace: None, + windows_pipe_name: Some(pipe_name.to_string()), + }, + handle, + cancel_token_clone, + ) + .await; + dogstatsd.spin().await; + }) + }; + + sleep(Duration::from_millis(100)).await; + + // First client - connect, send, disconnect + { + let mut client1 = ClientOptions::new().open(pipe_name).expect("client1 open"); + client1 + .write_all(b"test.metric:1|c\n") + .await + .expect("write1"); + client1.flush().await.expect("flush1"); + } // client1 drops here (disconnect) + + sleep(Duration::from_millis(100)).await; + + // Second client - connect and send (creates new pipe each time) + let mut client2 = ClientOptions::new().open(pipe_name).expect("client2 open"); + client2 + .write_all(b"test.metric:2|c\n") + .await + .expect("write2"); + client2.flush().await.expect("flush2"); + + sleep(Duration::from_millis(100)).await; + + // Verify both metrics received and aggregated + let response = handle.flush().await.expect("flush failed"); + assert!( + !response.series.is_empty(), + "Expected at least one series with metrics" + ); + + // Cleanup + cancel_token.cancel(); + let result = timeout(Duration::from_millis(500), dogstatsd_task).await; + assert!(result.is_ok(), "tasks should complete after cancellation"); + handle.shutdown().expect("shutdown failed"); +} + +#[cfg(test)] +#[cfg(windows)] +#[tokio::test] +async fn test_named_pipe_cancellation() { + let pipe_name = r"\\.\pipe\test_dogstatsd_cancel"; + let (service, handle) = AggregatorService::new(SortedTags::parse("test:value").unwrap(), 1_024) + .expect("aggregator service creation failed"); + tokio::spawn(service.run()); + + let cancel_token = CancellationToken::new(); + + // Start DogStatsD server + let dogstatsd_task = { + let handle = handle.clone(); + let cancel_token_clone = cancel_token.clone(); + tokio::spawn(async move { + let dogstatsd = DogStatsD::new( + &DogStatsDConfig { + host: String::new(), + port: 0, + metric_namespace: None, + windows_pipe_name: Some(pipe_name.to_string()), + }, + handle, + cancel_token_clone, + ) + .await; + dogstatsd.spin().await; + }) + }; + + sleep(Duration::from_millis(100)).await; + + // Cancel immediately + cancel_token.cancel(); + + // Task should complete quickly + let result = timeout(Duration::from_millis(500), dogstatsd_task).await; + assert!(result.is_ok(), "task should complete after cancellation"); + + handle.shutdown().expect("shutdown failed"); +} + +#[cfg(test)] +#[cfg(windows)] +#[tokio::test] +async fn test_buffer_split_message() { + let pipe_name = r"\\.\pipe\test_dogstatsd_buffer_split"; + let (service, handle) = AggregatorService::new(SortedTags::parse("test:value").unwrap(), 1_024) + .expect("aggregator service creation failed"); + tokio::spawn(service.run()); + + let cancel_token = CancellationToken::new(); + + // Start DogStatsD server + let dogstatsd_task = { + let handle = handle.clone(); + let cancel_token_clone = cancel_token.clone(); + tokio::spawn(async move { + let dogstatsd = DogStatsD::new( + &DogStatsDConfig { + host: String::new(), + port: 0, + metric_namespace: None, + windows_pipe_name: Some(pipe_name.to_string()), + }, + handle, + cancel_token_clone, + ) + .await; + dogstatsd.spin().await; + }) + }; + + sleep(Duration::from_millis(100)).await; + + // Connect client and send partial message (no newline) + let mut client = ClientOptions::new().open(pipe_name).expect("client open"); + client + .write_all(b"test.split:1|") + .await + .expect("write partial"); + client.flush().await.expect("flush partial"); + + // Wait briefly to simulate message arriving in separate reads + sleep(Duration::from_millis(50)).await; + + // Verify no metrics yet - buffer should be holding incomplete message + let response = handle.flush().await.expect("flush failed"); + assert!( + response.series.is_empty(), + "Expected no series from incomplete message without newline" + ); + + // Send the completion of the message + client.write_all(b"c\n").await.expect("write completion"); + client.flush().await.expect("flush completion"); + + sleep(Duration::from_millis(100)).await; + + // Verify metric was received and aggregated + let response = handle.flush().await.expect("flush failed"); + assert!( + !response.series.is_empty(), + "Expected at least one series with metrics from buffered split message" + ); + + // Cleanup + cancel_token.cancel(); + let result = timeout(Duration::from_millis(500), dogstatsd_task).await; + assert!(result.is_ok(), "tasks should complete after cancellation"); + handle.shutdown().expect("shutdown failed"); +}