diff --git a/Cargo.lock b/Cargo.lock index 43f29c02f..0665e9c9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,9 +47,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ "cipher", "cpubits", @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -149,7 +149,7 @@ dependencies = [ "ndk-context", "ndk-sys", "num_enum", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -160,9 +160,9 @@ checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -274,7 +274,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -285,7 +285,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "synstructure", ] @@ -297,7 +297,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -327,14 +327,14 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", @@ -364,9 +364,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" dependencies = [ "aws-lc-sys", "zeroize", @@ -374,9 +374,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.42.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" dependencies = [ "cc", "cmake", @@ -536,13 +536,13 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.10.2" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -565,9 +565,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bytesize" -version = "2.4.2" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7c8918969267b2932ffd5655509bbbea0833823058c378876953217f5fc50e" +checksum = "7354288c522e7e980fafd2075d63d1285794c3a6a16cdd492f189ea406e5f18b" [[package]] name = "calloop" @@ -612,9 +612,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.66" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", "jobserver", @@ -636,9 +636,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.5" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", "clap_derive", @@ -714,9 +714,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.5" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" dependencies = [ "anstream", "anstyle", @@ -1177,7 +1177,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1188,9 +1188,9 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] name = "data-encoding" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] name = "der" @@ -1236,7 +1236,7 @@ checksum = "59600e2c2d636fde9b65e99cc6445ac770c63d3628195ff39932b8d6d7409903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1253,7 +1253,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1275,7 +1275,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1324,7 +1324,7 @@ dependencies = [ "diplomat_core", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1344,7 +1344,7 @@ dependencies = [ "serde", "smallvec", "strck_ident", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -1365,13 +1365,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -1508,9 +1508,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "elliptic-curve" @@ -1576,9 +1576,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fdeflate" @@ -1609,12 +1609,12 @@ dependencies = [ "embed-resource", "ironrdp", "ironrdp-cliprdr-native", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc-pipe-proxy", "ironrdp-rdcleanpath", "ironrdp-vmconnect", "sspi", - "thiserror 2.0.19", + "thiserror 2.0.20", "tracing", "tracing-subscriber", ] @@ -1627,9 +1627,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "flagset" @@ -1675,13 +1675,13 @@ dependencies = [ [[package]] name = "foreign-types-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -1719,9 +1719,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1734,9 +1734,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1744,15 +1744,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1761,32 +1761,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-timer" @@ -1796,9 +1796,9 @@ checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -1892,9 +1892,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "gloo-net" @@ -1910,7 +1910,7 @@ dependencies = [ "http", "js-sys", "pin-project", - "thiserror 2.0.19", + "thiserror 2.0.20", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -2055,9 +2055,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -2065,9 +2065,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -2075,9 +2075,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -2094,9 +2094,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "subtle", "typenum", @@ -2357,9 +2357,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "iron-remote-desktop" @@ -2375,7 +2375,7 @@ dependencies = [ [[package]] name = "ironrdp" -version = "0.17.0" +version = "0.18.0" dependencies = [ "anyhow", "async-trait", @@ -2386,7 +2386,7 @@ dependencies = [ "ironrdp-cliprdr", "ironrdp-cliprdr-native", "ironrdp-connector", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-echo", @@ -2403,7 +2403,7 @@ dependencies = [ "ironrdp-vmconnect", "opus2", "pico-args", - "rand 0.9.4", + "rand 0.9.5", "sspi", "tokio-rustls", "tracing", @@ -2413,11 +2413,11 @@ dependencies = [ [[package]] name = "ironrdp-acceptor" -version = "0.10.0" +version = "0.11.0" dependencies = [ "ironrdp-async", "ironrdp-connector", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "ironrdp-svc", "tracing", @@ -2434,7 +2434,7 @@ dependencies = [ "ironrdp-cliprdr", "ironrdp-cliprdr-native", "ironrdp-connector", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-daemon", "ironrdp-input", "ironrdp-pdu", @@ -2457,7 +2457,7 @@ dependencies = [ [[package]] name = "ironrdp-agent" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "bytes", @@ -2479,10 +2479,10 @@ dependencies = [ [[package]] name = "ironrdp-ainput" -version = "0.8.0" +version = "0.8.1" dependencies = [ "bitflags 2.13.1", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "num-derive 0.5.1", "num-traits", @@ -2490,11 +2490,11 @@ dependencies = [ [[package]] name = "ironrdp-async" -version = "0.10.0" +version = "0.11.0" dependencies = [ "bytes", "ironrdp-connector", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "tracing", ] @@ -2511,32 +2511,32 @@ dependencies = [ [[package]] name = "ironrdp-blocking" -version = "0.10.0" +version = "0.11.0" dependencies = [ "bytes", "ironrdp-connector", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "tracing", ] [[package]] name = "ironrdp-bulk" -version = "0.1.1" +version = "0.2.0" dependencies = [ "criterion", ] [[package]] name = "ironrdp-cfg" -version = "0.1.0" +version = "0.2.0" dependencies = [ "ironrdp-propertyset", ] [[package]] name = "ironrdp-client" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "futures-util", @@ -2544,7 +2544,7 @@ dependencies = [ "ironrdp-cliprdr", "ironrdp-cliprdr-native", "ironrdp-connector", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-dvc-com-plugin", @@ -2576,10 +2576,10 @@ dependencies = [ [[package]] name = "ironrdp-cliprdr" -version = "0.7.0" +version = "0.7.1" dependencies = [ "bitflags 2.13.1", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "ironrdp-svc", "tracing", @@ -2588,34 +2588,34 @@ dependencies = [ [[package]] name = "ironrdp-cliprdr-format" -version = "0.2.0" +version = "0.2.1" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "png", ] [[package]] name = "ironrdp-cliprdr-native" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ironrdp-cliprdr", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "tracing", "windows", ] [[package]] name = "ironrdp-connector" -version = "0.10.0" +version = "0.11.0" dependencies = [ - "ironrdp-core 0.2.1", - "ironrdp-error 0.2.0", + "ironrdp-core 0.3.0", + "ironrdp-error 0.2.1", "ironrdp-pdu", "ironrdp-svc", "picky", "picky-asn1-der", "picky-asn1-x509", - "rand 0.9.4", + "rand 0.9.5", "sspi", "tracing", "url", @@ -2632,9 +2632,9 @@ dependencies = [ [[package]] name = "ironrdp-core" -version = "0.2.1" +version = "0.3.0" dependencies = [ - "ironrdp-error 0.2.0", + "ironrdp-error 0.2.1", ] [[package]] @@ -2664,9 +2664,9 @@ dependencies = [ [[package]] name = "ironrdp-displaycontrol" -version = "0.8.0" +version = "0.8.1" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "ironrdp-pdu", "ironrdp-svc", @@ -2675,9 +2675,9 @@ dependencies = [ [[package]] name = "ironrdp-dvc" -version = "0.8.0" +version = "0.9.0" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "ironrdp-svc", "tracing", @@ -2685,9 +2685,9 @@ dependencies = [ [[package]] name = "ironrdp-dvc-com-plugin" -version = "0.1.3" +version = "0.1.4" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "ironrdp-pdu", "ironrdp-svc", @@ -2698,10 +2698,10 @@ dependencies = [ [[package]] name = "ironrdp-dvc-pipe-proxy" -version = "0.5.0" +version = "0.5.1" dependencies = [ "async-trait", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "ironrdp-pdu", "ironrdp-svc", @@ -2711,9 +2711,9 @@ dependencies = [ [[package]] name = "ironrdp-echo" -version = "0.4.0" +version = "0.4.1" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "ironrdp-pdu", "tracing", @@ -2721,12 +2721,12 @@ dependencies = [ [[package]] name = "ironrdp-egfx" -version = "0.3.0" +version = "0.3.1" dependencies = [ "arbitrary", "bit_field", "bitflags 2.13.1", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "ironrdp-graphics", "ironrdp-pdu", @@ -2742,11 +2742,11 @@ checksum = "4a9d7794e854eef2f13fdf79c8502bcc567a75a15fd0522885f37739386a4cef" [[package]] name = "ironrdp-error" -version = "0.2.0" +version = "0.2.1" [[package]] name = "ironrdp-futures" -version = "0.8.0" +version = "0.8.1" dependencies = [ "futures-util", "ironrdp-async", @@ -2760,7 +2760,7 @@ dependencies = [ "ironrdp-bulk", "ironrdp-cliprdr", "ironrdp-cliprdr-format", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-egfx", @@ -2773,7 +2773,7 @@ dependencies = [ [[package]] name = "ironrdp-graphics" -version = "0.9.0" +version = "0.10.0" dependencies = [ "bit_field", "bitflags 2.13.1", @@ -2782,7 +2782,7 @@ dependencies = [ "bytemuck", "byteorder", "expect-test", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "num-derive 0.5.1", "num-traits", @@ -2792,7 +2792,7 @@ dependencies = [ [[package]] name = "ironrdp-input" -version = "0.7.0" +version = "0.7.1" dependencies = [ "bitvec", "ironrdp-pdu", @@ -2801,7 +2801,7 @@ dependencies = [ [[package]] name = "ironrdp-mstsgu" -version = "0.0.1" +version = "0.0.2" dependencies = [ "base64", "bitflags 2.13.1", @@ -2809,8 +2809,8 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "ironrdp-core 0.2.1", - "ironrdp-error 0.2.0", + "ironrdp-core 0.3.0", + "ironrdp-error 0.2.1", "ironrdp-tls", "log", "tokio", @@ -2821,14 +2821,14 @@ dependencies = [ [[package]] name = "ironrdp-nscodec" -version = "0.2.0" +version = "0.2.1" dependencies = [ "ironrdp-graphics", ] [[package]] name = "ironrdp-pdu" -version = "0.9.0" +version = "0.10.0" dependencies = [ "arbitrary", "bit_field", @@ -2837,8 +2837,8 @@ dependencies = [ "der-parser", "expect-test", "hmac 0.12.1", - "ironrdp-core 0.2.1", - "ironrdp-error 0.2.0", + "ironrdp-core 0.3.0", + "ironrdp-error 0.2.1", "md-5 0.10.6", "num-bigint 0.4.8", "num-derive 0.5.1", @@ -2862,25 +2862,25 @@ version = "0.1.0" name = "ironrdp-rail" version = "0.1.0" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-svc", ] [[package]] name = "ironrdp-rdcleanpath" -version = "0.2.2" +version = "0.2.3" dependencies = [ "der 0.8.1", ] [[package]] name = "ironrdp-rdpdr" -version = "0.7.0" +version = "0.8.0" dependencies = [ "bitflags 2.13.1", "getrandom 0.3.4", - "ironrdp-core 0.2.1", - "ironrdp-error 0.2.0", + "ironrdp-core 0.3.0", + "ironrdp-error 0.2.1", "ironrdp-pdu", "ironrdp-svc", "tracing", @@ -2888,9 +2888,9 @@ dependencies = [ [[package]] name = "ironrdp-rdpdr-native" -version = "0.7.0" +version = "0.7.1" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "ironrdp-rdpdr", "ironrdp-svc", @@ -2903,7 +2903,7 @@ dependencies = [ name = "ironrdp-rdpeai" version = "0.1.0" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "ironrdp-pdu", "ironrdp-rdpsnd", @@ -2916,7 +2916,7 @@ name = "ironrdp-rdpei" version = "0.1.0" dependencies = [ "bitflags 2.13.1", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "ironrdp-pdu", "ironrdp-svc", @@ -2927,7 +2927,7 @@ dependencies = [ name = "ironrdp-rdpeusb" version = "0.1.0" dependencies = [ - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-dvc", "ironrdp-pdu", "ironrdp-str", @@ -2942,10 +2942,10 @@ dependencies = [ [[package]] name = "ironrdp-rdpsnd" -version = "0.9.0" +version = "0.10.0" dependencies = [ "bitflags 2.13.1", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "ironrdp-svc", "tracing", @@ -2954,12 +2954,12 @@ dependencies = [ [[package]] name = "ironrdp-rdpsnd-native" -version = "0.7.0" +version = "0.7.1" dependencies = [ "anyhow", "bytemuck", "cpal", - "ironrdp-error 0.2.0", + "ironrdp-error 0.2.1", "ironrdp-rdpeai", "ironrdp-rdpsnd", "opus2", @@ -2972,7 +2972,7 @@ name = "ironrdp-rpc" version = "0.1.0" dependencies = [ "anyhow", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-input", "ironrdp-pdu", "ironrdp-propertyset", @@ -2985,7 +2985,7 @@ dependencies = [ [[package]] name = "ironrdp-server" -version = "0.13.0" +version = "0.14.0" dependencies = [ "anyhow", "async-trait", @@ -2994,7 +2994,7 @@ dependencies = [ "ironrdp-ainput", "ironrdp-async", "ironrdp-cliprdr", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-echo", @@ -3006,7 +3006,7 @@ dependencies = [ "ironrdp-svc", "ironrdp-tokio", "qoicoubeh", - "rand 0.9.4", + "rand 0.9.5", "rayon", "rustls-pemfile", "tokio", @@ -3019,13 +3019,13 @@ dependencies = [ [[package]] name = "ironrdp-session" -version = "0.11.0" +version = "0.12.0" dependencies = [ "ironrdp-bulk", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-displaycontrol", "ironrdp-dvc", - "ironrdp-error 0.2.0", + "ironrdp-error 0.2.1", "ironrdp-graphics", "ironrdp-pdu", "ironrdp-rdpei", @@ -3041,18 +3041,18 @@ version = "0.0.0" [[package]] name = "ironrdp-str" -version = "0.1.1" +version = "0.1.2" dependencies = [ "bytemuck", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", ] [[package]] name = "ironrdp-svc" -version = "0.8.0" +version = "0.8.1" dependencies = [ "bitflags 2.13.1", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", ] @@ -3071,12 +3071,12 @@ dependencies = [ "ironrdp-cliprdr", "ironrdp-cliprdr-format", "ironrdp-connector", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-displaycontrol", "ironrdp-dvc", "ironrdp-echo", "ironrdp-egfx", - "ironrdp-error 0.2.0", + "ironrdp-error 0.2.1", "ironrdp-fuzzing", "ironrdp-graphics", "ironrdp-input", @@ -3114,7 +3114,7 @@ dependencies = [ "ironrdp-async", "ironrdp-bulk", "ironrdp-client", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-daemon", "ironrdp-dvc", "ironrdp-dvc-pipe-proxy", @@ -3139,7 +3139,7 @@ dependencies = [ [[package]] name = "ironrdp-tls" -version = "0.2.2" +version = "0.2.3" dependencies = [ "rustls-native-certs", "tokio", @@ -3150,7 +3150,7 @@ dependencies = [ [[package]] name = "ironrdp-tokio" -version = "0.10.0" +version = "0.10.1" dependencies = [ "ironrdp-async", "ironrdp-connector", @@ -3161,7 +3161,7 @@ dependencies = [ [[package]] name = "ironrdp-viewer" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "clap", @@ -3192,7 +3192,7 @@ version = "0.1.0" dependencies = [ "ironrdp-async", "ironrdp-connector", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-pdu", "tracing", ] @@ -3214,7 +3214,7 @@ dependencies = [ "iron-remote-desktop", "ironrdp", "ironrdp-cliprdr-format", - "ironrdp-core 0.2.1", + "ironrdp-core 0.3.0", "ironrdp-futures", "ironrdp-pdu", "ironrdp-propertyset", @@ -3305,7 +3305,7 @@ dependencies = [ "jni-sys 0.4.1", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -3320,7 +3320,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3348,7 +3348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3363,9 +3363,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -3374,9 +3374,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -3417,14 +3417,14 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" dependencies = [ "bitflags 2.13.1", "libc", "plain", - "redox_syscall 0.9.0", + "redox_syscall 0.9.2", ] [[package]] @@ -3569,9 +3569,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "log", @@ -3674,7 +3674,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8e4f1efbc5536f3e43f40e089abc7562b3e0bc48dfee57444558d45c31dee20" dependencies = [ "now-proto-pdu", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", ] @@ -3734,7 +3734,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -3750,9 +3750,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -3785,7 +3785,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4148,19 +4148,19 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openh264" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b2b561d2103303e233779545da757ecd35bad82188d619a6d8901f3007e1ff" +checksum = "fcc9071a0b5f9c501ddd01066c2600d922cc799dc450a52975222bcda7755a08" dependencies = [ "openh264-sys2", - "wide 1.5.0", + "wide 1.6.1", ] [[package]] name = "openh264-sys2" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a8867e48183bbd9147380227448c065fe456eb30b0ebc68929809c36c30985" +checksum = "6ada29a4cadc13d4d326737af87c13e224210d7d99fe47594e9f3f9b0102fd70" dependencies = [ "cc", "libloading", @@ -4191,7 +4191,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4390,7 +4390,7 @@ dependencies = [ "sha1 0.11.0", "sha2 0.11.0", "sha3", - "thiserror 2.0.19", + "thiserror 2.0.20", "x25519-dalek", "zeroize", ] @@ -4459,7 +4459,7 @@ dependencies = [ "rand_core 0.10.1", "serde", "sha1 0.11.0", - "thiserror 2.0.19", + "thiserror 2.0.20", "uuid", ] @@ -4486,7 +4486,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -4611,9 +4611,9 @@ dependencies = [ [[package]] name = "polyval" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" dependencies = [ "cpubits", "cpufeatures 0.3.0", @@ -4626,7 +4626,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" dependencies = [ - "rand 0.8.6", + "rand 0.8.7", ] [[package]] @@ -4706,9 +4706,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -4723,7 +4723,7 @@ dependencies = [ "bit-vec", "bitflags 2.13.1", "num-traits", - "rand 0.9.4", + "rand 0.9.5", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -4755,9 +4755,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" -version = "0.39.4" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] @@ -4776,7 +4776,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.19", + "thiserror 2.0.20", "tokio", "tracing", "web-time", @@ -4798,7 +4798,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.19", + "thiserror 2.0.20", "tinyvec", "tracing", "web-time", @@ -4820,9 +4820,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -4847,9 +4847,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -4858,9 +4858,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -4994,18 +4994,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" +checksum = "f1c93da5bb2c5d4e6c0ef7abeead62c89169a0a4882bfb83ac892f2423aea2fe" dependencies = [ "bitflags 2.13.1", ] [[package]] name = "regex" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -5015,9 +5015,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.15" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -5165,7 +5165,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.118", + "syn 2.0.119", "unicode-ident", ] @@ -5259,9 +5259,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -5296,9 +5296,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", @@ -5306,9 +5306,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" dependencies = [ "aws-lc-rs", "ring", @@ -5351,9 +5351,9 @@ dependencies = [ [[package]] name = "safe_arch" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7caad094bd561859bcd467734a720c3c1f5d1f338995351fefe2190c45efed" +checksum = "3a52ec151f024d703f9fd65abb7cbe81e7cdb39f18917a3a37e3014470dc7c59" dependencies = [ "bytemuck", ] @@ -5455,9 +5455,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -5475,22 +5475,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -5650,15 +5650,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -5718,9 +5718,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -5760,9 +5760,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ "lock_api", ] @@ -5906,9 +5906,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -5943,7 +5943,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -5997,11 +5997,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ - "thiserror-impl 2.0.19", + "thiserror-impl 2.0.20", ] [[package]] @@ -6012,14 +6012,14 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", @@ -6028,18 +6028,18 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "js-sys", @@ -6132,9 +6132,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -6163,14 +6163,14 @@ checksum = "2d2e76690929402faae40aebdda620a2c0e25dd6d3b9afe48867dfd95991f4bd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -6185,13 +6185,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 3.0.3", ] [[package]] @@ -6248,9 +6248,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.2+spec-1.1.0" +version = "1.1.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" dependencies = [ "indexmap", "serde_core", @@ -6272,9 +6272,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.12+spec-1.1.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ "indexmap", "toml_datetime", @@ -6284,18 +6284,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" [[package]] name = "tower" @@ -6362,7 +6362,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6442,11 +6442,11 @@ dependencies = [ "httparse", "log", "native-tls", - "rand 0.9.4", + "rand 0.9.5", "rustls", "rustls-pki-types", "sha1 0.10.7", - "thiserror 2.0.19", + "thiserror 2.0.20", ] [[package]] @@ -6521,9 +6521,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.4" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -6557,7 +6557,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6643,9 +6643,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -6656,9 +6656,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -6666,9 +6666,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6676,31 +6676,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "wayland-backend" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +checksum = "016ccf01d1c58b6f8999612813e17c9b2390f7d70671428869913310f83f54b8" dependencies = [ "cc", "downcast-rs", @@ -6712,9 +6712,9 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.14" +version = "0.31.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" dependencies = [ "bitflags 2.13.1", "rustix 1.1.4", @@ -6784,9 +6784,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.10" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ "proc-macro2", "quick-xml", @@ -6807,9 +6807,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -6827,18 +6827,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" dependencies = [ "rustls-pki-types", ] [[package]] name = "whoami" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" dependencies = [ "libc", "libredox", @@ -6859,12 +6859,12 @@ dependencies = [ [[package]] name = "wide" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfdfe6a32973f2d1b268b8895845a8a96cac2f0191e72c27cc929036060dbf89" +checksum = "de2aaf408e58689c2096682331b1f42bb2d9f2ed6b11560407d023cd0a6c634e" dependencies = [ "bytemuck", - "safe_arch 1.0.0", + "safe_arch 1.1.0", ] [[package]] @@ -6957,7 +6957,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -6968,7 +6968,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -7236,9 +7236,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] @@ -7354,9 +7354,9 @@ dependencies = [ [[package]] name = "xcursor" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" [[package]] name = "xkbcommon-dl" @@ -7427,37 +7427,37 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "synstructure", ] [[package]] name = "yuv" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d85a782d94ee43f078bcfd6fa82d4e6a5b2d1cfbbad168e4df5a9f7b39ef48c" +checksum = "220655e1c245693beb13b377d3174b9efcb1645018d1d4ec53903fc211b135ae" dependencies = [ "num-traits", ] [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -7477,7 +7477,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", "synstructure", ] @@ -7498,7 +7498,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] @@ -7531,14 +7531,14 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.118", + "syn 2.0.119", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd-safe" diff --git a/crates/ironrdp-acceptor/CHANGELOG.md b/crates/ironrdp-acceptor/CHANGELOG.md index cd78fe14b..05ae13143 100644 --- a/crates/ironrdp-acceptor/CHANGELOG.md +++ b/crates/ironrdp-acceptor/CHANGELOG.md @@ -6,6 +6,122 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.11.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.10.0...ironrdp-acceptor-v0.11.0)] - 2026-08-13 + +### Security + +- Validate auto-reconnect cookies ([#1509](https://github.com/Devolutions/IronRDP/issues/1509)) ([44f675e244](https://github.com/Devolutions/IronRDP/commit/44f675e244ee76b5311756668ffbbe28e98c7175)) + + ## Summary + - parse and carry `ARC_CS_PRIVATE_PACKET` data through the acceptor + - validate returning Enhanced RDP Security cookies with HMAC-MD5 before + reconnecting + - rotate reconnect randoms per connection and hourly, with runtime + cookie updates + - restrict cookie authentication to TLS/Hybrid and document the behavior + + ## Testing + - `cargo test -p ironrdp-pdu -p ironrdp-acceptor -p ironrdp-server` + - `cargo clippy -p ironrdp-pdu -p ironrdp-acceptor -p ironrdp-server + --all-targets -- -D warnings` + +### Features + +- Expose client multitransport flags on AcceptorResult ([#1453](https://github.com/Devolutions/IronRDP/issues/1453)) ([f0fc215555](https://github.com/Devolutions/IronRDP/commit/f0fc215555394a89510ff85c7b8a93b20e878074)) + + ## What + + The acceptor already parses the client's GCC `MultiTransportChannelData` + block (MS-RDPBCGR Β§2.2.1.3.8) into `ClientGccBlocks` during + `BasicSettingsWaitInitial` and then discards it, keeping only the + early-capability flags, core desktop size, and keyboard layout. This + surfaces the client's multitransport (MS-RDPEMT) capability flags on + `AcceptorResult`. + + ## Why + + A server implementing UDP multitransport needs to know whether the + client advertised support (`SOFT_SYNC_TCP_TO_UDP`, + `TRANSPORT_TYPE_UDP_FEC{R,L}`) before deciding whether to send a Server + Initiate Multitransport Request. Today that information is parsed and + thrown away, so there's no way for a downstream server to see it. + + ## Shape + + Purely additive, mirroring the existing `keyboard_layout` ([#1397](https://github.com/Devolutions/IronRDP/issues/1397)) and + desktop-size ([#1373](https://github.com/Devolutions/IronRDP/issues/1373)) surfacing of GCC client data the acceptor already + parses: + + - new private `multitransport_flags: gcc::MultiTransportFlags` field on + `Acceptor`, captured from `gcc_blocks.multi_transport_channel`; + - new `pub multitransport_flags: gcc::MultiTransportFlags` field on + `AcceptorResult`; + - empty when the client sends no multitransport block; + - carried across a deactivation-reactivation like the sibling fields. + + No behavior change β€” the acceptor just stops discarding a block it + already decodes. + + `cargo clippy -p ironrdp-acceptor --all-targets` and `cargo fmt --check` + are clean. + +- [**breaking**] Clamp honored client desktop size to an operator maximum ([#1404](https://github.com/Devolutions/IronRDP/issues/1404)) ([d3747a05b2](https://github.com/Devolutions/IronRDP/commit/d3747a05b202ba2d87ac19698354ae7e487850a2)) + + Follow-up to #1373 (the resource-hardening angle you flagged in review β€” + thanks for the go-ahead πŸ™‚). + + ## Problem + + `#1373` gated honor-client-desktop-size behind a bare `bool`. With it + on, the acceptor adopts the client-requested desktop size bounded only + by the protocol range `[200, 8192]`. But the desktop size is a + client-controlled `u16`, and the server still builds its + framebuffer/encoder from the negotiated size β€” so a client could request + e.g. `8192x8192` and drive the server's allocation off an untrusted + number (~256 MiB per frame buffer). Mild, and only on an opt-in + default-off path, but it's a resource-exhaustion vector driven purely by + a number the client picks. + + Your review comment: *"[200, 8192] is a protocol ceiling, not a resource + guard … tracked the 'clamp/range policy rather than a bare bool' idea as + a future follow-up (an operator-set max size)."* This is that PR. + + ## Change + + Replace the `bool` with `Option` carrying an **operator-set + maximum**: + + - `None` (default) β€” disabled; always enforce the server-provided size + (unchanged behavior). + - `Some(max)` β€” honor the client's request, **clamped per dimension to + `max`**. The client can ask for a smaller desktop, never a larger one. + + The acceptor clamps the requested `width`/`height` to `max` *before* the + existing `validate_desktop_size` protocol-range check, so the negotiated + size can never exceed what the operator is willing to render β€” set `max` + to the host display's native resolution (or whatever ceiling the server + can afford). + +- Support runtime-defined static virtual channels ([#1517](https://github.com/Devolutions/IronRDP/issues/1517)) ([8b4c483ba0](https://github.com/Devolutions/IronRDP/commit/8b4c483ba0c900a8de0b2718347754f56dd363ba)) + + ## Summary + - add keyed runtime-defined static-channel registration, lookup, and + negotiated ID attachment + - enforce the static-channel limit and reject malformed SVC fragment + sequences + - wire generic connector, acceptor, and session name-based dispatch + support + + ## Testing + - `cargo test -p ironrdp-testsuite-core --test integration_tests_core + svc::` + - `cargo clippy -p ironrdp-testsuite-core --test integration_tests_core + -- -D warnings` + + --------- + + + ## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.9.0...ironrdp-acceptor-v0.10.0)] - 2026-07-10 ### Features diff --git a/crates/ironrdp-acceptor/Cargo.toml b/crates/ironrdp-acceptor/Cargo.toml index 71968d35a..525aa2adf 100644 --- a/crates/ironrdp-acceptor/Cargo.toml +++ b/crates/ironrdp-acceptor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-acceptor" -version = "0.10.0" +version = "0.11.0" readme = "README.md" description = "State machines to drive an RDP connection acceptance sequence" edition.workspace = true @@ -17,11 +17,11 @@ doctest = false test = false [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public -ironrdp-connector = { path = "../ironrdp-connector", version = "0.10" } # public -ironrdp-async = { path = "../ironrdp-async", version = "0.10" } # public +ironrdp-connector = { path = "../ironrdp-connector", version = "0.11" } # public +ironrdp-async = { path = "../ironrdp-async", version = "0.11" } # public tracing = { version = "0.1", features = ["log"] } [lints] diff --git a/crates/ironrdp-activex/Cargo.toml b/crates/ironrdp-activex/Cargo.toml index c80b3069d..9b4516233 100644 --- a/crates/ironrdp-activex/Cargo.toml +++ b/crates/ironrdp-activex/Cargo.toml @@ -20,21 +20,21 @@ doctest = false [target.'cfg(windows)'.dependencies] anyhow = "1" -ironrdp-client = { path = "../ironrdp-client", version = "0.1", features = ["clipboard", "dvc-com-plugin", "gateway", "rdpdr", "rustls", "sound"] } +ironrdp-client = { path = "../ironrdp-client", version = "0.2", features = ["clipboard", "dvc-com-plugin", "gateway", "rdpdr", "rustls", "sound"] } ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.7" } ironrdp-cliprdr-native = { path = "../ironrdp-cliprdr-native", version = "0.7" } -ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.1" } -ironrdp-connector = { path = "../ironrdp-connector", version = "0.10" } -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } +ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.2" } +ironrdp-connector = { path = "../ironrdp-connector", version = "0.11" } +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } ironrdp-daemon = { path = "../ironrdp-daemon", version = "0.1" } ironrdp-input = { path = "../ironrdp-input", version = "0.7" } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } ironrdp-rdpei = { path = "../ironrdp-rdpei", version = "0.1" } ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } ironrdp-rail = { path = "../ironrdp-rail", version = "0.1" } ironrdp-rdpdr-native = { path = "../ironrdp-rdpdr-native", version = "0.7" } ironrdp-rpc = { path = "../ironrdp-rpc", version = "0.1" } -ironrdp-session = { path = "../ironrdp-session", version = "0.11" } +ironrdp-session = { path = "../ironrdp-session", version = "0.12" } ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } ironrdp-tls = { path = "../ironrdp-tls", version = "0.2" } png = "0.18" diff --git a/crates/ironrdp-agent/CHANGELOG.md b/crates/ironrdp-agent/CHANGELOG.md index 46674e619..f2a096d5f 100644 --- a/crates/ironrdp-agent/CHANGELOG.md +++ b/crates/ironrdp-agent/CHANGELOG.md @@ -6,6 +6,202 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-agent-v0.1.0...ironrdp-agent-v0.2.0)] - 2026-08-13 + +### Security + +- Restrict Windows named pipe to the current user ([#1482](https://github.com/Devolutions/IronRDP/issues/1482)) ([26821836b7](https://github.com/Devolutions/IronRDP/commit/26821836b78e0670f4a0a41eb7021f6e1c256be4)) + + ## Summary + + The `ironrdp-agent` Windows named-pipe listener inherited the pipe + namespace's default ACL, so **any local user** could connect to a + running daemon and drive the session β€” inject input, capture + screenshots, read logs, and trigger NOW remote execution. The Unix + listener already locks its socket to `0o600` (owner-only); this mirrors + that stance on Windows. + + ## The fix + + Build a **protected DACL** (`D:P(A;;GA;;;{user-sid})`) granting + `GENERIC_ALL` only to the current user's SID, and apply it to **every** + `CreateNamedPipeW` instance (both the first in `bind` and the + replacement minted in `accept`) via tokio's + `create_with_security_attributes_raw`. The descriptor is cached on the + `Listener` and reused across instances (the kernel copies it on each + `CreateNamedPipeW`, so it only needs to outlive each synchronous call). + + Implementation follows the established windows-FFI style used by sibling + crates (`ironrdp-cliprdr-native`): the `windows` crate (0.62, matching + siblings), RAII guards (`OwnedTokenHandle`, `OwnedSecurityDescriptor`) + for `CloseHandle`/`LocalFree` cleanup, one unsafe op per block with `// + SAFETY:` comments, `.cast::<>()` + `try_from` instead of `as` casts, and + `tracing::warn!` on cleanup failure. + + ## Why this matters + + On shared Windows hosts the `\\.\pipe\` namespace is reachable by every + local user by default. This is the last line of defense against a + different local user taking over a running agent session. It closes the + asymmetry with the Unix path, which already documents the `/tmp` + fallback as world-writable and explicitly sets `0o600`. + + ## Test coverage + + `transport.rs` previously had **zero tests**. Added a Windows-gated + `#[cfg(test)]` smoke test + (`security_descriptor_for_current_user_is_non_null`) that exercises the + full token β†’ SID β†’ SDDL β†’ security-descriptor pipeline for the current + process and asserts a non-null descriptor is produced β€” the happy path + `Listener::bind` depends on. (Asserting the ACL denies a *different* + user requires a second token context and is out of scope.) + + ## Verification + + All `cargo xtask` CI-equivalent checks pass on a clean tree: + + - `cargo xtask check fmt -v` β†’ All good + - `cargo xtask check lints -v` (workspace clippy `--all-targets -D + warnings`) β†’ All good (ironrdp-agent produces zero warnings) + - `cargo test -p ironrdp-agent` β†’ 12 passed; 0 failed (incl. the new + test) + - `cargo xtask check locks -v` β†’ All good + + ## Files + + - `crates/ironrdp-agent/Cargo.toml` β€” add `windows = "0.62"` + (cfg(windows), + Win32_Foundation/Security/Security_Authorization/System_Threading) + - `crates/ironrdp-agent/src/transport.rs` β€” + `OwnedTokenHandle`/`OwnedSecurityDescriptor` RAII guards, + `create_server_instance` helper, `Listener` caches + reuses the SD; new + smoke test + - `Cargo.lock` β€” +1 line registering `windows` as an ironrdp-agent dep + (no new versions; crates already present via siblings) + + ## Release + + This is a `fix(agent):` conventional commit, picked up by release-plz + into the next release cycle (alongside the existing `feat(agent)` + NOW-integration commit) to propose the `0.2.0` bump once merged to + `master`. CHANGELOG is auto-generated β€” no manual edit needed. + +- Connect to Windows Sandbox named pipes ([#1580](https://github.com/Devolutions/IronRDP/issues/1580)) ([39b020343d](https://github.com/Devolutions/IronRDP/commit/39b020343d962962bfbefc89939be64d5c716196)) + + Windows Sandbox's default attach path is a local named pipe carrying + plain TPKT/X.224 with PROTOCOL_RDP and ENCRYPTION_LEVEL_NONE, not + TCP:3389 or VMConnect. Allow the connector and client to complete that + sequence only via an explicit opt-in (`enable_standard_rdp_security`; + NamedPipe enables it), and teach ironrdp-agent to resolve pipe path and + guest credentials from WindowsSandboxServer after `wsb start`. + + Adds Transport::NamedPipe, ironrdp_named_pipe/ironrdp_sandbox_id + properties, sandbox list/config/stop CLI helpers via an in-process + h2/gRPC client on the per-user `\\.\pipe\wsandbox\{guid}` pipe (no .NET + helper), and connect --sandbox-id / --sandbox-pipe. Sandbox-derived + properties are the merge base; explicit .rdp/--prop/flags override them + while NamedPipe TLS/CredSSP stay forced off. Local :2179+PCB remains + unsupported. + +### Features + +- Integrate NOW client ([#1451](https://github.com/Devolutions/IronRDP/issues/1451)) ([405770e2a6](https://github.com/Devolutions/IronRDP/commit/405770e2a6232f506e10f88ab6cb413d6bec7c5b)) + + ## Summary + - Integrate the released registry `now-client = "0.1.0"` into + `ironrdp-agent`; no Git, path, patch, or vendored dependency is used. + - Add a per-session `Devolutions::Now::Agent` DVC endpoint with + 30-second initial readiness and 10-second reconnect deadlines. + - Add durable NOW operations, IPC streaming/retention, supported CLI + forms (`run`, `powershell`, `pwsh`, `exec process`, `exec batch`), safe + PowerShell defaults, raw output forwarding, and remote exit propagation. + - Add IPC/retention coverage and an adapter regression proving immediate + Run frames are quarantined before a following Process execution. + - Do not modify Gateway or `ironrdp-dvc-pipe-proxy`. + + ## Validation + - `cargo test -p ironrdp-agent` + - `cargo test -p ironrdp-testsuite-extra --test integration_tests_extra + agent` + - `cargo clippy -p ironrdp-agent --all-targets -- -D warnings` + - `cargo xtask check fmt -v` + - `cargo xtask check tests -v` + - `cargo xtask check locks -v` + + `cargo xtask check lints -v` is blocked by a pre-existing `ironrdp-str` + `manual_is_multiple_of` lint under the available Rust 1.90 toolchain. + `cargo xtask check typos -v` cannot run because `typos-cli` is not + installed. Authorized-VM end-to-end validation remains pending access to + the designated RDP/NOW endpoint. + + --------- + +- Expose generic session configuration and lifecycle APIs ([#1522](https://github.com/Devolutions/IronRDP/issues/1522)) ([57b1366650](https://github.com/Devolutions/IronRDP/commit/57b13666506dc40c15b4c4702d35150beee99133)) + + ## Summary + - expose generic client configuration for connection metadata, + compression, shell/work directory, audio, and runtime static-channel + factories + - add bounded input delivery with independent close cancellation, host + clipboard plumbing, lifecycle events, and Display Control resize + readiness/fallback handling + - update agent, viewer, web, FFI, examples, and tests for the generic + APIs + + ## Stack dependencies + This PR is stacked on `copilot/tls-validation-policy` (`b2bbcece`), + which already includes the merged runtime static-channel support from + `master`. It intentionally contains no TLS implementation/policy, + ActiveX/COM, SVC implementation, decompression, or bitmap-recovery + changes. + + ## Validation + - `cargo fmt --check --all` + - `cargo xtask check tests --no-run -v` + - `cargo xtask check lints -v` + - `cargo test -p ironrdp-client --lib --features rustls` + - `cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi` + + --------- + +- Configure static RDPDR drives ([#1617](https://github.com/Devolutions/IronRDP/issues/1617)) ([b06cd71e0f](https://github.com/Devolutions/IronRDP/commit/b06cd71e0f2845893db2123c8280f4a80e1b5a36)) + + Allow an agent daemon to opt in to fixed Windows filesystem drives. + + Validate names, roots, and duplicate definitions before listening, then + attach the native RDPDR backend factory to each client. + +- Add authorized RDPDR harness ([#1620](https://github.com/Devolutions/IronRDP/issues/1620)) ([24eb1f62f9](https://github.com/Devolutions/IronRDP/commit/24eb1f62f9aadf36fb6b3b588ecda390fcad2b38)) + + Add an opt-in Windows harness that verifies \\tsclient direct PowerShell + and Explorer copy paths for an explicitly authorized endpoint. + + Keep TLS certificate and hostname validation strict by default, but + permit a daemon-start-only bypass for the authorized test endpoint. Add + bounded all-or-nothing Unicode text input for remote commands without + expanding ActiveX. + +- Add RAIL audit commands ([#1646](https://github.com/Devolutions/IronRDP/issues/1646)) ([77759dca03](https://github.com/Devolutions/IronRDP/commit/77759dca032eb829b5be54a3c44d9be92252cf41)) + + Expose bounded client-validated RAIL events and RemoteApp launch + requests through the daemon IPC so headless agents can verify sessions. + + Preserve cursors across resize reconnects, wake waiting readers for + locally queued launches, and redact launch data in logs. + + Report terminal local Execute failures without disrupting an otherwise + valid RDP session. + +### Please Sort + +- Add agentic RDP CLI and localhost CI workflow ([#1289](https://github.com/Devolutions/IronRDP/issues/1289)) ([ffedb69ca8](https://github.com/Devolutions/IronRDP/commit/ffedb69ca8d4f2dec5a0649fa2cc4758ee74d13f)) + +- Extract reusable RDP daemon support ([#1543](https://github.com/Devolutions/IronRDP/issues/1543)) ([dc4692538e](https://github.com/Devolutions/IronRDP/commit/dc4692538e67ca089969879b7c62b69558e128e6)) + +- Add ActiveX RPC backend for ironrdp-agent ([#1544](https://github.com/Devolutions/IronRDP/issues/1544)) ([c31e43f755](https://github.com/Devolutions/IronRDP/commit/c31e43f755dbf24a302c86ca1ef8ba186d51216e)) + + + ## [[0.1.0](https://github.com/Devolutions/IronRDP/releases/tag/ironrdp-agent-v0.1.0)] - 2026-07-10 Initial release. diff --git a/crates/ironrdp-agent/Cargo.toml b/crates/ironrdp-agent/Cargo.toml index 282c92d05..8e91e6e86 100644 --- a/crates/ironrdp-agent/Cargo.toml +++ b/crates/ironrdp-agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-agent" -version = "0.1.0" +version = "0.2.0" readme = "README.md" description = "CLI-driven, daemon-backed agentic RDP client suitable for LLM consumption" edition.workspace = true @@ -22,7 +22,7 @@ test = false [dependencies] # Configuration model and codecs ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } -ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.1" } +ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.2" } ironrdp-rdpfile = { path = "../ironrdp-rdpfile", version = "0.1" } ironrdp-input = { path = "../ironrdp-input", version = "0.7" } ironrdp-daemon = { path = "../ironrdp-daemon", version = "0.1" } diff --git a/crates/ironrdp-ainput/CHANGELOG.md b/crates/ironrdp-ainput/CHANGELOG.md index dd5cc2041..55dd7610f 100644 --- a/crates/ironrdp-ainput/CHANGELOG.md +++ b/crates/ironrdp-ainput/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.8.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-ainput-v0.8.0...ironrdp-ainput-v0.8.1)] - 2026-08-13 + + + ## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-ainput-v0.7.0...ironrdp-ainput-v0.8.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-ainput/Cargo.toml b/crates/ironrdp-ainput/Cargo.toml index cc72eb623..ae016c879 100644 --- a/crates/ironrdp-ainput/Cargo.toml +++ b/crates/ironrdp-ainput/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-ainput" -version = "0.8.0" +version = "0.8.1" readme = "README.md" description = "AInput dynamic channel implementation" edition.workspace = true @@ -17,8 +17,8 @@ doctest = false test = false [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public bitflags = "2.11" num-derive.workspace = true # TODO: remove num-traits.workspace = true # TODO: remove diff --git a/crates/ironrdp-async/CHANGELOG.md b/crates/ironrdp-async/CHANGELOG.md index 34a854a38..4a3d45b5a 100644 --- a/crates/ironrdp-async/CHANGELOG.md +++ b/crates/ironrdp-async/CHANGELOG.md @@ -6,6 +6,180 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.11.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-async-v0.10.0...ironrdp-async-v0.11.0)] - 2026-08-13 + +### Security + +- [**breaking**] Implement multitransport bootstrapping handshake ([#1098](https://github.com/Devolutions/IronRDP/issues/1098)) ([e45fbfe0f5](https://github.com/Devolutions/IronRDP/commit/e45fbfe0f597011706e77fc174ca14e5e9d435b9)) + + ## Summary + + Makes the `MultitransportBootstrapping` state functional instead of a + no-op + pass-through. After licensing the server may send 0, 1, or 2 Initiate + Multitransport Request PDUs before capabilities exchange. Each one is + surfaced + to the application, which establishes UDP transport (RDPEUDP2 + TLS + + RDPEMT) + or declines, and the connector reports the outcome back to the server. + + ## API + + Mirrors the existing `should_perform_X()` pause-point pattern used by + TLS + upgrade and CredSSP, but uses `complete_X()` / `skip_X()` rather than + `mark_X_as_done()` because completion carries result data: + + - `should_perform_multitransport()`: true while a request awaits an + outcome + - `multitransport_request()`: the request awaiting an outcome, or `None` + - `complete_multitransport(result, output)`: report the outcome, resume + - `skip_multitransport(output)`: decline, resume + + `complete_multitransport` accepts a `MultitransportResult` (a `Success` + / + `Failure(hresult)` enum) rather than a caller-built response PDU. The + connector + builds the response internally from the stored request ID. + + Requests are surfaced one at a time rather than as a batch. There is no + end + marker for the set, and MS-RDPBCGR 3.2.5.15.1 requires the client to act + on a + request as soon as it decodes one, so waiting to learn how many are + coming is + not an option the protocol offers. `should_perform_multitransport()` can + therefore come round twice; the caller answers reliable and lossy + separately. + + ## Approach + + **Routing.** Requests arrive on the negotiated MCS message channel + (2.2.15.1) and the Demand Active on the I/O channel, so the channel + decides + which is which. The message channel also carries NetworkAutoDetect since + #1348, + so a decode still confirms what arrived there, but the I/O channel is + never + speculatively decoded as multitransport. A PDU on neither channel is an + error. + + For the decode to be a sound confirmation the request decoder must + reject a + Demand Active, so this PR also tightens `MultitransportRequestPdu` to + require + the exact `SEC_TRANSPORT_REQ` security-header flag. + + **Yielding.** Each request is surfaced the moment it decodes. Responding + returns the connector to `MultitransportBootstrapping` to read whatever + comes + next, which may be a second request or the Demand Active. Nothing is + buffered + and nothing is replayed: when the request is surfaced the Demand Active + has not + arrived yet. + + **Soft-Sync.** The Initiate Multitransport Response is the Soft-Sync + signalling path (2.2.15.2), permitted only when both peers advertised + `SOFTSYNC_TCP_TO_UDP` in their GCC `MultiTransportChannelData`. The + server's + block is retained from the GCC exchange and checked against the client's + configured flags. One rule covers both paths: + + - Soft-Sync negotiated: always respond, `S_OK` or `E_ABORT`, including + on + `skip_multitransport()`, which 3.2.5.15.1 requires. Both the async and + blocking drivers skip automatically, so without this every default + client + leaves a compliant server waiting. + - Not negotiated: never respond. The outcome is reported in band on the + new + transport, and putting anything on the main channel would be the + violation. + + The response goes on the message channel per 2.2.15.2 and 3.2.5.15.2. If + Soft-Sync was negotiated but no message channel exists the connector + errors + rather than falling back to the I/O channel, and that check runs before + the + pending state is taken, so the caller is left with a connector it can + still + inspect or decline from. + + ## Wire behaviour + + On the wire TCP and UDP negotiation happen in parallel: the UDP + transport is + established alongside the ongoing TCP handshake, and its completion + signals the + dynamic-channel layer that subsequent channels may migrate to UDP. The + connector's API yield point here is a Rust affordance, not a + spec-mandated TCP + pause. Thanks to @hardening for the correction. + + ## Tests + + Connector state-machine tests in `ironrdp-testsuite-core` drive the + public API + with the shared `SERVER_DEMAND_ACTIVE` fixture: + + - a request is surfaced on arrival, without waiting for a following PDU + (regression test for the stall); + - responding returns to bootstrapping so a second request is read + normally; + - a third request is rejected per the 2.2.15.1 cap; + - a Demand Active on the I/O channel ends bootstrapping; + - the response targets the message channel, decoded back off the wire; + - a `Failure` result is carried through; + - `skip` sends `E_ABORT` under Soft-Sync, and nothing without it; + - `complete` emits nothing without Soft-Sync but still resumes; + - a failed response leaves the connector in `MultitransportPending`, + still able + to report or decline, rather than `Consumed`; + - `complete` / `skip` outside `MultitransportPending` error; + - a Demand Active's user data does not decode as a + `MultitransportRequestPdu` + (regression test for the decoder tightening above). + +### Features + +- Hyper-V vmconnect support ([#1503](https://github.com/Devolutions/IronRDP/issues/1503)) ([a7cc067d50](https://github.com/Devolutions/IronRDP/commit/a7cc067d5069cbbcb13bae3e0561c0611da3bcf6)) + + Adds Hyper-V VMConnect's direct ordering: PCB β†’ TLS β†’ CredSSP β†’ X.224. + + Enhanced Session is the default (`GUID;EnhancedMode=1`), with + `--vmconnect-basic` for the synthetic console. Kept this separate in + `ironrdp-vmconnect`; no SPN changes. + + Tested against the nested Hyper-V lab: + - Enhanced: `HYBRID_EX`, rendered 1280Γ—720 + - Basic: `HYBRID`, rendered 1280Γ—720 + - `cargo xtask check fmt/lints/tests -v` + + --------- + +- Support Hyper-V connection ordering ([#1505](https://github.com/Devolutions/IronRDP/issues/1505)) ([5c1816244e](https://github.com/Devolutions/IronRDP/commit/5c1816244e83187a04249e9d9c240d096cb78f55)) + + Hyper-V over RDCleanPath needs PCB β†’ TLS on the proxy, then CredSSP β†’ + X.224 on the client. Ordinary RDCleanPath stays X.224-first. + + Still VERSION_1 with the same DER fields. An explicit VMConnect request + carries a Unicode PCB payload in `preconnection_blob` with no X.224; the + proxy encodes the binary PCB. Generic PCB requests keep their existing + X.224-first behavior. + + Gateway reference implementation: + [Devolutions/devolutions-gateway#1372](https://github.com/Devolutions/devolutions-gateway/pull/1372) + + Checked locally: Rust builds, formatting, Svelte typecheck, and .NET + build. Real nested Hyper-V E2E through Gateway: Native rendered 18 + frames, Avalonia connected and rendered its first frame, and Web + rendered a non-empty 1280Γ—720 canvas. + + --------- + + + ## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-async-v0.9.0...ironrdp-async-v0.10.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-async/Cargo.toml b/crates/ironrdp-async/Cargo.toml index 749f3257c..531d18ede 100644 --- a/crates/ironrdp-async/Cargo.toml +++ b/crates/ironrdp-async/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-async" -version = "0.10.0" +version = "0.11.0" readme = "README.md" description = "Provides `Future`s wrapping the IronRDP state machines conveniently" edition.workspace = true @@ -17,9 +17,9 @@ doctest = false test = false [dependencies] -ironrdp-connector = { path = "../ironrdp-connector", version = "0.10" } # public -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-connector = { path = "../ironrdp-connector", version = "0.11" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public tracing = { version = "0.1", features = ["log"] } bytes = "1" # public diff --git a/crates/ironrdp-blocking/CHANGELOG.md b/crates/ironrdp-blocking/CHANGELOG.md index ced129c3f..161ca64bd 100644 --- a/crates/ironrdp-blocking/CHANGELOG.md +++ b/crates/ironrdp-blocking/CHANGELOG.md @@ -6,6 +6,143 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.11.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-blocking-v0.10.0...ironrdp-blocking-v0.11.0)] - 2026-08-13 + +### Security + +- [**breaking**] Implement multitransport bootstrapping handshake ([#1098](https://github.com/Devolutions/IronRDP/issues/1098)) ([e45fbfe0f5](https://github.com/Devolutions/IronRDP/commit/e45fbfe0f597011706e77fc174ca14e5e9d435b9)) + + ## Summary + + Makes the `MultitransportBootstrapping` state functional instead of a + no-op + pass-through. After licensing the server may send 0, 1, or 2 Initiate + Multitransport Request PDUs before capabilities exchange. Each one is + surfaced + to the application, which establishes UDP transport (RDPEUDP2 + TLS + + RDPEMT) + or declines, and the connector reports the outcome back to the server. + + ## API + + Mirrors the existing `should_perform_X()` pause-point pattern used by + TLS + upgrade and CredSSP, but uses `complete_X()` / `skip_X()` rather than + `mark_X_as_done()` because completion carries result data: + + - `should_perform_multitransport()`: true while a request awaits an + outcome + - `multitransport_request()`: the request awaiting an outcome, or `None` + - `complete_multitransport(result, output)`: report the outcome, resume + - `skip_multitransport(output)`: decline, resume + + `complete_multitransport` accepts a `MultitransportResult` (a `Success` + / + `Failure(hresult)` enum) rather than a caller-built response PDU. The + connector + builds the response internally from the stored request ID. + + Requests are surfaced one at a time rather than as a batch. There is no + end + marker for the set, and MS-RDPBCGR 3.2.5.15.1 requires the client to act + on a + request as soon as it decodes one, so waiting to learn how many are + coming is + not an option the protocol offers. `should_perform_multitransport()` can + therefore come round twice; the caller answers reliable and lossy + separately. + + ## Approach + + **Routing.** Requests arrive on the negotiated MCS message channel + (2.2.15.1) and the Demand Active on the I/O channel, so the channel + decides + which is which. The message channel also carries NetworkAutoDetect since + #1348, + so a decode still confirms what arrived there, but the I/O channel is + never + speculatively decoded as multitransport. A PDU on neither channel is an + error. + + For the decode to be a sound confirmation the request decoder must + reject a + Demand Active, so this PR also tightens `MultitransportRequestPdu` to + require + the exact `SEC_TRANSPORT_REQ` security-header flag. + + **Yielding.** Each request is surfaced the moment it decodes. Responding + returns the connector to `MultitransportBootstrapping` to read whatever + comes + next, which may be a second request or the Demand Active. Nothing is + buffered + and nothing is replayed: when the request is surfaced the Demand Active + has not + arrived yet. + + **Soft-Sync.** The Initiate Multitransport Response is the Soft-Sync + signalling path (2.2.15.2), permitted only when both peers advertised + `SOFTSYNC_TCP_TO_UDP` in their GCC `MultiTransportChannelData`. The + server's + block is retained from the GCC exchange and checked against the client's + configured flags. One rule covers both paths: + + - Soft-Sync negotiated: always respond, `S_OK` or `E_ABORT`, including + on + `skip_multitransport()`, which 3.2.5.15.1 requires. Both the async and + blocking drivers skip automatically, so without this every default + client + leaves a compliant server waiting. + - Not negotiated: never respond. The outcome is reported in band on the + new + transport, and putting anything on the main channel would be the + violation. + + The response goes on the message channel per 2.2.15.2 and 3.2.5.15.2. If + Soft-Sync was negotiated but no message channel exists the connector + errors + rather than falling back to the I/O channel, and that check runs before + the + pending state is taken, so the caller is left with a connector it can + still + inspect or decline from. + + ## Wire behaviour + + On the wire TCP and UDP negotiation happen in parallel: the UDP + transport is + established alongside the ongoing TCP handshake, and its completion + signals the + dynamic-channel layer that subsequent channels may migrate to UDP. The + connector's API yield point here is a Rust affordance, not a + spec-mandated TCP + pause. Thanks to @hardening for the correction. + + ## Tests + + Connector state-machine tests in `ironrdp-testsuite-core` drive the + public API + with the shared `SERVER_DEMAND_ACTIVE` fixture: + + - a request is surfaced on arrival, without waiting for a following PDU + (regression test for the stall); + - responding returns to bootstrapping so a second request is read + normally; + - a third request is rejected per the 2.2.15.1 cap; + - a Demand Active on the I/O channel ends bootstrapping; + - the response targets the message channel, decoded back off the wire; + - a `Failure` result is carried through; + - `skip` sends `E_ABORT` under Soft-Sync, and nothing without it; + - `complete` emits nothing without Soft-Sync but still resumes; + - a failed response leaves the connector in `MultitransportPending`, + still able + to report or decline, rather than `Consumed`; + - `complete` / `skip` outside `MultitransportPending` error; + - a Demand Active's user data does not decode as a + `MultitransportRequestPdu` + (regression test for the decoder tightening above). + + + ## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-blocking-v0.9.0...ironrdp-blocking-v0.10.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-blocking/Cargo.toml b/crates/ironrdp-blocking/Cargo.toml index 8ce213293..b19108df8 100644 --- a/crates/ironrdp-blocking/Cargo.toml +++ b/crates/ironrdp-blocking/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-blocking" -version = "0.10.0" +version = "0.11.0" readme = "README.md" description = "Blocking I/O abstraction wrapping the IronRDP state machines conveniently" edition.workspace = true @@ -17,9 +17,9 @@ doctest = false test = false [dependencies] -ironrdp-connector = { path = "../ironrdp-connector", version = "0.10" } # public -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-connector = { path = "../ironrdp-connector", version = "0.11" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public tracing = { version = "0.1", features = ["log"] } bytes = "1" # public diff --git a/crates/ironrdp-bulk/CHANGELOG.md b/crates/ironrdp-bulk/CHANGELOG.md index cfbe2a485..a6fee734d 100644 --- a/crates/ironrdp-bulk/CHANGELOG.md +++ b/crates/ironrdp-bulk/CHANGELOG.md @@ -6,6 +6,127 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-bulk-v0.1.1...ironrdp-bulk-v0.2.0)] - 2026-08-13 + +### Bug Fixes + +- [**breaking**] Always own a bulk decompressor for FastPath updates ([#1255](https://github.com/Devolutions/IronRDP/issues/1255)) ([0dc0194418](https://github.com/Devolutions/IronRDP/commit/0dc0194418375d504a8041b75ba250dc8eeb21ad)) + + ## Summary + + - A compressed FastPath update is dropped whenever the client did not + negotiate compression, because the decompressor is only built when a + compression type was negotiated. Servers send compressed updates + regardless, for example on a full-frame redraw after a resize, and the + session then fails. Closes #1193. + - The negotiated type is the wrong thing to condition on. It describes + what the client would send, and nothing in `ironrdp-session`, + `ironrdp-client`, `ironrdp-web` or the FFI ever compresses outbound. On + the receive path `BulkCompressor` holds a context per algorithm and + `decompress` selects one per update from the packet's own type bits, so + a decompressor built with any type decodes all of them. + - The `Processor` now owns the decompressor and builds it on the first + update that needs one. `ProcessorBuilder` has no corresponding field, so + there is no `None` a consumer can pass and no path that drops a + compressed update. + - On demand rather than at construction because `ironrdp-web` hardcodes + `compression_type: None` in `build_config` and so never negotiates + compression. Constructing eagerly would charge every web session for a + full set of algorithm contexts, and the two XCRUSH history buffers alone + are 2 MB each, for a decompressor most of those sessions never use. That + consumer is also the one most exposed to this bug, for the same reason. + - `BulkCompressor::new` is now infallible. Its only failure path was a + self-check over NCRUSH's static Huffman tables, a compile-time + invariant, now a `debug_assert`. + + ## Relationship to #1474 + + #1474 is kept, not reverted. `ActiveStage::reactivate` is adopted as the + reactivation entry point at all four call sites it introduced: native + client, web, FFI and the e2e test. + + What this PR removes is the `compression_type` retained on `ActiveStage` + and the `make_bulk_decompressor` helper, because an on-demand + decompressor makes both unnecessary. `reactivate` keeps its behaviour + and loses only the compression plumbing. + + #1474 closed the reactivation instance of #1193, where a rebuild passed + `None` and silently disabled decompression for the rest of the session. + The general case is still open on master: when compression was never + negotiated the retained type is `None`, `make_bulk_decompressor` returns + `None`, and every compressed update takes the drop path in + `fast_path.rs` for the lifetime of the session. Conditioning on the + negotiated type gates the ability to receive on what was negotiated to + send, and nothing sends. + + The evidence that removing the field is safe is #1474's own test. + `test_reactivation_processes_compressed_fastpath_updates` passes + unchanged with `compression_type` gone from the builder: the rebuilt + processor decompresses because every processor can, not because a type + was carried across the rebuild. + + ## Validation + + `cargo xtask check fmt/lints/tests/typos/locks` all pass. + + The gated regression test is + `testsuite-core/tests/session/fast_path.rs`, which renders the same + bitmap update plain and bulk-compressed through fresh processors and + asserts identical framebuffers. #1474's + `test_reactivation_processes_compressed_fastpath_updates` in + `testsuite-extra` passes unchanged. + + There is also an inline test in `fast_path.rs` pinning the allocation + invariant, that no contexts are built until an update needs them. Note + that `ironrdp-session` sets `[lib] test = false`, so inline tests in + this crate are not run by `cargo test --workspace`; it runs under `cargo + test -p ironrdp-session --lib`. + + ## Notes + + - This addresses the four points from the 2026-06-24 review. Point 4, + that the `Option` is misleading, is the shape of this change: it is gone + from the public API, and the private one that remains carries no + implication that a consumer could choose not to decompress. Point 1, + whether a cold `Rdp61` context decodes `RDP40` and `RDP50` updates + correctly, is a non-issue: `decompress` selects the algorithm per update + through `CompressionType::from_flags` against per-algorithm receive + contexts, so the construction-time type never constrains the receive + path. Point 3, silent degradation if the constructor fails, is removed + by making `new` infallible. Point 2 is the tests above. + - Breaking across two crates, hence the `fix(bulk,session)!` scope: + `ProcessorBuilder` loses `bulk_decompressor`, `ActiveStageBuilder` loses + `compression_type`, and `ironrdp_bulk::BulkCompressor::new` returns + `Self`. + - Incidental: `ironrdp-session` no longer exposes any `ironrdp_bulk` + type in its public API, so that dependency's lack of a `# public` marker + in `Cargo.toml` is now correct. + +- Share bulk decompression across output paths ([#1518](https://github.com/Devolutions/IronRDP/issues/1518)) ([6151e21bf5](https://github.com/Devolutions/IronRDP/commit/6151e21bf58b7297e9b4abc2167aa36fc2ba77e4)) + + Bulk compression state is stream-wide, but Fast-Path and slow-path + outputs previously used separate or missing decompression paths. This + could corrupt history-dependent server updates or leave negotiated + slow-path compression undecodable. + + This change owns the negotiated bulk decompressor in `ActiveStage` and + passes it to both X.224 and Fast-Path processing. It retains Share Data + compression metadata through the PDU context, resets decompression + history on reactivation, and initializes consumers from the connection's + negotiated compression type. + + Fast-Path now decompresses each fragment before reassembly so + compression flags apply at packet boundaries. Failures expose bounded + protocol metadata without retaining remote payloads or decoder details. + + Tests cover Share Data metadata propagation, slow-path decompression + behavior, fragmented Fast-Path reassembly and bounded errors, and + compressed Fast-Path updates after reactivation. + + --------- + + + ## [[0.1.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-bulk-v0.1.0...ironrdp-bulk-v0.1.1)] - 2026-05-27 ### Bug Fixes diff --git a/crates/ironrdp-bulk/Cargo.toml b/crates/ironrdp-bulk/Cargo.toml index 200919bcc..78e0233da 100644 --- a/crates/ironrdp-bulk/Cargo.toml +++ b/crates/ironrdp-bulk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-bulk" -version = "0.1.1" +version = "0.2.0" description = "Bulk compression algorithms (MPPC, XCRUSH, NCRUSH) for IronRDP" edition.workspace = true rust-version = "1.94" diff --git a/crates/ironrdp-cfg/CHANGELOG.md b/crates/ironrdp-cfg/CHANGELOG.md index 62378530e..a217d84bf 100644 --- a/crates/ironrdp-cfg/CHANGELOG.md +++ b/crates/ironrdp-cfg/CHANGELOG.md @@ -6,6 +6,50 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-cfg-v0.1.0...ironrdp-cfg-v0.2.0)] - 2026-08-13 + +### Security + +- Connect to Windows Sandbox named pipes ([#1580](https://github.com/Devolutions/IronRDP/issues/1580)) ([39b020343d](https://github.com/Devolutions/IronRDP/commit/39b020343d962962bfbefc89939be64d5c716196)) + + Windows Sandbox's default attach path is a local named pipe carrying + plain TPKT/X.224 with PROTOCOL_RDP and ENCRYPTION_LEVEL_NONE, not + TCP:3389 or VMConnect. Allow the connector and client to complete that + sequence only via an explicit opt-in (`enable_standard_rdp_security`; + NamedPipe enables it), and teach ironrdp-agent to resolve pipe path and + guest credentials from WindowsSandboxServer after `wsb start`. + + Adds Transport::NamedPipe, ironrdp_named_pipe/ironrdp_sandbox_id + properties, sandbox list/config/stop CLI helpers via an in-process + h2/gRPC client on the per-user `\\.\pipe\wsandbox\{guid}` pipe (no .NET + helper), and connect --sandbox-id / --sandbox-pipe. Sandbox-derived + properties are the merge base; explicit .rdp/--prop/flags override them + while NamedPipe TLS/CredSSP stay forced off. Local :2179+PCB remains + unsupported. + +### Features + +- Add RemoteApp channel support ([#1637](https://github.com/Devolutions/IronRDP/issues/1637)) ([ab48c6cb8c](https://github.com/Devolutions/IronRDP/commit/ab48c6cb8c017504f8a92799aeb91b821c50a13a)) + + Configure and negotiate RAIL connections, then route its static channel + through the portable client with bounded request queues and server + control events. + +- Wire MS-RDPEAI capture into Windows client and ActiveX ([#1642](https://github.com/Devolutions/IronRDP/issues/1642)) ([205fe038cc](https://github.com/Devolutions/IronRDP/commit/205fe038cc693598adf803fe181526b789b2ec3d)) + + Add the client MS-RDPEAI capture path on top of hardened RDPSND + playback: connector CFG + static channel wiring, CPAL PCM capture + backend, ironrdp-client --audio-capture, and ActiveX + AudioCaptureRedirectionMode. + + PCM capture only accepts encode formats that match the Open capture + stream, rejects non-16-bit capture (Data PDU size contract), and gates + the capture backend behind ironrdp-rdpsnd-native/capture. + + Depends on #1648 (playback). + + + ## [[0.1.0](https://github.com/Devolutions/IronRDP/releases/tag/ironrdp-cfg-v0.1.0)] - 2026-07-10 Initial release. diff --git a/crates/ironrdp-cfg/Cargo.toml b/crates/ironrdp-cfg/Cargo.toml index 488b9f971..14b6d52b3 100644 --- a/crates/ironrdp-cfg/Cargo.toml +++ b/crates/ironrdp-cfg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-cfg" -version = "0.1.0" +version = "0.2.0" readme = "README.md" description = "IronRDP utilities for ironrdp-cfgstore" edition.workspace = true diff --git a/crates/ironrdp-client/CHANGELOG.md b/crates/ironrdp-client/CHANGELOG.md index cdbc79f70..57954e38b 100644 --- a/crates/ironrdp-client/CHANGELOG.md +++ b/crates/ironrdp-client/CHANGELOG.md @@ -6,6 +6,648 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-client-v0.1.0...ironrdp-client-v0.2.0)] - 2026-08-13 + +### Security + +- [**breaking**] Support session resume via the auto-reconnect cookie ([#1501](https://github.com/Devolutions/IronRDP/issues/1501)) ([74b3365c1f](https://github.com/Devolutions/IronRDP/commit/74b3365c1f98c0da6feed7507779c67e1b8e6d08)) + + > **Rebased onto post-#1522 master.** #1509 landed the server half of + #1508 while this was open, including the `ClientAutoReconnect` + structure. This PR no longer declares it; it extends it, and picks up + the parts #1509 did not build. + + ## What + + The client half of automatic reconnection. The session layer surfaces + the Server Auto-Reconnect Cookie, `ironrdp-pdu` derives and verifies the + client's response to it, and the connector sends that response when + resuming a session. + + ## Why + + A client whose connection drops ungracefully can reattach to its session + instead of making the user log on again, provided it returns the cookie + the server issued during logon ([MS-RDPBCGR] 1.3.1.5). + + #1509 built the server side of that: it validates a returning + `ARC_CS_PRIVATE_PACKET` and rotates the random. Nothing answers it. + `ironrdp-session` decodes the cookie and drops it, `ironrdp-connector` + has no way to send one back, and `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` still sits in + `ironrdp-client`. So `ironrdp-client` cannot resume a session against + `ironrdp-server`, and the validation #1509 added has no in-tree + counterpart to exercise it. + + The wire encoding was already there. `ExtendedClientOptionalInfo` + carries, encodes and decodes a 28-byte `autoReconnectCookie` and its + builder already had a `reconnect_cookie` step; `ServerAutoReconnect` + already decoded; #1509 added `ClientAutoReconnect` and its decode. + Nothing connected them. + + ## The three parts + + **Receive.** `SaveSessionInfo` now also surfaces the cookie, as + `ProcessorOutput::AutoReconnectCookie` and + `ActiveStageOutput::AutoReconnectCookie`. #1522 added a `SaveSessionInfo + { logon_complete }` output on that same handler; the two coexist rather + than compete, since both are read off one PDU and neither supersedes the + other. The handler emits the logon notification unconditionally and + appends the cookie when one is present, and a test pins that surfacing + the cookie does not suppress the notification. #1509's server replaces + the cookie whenever a client connects and again hourly ([MS-RDPBCGR] + 3.3.6.2), so this can arrive more than once in a session and the + consumer keeps the most recent. + + **Derive.** `ClientAutoReconnect::from_server_cookie` implements + [MS-RDPBCGR] 5.5: + + > The auto-reconnect random is used to key the HMAC function + ([RFC2104]), which uses MD5 as the iterative hash function. The security + verifier is derived by applying the HMAC to the client random received + in Step 3. + > + > `SecurityVerifier = HMAC(AutoReconnectRandom, ClientRandom)` + > + > When Enhanced RDP Security is in effect the client random value is not + generated (section 5.3.2). In this case, for the purpose of generating + the security verifier, the client random is assumed to be an array of 32 + zero bytes. + + IronRDP implements no Standard RDP Security path (there is no Security + Exchange PDU), so the zero-client-random case is the only one that + arises. As 5.5 notes, that makes the verifier constant for a given + cookie, so it proves possession of the cookie and nothing more; session + security comes from the outer TLS/CredSSP handshake. + + @clintcan independently confirmed this construction against real + **mstsc** while validating #1509 + ([comment](https://github.com/Devolutions/IronRDP/pull/1509#issuecomment-5151200681)): + a Windows client's `ARC_CS_PRIVATE_PACKET` verifies against + `HMAC-MD5(random_bits, [0u8; 32])`. That is the same derivation + implemented here, so the two halves interoperate with Microsoft's client + and not only with each other. + + **Send.** `ClientConnector::with_auto_reconnect_cookie` takes the cookie + last received and makes the connector put the derived Client + Auto-Reconnect Packet ([MS-RDPBCGR] 2.2.4.3) in the Client Info PDU. + Absent, that PDU is byte-for-byte what it was. + + Unlike the server packet, this structure has no enclosing logon-info + field header, so it encodes to exactly the 28 bytes the cookie field + expects. `to_bytes` writes that layout directly rather than going + through `Encode`, so filling a fixed-size field has no error path a + caller must handle; a test pins the two to agree. + + ## One derivation, not two + + Putting `from_server_cookie` in `ironrdp-pdu` would leave the workspace + with two implementations of 5.5, since #1509 added a private HMAC to + `ironrdp-server`. So `ClientAutoReconnect` also gains `verify`, and the + server routes through it. + + `verify` keeps the constant-time comparison the server had. The verifier + is the whole credential, so a comparison returning early on the first + differing byte would let a peer recover it a byte at a time from the + timing; the session identifier is not secret and is compared normally. + `ironrdp-server` keeps the policy around the check, which cookies are + live and whether the security protocol permits auto-reconnect, and drops + its `hmac` and `md-5` dependencies. `hmac` moves to `ironrdp-pdu` as + `default-features = false`; the crate's full feature powerset still + checks clean, including `--no-default-features`. + + I would rather not have reached into `ironrdp-server` in a + `pdu,session,connector` change, but the alternative was shipping the + duplicate and filing a follow-up to remove it, which is a worse trade + for reviewer time. + + ## Tests that were not running + + That move also rehomes the known-answer tests @clintcan contributed on + #1509. They went in as an inline `#[cfg(test)]` module in + `crates/ironrdp-server/src/server.rs`, and that crate sets `[lib] test = + false`, so they have never executed in CI. They now live in + `ironrdp-testsuite-core` against the public API, where CI runs them: his + HMAC-MD5 reference vector is kept as a second vector alongside a + differently-keyed one, plus the cases for a tampered verifier and a + mismatched logon ID. + + Worth flagging separately: `ironrdp-server` is not alone. + `ironrdp-agent`, `ironrdp-session` and `ironrdp-web` also set `[lib] + test = false` and between them carry 16 files of inline `#[cfg(test)]` + modules that CI never runs. That is out of scope here, but I am happy to + open an issue if it would be useful. + + ## Breaking changes + + `ActiveStageOutput` and `x224::ProcessorOutput` gain a variant, and + `ClientConnector` gains a public field, so exhaustive matches and struct + literals need updating. + + Confirmed with `cargo-semver-checks` against the merge-base: those three + are the only findings this branch introduces. The others it reports on + `master` today (`ShareDataPdu::Compressed` and the `ShareDataCtx` fields + from #1518, `ProcessorBuilder.bulk_decompressor` from #1518, + `ServerEvent::SetAutoReconnectCookie` from #1509) are present on + `master` unchanged. The `ironrdp-pdu` additions are additive. + + ## Scope + + This is the library half. `ironrdp-client`, `ironrdp-web` and the FFI + bindings gain an arm for the new output but none of them reconnect + automatically yet; that is the remaining part of #271, and the existing + `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` in `ironrdp-client` marks where it goes. + + I kept receive, derive and send together deliberately. Split up, none of + them is usable on its own: without the receive half there is no way to + obtain a cookie, and without the send half there is nothing to do with + one. + + ## Tests + + Thirteen, all in `ironrdp-testsuite-core`. + + On the packet and the derivation: the `SecurityVerifier` matches two + independently computed HMAC-MD5 vectors of 32 zero bytes under different + keys, so the tests pin the derivation rather than restating the code; + the logon ID carries over from the server cookie; the encoding matches + the 2.2.4.3 field layout byte for byte with `cbLen` fixed at `0x1C`; + `to_bytes` agrees with `Encode`; it round-trips; and it rejects both a + wrong packet length and an unknown version. + + On verification: a derived answer is accepted, a single flipped byte in + the verifier is rejected, a correct verifier under a different logon ID + is rejected, and an answer derived from a different random is rejected. + + On the surfacing path: a Save Session Info PDU framed the way a server + sends it, through the real x224 processor, yields an + `AutoReconnectCookie` carrying the right logon ID and random bits, + alongside #1522's logon notification rather than in place of it; and one + without a cookie surfaces no cookie. + + ## Verification + + `cargo xtask check fmt/lints/tests/typos/locks` all pass on 1.94.1, + including a `fuzz/` build before the lock check. + + ## Note + + #1496 also touches the `ClientAutoReconnect` declaration. Whichever of + the two lands second needs a one-line rebase on the derive attribute; + happy to take that in either order. + +- Connect to Windows Sandbox named pipes ([#1580](https://github.com/Devolutions/IronRDP/issues/1580)) ([39b020343d](https://github.com/Devolutions/IronRDP/commit/39b020343d962962bfbefc89939be64d5c716196)) + + Windows Sandbox's default attach path is a local named pipe carrying + plain TPKT/X.224 with PROTOCOL_RDP and ENCRYPTION_LEVEL_NONE, not + TCP:3389 or VMConnect. Allow the connector and client to complete that + sequence only via an explicit opt-in (`enable_standard_rdp_security`; + NamedPipe enables it), and teach ironrdp-agent to resolve pipe path and + guest credentials from WindowsSandboxServer after `wsb start`. + + Adds Transport::NamedPipe, ironrdp_named_pipe/ironrdp_sandbox_id + properties, sandbox list/config/stop CLI helpers via an in-process + h2/gRPC client on the per-user `\\.\pipe\wsandbox\{guid}` pipe (no .NET + helper), and connect --sandbox-id / --sandbox-pipe. Sandbox-derived + properties are the merge base; explicit .rdp/--prop/flags override them + while NamedPipe TLS/CredSSP stay forced off. Local :2179+PCB remains + unsupported. + +### Features + +- Expose the server's Input capability flags on ConnectionResult ([#1488](https://github.com/Devolutions/IronRDP/issues/1488)) ([9bcf13438c](https://github.com/Devolutions/IronRDP/commit/9bcf13438cb068b53a8cb0dc23477c498727d49f)) + + Per [MS-RDPBCGR] 2.2.8.1.2, a client must not send fast-path input + events unless the server advertised `INPUT_FLAG_FASTPATH_INPUT` or + `INPUT_FLAG_FASTPATH_INPUT2` in its Input Capability Set. Today the + connector discards the server's capability sets after Demand Active, so + client code has no way to honour that requirement. + + This PR captures the server's Input capability flags during capabilities + exchange, carries them through the `ConnectionFinalization`/`Finalized` + activation states (so they are refreshed correctly across a + Deactivation-Reactivation Sequence too), and surfaces them as + `ConnectionResult::input_flags`. The session layer can then choose + between fast-path and slow-path input per server. + + ### Motivation / real-world interop + + This is not theoretical: VirtualBox's VRDP server closes the connection + outright on receiving a fast-path input PDU β€” its `VBox.log` reports + + ``` + VRDP: Network packet length is incorrect 0x0004. Closing connection. + ``` + + (a single fast-path scancode event is a 4-byte packet). VirtualBox never + advertises fast-path input; its Demand Active offers + `InputFlags(SCANCODES)` alone. mstsc and FreeRDP honour the negotiation + and fall back to slow-path `TS_INPUT_PDU`s, which is why they work + against VRDE. + + Haven (Android RDP client built on IronRDP) has been shipping this exact + change as a vendored-connector patch since v5.86.1, with the slow-path + fallback keyed off `ConnectionResult::input_flags`. Verified against a + real VirtualBox 7.2.6 VRDE server: before the gate, the first arrow-key + press killed the session with the log line above; with the gate, + extended input sessions run clean, and a fast-path-capable server on the + same host still takes the fast-path branch. (Discussed in #1158; this is + the third and last piece Haven carries in its connector fork, alongside + #1237 and #1472.) + + ### Changes + + - `connection_activation.rs`: capture `input_flags` from the + `CapabilitySet::Input` in Server Demand Active (empty if absent); add + the field to `ConnectionActivationState::{ConnectionFinalization, + Finalized}`. + - `connection.rs`: add `ConnectionResult::input_flags`, populated from + the `Finalized` state. + - Call sites in `ironrdp-client`, `ironrdp-web`, ffi, and the e2e test + updated for the new variant field (all currently ignore it). + + ### Testing + + - Two new integration tests in + `ironrdp-testsuite-core/tests/session/connection_activation.rs`: the + fixture's Demand Active yields `SCANCODES | MOUSEX | UNICODE | + FASTPATH_INPUT_2` in the `ConnectionFinalization` state, and a Demand + Active with the Input capability stripped yields `InputFlags::empty()`. + - `cargo check --workspace --all-targets`, `cargo clippy --workspace + --all-targets`, and `cargo fmt --check` are clean; the 7 activation + tests pass. + +- Expose generic session configuration and lifecycle APIs ([#1522](https://github.com/Devolutions/IronRDP/issues/1522)) ([57b1366650](https://github.com/Devolutions/IronRDP/commit/57b13666506dc40c15b4c4702d35150beee99133)) + + ## Summary + - expose generic client configuration for connection metadata, + compression, shell/work directory, audio, and runtime static-channel + factories + - add bounded input delivery with independent close cancellation, host + clipboard plumbing, lifecycle events, and Display Control resize + readiness/fallback handling + - update agent, viewer, web, FFI, examples, and tests for the generic + APIs + + ## Stack dependencies + This PR is stacked on `copilot/tls-validation-policy` (`b2bbcece`), + which already includes the merged runtime static-channel support from + `master`. It intentionally contains no TLS implementation/policy, + ActiveX/COM, SVC implementation, decompression, or bitmap-recovery + changes. + + ## Validation + - `cargo fmt --check --all` + - `cargo xtask check tests --no-run -v` + - `cargo xtask check lints -v` + - `cargo test -p ironrdp-client --lib --features rustls` + - `cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi` + + --------- + +- Add IronRDP ActiveX COM server ([#1523](https://github.com/Devolutions/IronRDP/issues/1523)) ([ee58b7c5f2](https://github.com/Devolutions/IronRDP/commit/ee58b7c5f283ef64be93a8483242f49070c6cdc9)) + + ## Summary + - Add the IronRDP ActiveX COM server and native MSTSC host integration. + - Provide bounded native-host diagnostics, credential-bridge support, + and an AxHost test harness. + - Preserve the minimal client, connector, and error integrations needed + by the control. + + ## Validation + - cargo check -p ironrdp-activex + - Focused ironrdp-error and ironrdp-connector tests + - cargo test -p ironrdp-activex --lib --no-run + - cargo fmt --all -- --check + - cargo xtask check locks -v + + --------- + +- Hyper-V vmconnect support ([#1503](https://github.com/Devolutions/IronRDP/issues/1503)) ([a7cc067d50](https://github.com/Devolutions/IronRDP/commit/a7cc067d5069cbbcb13bae3e0561c0611da3bcf6)) + + Adds Hyper-V VMConnect's direct ordering: PCB β†’ TLS β†’ CredSSP β†’ X.224. + + Enhanced Session is the default (`GUID;EnhancedMode=1`), with + `--vmconnect-basic` for the synthetic console. Kept this separate in + `ironrdp-vmconnect`; no SPN changes. + + Tested against the nested Hyper-V lab: + - Enhanced: `HYBRID_EX`, rendered 1280Γ—720 + - Basic: `HYBRID`, rendered 1280Γ—720 + - `cargo xtask check fmt/lints/tests -v` + + --------- + +- Wire RDPDR backends into client connections ([#1600](https://github.com/Devolutions/IronRDP/issues/1600)) ([1fbc9bab0b](https://github.com/Devolutions/IronRDP/commit/1fbc9bab0bc26d8fe0789d5215005d7ea22e2a54)) + + Build a fresh RDPDR backend product for every connection attempt. + + Attach RDPDR only when its product has filesystem devices, advertise + RDPSND for Windows interoperability, and deliver deferred responses. + +- Negotiate static channel chunk sizing ([#1622](https://github.com/Devolutions/IronRDP/issues/1622)) ([4e3903fbbe](https://github.com/Devolutions/IronRDP/commit/4e3903fbbef2904505f35e3437a7106807ac5987)) + + Use the validated server VCChunkSize for outgoing static virtual channel + data and retain 1600-byte chunks when it is absent or invalid. + + Apply refreshed values after reactivation across native, web, and FFI + active stages while preserving channel flags. + +- Support Hyper-V connection ordering ([#1505](https://github.com/Devolutions/IronRDP/issues/1505)) ([5c1816244e](https://github.com/Devolutions/IronRDP/commit/5c1816244e83187a04249e9d9c240d096cb78f55)) + + Hyper-V over RDCleanPath needs PCB β†’ TLS on the proxy, then CredSSP β†’ + X.224 on the client. Ordinary RDCleanPath stays X.224-first. + + Still VERSION_1 with the same DER fields. An explicit VMConnect request + carries a Unicode PCB payload in `preconnection_blob` with no X.224; the + proxy encodes the binary PCB. Generic PCB requests keep their existing + X.224-first behavior. + + Gateway reference implementation: + [Devolutions/devolutions-gateway#1372](https://github.com/Devolutions/devolutions-gateway/pull/1372) + + Checked locally: Rust builds, formatting, Svelte typecheck, and .NET + build. Real nested Hyper-V E2E through Gateway: Native rendered 18 + frames, Avalonia connected and rendered its first frame, and Web + rendered a non-empty 1280Γ—720 canvas. + + --------- + +- Forward negotiated windowing orders ([#1631](https://github.com/Devolutions/IronRDP/issues/1631)) ([0c3fbe78b4](https://github.com/Devolutions/IronRDP/commit/0c3fbe78b4366533b9fcea046b2b53654e003a72)) + + Preserve Window List support during activation. + Forward validated orders through ActiveStage and the raw FFI output. + Desktop and web consumers retain their existing behavior. + +- Add RemoteApp channel support ([#1637](https://github.com/Devolutions/IronRDP/issues/1637)) ([ab48c6cb8c](https://github.com/Devolutions/IronRDP/commit/ab48c6cb8c017504f8a92799aeb91b821c50a13a)) + + Configure and negotiate RAIL connections, then route its static channel + through the portable client with bounded request queues and server + control events. + +- Project RemoteApp windows ([#1641](https://github.com/Devolutions/IronRDP/issues/1641)) ([f5554f40dc](https://github.com/Devolutions/IronRDP/commit/f5554f40dc280d93506ea8352e3992e641d58e96)) + + Project server-authoritative RAIL windows for an enabled ActiveX + RemoteApp session and launch the configured program after RAIL becomes + available. + + Forward validated opaque windowing orders to the worker, maintain their + basic HWND lifecycle, and retain windows until the server removes them. + Leave desktop behavior and unsupported shell features unchanged. + +- Add RAIL audit commands ([#1646](https://github.com/Devolutions/IronRDP/issues/1646)) ([77759dca03](https://github.com/Devolutions/IronRDP/commit/77759dca032eb829b5be54a3c44d9be92252cf41)) + + Expose bounded client-validated RAIL events and RemoteApp launch + requests through the daemon IPC so headless agents can verify sessions. + + Preserve cursors across resize reconnects, wake waiting readers for + locally queued launches, and redact launch data in logs. + + Report terminal local Execute failures without disrupting an otherwise + valid RDP session. + +- Add Input DVC and ActiveX touch ([#1647](https://github.com/Devolutions/IronRDP/issues/1647)) ([a912e19bd2](https://github.com/Devolutions/IronRDP/commit/a912e19bd2bb31f403fd7c35c8efd729a5ab5f6f)) + + Implement MS-RDPEI for multi-touch over the dynamic virtual channel + Microsoft::Windows::RDS::Input, and wire Windows pointer messages in + ActiveX through session encode helpers. + + Introduce ironrdp-rdpei PDUs and processors, register the channel from + the client, encode touch frames from ActiveX WM_POINTER*, and cover the + protocol with unit and integration tests. + +- Harden Windows client playback path ([#1648](https://github.com/Devolutions/IronRDP/issues/1648)) ([2d9a9bf114](https://github.com/Devolutions/IronRDP/commit/2d9a9bf114dcf41a1ddc7343f564bc2e8d1d06db)) + + Keep client format order for wFormatNo, play pre-v8 Wave PDUs, and apply + volume on a broader CPAL PCM offer so ActiveX mode 0 can redirect remote + audio reliably. + + Also fix clippy noise in the RDPSND client suite and keep interleaved + volume L/R phase stable across wave blocks. Volume scaling is a simple + amplitude map, not a logarithmic MS-RDPEA model. + +- Wire MS-RDPEAI capture into Windows client and ActiveX ([#1642](https://github.com/Devolutions/IronRDP/issues/1642)) ([205fe038cc](https://github.com/Devolutions/IronRDP/commit/205fe038cc693598adf803fe181526b789b2ec3d)) + + Add the client MS-RDPEAI capture path on top of hardened RDPSND + playback: connector CFG + static channel wiring, CPAL PCM capture + backend, ironrdp-client --audio-capture, and ActiveX + AudioCaptureRedirectionMode. + + PCM capture only accepts encode formats that match the Open capture + stream, rejects non-16-bit capture (Data PDU size contract), and gates + the capture backend behind ironrdp-rdpsnd-native/capture. + + Depends on #1648 (playback). + +### Bug Fixes + +- Preserve bulk compression across reactivation ([#1474](https://github.com/Devolutions/IronRDP/issues/1474)) ([8fcffb9e8f](https://github.com/Devolutions/IronRDP/commit/8fcffb9e8f1a2c468321c05a56ec96144316c90a)) + + Any session that reactivates (Deactivate All β†’ re-activate) loses bulk + decompression and dies right after. Windows consoles reactivate right + after logon, and compression is on by default, so this hits pretty + easily. + + The reactivation path rebuilt the FastPath processor with + [`bulk_decompressor: + None`](https://github.com/Devolutions/IronRDP/blob/079b4842/crates/ironrdp-client/src/rdp.rs#L988). + After that every compressed update got parsed as a raw bitmap: + + ``` + Received compressed FastPath data but no decompressor is configured + BitmapData decode NotEnoughBytes: received 1662, expected 17134 + ``` + +- [**breaking**] Always own a bulk decompressor for FastPath updates ([#1255](https://github.com/Devolutions/IronRDP/issues/1255)) ([0dc0194418](https://github.com/Devolutions/IronRDP/commit/0dc0194418375d504a8041b75ba250dc8eeb21ad)) + + ## Summary + + - A compressed FastPath update is dropped whenever the client did not + negotiate compression, because the decompressor is only built when a + compression type was negotiated. Servers send compressed updates + regardless, for example on a full-frame redraw after a resize, and the + session then fails. Closes #1193. + - The negotiated type is the wrong thing to condition on. It describes + what the client would send, and nothing in `ironrdp-session`, + `ironrdp-client`, `ironrdp-web` or the FFI ever compresses outbound. On + the receive path `BulkCompressor` holds a context per algorithm and + `decompress` selects one per update from the packet's own type bits, so + a decompressor built with any type decodes all of them. + - The `Processor` now owns the decompressor and builds it on the first + update that needs one. `ProcessorBuilder` has no corresponding field, so + there is no `None` a consumer can pass and no path that drops a + compressed update. + - On demand rather than at construction because `ironrdp-web` hardcodes + `compression_type: None` in `build_config` and so never negotiates + compression. Constructing eagerly would charge every web session for a + full set of algorithm contexts, and the two XCRUSH history buffers alone + are 2 MB each, for a decompressor most of those sessions never use. That + consumer is also the one most exposed to this bug, for the same reason. + - `BulkCompressor::new` is now infallible. Its only failure path was a + self-check over NCRUSH's static Huffman tables, a compile-time + invariant, now a `debug_assert`. + + ## Relationship to #1474 + + #1474 is kept, not reverted. `ActiveStage::reactivate` is adopted as the + reactivation entry point at all four call sites it introduced: native + client, web, FFI and the e2e test. + + What this PR removes is the `compression_type` retained on `ActiveStage` + and the `make_bulk_decompressor` helper, because an on-demand + decompressor makes both unnecessary. `reactivate` keeps its behaviour + and loses only the compression plumbing. + + #1474 closed the reactivation instance of #1193, where a rebuild passed + `None` and silently disabled decompression for the rest of the session. + The general case is still open on master: when compression was never + negotiated the retained type is `None`, `make_bulk_decompressor` returns + `None`, and every compressed update takes the drop path in + `fast_path.rs` for the lifetime of the session. Conditioning on the + negotiated type gates the ability to receive on what was negotiated to + send, and nothing sends. + + The evidence that removing the field is safe is #1474's own test. + `test_reactivation_processes_compressed_fastpath_updates` passes + unchanged with `compression_type` gone from the builder: the rebuilt + processor decompresses because every processor can, not because a type + was carried across the rebuild. + + ## Validation + + `cargo xtask check fmt/lints/tests/typos/locks` all pass. + + The gated regression test is + `testsuite-core/tests/session/fast_path.rs`, which renders the same + bitmap update plain and bulk-compressed through fresh processors and + asserts identical framebuffers. #1474's + `test_reactivation_processes_compressed_fastpath_updates` in + `testsuite-extra` passes unchanged. + + There is also an inline test in `fast_path.rs` pinning the allocation + invariant, that no contexts are built until an update needs them. Note + that `ironrdp-session` sets `[lib] test = false`, so inline tests in + this crate are not run by `cargo test --workspace`; it runs under `cargo + test -p ironrdp-session --lib`. + + ## Notes + + - This addresses the four points from the 2026-06-24 review. Point 4, + that the `Option` is misleading, is the shape of this change: it is gone + from the public API, and the private one that remains carries no + implication that a consumer could choose not to decompress. Point 1, + whether a cold `Rdp61` context decodes `RDP40` and `RDP50` updates + correctly, is a non-issue: `decompress` selects the algorithm per update + through `CompressionType::from_flags` against per-algorithm receive + contexts, so the construction-time type never constrains the receive + path. Point 3, silent degradation if the constructor fails, is removed + by making `new` infallible. Point 2 is the tests above. + - Breaking across two crates, hence the `fix(bulk,session)!` scope: + `ProcessorBuilder` loses `bulk_decompressor`, `ActiveStageBuilder` loses + `compression_type`, and `ironrdp_bulk::BulkCompressor::new` returns + `Self`. + - Incidental: `ironrdp-session` no longer exposes any `ironrdp_bulk` + type in its public API, so that dependency's lack of a `# public` marker + in `Cargo.toml` is now correct. + +- Make certificate validation explicit ([#1520](https://github.com/Devolutions/IronRDP/issues/1520)) ([f1d53c78d3](https://github.com/Devolutions/IronRDP/commit/f1d53c78d390de1c6778773cdc859d59901466f0)) + + IronRDP deployments commonly use self-signed or private-CA certificates. + This keeps the historical permissive behavior for unmodified callers + while making platform-root and server-name validation an explicit + opt-in. + + ## Approach + + - Keep `upgrade` and `ConfigBuilder` defaults compatible with existing + self-signed endpoints, including the prior native-TLS SNI behavior. + - Expose `CertificateValidation::Strict` for callers that require normal + certificate-chain and hostname validation. + - Retain the Rustls callback path for certificate pinning or other + explicit exception decisions; configuring a callback selects strict + validation before invoking it. + - Preserve CredSSP's existing public-key binding and disabled TLS + resumption behavior. + + MS-CSSP section 3.1.5 does not require a common trusted CA root and + permits servers to use self-signed certificates, so strict verification + cannot be introduced as a transparent default. + + ## Validation + + - `cargo xtask check fmt -v` + - `cargo xtask check lints -v` + - Focused Rustls default/strict/callback runtime test + - Focused native-TLS default/strict runtime test + + --------- + +- Share bulk decompression across output paths ([#1518](https://github.com/Devolutions/IronRDP/issues/1518)) ([6151e21bf5](https://github.com/Devolutions/IronRDP/commit/6151e21bf58b7297e9b4abc2167aa36fc2ba77e4)) + + Bulk compression state is stream-wide, but Fast-Path and slow-path + outputs previously used separate or missing decompression paths. This + could corrupt history-dependent server updates or leave negotiated + slow-path compression undecodable. + + This change owns the negotiated bulk decompressor in `ActiveStage` and + passes it to both X.224 and Fast-Path processing. It retains Share Data + compression metadata through the PDU context, resets decompression + history on reactivation, and initializes consumers from the connection's + negotiated compression type. + + Fast-Path now decompresses each fragment before reassembly so + compression flags apply at packet boundaries. Failures expose bounded + protocol metadata without retaining remote payloads or decoder details. + + Tests cover Share Data metadata propagation, slow-path decompression + behavior, fragmented Fast-Path reassembly and bounded errors, and + compressed Fast-Path updates after reactivation. + + --------- + +- Recover from malformed bitmap updates ([#1521](https://github.com/Devolutions/IronRDP/issues/1521)) ([20e2d414e5](https://github.com/Devolutions/IronRDP/commit/20e2d414e5ac060db25a100ed219f417e19f79b2)) + + ## Summary + - safely discard malformed bitmap and pointer updates without + terminating the session + - request at most one capability-gated full redraw per activation + - propagate Refresh Rect and Suppress Output support through the + connector and generic client + - retain FFI compatibility and focused malformed-update regression + coverage + + ## Stack + Depends on `copilot/fix-session-share-bulk-decompression` (`47270c2a`). + + ## Validation + - `cargo fmt --all -- --check` + - `cargo test -p ironrdp-session --lib` + - `cargo test -p ironrdp-error --lib` + - `cargo check -p ironrdp-connector` + - `cargo check -p ironrdp-client --features native-tls` + - `cargo check -p ffi --features ironrdp/native-tls` + + --------- + +- [**breaking**] Replace DVC wrappers with typed accessors ([#1377](https://github.com/Devolutions/IronRDP/issues/1377)) ([d43ecf9a54](https://github.com/Devolutions/IronRDP/commit/d43ecf9a54363d37e0c485a1e9e73da0d47ae540)) + + Follow-up to #1368. This is not urgent; review whenever the DVC API + direction is worth revisiting. + + Rework DVC channel access APIs so callers can recover a typed processor + together with its dynamic channel id, without exposing internal channel + wrapper types. + + - Add typed borrowed DVC accessors carrying both channel id and + processor borrow for `DrdynvcClient`. + - Keep dynamic channel wrapper types private. + - Align client listener/registration APIs on `DvcClientProcessor`. + +### Build + +- Bump the crypto group across 1 directory with 3 updates ([#1449](https://github.com/Devolutions/IronRDP/issues/1449)) ([e1725e8c8a](https://github.com/Devolutions/IronRDP/commit/e1725e8c8a581b83835647b6ee563a5b3f6c7a1b)) + + + ## [[0.1.0](https://github.com/Devolutions/IronRDP/releases/tag/ironrdp-client-v0.1.0)] - 2026-07-10 Initial release. diff --git a/crates/ironrdp-client/Cargo.toml b/crates/ironrdp-client/Cargo.toml index f23bcfb4c..5b607308f 100644 --- a/crates/ironrdp-client/Cargo.toml +++ b/crates/ironrdp-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-client" -version = "0.1.0" +version = "0.2.0" readme = "README.md" description = "Portable RDP client engine without GPU acceleration" edition.workspace = true @@ -59,13 +59,13 @@ all = [ [dependencies] # Protocols (core features always on) -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public -ironrdp-connector = { path = "../ironrdp-connector", version = "0.10" } # public -ironrdp-session = { path = "../ironrdp-session", version = "0.11" } # public -ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.9" } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public +ironrdp-connector = { path = "../ironrdp-connector", version = "0.11" } # public +ironrdp-session = { path = "../ironrdp-session", version = "0.12" } # public +ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.10" } # public ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.8" } ironrdp-echo = { path = "../ironrdp-echo", version = "0.4" } ironrdp-rdpei = { path = "../ironrdp-rdpei", version = "0.1" } @@ -73,20 +73,20 @@ ironrdp-tls = { path = "../ironrdp-tls", version = "0.2" } # public ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.10", features = ["reqwest"] } ironrdp-rdcleanpath = { path = "../ironrdp-rdcleanpath", version = "0.2" } ironrdp-vmconnect = { path = "../ironrdp-vmconnect", version = "0.1", optional = true } -ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.1" } +ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.2" } ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } # public ironrdp-rail = { path = "../ironrdp-rail", version = "0.1" } # Optional protocol crates (activated by features above) ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.7", optional = true } # public -ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.7", optional = true } # public -ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.9", optional = true } +ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.8", optional = true } # public +ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.10", optional = true } ironrdp-rdpeai = { path = "../ironrdp-rdpeai", version = "0.1", optional = true } # Optional backend crates (activated by features above) ironrdp-rdpsnd-native = { path = "../ironrdp-rdpsnd-native", version = "0.7", optional = true } ironrdp-cliprdr-native = { path = "../ironrdp-cliprdr-native", version = "0.7", optional = true } -ironrdp-mstsgu = { path = "../ironrdp-mstsgu", version = "0.0.1", optional = true } +ironrdp-mstsgu = { path = "../ironrdp-mstsgu", version = "0.0.2", optional = true } ironrdp-dvc-pipe-proxy = { path = "../ironrdp-dvc-pipe-proxy", version = "0.5", optional = true } # Logging diff --git a/crates/ironrdp-cliprdr-format/CHANGELOG.md b/crates/ironrdp-cliprdr-format/CHANGELOG.md index 8af3400ce..b10fcdd3f 100644 --- a/crates/ironrdp-cliprdr-format/CHANGELOG.md +++ b/crates/ironrdp-cliprdr-format/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-format-v0.2.0...ironrdp-cliprdr-format-v0.2.1)] - 2026-08-13 + + + ## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-format-v0.1.4...ironrdp-cliprdr-format-v0.2.0)] - 2026-05-27 ### Build diff --git a/crates/ironrdp-cliprdr-format/Cargo.toml b/crates/ironrdp-cliprdr-format/Cargo.toml index 54c44fdd1..426c868e4 100644 --- a/crates/ironrdp-cliprdr-format/Cargo.toml +++ b/crates/ironrdp-cliprdr-format/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-cliprdr-format" -version = "0.2.0" +version = "0.2.1" readme = "README.md" description = "CLIPRDR format conversion library" edition.workspace = true @@ -17,7 +17,7 @@ doctest = false test = false [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["std"] } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["std"] } # public png = "0.18" [lints] diff --git a/crates/ironrdp-cliprdr-native/CHANGELOG.md b/crates/ironrdp-cliprdr-native/CHANGELOG.md index 5bf69398c..6c2cf5933 100644 --- a/crates/ironrdp-cliprdr-native/CHANGELOG.md +++ b/crates/ironrdp-cliprdr-native/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.7.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-native-v0.7.0...ironrdp-cliprdr-native-v0.7.1)] - 2026-08-13 + + + ## [[0.7.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-native-v0.6.0...ironrdp-cliprdr-native-v0.7.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-cliprdr-native/Cargo.toml b/crates/ironrdp-cliprdr-native/Cargo.toml index c4c55328c..af145e9ab 100644 --- a/crates/ironrdp-cliprdr-native/Cargo.toml +++ b/crates/ironrdp-cliprdr-native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-cliprdr-native" -version = "0.7.0" +version = "0.7.1" readme = "README.md" description = "Native CLIPRDR static channel backend implementations for IronRDP" edition.workspace = true @@ -18,7 +18,7 @@ test = false [dependencies] ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.7" } # public -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } tracing = { version = "0.1", features = ["log"] } [target.'cfg(windows)'.dependencies] diff --git a/crates/ironrdp-cliprdr/CHANGELOG.md b/crates/ironrdp-cliprdr/CHANGELOG.md index c2c748d09..c9050e359 100644 --- a/crates/ironrdp-cliprdr/CHANGELOG.md +++ b/crates/ironrdp-cliprdr/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.7.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-v0.7.0...ironrdp-cliprdr-v0.7.1)] - 2026-08-13 + + + ## [[0.7.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-cliprdr-v0.6.0...ironrdp-cliprdr-v0.7.0)] - 2026-07-10 ### Features diff --git a/crates/ironrdp-cliprdr/Cargo.toml b/crates/ironrdp-cliprdr/Cargo.toml index a4d4f6ad3..0e8a931af 100644 --- a/crates/ironrdp-cliprdr/Cargo.toml +++ b/crates/ironrdp-cliprdr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-cliprdr" -version = "0.7.0" +version = "0.7.1" readme = "README.md" description = "CLIPRDR static channel for clipboard implemented as described in MS-RDPECLIP" edition.workspace = true @@ -22,8 +22,8 @@ test = false __test = ["dep:visibility"] [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public tracing = { version = "0.1", features = ["log"] } bitflags = "2.11" diff --git a/crates/ironrdp-connector/CHANGELOG.md b/crates/ironrdp-connector/CHANGELOG.md index 65a88959b..924ba0aac 100644 --- a/crates/ironrdp-connector/CHANGELOG.md +++ b/crates/ironrdp-connector/CHANGELOG.md @@ -6,6 +6,686 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.11.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-connector-v0.10.0...ironrdp-connector-v0.11.0)] - 2026-08-13 + +### Security + +- [**breaking**] Implement multitransport bootstrapping handshake ([#1098](https://github.com/Devolutions/IronRDP/issues/1098)) ([e45fbfe0f5](https://github.com/Devolutions/IronRDP/commit/e45fbfe0f597011706e77fc174ca14e5e9d435b9)) + + ## Summary + + Makes the `MultitransportBootstrapping` state functional instead of a + no-op + pass-through. After licensing the server may send 0, 1, or 2 Initiate + Multitransport Request PDUs before capabilities exchange. Each one is + surfaced + to the application, which establishes UDP transport (RDPEUDP2 + TLS + + RDPEMT) + or declines, and the connector reports the outcome back to the server. + + ## API + + Mirrors the existing `should_perform_X()` pause-point pattern used by + TLS + upgrade and CredSSP, but uses `complete_X()` / `skip_X()` rather than + `mark_X_as_done()` because completion carries result data: + + - `should_perform_multitransport()`: true while a request awaits an + outcome + - `multitransport_request()`: the request awaiting an outcome, or `None` + - `complete_multitransport(result, output)`: report the outcome, resume + - `skip_multitransport(output)`: decline, resume + + `complete_multitransport` accepts a `MultitransportResult` (a `Success` + / + `Failure(hresult)` enum) rather than a caller-built response PDU. The + connector + builds the response internally from the stored request ID. + + Requests are surfaced one at a time rather than as a batch. There is no + end + marker for the set, and MS-RDPBCGR 3.2.5.15.1 requires the client to act + on a + request as soon as it decodes one, so waiting to learn how many are + coming is + not an option the protocol offers. `should_perform_multitransport()` can + therefore come round twice; the caller answers reliable and lossy + separately. + + ## Approach + + **Routing.** Requests arrive on the negotiated MCS message channel + (2.2.15.1) and the Demand Active on the I/O channel, so the channel + decides + which is which. The message channel also carries NetworkAutoDetect since + #1348, + so a decode still confirms what arrived there, but the I/O channel is + never + speculatively decoded as multitransport. A PDU on neither channel is an + error. + + For the decode to be a sound confirmation the request decoder must + reject a + Demand Active, so this PR also tightens `MultitransportRequestPdu` to + require + the exact `SEC_TRANSPORT_REQ` security-header flag. + + **Yielding.** Each request is surfaced the moment it decodes. Responding + returns the connector to `MultitransportBootstrapping` to read whatever + comes + next, which may be a second request or the Demand Active. Nothing is + buffered + and nothing is replayed: when the request is surfaced the Demand Active + has not + arrived yet. + + **Soft-Sync.** The Initiate Multitransport Response is the Soft-Sync + signalling path (2.2.15.2), permitted only when both peers advertised + `SOFTSYNC_TCP_TO_UDP` in their GCC `MultiTransportChannelData`. The + server's + block is retained from the GCC exchange and checked against the client's + configured flags. One rule covers both paths: + + - Soft-Sync negotiated: always respond, `S_OK` or `E_ABORT`, including + on + `skip_multitransport()`, which 3.2.5.15.1 requires. Both the async and + blocking drivers skip automatically, so without this every default + client + leaves a compliant server waiting. + - Not negotiated: never respond. The outcome is reported in band on the + new + transport, and putting anything on the main channel would be the + violation. + + The response goes on the message channel per 2.2.15.2 and 3.2.5.15.2. If + Soft-Sync was negotiated but no message channel exists the connector + errors + rather than falling back to the I/O channel, and that check runs before + the + pending state is taken, so the caller is left with a connector it can + still + inspect or decline from. + + ## Wire behaviour + + On the wire TCP and UDP negotiation happen in parallel: the UDP + transport is + established alongside the ongoing TCP handshake, and its completion + signals the + dynamic-channel layer that subsequent channels may migrate to UDP. The + connector's API yield point here is a Rust affordance, not a + spec-mandated TCP + pause. Thanks to @hardening for the correction. + + ## Tests + + Connector state-machine tests in `ironrdp-testsuite-core` drive the + public API + with the shared `SERVER_DEMAND_ACTIVE` fixture: + + - a request is surfaced on arrival, without waiting for a following PDU + (regression test for the stall); + - responding returns to bootstrapping so a second request is read + normally; + - a third request is rejected per the 2.2.15.1 cap; + - a Demand Active on the I/O channel ends bootstrapping; + - the response targets the message channel, decoded back off the wire; + - a `Failure` result is carried through; + - `skip` sends `E_ABORT` under Soft-Sync, and nothing without it; + - `complete` emits nothing without Soft-Sync but still resumes; + - a failed response leaves the connector in `MultitransportPending`, + still able + to report or decline, rather than `Consumed`; + - `complete` / `skip` outside `MultitransportPending` error; + - a Demand Active's user data does not decode as a + `MultitransportRequestPdu` + (regression test for the decoder tightening above). + +- [**breaking**] Support session resume via the auto-reconnect cookie ([#1501](https://github.com/Devolutions/IronRDP/issues/1501)) ([74b3365c1f](https://github.com/Devolutions/IronRDP/commit/74b3365c1f98c0da6feed7507779c67e1b8e6d08)) + + > **Rebased onto post-#1522 master.** #1509 landed the server half of + #1508 while this was open, including the `ClientAutoReconnect` + structure. This PR no longer declares it; it extends it, and picks up + the parts #1509 did not build. + + ## What + + The client half of automatic reconnection. The session layer surfaces + the Server Auto-Reconnect Cookie, `ironrdp-pdu` derives and verifies the + client's response to it, and the connector sends that response when + resuming a session. + + ## Why + + A client whose connection drops ungracefully can reattach to its session + instead of making the user log on again, provided it returns the cookie + the server issued during logon ([MS-RDPBCGR] 1.3.1.5). + + #1509 built the server side of that: it validates a returning + `ARC_CS_PRIVATE_PACKET` and rotates the random. Nothing answers it. + `ironrdp-session` decodes the cookie and drops it, `ironrdp-connector` + has no way to send one back, and `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` still sits in + `ironrdp-client`. So `ironrdp-client` cannot resume a session against + `ironrdp-server`, and the validation #1509 added has no in-tree + counterpart to exercise it. + + The wire encoding was already there. `ExtendedClientOptionalInfo` + carries, encodes and decodes a 28-byte `autoReconnectCookie` and its + builder already had a `reconnect_cookie` step; `ServerAutoReconnect` + already decoded; #1509 added `ClientAutoReconnect` and its decode. + Nothing connected them. + + ## The three parts + + **Receive.** `SaveSessionInfo` now also surfaces the cookie, as + `ProcessorOutput::AutoReconnectCookie` and + `ActiveStageOutput::AutoReconnectCookie`. #1522 added a `SaveSessionInfo + { logon_complete }` output on that same handler; the two coexist rather + than compete, since both are read off one PDU and neither supersedes the + other. The handler emits the logon notification unconditionally and + appends the cookie when one is present, and a test pins that surfacing + the cookie does not suppress the notification. #1509's server replaces + the cookie whenever a client connects and again hourly ([MS-RDPBCGR] + 3.3.6.2), so this can arrive more than once in a session and the + consumer keeps the most recent. + + **Derive.** `ClientAutoReconnect::from_server_cookie` implements + [MS-RDPBCGR] 5.5: + + > The auto-reconnect random is used to key the HMAC function + ([RFC2104]), which uses MD5 as the iterative hash function. The security + verifier is derived by applying the HMAC to the client random received + in Step 3. + > + > `SecurityVerifier = HMAC(AutoReconnectRandom, ClientRandom)` + > + > When Enhanced RDP Security is in effect the client random value is not + generated (section 5.3.2). In this case, for the purpose of generating + the security verifier, the client random is assumed to be an array of 32 + zero bytes. + + IronRDP implements no Standard RDP Security path (there is no Security + Exchange PDU), so the zero-client-random case is the only one that + arises. As 5.5 notes, that makes the verifier constant for a given + cookie, so it proves possession of the cookie and nothing more; session + security comes from the outer TLS/CredSSP handshake. + + @clintcan independently confirmed this construction against real + **mstsc** while validating #1509 + ([comment](https://github.com/Devolutions/IronRDP/pull/1509#issuecomment-5151200681)): + a Windows client's `ARC_CS_PRIVATE_PACKET` verifies against + `HMAC-MD5(random_bits, [0u8; 32])`. That is the same derivation + implemented here, so the two halves interoperate with Microsoft's client + and not only with each other. + + **Send.** `ClientConnector::with_auto_reconnect_cookie` takes the cookie + last received and makes the connector put the derived Client + Auto-Reconnect Packet ([MS-RDPBCGR] 2.2.4.3) in the Client Info PDU. + Absent, that PDU is byte-for-byte what it was. + + Unlike the server packet, this structure has no enclosing logon-info + field header, so it encodes to exactly the 28 bytes the cookie field + expects. `to_bytes` writes that layout directly rather than going + through `Encode`, so filling a fixed-size field has no error path a + caller must handle; a test pins the two to agree. + + ## One derivation, not two + + Putting `from_server_cookie` in `ironrdp-pdu` would leave the workspace + with two implementations of 5.5, since #1509 added a private HMAC to + `ironrdp-server`. So `ClientAutoReconnect` also gains `verify`, and the + server routes through it. + + `verify` keeps the constant-time comparison the server had. The verifier + is the whole credential, so a comparison returning early on the first + differing byte would let a peer recover it a byte at a time from the + timing; the session identifier is not secret and is compared normally. + `ironrdp-server` keeps the policy around the check, which cookies are + live and whether the security protocol permits auto-reconnect, and drops + its `hmac` and `md-5` dependencies. `hmac` moves to `ironrdp-pdu` as + `default-features = false`; the crate's full feature powerset still + checks clean, including `--no-default-features`. + + I would rather not have reached into `ironrdp-server` in a + `pdu,session,connector` change, but the alternative was shipping the + duplicate and filing a follow-up to remove it, which is a worse trade + for reviewer time. + + ## Tests that were not running + + That move also rehomes the known-answer tests @clintcan contributed on + #1509. They went in as an inline `#[cfg(test)]` module in + `crates/ironrdp-server/src/server.rs`, and that crate sets `[lib] test = + false`, so they have never executed in CI. They now live in + `ironrdp-testsuite-core` against the public API, where CI runs them: his + HMAC-MD5 reference vector is kept as a second vector alongside a + differently-keyed one, plus the cases for a tampered verifier and a + mismatched logon ID. + + Worth flagging separately: `ironrdp-server` is not alone. + `ironrdp-agent`, `ironrdp-session` and `ironrdp-web` also set `[lib] + test = false` and between them carry 16 files of inline `#[cfg(test)]` + modules that CI never runs. That is out of scope here, but I am happy to + open an issue if it would be useful. + + ## Breaking changes + + `ActiveStageOutput` and `x224::ProcessorOutput` gain a variant, and + `ClientConnector` gains a public field, so exhaustive matches and struct + literals need updating. + + Confirmed with `cargo-semver-checks` against the merge-base: those three + are the only findings this branch introduces. The others it reports on + `master` today (`ShareDataPdu::Compressed` and the `ShareDataCtx` fields + from #1518, `ProcessorBuilder.bulk_decompressor` from #1518, + `ServerEvent::SetAutoReconnectCookie` from #1509) are present on + `master` unchanged. The `ironrdp-pdu` additions are additive. + + ## Scope + + This is the library half. `ironrdp-client`, `ironrdp-web` and the FFI + bindings gain an arm for the new output but none of them reconnect + automatically yet; that is the remaining part of #271, and the existing + `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` in `ironrdp-client` marks where it goes. + + I kept receive, derive and send together deliberately. Split up, none of + them is usable on its own: without the receive half there is no way to + obtain a cookie, and without the send half there is nothing to do with + one. + + ## Tests + + Thirteen, all in `ironrdp-testsuite-core`. + + On the packet and the derivation: the `SecurityVerifier` matches two + independently computed HMAC-MD5 vectors of 32 zero bytes under different + keys, so the tests pin the derivation rather than restating the code; + the logon ID carries over from the server cookie; the encoding matches + the 2.2.4.3 field layout byte for byte with `cbLen` fixed at `0x1C`; + `to_bytes` agrees with `Encode`; it round-trips; and it rejects both a + wrong packet length and an unknown version. + + On verification: a derived answer is accepted, a single flipped byte in + the verifier is rejected, a correct verifier under a different logon ID + is rejected, and an answer derived from a different random is rejected. + + On the surfacing path: a Save Session Info PDU framed the way a server + sends it, through the real x224 processor, yields an + `AutoReconnectCookie` carrying the right logon ID and random bits, + alongside #1522's logon notification rather than in place of it; and one + without a cookie surfaces no cookie. + + ## Verification + + `cargo xtask check fmt/lints/tests/typos/locks` all pass on 1.94.1, + including a `fuzz/` build before the lock check. + + ## Note + + #1496 also touches the `ClientAutoReconnect` declaration. Whichever of + the two lands second needs a one-line rebase on the derive attribute; + happy to take that in either order. + +- Connect to Windows Sandbox named pipes ([#1580](https://github.com/Devolutions/IronRDP/issues/1580)) ([39b020343d](https://github.com/Devolutions/IronRDP/commit/39b020343d962962bfbefc89939be64d5c716196)) + + Windows Sandbox's default attach path is a local named pipe carrying + plain TPKT/X.224 with PROTOCOL_RDP and ENCRYPTION_LEVEL_NONE, not + TCP:3389 or VMConnect. Allow the connector and client to complete that + sequence only via an explicit opt-in (`enable_standard_rdp_security`; + NamedPipe enables it), and teach ironrdp-agent to resolve pipe path and + guest credentials from WindowsSandboxServer after `wsb start`. + + Adds Transport::NamedPipe, ironrdp_named_pipe/ironrdp_sandbox_id + properties, sandbox list/config/stop CLI helpers via an in-process + h2/gRPC client on the per-user `\\.\pipe\wsandbox\{guid}` pipe (no .NET + helper), and connect --sandbox-id / --sandbox-pipe. Sandbox-derived + properties are the merge base; explicit .rdp/--prop/flags override them + while NamedPipe TLS/CredSSP stay forced off. Local :2179+PCB remains + unsupported. + +### Features + +- Expose the server's Input capability flags on ConnectionResult ([#1488](https://github.com/Devolutions/IronRDP/issues/1488)) ([9bcf13438c](https://github.com/Devolutions/IronRDP/commit/9bcf13438cb068b53a8cb0dc23477c498727d49f)) + + Per [MS-RDPBCGR] 2.2.8.1.2, a client must not send fast-path input + events unless the server advertised `INPUT_FLAG_FASTPATH_INPUT` or + `INPUT_FLAG_FASTPATH_INPUT2` in its Input Capability Set. Today the + connector discards the server's capability sets after Demand Active, so + client code has no way to honour that requirement. + + This PR captures the server's Input capability flags during capabilities + exchange, carries them through the `ConnectionFinalization`/`Finalized` + activation states (so they are refreshed correctly across a + Deactivation-Reactivation Sequence too), and surfaces them as + `ConnectionResult::input_flags`. The session layer can then choose + between fast-path and slow-path input per server. + + ### Motivation / real-world interop + + This is not theoretical: VirtualBox's VRDP server closes the connection + outright on receiving a fast-path input PDU β€” its `VBox.log` reports + + ``` + VRDP: Network packet length is incorrect 0x0004. Closing connection. + ``` + + (a single fast-path scancode event is a 4-byte packet). VirtualBox never + advertises fast-path input; its Demand Active offers + `InputFlags(SCANCODES)` alone. mstsc and FreeRDP honour the negotiation + and fall back to slow-path `TS_INPUT_PDU`s, which is why they work + against VRDE. + + Haven (Android RDP client built on IronRDP) has been shipping this exact + change as a vendored-connector patch since v5.86.1, with the slow-path + fallback keyed off `ConnectionResult::input_flags`. Verified against a + real VirtualBox 7.2.6 VRDE server: before the gate, the first arrow-key + press killed the session with the log line above; with the gate, + extended input sessions run clean, and a fast-path-capable server on the + same host still takes the fast-path branch. (Discussed in #1158; this is + the third and last piece Haven carries in its connector fork, alongside + #1237 and #1472.) + + ### Changes + + - `connection_activation.rs`: capture `input_flags` from the + `CapabilitySet::Input` in Server Demand Active (empty if absent); add + the field to `ConnectionActivationState::{ConnectionFinalization, + Finalized}`. + - `connection.rs`: add `ConnectionResult::input_flags`, populated from + the `Finalized` state. + - Call sites in `ironrdp-client`, `ironrdp-web`, ffi, and the e2e test + updated for the new variant field (all currently ignore it). + + ### Testing + + - Two new integration tests in + `ironrdp-testsuite-core/tests/session/connection_activation.rs`: the + fixture's Demand Active yields `SCANCODES | MOUSEX | UNICODE | + FASTPATH_INPUT_2` in the `ConnectionFinalization` state, and a Demand + Active with the Input capability stripped yields `InputFlags::empty()`. + - `cargo check --workspace --all-targets`, `cargo clippy --workspace + --all-targets`, and `cargo fmt --check` are clean; the 7 activation + tests pass. + +- Support runtime-defined static virtual channels ([#1517](https://github.com/Devolutions/IronRDP/issues/1517)) ([8b4c483ba0](https://github.com/Devolutions/IronRDP/commit/8b4c483ba0c900a8de0b2718347754f56dd363ba)) + + ## Summary + - add keyed runtime-defined static-channel registration, lookup, and + negotiated ID attachment + - enforce the static-channel limit and reject malformed SVC fragment + sequences + - wire generic connector, acceptor, and session name-based dispatch + support + + ## Testing + - `cargo test -p ironrdp-testsuite-core --test integration_tests_core + svc::` + - `cargo clippy -p ironrdp-testsuite-core --test integration_tests_core + -- -D warnings` + + --------- + +- Expose generic session configuration and lifecycle APIs ([#1522](https://github.com/Devolutions/IronRDP/issues/1522)) ([57b1366650](https://github.com/Devolutions/IronRDP/commit/57b13666506dc40c15b4c4702d35150beee99133)) + + ## Summary + - expose generic client configuration for connection metadata, + compression, shell/work directory, audio, and runtime static-channel + factories + - add bounded input delivery with independent close cancellation, host + clipboard plumbing, lifecycle events, and Display Control resize + readiness/fallback handling + - update agent, viewer, web, FFI, examples, and tests for the generic + APIs + + ## Stack dependencies + This PR is stacked on `copilot/tls-validation-policy` (`b2bbcece`), + which already includes the merged runtime static-channel support from + `master`. It intentionally contains no TLS implementation/policy, + ActiveX/COM, SVC implementation, decompression, or bitmap-recovery + changes. + + ## Validation + - `cargo fmt --check --all` + - `cargo xtask check tests --no-run -v` + - `cargo xtask check lints -v` + - `cargo test -p ironrdp-client --lib --features rustls` + - `cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi` + + --------- + +- Surface ShareDataPdu variant in unexpected-PDU errors ([#1329](https://github.com/Devolutions/IronRDP/issues/1329)) ([df1f7e7faa](https://github.com/Devolutions/IronRDP/commit/df1f7e7faaf068435bfbbe1efcb4a8800ebb3d9f)) + + ## Summary + + - Addresses ask 1 of #1232: when the server sends a + `ShareControlPdu::Data` wrapping an unexpected `ShareDataPdu`, the three + error sites in `headers.rs` and `connection_activation.rs` now drill + into the `Data` wrapper and surface the inner variant name instead of + reporting only `"Data"`. + - For `ServerSetErrorInfo` specifically (the asker's high-value case), + the existing `ErrorInfo::description()` is appended so callers can see + why the server rejected the session without substring matching on the + `Reason` string. + - New `pub fn describe_unexpected_share_control_pdu` in `headers.rs` + centralizes the formatting; `decode_share_data`, `decode_io_channel`, + and `ConnectionActivation::CapabilitiesExchange` all route through it. + - Non-`Data` variants continue to use the outer `as_short_name()`, so + diagnostics for `ServerDeactivateAll` and `ClientConfirmActive` are + preserved verbatim. + + ## Validation + + - Three unit tests in `headers::tests` cover the helper: a non-`Data` + variant (`ServerDeactivateAll`), a `Data` wrapper around a + non-SetErrorInfo inner (`Update(Vec::new())`), and a `Data` wrapper + around `ServerSetErrorInfo` carrying + `ProtocolIndependentCode::ServerDeniedConnection`. + - `cargo xtask check fmt/lints/tests/typos/locks` all pass. + + ## Notes + + - Helper is `pub`, not `pub(crate)`: it has to be, since + `ironrdp-connector`'s `connection_activation.rs` calls it cross-crate. + That adds + `ironrdp_pdu::rdp::headers::describe_unexpected_share_control_pdu` to + `ironrdp-pdu`'s public surface. Additive and non-breaking, confirmed by + `cargo semver-checks --baseline-rev `: no update required + for either `ironrdp-pdu` or `ironrdp-connector`. + - Ask 2 from #1232 (an optional structured `ConnectorErrorKind` variant + for "server rejected at capabilities phase") is intentionally deferred. + The asker framed it as optional and the wire-level information is now + available in the `Reason` string. + - `Refs #1232` rather than `Closes` so the issue stays open while you + decide on ask 2. + +- Add IronRDP ActiveX COM server ([#1523](https://github.com/Devolutions/IronRDP/issues/1523)) ([ee58b7c5f2](https://github.com/Devolutions/IronRDP/commit/ee58b7c5f283ef64be93a8483242f49070c6cdc9)) + + ## Summary + - Add the IronRDP ActiveX COM server and native MSTSC host integration. + - Provide bounded native-host diagnostics, credential-bridge support, + and an AxHost test harness. + - Preserve the minimal client, connector, and error integrations needed + by the control. + + ## Validation + - cargo check -p ironrdp-activex + - Focused ironrdp-error and ironrdp-connector tests + - cargo test -p ironrdp-activex --lib --no-run + - cargo fmt --all -- --check + - cargo xtask check locks -v + + --------- + +- Hyper-V vmconnect support ([#1503](https://github.com/Devolutions/IronRDP/issues/1503)) ([a7cc067d50](https://github.com/Devolutions/IronRDP/commit/a7cc067d5069cbbcb13bae3e0561c0611da3bcf6)) + + Adds Hyper-V VMConnect's direct ordering: PCB β†’ TLS β†’ CredSSP β†’ X.224. + + Enhanced Session is the default (`GUID;EnhancedMode=1`), with + `--vmconnect-basic` for the synthetic console. Kept this separate in + `ironrdp-vmconnect`; no SPN changes. + + Tested against the nested Hyper-V lab: + - Enhanced: `HYBRID_EX`, rendered 1280Γ—720 + - Basic: `HYBRID`, rendered 1280Γ—720 + - `cargo xtask check fmt/lints/tests -v` + + --------- + +- Support connection correlation info ([#1582](https://github.com/Devolutions/IronRDP/issues/1582)) ([c4483617ba](https://github.com/Devolutions/IronRDP/commit/c4483617ba05c31182b58c58be66bd41120a076d)) + + Encode the optional 36-byte X.224 RDP_NEG_CORRELATION_INFO block and + reject malformed negotiation records. + +- Negotiate static channel chunk sizing ([#1622](https://github.com/Devolutions/IronRDP/issues/1622)) ([4e3903fbbe](https://github.com/Devolutions/IronRDP/commit/4e3903fbbef2904505f35e3437a7106807ac5987)) + + Use the validated server VCChunkSize for outgoing static virtual channel + data and retain 1600-byte chunks when it is absent or invalid. + + Apply refreshed values after reactivation across native, web, and FFI + active stages while preserving channel flags. + +- Forward negotiated windowing orders ([#1631](https://github.com/Devolutions/IronRDP/issues/1631)) ([0c3fbe78b4](https://github.com/Devolutions/IronRDP/commit/0c3fbe78b4366533b9fcea046b2b53654e003a72)) + + Preserve Window List support during activation. + Forward validated orders through ActiveStage and the raw FFI output. + Desktop and web consumers retain their existing behavior. + +- Add RemoteApp channel support ([#1637](https://github.com/Devolutions/IronRDP/issues/1637)) ([ab48c6cb8c](https://github.com/Devolutions/IronRDP/commit/ab48c6cb8c017504f8a92799aeb91b821c50a13a)) + + Configure and negotiate RAIL connections, then route its static channel + through the portable client with bounded request queues and server + control events. + +- Wire MS-RDPEAI capture into Windows client and ActiveX ([#1642](https://github.com/Devolutions/IronRDP/issues/1642)) ([205fe038cc](https://github.com/Devolutions/IronRDP/commit/205fe038cc693598adf803fe181526b789b2ec3d)) + + Add the client MS-RDPEAI capture path on top of hardened RDPSND + playback: connector CFG + static channel wiring, CPAL PCM capture + backend, ironrdp-client --audio-capture, and ActiveX + AudioCaptureRedirectionMode. + + PCM capture only accepts encode formats that match the Open capture + stream, rejects non-16-bit capture (Data PDU size contract), and gates + the capture backend behind ironrdp-rdpsnd-native/capture. + + Depends on #1648 (playback). + +### Bug Fixes + +- Surface server error-info disconnect during reactivation ([#1467](https://github.com/Devolutions/IronRDP/issues/1467)) ([f57d38ff74](https://github.com/Devolutions/IronRDP/commit/f57d38ff74624b99ebcc1369c220b646dd261b71)) + + ## Summary + + After a Deactivate-All, a server may end the session (MS-RDPBCGR + 1.3.1.3) instead of reactivating, sending a Set Error Info PDU that + carries the disconnect reason. `ConnectionActivationSequence`'s + Capabilities Exchange step only recognized `ServerDemandActive` (and + skipped `ServerDeactivateAll`), so the Error Info PDU fell through to a + generic "unexpected Share Control PDU" error and the real reason was + lost. + + - Handle `ServerSetErrorInfo` in Capabilities Exchange the way + `ConnectionFinalizationSequence` already does: return a `reason` error + carrying the error-info description. + - `ERRINFO_NONE` is informational, so it is skipped (stay in + Capabilities Exchange, await Demand Active) rather than treated as + fatal, matching the finalization sequence. + + Found while validating the client against GNOME Remote Desktop ([#1446](https://github.com/Devolutions/IronRDP/issues/1446)): + grd ends the session right after activation when its backend screencast + session cannot be created (for example a locked desktop), and the client + surfaced only an opaque error at that point. + + ## Validation + + `cargo xtask check fmt`, `lints`, `tests`, `typos`, `locks` all pass. + Two new integration tests in `tests/session/connection_activation.rs` + cover the disconnect-reason path and the benign `ERRINFO_NONE` path. + + ## Notes + + No wire-format or public-API change: this only improves the error + surfaced on an existing failure path. + +- Recover from malformed bitmap updates ([#1521](https://github.com/Devolutions/IronRDP/issues/1521)) ([20e2d414e5](https://github.com/Devolutions/IronRDP/commit/20e2d414e5ac060db25a100ed219f417e19f79b2)) + + ## Summary + - safely discard malformed bitmap and pointer updates without + terminating the session + - request at most one capability-gated full redraw per activation + - propagate Refresh Rect and Suppress Output support through the + connector and generic client + - retain FFI compatibility and focused malformed-update regression + coverage + + ## Stack + Depends on `copilot/fix-session-share-bulk-decompression` (`47270c2a`). + + ## Validation + - `cargo fmt --all -- --check` + - `cargo test -p ironrdp-session --lib` + - `cargo test -p ironrdp-error --lib` + - `cargo check -p ironrdp-connector` + - `cargo check -p ironrdp-client --features native-tls` + - `cargo check -p ffi --features ironrdp/native-tls` + + --------- + +- Answer connect-time Bandwidth Measure to unblock FreeRDP servers ([#1465](https://github.com/Devolutions/IronRDP/issues/1465)) ([f736b4e8b9](https://github.com/Devolutions/IronRDP/commit/f736b4e8b901f8889435449c26421dd45ec05bf4)) + + ## Summary + + - The connector answers only the RTT auto-detect request at connect time + and returns nothing for the Bandwidth Measure Stop, on the assumption + that skipping it does not stall the sequence. + - That assumption fails for FreeRDP-based servers: GNOME Remote Desktop + blocks in its `AWAIT_BW_RESULT` state until it receives a Bandwidth + Measure Results reply and never proceeds to licensing, so the connection + hangs right after the Client Info PDU. Windows servers tolerate the + omission, which hid it. + - Reply to a connect-time `BandwidthMeasureStop` with a + `BandwidthMeasureResults` PDU carrying the payload size the server + handed us, over a nominal interval. + + ## Scope + + This is the unblock alone. The reported interval is nominal + (`time_delta_ms: 1`) because the sans-I/O layer has no time source: + nothing reaches the connector that says when the bytes arrived. The + figure is an informational QoS hint and the server proceeds on receipt, + which is what unsticks the connection. + + Measuring it properly needs an arrival time threaded down from the I/O + driver, which is a breaking change to `Sequence::step` and does not + belong in a fix aimed at getting FreeRDP servers connecting. It is + #1530, stacked on this branch: it introduces `MonotonicInstant`, has + `Framed` record when each read completed, and replaces the nominal + figure here with the real Start-to-Stop interval and accumulated byte + count. + + Split out at @CBenoit's suggestion in review. + + ## Validation + + - `cargo xtask check fmt/typos/lints/tests/locks` all pass on the pinned + toolchain. + - Regression test: a connect-time Bandwidth Measure Stop produces a + response frame and the auto-detect phase continues. + - Reproduced and fixed live against gnome-remote-desktop 49: before, the + connector stalled after Client Info with grd in `AWAIT_BW_RESULT`; + after, grd proceeds through licensing and DEMAND_ACTIVE to an active + session. + + ## Notes + + - Found while bringing up the client-side Graphics Pipeline against grd + ([#1446](https://github.com/Devolutions/IronRDP/issues/1446)), but it is an independent connector bug affecting any connection + to a FreeRDP-based server, not specific to EGFX. + - Previously depended on #1511 for a zero-`payloadLength` regression + test. #1511 has merged, and that test moved out with the rest of the + measurement work, so there is no dependency left here. + + + ## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-connector-v0.9.0...ironrdp-connector-v0.10.0)] - 2026-07-10 ### Security diff --git a/crates/ironrdp-connector/Cargo.toml b/crates/ironrdp-connector/Cargo.toml index 5507ea81c..d23efe262 100644 --- a/crates/ironrdp-connector/Cargo.toml +++ b/crates/ironrdp-connector/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-connector" -version = "0.10.0" +version = "0.11.0" readme = "README.md" description = "State machines to drive an RDP connection sequence" edition.workspace = true @@ -23,9 +23,9 @@ qoiz = ["ironrdp-pdu/qoiz"] [dependencies] ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public ironrdp-error = { path = "../ironrdp-error", version = "0.2" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["std"] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", features = ["std"] } # public sspi = { version = "0.21", features = ["scard"] } url = "2.5" # public rand = { version = "0.9", features = ["std"] } # TODO: dependency injection? diff --git a/crates/ironrdp-core/CHANGELOG.md b/crates/ironrdp-core/CHANGELOG.md index 33b4c23fa..b5ef97a38 100644 --- a/crates/ironrdp-core/CHANGELOG.md +++ b/crates/ironrdp-core/CHANGELOG.md @@ -6,6 +6,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.3.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-core-v0.2.1...ironrdp-core-v0.3.0)] - 2026-08-13 + +### Features + +- Add NonEmpty ([#1444](https://github.com/Devolutions/IronRDP/issues/1444)) ([bdcc0ceec3](https://github.com/Devolutions/IronRDP/commit/bdcc0ceec3aaa19441917db02c36cd3be2f58465)) + + Add a `NonEmpty` collection guaranteeing at least one element. The + first element (head) is stored inline, so a single-element `NonEmpty` + performs no heap allocation, and `first()` is infallible while `len()` + returns a `NonZeroUsize`, and callers never branch on an "is it empty?" + case. + +### Bug Fixes + +- Rename {Read,Write}Cursor::rewinded into rewound ([#1529](https://github.com/Devolutions/IronRDP/issues/1529)) ([c85b089b46](https://github.com/Devolutions/IronRDP/commit/c85b089b4617176240b41482be65a77c9ad76a07)) + + + ## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-core-v0.2.0...ironrdp-core-v0.2.1)] - 2026-07-10 ### Features diff --git a/crates/ironrdp-core/Cargo.toml b/crates/ironrdp-core/Cargo.toml index 6d6f8a0a0..1a37c4c6d 100644 --- a/crates/ironrdp-core/Cargo.toml +++ b/crates/ironrdp-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-core" -version = "0.2.1" +version = "0.3.0" readme = "README.md" description = "IronRDP common traits and types" edition.workspace = true diff --git a/crates/ironrdp-daemon/Cargo.toml b/crates/ironrdp-daemon/Cargo.toml index 9c32d5101..9ad73ec21 100644 --- a/crates/ironrdp-daemon/Cargo.toml +++ b/crates/ironrdp-daemon/Cargo.toml @@ -13,10 +13,10 @@ keywords.workspace = true categories.workspace = true [dependencies] -ironrdp-client = { path = "../ironrdp-client", version = "0.1", features = ["rustls", "dvc-pipe-proxy", "rdpdr"] } # public -ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.1" } +ironrdp-client = { path = "../ironrdp-client", version = "0.2", features = ["rustls", "dvc-pipe-proxy", "rdpdr"] } # public +ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.2" } ironrdp-input = { path = "../ironrdp-input", version = "0.7" } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } # public ironrdp-rpc = { path = "../ironrdp-rpc", version = "0.1" } # public ironrdp-tls = { path = "../ironrdp-tls", version = "0.2" } diff --git a/crates/ironrdp-displaycontrol/CHANGELOG.md b/crates/ironrdp-displaycontrol/CHANGELOG.md index 0d09e4ba4..81790146f 100644 --- a/crates/ironrdp-displaycontrol/CHANGELOG.md +++ b/crates/ironrdp-displaycontrol/CHANGELOG.md @@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.8.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-displaycontrol-v0.8.0...ironrdp-displaycontrol-v0.8.1)] - 2026-08-13 + +### Bug Fixes + +- Decode the full headered DISPLAYCONTROL_CAPS_PDU ([#1442](https://github.com/Devolutions/IronRDP/issues/1442)) ([3b66961a8b](https://github.com/Devolutions/IronRDP/commit/3b66961a8b2ec5bb2d49175c6970e4a480348b3f)) + + ## Summary + + + ## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-displaycontrol-v0.7.0...ironrdp-displaycontrol-v0.8.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-displaycontrol/Cargo.toml b/crates/ironrdp-displaycontrol/Cargo.toml index f6370acc2..6fdaf9af4 100644 --- a/crates/ironrdp-displaycontrol/Cargo.toml +++ b/crates/ironrdp-displaycontrol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-displaycontrol" -version = "0.8.0" +version = "0.8.1" readme = "README.md" description = "Display control dynamic channel extension implementation" edition.workspace = true @@ -17,9 +17,9 @@ doctest = false test = false [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public tracing = { version = "0.1", features = ["log"] } diff --git a/crates/ironrdp-dvc-com-plugin/CHANGELOG.md b/crates/ironrdp-dvc-com-plugin/CHANGELOG.md index 9cca74fac..3aaf83074 100644 --- a/crates/ironrdp-dvc-com-plugin/CHANGELOG.md +++ b/crates/ironrdp-dvc-com-plugin/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.1.4](https://github.com/Devolutions/IronRDP/compare/ironrdp-dvc-com-plugin-v0.1.3...ironrdp-dvc-com-plugin-v0.1.4)] - 2026-08-13 + + + ## [[0.1.3](https://github.com/Devolutions/IronRDP/compare/ironrdp-dvc-com-plugin-v0.1.2...ironrdp-dvc-com-plugin-v0.1.3)] - 2026-07-10 ### Bug Fixes diff --git a/crates/ironrdp-dvc-com-plugin/Cargo.toml b/crates/ironrdp-dvc-com-plugin/Cargo.toml index 9c25af6a0..6594e28c4 100644 --- a/crates/ironrdp-dvc-com-plugin/Cargo.toml +++ b/crates/ironrdp-dvc-com-plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-dvc-com-plugin" -version = "0.1.3" +version = "0.1.4" readme = "README.md" description = "DVC COM client plugin loader for IronRDP (Windows)" edition.workspace = true @@ -19,9 +19,9 @@ test = false [dependencies] [target.'cfg(windows)'.dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } tracing = { version = "0.1", features = ["log"] } windows = { version = "0.62", features = [ diff --git a/crates/ironrdp-dvc-pipe-proxy/CHANGELOG.md b/crates/ironrdp-dvc-pipe-proxy/CHANGELOG.md index 6aacd9649..bf5ade742 100644 --- a/crates/ironrdp-dvc-pipe-proxy/CHANGELOG.md +++ b/crates/ironrdp-dvc-pipe-proxy/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.5.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-dvc-pipe-proxy-v0.5.0...ironrdp-dvc-pipe-proxy-v0.5.1)] - 2026-08-13 + +### Bug Fixes + +- Handle pre-connected Windows clients ([#1447](https://github.com/Devolutions/IronRDP/issues/1447)) ([079b48422b](https://github.com/Devolutions/IronRDP/commit/079b48422b0b78d37beb76994950a2a07c442a94)) + + + ## [[0.5.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-dvc-pipe-proxy-v0.4.1...ironrdp-dvc-pipe-proxy-v0.5.0)] - 2026-07-10 ### Bug Fixes diff --git a/crates/ironrdp-dvc-pipe-proxy/Cargo.toml b/crates/ironrdp-dvc-pipe-proxy/Cargo.toml index a87a76f71..91bf66d15 100644 --- a/crates/ironrdp-dvc-pipe-proxy/Cargo.toml +++ b/crates/ironrdp-dvc-pipe-proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-dvc-pipe-proxy" -version = "0.5.0" +version = "0.5.1" readme = "README.md" description = "DVC named pipe proxy for IronRDP" edition.workspace = true @@ -17,9 +17,9 @@ doctest = false test = false [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public (PduResult type) -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public (PduResult type) +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public (SvcMessage type) tracing = { version = "0.1", features = ["log"] } diff --git a/crates/ironrdp-dvc/CHANGELOG.md b/crates/ironrdp-dvc/CHANGELOG.md index 854052722..26deebd49 100644 --- a/crates/ironrdp-dvc/CHANGELOG.md +++ b/crates/ironrdp-dvc/CHANGELOG.md @@ -6,6 +6,70 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.9.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-dvc-v0.8.0...ironrdp-dvc-v0.9.0)] - 2026-08-13 + +### Features + +- Expose generic session configuration and lifecycle APIs ([#1522](https://github.com/Devolutions/IronRDP/issues/1522)) ([57b1366650](https://github.com/Devolutions/IronRDP/commit/57b13666506dc40c15b4c4702d35150beee99133)) + + ## Summary + - expose generic client configuration for connection metadata, + compression, shell/work directory, audio, and runtime static-channel + factories + - add bounded input delivery with independent close cancellation, host + clipboard plumbing, lifecycle events, and Display Control resize + readiness/fallback handling + - update agent, viewer, web, FFI, examples, and tests for the generic + APIs + + ## Stack dependencies + This PR is stacked on `copilot/tls-validation-policy` (`b2bbcece`), + which already includes the merged runtime static-channel support from + `master`. It intentionally contains no TLS implementation/policy, + ActiveX/COM, SVC implementation, decompression, or bitmap-recovery + changes. + + ## Validation + - `cargo fmt --check --all` + - `cargo xtask check tests --no-run -v` + - `cargo xtask check lints -v` + - `cargo test -p ironrdp-client --lib --features rustls` + - `cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi` + + --------- + +- [**breaking**] Add Soft-Sync PDU support ([#1584](https://github.com/Devolutions/IronRDP/issues/1584)) ([bd630842ba](https://github.com/Devolutions/IronRDP/commit/bd630842bacc49cc129e613610482023a0f760db)) + + Add Soft-Sync codecs and DVC dispatch for tunnel assignments. + + Keep decoding forward compatible and bound peer-controlled allocations. + Reject exchanges before their required multitransport endpoint is ready. + +- Create channels with assigned IDs ([#1416](https://github.com/Devolutions/IronRDP/issues/1416)) ([41293c2442](https://github.com/Devolutions/IronRDP/commit/41293c2442dfb2da6b61ca05a0c842706d048fc1)) + + Reserve the channel ID before constructing its processor so the processor + and its dependencies can use the ID during initialization. + + Add a fallible builder API that preserves construction errors. + +### Bug Fixes + +- [**breaking**] Replace DVC wrappers with typed accessors ([#1377](https://github.com/Devolutions/IronRDP/issues/1377)) ([d43ecf9a54](https://github.com/Devolutions/IronRDP/commit/d43ecf9a54363d37e0c485a1e9e73da0d47ae540)) + + Follow-up to #1368. This is not urgent; review whenever the DVC API + direction is worth revisiting. + + Rework DVC channel access APIs so callers can recover a typed processor + together with its dynamic channel id, without exposing internal channel + wrapper types. + + - Add typed borrowed DVC accessors carrying both channel id and + processor borrow for `DrdynvcClient`. + - Keep dynamic channel wrapper types private. + - Align client listener/registration APIs on `DvcClientProcessor`. + + + ## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-dvc-v0.7.0...ironrdp-dvc-v0.8.0)] - 2026-07-10 ### Features diff --git a/crates/ironrdp-dvc/Cargo.toml b/crates/ironrdp-dvc/Cargo.toml index 071bd0dc1..340e0b178 100644 --- a/crates/ironrdp-dvc/Cargo.toml +++ b/crates/ironrdp-dvc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-dvc" -version = "0.8.0" +version = "0.9.0" readme = "README.md" description = "DRDYNVC static channel implementation and traits to implement dynamic virtual channels" edition.workspace = true @@ -21,9 +21,9 @@ default = [] std = [] [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["alloc"] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", features = ["alloc"] } # public tracing = { version = "0.1", features = ["log"] } [lints] diff --git a/crates/ironrdp-echo/CHANGELOG.md b/crates/ironrdp-echo/CHANGELOG.md index 1b4f37fe2..832709b7e 100644 --- a/crates/ironrdp-echo/CHANGELOG.md +++ b/crates/ironrdp-echo/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.4.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-echo-v0.4.0...ironrdp-echo-v0.4.1)] - 2026-08-13 + + + ## [[0.4.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-echo-v0.3.0...ironrdp-echo-v0.4.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-echo/Cargo.toml b/crates/ironrdp-echo/Cargo.toml index b7e7f525d..ed2cc0860 100644 --- a/crates/ironrdp-echo/Cargo.toml +++ b/crates/ironrdp-echo/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-echo" -version = "0.4.0" +version = "0.4.1" readme = "README.md" description = "Virtual channel echo extension implementation" edition.workspace = true @@ -17,9 +17,9 @@ doctest = false test = false [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public tracing = { version = "0.1", features = ["log"] } [lints] diff --git a/crates/ironrdp-egfx/CHANGELOG.md b/crates/ironrdp-egfx/CHANGELOG.md index fb6d836b3..9e320352a 100644 --- a/crates/ironrdp-egfx/CHANGELOG.md +++ b/crates/ironrdp-egfx/CHANGELOG.md @@ -5,6 +5,140 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.3.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-egfx-v0.3.0...ironrdp-egfx-v0.3.1)] - 2026-08-13 + +### Features + +- Add ClearCodec client-side decode dispatch ([#1175](https://github.com/Devolutions/IronRDP/issues/1175)) ([714dce4662](https://github.com/Devolutions/IronRDP/commit/714dce46627e299c57d82f4f6a5c18067a95bffa)) + + Follow-up to #1174. Supersedes #1195 (the standalone server-helper PR; + its 46-line `send_clearcodec_frame()` is included here). + + Wires ClearCodec into the EGFX client's WireToSurface1 codec dispatch, + matching the existing AVC420 and Uncompressed decode patterns. + +- Composite client surface commands into pixel buffers ([#1460](https://github.com/Devolutions/IronRDP/issues/1460)) ([9cd36952ca](https://github.com/Devolutions/IronRDP/commit/9cd36952ca18196cf72c85dc42a79d5d1f5620d5)) + +- Decode Planar bitmaps in the client ([#1507](https://github.com/Devolutions/IronRDP/issues/1507)) ([66c8a81be0](https://github.com/Devolutions/IronRDP/commit/66c8a81be0a9f966e3cf4935ca2a0274d10b063f)) + + Wires `RDPGFX_CODECID_PLANAR` (0x000A) into the EGFX client's + `WireToSurface1` dispatch, alongside the existing ClearCodec and + Uncompressed paths. + +- Add egfx_avc420_decode oracle and target ([#1326](https://github.com/Devolutions/IronRDP/issues/1326)) ([cafbef1c9f](https://github.com/Devolutions/IronRDP/commit/cafbef1c9faba78acb3e33a39556eb4c4c78c6d4)) + + This change adds an assertion-or-panic fuzz oracle for the AVC + length-prefix to Annex-B conversion that runs inside + `OpenH264Decoder::decode` before any OpenH264 entry point. The same + change refactors the conversion from a private method on + `OpenH264Decoder` into two public free functions in + `ironrdp_egfx::pdu::avc`. The first function, `avc_to_annex_b`, + returns a fresh `Vec` and is symmetric with the existing + `annex_b_to_avc`. The second function, `avc_to_annex_b_into`, writes + into a caller-provided buffer and preserves the per-frame buffer-reuse + optimization that `OpenH264Decoder` relied on. + + The conversion is now available unconditionally to any consumer of + `ironrdp-egfx`. The previous `#[cfg(feature = "openh264")]` gating + went with the location, not the bytes-to-bytes logic, so lifting the + function out of `decode.rs` removed the gate too. + + The new oracle exercises two input distributions on every fuzz call: + + - Direct path: the oracle calls `avc_to_annex_b(data)` on the raw fuzz + input. This exercises the wrapper on arbitrary byte distributions, + including inputs that do not parse as `Avc420BitmapStream`. + - Decode-chain path: the oracle tries + `Avc420BitmapStream::decode(data)`; + on success it calls `avc_to_annex_b(stream.data)`. This exercises the + wrapper on the realistic post-decode payload distribution. + + The oracle catches panics in the wrapper, OOM allocation from + attacker-controlled NAL length encoding, and contract violations on the + produced Annex-B byte stream. The oracle does NOT catch OpenH264 + internal bugs (OSS-Fuzz coverage), the YUV-to-RGBA conversion path + downstream of OpenH264 (separate workstream), or AVC444 luma plus + chroma split (sibling target). + + Smoke fuzz ran 10,922,695 iterations in 31 seconds at ~352K exec/s + sustained with zero crashes. Coverage settled at 158 lines, 456 + features, 69 corpus entries. + + The new target auto-discovers into CI via the `cargo xtask fuzz list` + dynamic fan-out mechanism. The new `check_egfx_avc420_decode` + regression-replay test in + `crates/ironrdp-testsuite-core/tests/fuzz_regression.rs` runs against + the seed corpus and passes. + +### Bug Fixes + +- Preserve AVC_DISABLED during negotiation ([#1490](https://github.com/Devolutions/IronRDP/issues/1490)) ([c5bd574c8a](https://github.com/Devolutions/IronRDP/commit/c5bd574c8ac7e4ba92c31348187a196d3e84ae70)) + +- Add spec-compliant Planar frame sender ([#1498](https://github.com/Devolutions/IronRDP/issues/1498)) ([409b256b41](https://github.com/Devolutions/IronRDP/commit/409b256b4168b3a63d97998da311fa9e761bef3e)) + + ## Summary + +- Bound the compositor's dirty-region metadata ([#1510](https://github.com/Devolutions/IronRDP/issues/1510)) ([81f7392422](https://github.com/Devolutions/IronRDP/commit/81f73924221ea994d7f5f32bc9113caa13551ae6)) + + ## Summary + + - The compositor charges materialized pixel buffers against + `MAX_COMPOSITOR_BYTES` but not the `DirtyRegion` entries that produce + them, so `frame` grows outside the budget. + - The repeat filter is O(1) and compares only against the previous + entry, so it collapses a rectangle repeated 65,535 times but not two + rectangles alternating. The frame stays open until the peer sends + `EndFrame`, and the peer chooses when that happens. + - `record_dirty` now charges one entry before pushing, and `EndFrame` + releases the whole set before materializing so the pixel copies can + spend what the metadata was holding. + + ## Cost to the peer + + `RDPGFX_POINT16` (2.2.1.1) is four bytes on the wire; `RDPGFX_RECT16` + (2.2.1.2) is eight. A `DirtyRegion` is ten bytes resident. Three + commands loop over these arrays and record one dirty region each: + + | PDU | Array element | Wire | Resident | Ratio | + |---|---|---|---|---| + | `RDPGFX_SOLIDFILL_PDU` (2.2.2.4) | `fillRects`, RECT16 | 8 | 10 | + 1.25x | + | `RDPGFX_SURFACE_TO_SURFACE_PDU` (2.2.2.5) | `destPts`, POINT16 | 4 | + 10 | 2.5x | + | `RDPGFX_CACHE_TO_SURFACE_PDU` (2.2.2.7) | `destPts`, POINT16 | 4 | 10 + | 2.5x | + + The ratios are modest. The point is that there was no ceiling at all, so + a sustained stream grows the queue until the client is out of memory + regardless of ratio. + + ## Validation + + - New test alternates two rectangles past the budget and asserts the + frame stops growing. Verified against a reverted fix: without the charge + it reaches 128 entries, with it 64. + - `cargo xtask check fmt/lints/tests/typos/locks` all pass. + + ## Notes + + - The charge counts logical entries, not `Vec` capacity. Since `Vec` + grows by doubling, resident bytes for `frame` can reach roughly twice + the charged figure. This matches the existing accounting, which charges + `data.len()` for pixel buffers rather than capacity; flagging it rather + than diverging from the established model. + - Refusing the charge drops the dirty region, so a starved client can + show stale pixels. That is the contract `materialize` already follows + for refused allocations, and `charge` logs the allocated and budget + figures. + - Reported by Copilot on #1462. Follows #1460, which introduced the + budget and the deferred materialization. + +- Advertise only capability sets the client can decode ([#1564](https://github.com/Devolutions/IronRDP/issues/1564)) ([b7657bcb67](https://github.com/Devolutions/IronRDP/commit/b7657bcb670b080c8cd19a9dff0078387cb8c478)) + + ## Summary + + + ## [[0.3.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-egfx-v0.2.0...ironrdp-egfx-v0.3.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-egfx/Cargo.toml b/crates/ironrdp-egfx/Cargo.toml index c4092470f..e22f00271 100644 --- a/crates/ironrdp-egfx/Cargo.toml +++ b/crates/ironrdp-egfx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-egfx" -version = "0.3.0" +version = "0.3.1" readme = "README.md" description = "Graphics pipeline dynamic channel extension implementation" edition.workspace = true @@ -19,10 +19,10 @@ doctest = false arbitrary = { version = "1", features = ["derive"], optional = true } bit_field = "0.10" bitflags = "2.11" -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public -ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.9" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public +ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.10" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public openh264 = { version = "0.9", optional = true, default-features = false } tracing = { version = "0.1", features = ["log"] } diff --git a/crates/ironrdp-error/CHANGELOG.md b/crates/ironrdp-error/CHANGELOG.md index 4614e9493..a3e5b21f1 100644 --- a/crates/ironrdp-error/CHANGELOG.md +++ b/crates/ironrdp-error/CHANGELOG.md @@ -6,6 +6,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-error-v0.2.0...ironrdp-error-v0.2.1)] - 2026-08-13 + +### Features + +- Add ergonomic cross-error mapping ([#1481](https://github.com/Devolutions/IronRDP/issues/1481)) ([71d63c75f5](https://github.com/Devolutions/IronRDP/commit/71d63c75f58228fa0960c3b52327e9694272e976)) + +- Add IronRDP ActiveX COM server ([#1523](https://github.com/Devolutions/IronRDP/issues/1523)) ([ee58b7c5f2](https://github.com/Devolutions/IronRDP/commit/ee58b7c5f283ef64be93a8483242f49070c6cdc9)) + + ## Summary + - Add the IronRDP ActiveX COM server and native MSTSC host integration. + - Provide bounded native-host diagnostics, credential-bridge support, + and an AxHost test harness. + - Preserve the minimal client, connector, and error integrations needed + by the control. + + ## Validation + - cargo check -p ironrdp-activex + - Focused ironrdp-error and ironrdp-connector tests + - cargo test -p ironrdp-activex --lib --no-run + - cargo fmt --all -- --check + - cargo xtask check locks -v + + --------- + +### Bug Fixes + +- Make source locations opt-in ([#1480](https://github.com/Devolutions/IronRDP/issues/1480)) ([f84cd01450](https://github.com/Devolutions/IronRDP/commit/f84cd01450e18d12838b225859878b311802b805)) + + Default error display omits locations; alternate formatting and reports + with explicit location opt-in preserve diagnostic context. + + + ## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-error-v0.1.3...ironrdp-error-v0.2.0)] - 2026-05-27 ### Features diff --git a/crates/ironrdp-error/Cargo.toml b/crates/ironrdp-error/Cargo.toml index 037fadff7..47edc939a 100644 --- a/crates/ironrdp-error/Cargo.toml +++ b/crates/ironrdp-error/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-error" -version = "0.2.0" +version = "0.2.1" readme = "README.md" description = "IronPDU generic error definition" edition.workspace = true diff --git a/crates/ironrdp-futures/CHANGELOG.md b/crates/ironrdp-futures/CHANGELOG.md index 1ffd5aa90..4fd2c37b4 100644 --- a/crates/ironrdp-futures/CHANGELOG.md +++ b/crates/ironrdp-futures/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.8.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-futures-v0.8.0...ironrdp-futures-v0.8.1)] - 2026-08-13 + + + ## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-futures-v0.7.0...ironrdp-futures-v0.8.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-futures/Cargo.toml b/crates/ironrdp-futures/Cargo.toml index 37838f2ac..4d7aa6c83 100644 --- a/crates/ironrdp-futures/Cargo.toml +++ b/crates/ironrdp-futures/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-futures" -version = "0.8.0" +version = "0.8.1" readme = "README.md" description = "`Framed*` traits implementation above futures’s traits" edition.workspace = true @@ -18,7 +18,7 @@ test = false [dependencies] futures-util = { version = "0.3", features = ["io"] } # public -ironrdp-async = { path = "../ironrdp-async", version = "0.10" } # public +ironrdp-async = { path = "../ironrdp-async", version = "0.11" } # public [lints] workspace = true diff --git a/crates/ironrdp-graphics/CHANGELOG.md b/crates/ironrdp-graphics/CHANGELOG.md index 8dba87b85..0ca00815e 100644 --- a/crates/ironrdp-graphics/CHANGELOG.md +++ b/crates/ironrdp-graphics/CHANGELOG.md @@ -6,6 +6,302 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-graphics-v0.9.0...ironrdp-graphics-v0.10.0)] - 2026-08-13 + +### Features + +- Add ClearCodec client-side decode dispatch ([#1175](https://github.com/Devolutions/IronRDP/issues/1175)) ([714dce4662](https://github.com/Devolutions/IronRDP/commit/714dce46627e299c57d82f4f6a5c18067a95bffa)) + + Follow-up to #1174. Supersedes #1195 (the standalone server-helper PR; + its 46-line `send_clearcodec_frame()` is included here). + + Wires ClearCodec into the EGFX client's WireToSurface1 codec dispatch, + matching the existing AVC420 and Uncompressed decode patterns. + +### Bug Fixes + +- Correct progressive base quantization scale ([#1499](https://github.com/Devolutions/IronRDP/issues/1499)) ([ccfe5bb8b3](https://github.com/Devolutions/IronRDP/commit/ccfe5bb8b3b1ddf447776e056d27cd14e2399ab7)) + + ## Summary + +- Decode indexed pointers and foreground RLE runs ([#1519](https://github.com/Devolutions/IronRDP/issues/1519)) ([ad19280762](https://github.com/Devolutions/IronRDP/commit/ad192807620bcc3a0467eaeb07173a79cb1da257)) + + ## Summary + + 4bpp and 8bpp New/Large pointer shapes previously could not use the + active session palette, and malformed or unsupported pointer data could + terminate the session. This decodes indexed XOR masks with the current + palette and falls back to the default cursor while evicting stale cached + data when decoding fails. + + It also corrects RLE foreground runs so only set-foreground variants + consume a foreground pixel. + + Palette updates now follow the RDP wire format (type, padding, 256 + packed RGB triplets) and are applied from both fast- and slow-path + updates, ensuring indexed pointer decoding uses the negotiated palette. + + ## Tests + + - `cargo test -p ironrdp-graphics -p ironrdp-session` + - `cargo test -p ironrdp-session palette` + - `cargo clippy -p ironrdp-graphics -p ironrdp-session --all-targets -- + -D warnings` + + --------- + +- Rename {Read,Write}Cursor::rewinded into rewound ([#1529](https://github.com/Devolutions/IronRDP/issues/1529)) ([c85b089b46](https://github.com/Devolutions/IronRDP/commit/c85b089b4617176240b41482be65a77c9ad76a07)) + +- Correct three RLGR encoder bugs per MS-RDPRFX spec ([#1179](https://github.com/Devolutions/IronRDP/issues/1179)) ([f26d06da6f](https://github.com/Devolutions/IronRDP/commit/f26d06da6f16bfb7fa57f8d4f67658b33c01bc01)) + + ## Summary + + Fixes three bugs in the RLGR entropy encoder + (`ironrdp-graphics::rlgr::encode`) identified by cross-referencing the + MS-RDPRFX Β§3.1.8.1.7.3 pseudocode and the FreeRDP reference + implementation (`rfx_rlgr.c`). + + - **RL mode trailing value**: the encoder skipped emitting sign bit + GR + code when input was exhausted after a zero run. The decoder + unconditionally reads these bits, so the encoder must always emit them. + Restructured to match FreeRDP's `GetNextInput` pattern. + - **RLGR3 exhausted second value**: used `unwrap_or(1)` as default + `twoMs` when the second value in a GR-mode pair was unavailable. + FreeRDP's `GetNextInput` returns 0 when exhausted, and `Get2MagSign(0) = + 0`, so the correct default is 0. + - **RLGR1 GR-mode kp update**: used `UP_GR` (4, the RL-mode constant) + instead of `UQ_GR` (3, the GR-mode constant) when updating `kp` for zero + symbols. Both the spec pseudocode and FreeRDP use `UQ_GR` here. + + Each fix is in a separate commit with full rationale and spec/FreeRDP + references. + + ### Spec errata note + + The MS-RDPRFX Β§4.2.4.1 reference hex dump appears to have been generated + with `UQ_GR=4` and `twoMs2=1` defaults β€” inconsistent with the normative + pseudocode in Β§3.1.8.1.7.3. Our encoder follows the pseudocode (which is + authoritative over example data) and matches FreeRDP. The Y test vector + is updated accordingly (2 bytes differ from the spec hex dump at indices + 939–940). Encoderβ†’decoder roundtrip is verified correct. + + ## Test plan + + - [x] All 12 existing RLGR unit tests pass (encode + decode, small + vectors + full 4096-coefficient Y/Cb/Cr datasets) + - [x] Encoderβ†’decoder roundtrip verified for Y, Cb, Cr components + - [x] `cargo clippy -p ironrdp-graphics` clean + - [x] `cargo xtask check lints -v` passes + + πŸ€– Generated with [Claude Code](https://claude.com/claude-code) + + --------- + +- [**breaking**] Do not panic when the RLGR output buffer is too small ([#1558](https://github.com/Devolutions/IronRDP/issues/1558)) ([71903c5509](https://github.com/Devolutions/IronRDP/commit/71903c550929dbbd1b1b881cc403a6a693b6e233)) + + ## Summary + + - `BitStream::output_bit` and `output_bits` indexed the output + `BitSlice` with no capacity check, so a tile whose entropy coding didn't + fit the caller's buffer panicked inside a function that already returns + `Result`. + - The RLGR decoder a few hundred lines down has been bounds-checked all + along: `try_split_bits!` breaks when bits run short, the loop guards on + `!bits.is_empty() && !output.is_empty()`, and run-length fills clamp + with `min(run, output.len())`. Only the encoder was unguarded. + - The writers now reserve before writing and record an overflow rather + than indexing past the end. `encode` turns that into + `RlgrError::OutputTooSmall`. + + ## Why it's reachable + + RLGR gives no compression guarantee, but callers size the output as + though it did. `ironrdp-server` splits a 12288-byte tile buffer into + three 4096-byte components, which is 2:1 against 4096 `i16` + coefficients. That holds comfortably at the default quantization table + and stops holding as the table gets lighter, so this is reachable from + configuration rather than from malformed input. + + The tile is still walked to completion after an overflow, so the error + reports the size the tile would have needed. A caller sizing on a ratio + needs that number to correct the ratio; it's the reason the variant + carries data. + + ## Retrying at the size the encoder asks for + + Detection alone was not a fix, which @mamoreau-devolutions was right to + push back on. `alloc_data` gave every component exactly 4096 bytes, and + the loop in `encoder/mod.rs` that grows a buffer on `NotEnoughBytes` + grows the whole-frame one, so it could never have helped here. A tile + that overflowed went from panicking to failing the update. + + The per-component reserve is a parameter now, and the tile-set encode + retries at exactly the size the encoder reports, growing monotonically + and stopping at twice a component's raw `i16` size. + + I did not take the fixed upper bound offered as an alternative. RLGR is + adaptive and unary-dominated in its worst case, so a bound loose enough + to be provable is megabytes per component, and anything small enough to + allocate is a guess. Since the encoder can say exactly what it needs, + retrying at that seemed better than inventing a constant. + + The retry lives in `RfxEncoder::encode` because `rfx::Tile` borrows out + of the buffer, so the buffer has to be sized before any borrow is taken. + Widening `Tile` would reshape a wire PDU type, and a per-tile fallback + needs the overflow buffers to outlive a `par_chunks_mut` borrow. + + `OutputTooSmall` is deliberately not mapped onto `NotEnoughBytes` to + reuse that existing loop, since the loop grows a different buffer and + would look like a fix without being one. + + ## Breaking change + + `RlgrError` gains an `OutputTooSmall` variant and isn't + `#[non_exhaustive]`, so exhaustive matches need updating. + + `ironrdp-graphics` is marked `# public` in `ironrdp-server`, + `ironrdp-client`, `ironrdp-egfx`, `ironrdp-session`, and + `ironrdp-nscodec` (under its `encoder` feature), so the bump cascades to + those. + + I considered reusing + `RlgrError::Io(io::Error::from(ErrorKind::WriteZero))` to avoid the + break. It discards both numbers the caller needs to act on, which + defeats the point. Marking the enum `#[non_exhaustive]` is a separate + breaking change and a wider policy question, so it isn't bundled here. + + ## Validation + + - `cargo xtask check fmt/lints/tests/typos/locks` all pass on the pinned + toolchain, `fuzz/` built before the lock check. + - Two tests added in + `crates/ironrdp-testsuite-core/tests/graphics/rlgr.rs`: an undersized + buffer reports `OutputTooSmall` with `needed > available` for both RLGR1 + and RLGR3, and a buffer of exactly the required size still succeeds. The + twelve existing `graphics::rlgr` tests are unchanged and still pass. + - Two more in `crates/ironrdp-testsuite-core/tests/server/rfx.rs` for + the retry: the reported size must be sufficient rather than merely + indicative, so encoding at a 64-byte reserve and retrying at exactly the + size reported has to succeed; and the default reserve still handles a + full-entropy tile, so the retry stays a fallback. Under-reporting the + size by one byte fails the first of those. + - Those two started life inline in `encoder/rfx.rs`, where they never + ran: `ironrdp-server` sets `[lib] test = false`, so `cargo test + --workspace` builds no unittest binary for it. They reach the encoder + through the crate's existing `__bench` feature, which now also exports + `rfx_enc_at` next to the two helpers already there, so no type had to be + widened. + - Driving the server pipeline (BGRA, `to_64x64_ycbcr_tile`, + `dwt::encode`, `quantization::encode`, `rlgr::encode` into 4096 bytes) + on a random-noise tile, a quantization table of all ones panics on + master and now returns `encoded tile needs 6018 bytes, output buffer is + 4096`. + - Spec-legal tables are unaffected, and that is measured rather than + assumed. Binary-searching the minimum per-component reserve for a set of + adversarial 64x64 tiles at `Quant::default()` gives a worst case of 2857 + bytes of the 4096 available (per-channel independent noise at full + swing, RLGR3); full-range noise needs 2379 under RLGR1. Taking that + worst pattern across the whole legal range from [MS-RDPRFX] 2.2.2.1.5, + RLGR1 needs 3768 bytes at quant 6, 2740 at 8, and 917 at 15. Nothing in + spec overflows, which is why this PR fixes the panic and stops there. + + ## Notes + + Found while looking at #1557, where an all-ones quantization table + panicked the encoder. That table is out of spec ([MS-RDPRFX] 2.2.2.1.5 + requires 6 to 15), and the configurability that issue asks for is + separate work. This change is only about not panicking. + + Touches `BitStream` and the tail of `encode`, deliberately staying clear + of the body of `encode` where #1370 and #1179 both have hunks. It should + rebase cleanly whichever of those lands first. + +- Don't emit a value after a run that ends the input ([#1569](https://github.com/Devolutions/IronRDP/issues/1569)) ([d9d2896c8c](https://github.com/Devolutions/IronRDP/commit/d9d2896c8cbc06cf1c7b82f45b19482d0633dcb2)) + + RL mode codes the following value's magnitude minus one, so it cannot + express + zero. #1179 began coding a zero there when the run consumed the rest of + the + input, and the decoder reconstructs magnitude as the coded value plus + one, so + every input ending in a zero run gained a trailing 1. + + That is what fails + `progressive_fractional_base_quantization_reconstructs_rgb` + from #1499 on master: quantized coefficients end in a zero run, so the + phantom + value lands in HH1 of each component. + + The trailing zeros are the run and nothing follows them. #1179's other + two + fixes are untouched. + + 2000 randomized round trips per entropy mode, 0 failures. Workspace + suite green. + +### Performance + +- Portable SIMD inverse DWT (wide + SWAR) ([#1383](https://github.com/Devolutions/IronRDP/issues/1383)) ([629154026d](https://github.com/Devolutions/IronRDP/commit/629154026de0eaaf16b93352b4cecbae49a87511)) + + ## Summary + + On the WASM web client, frame **decode** dominates (~93% of frame time + on a 1080p RemoteFX replay), and within decode the **RFX inverse DWT was + ~48%** (the YCbCrβ†’RGBA convert is already SIMD via `yuv`; the + entropy/RLE stages are inherently sequential). This vectorizes the + inverse DWT with the portable [`wide`](https://crates.io/crates/wide) + crate (`i16x8`), so the same code lowers to **wasm `simd128`, x86 + SSE/AVX, and ARM NEON** β€” desktop and browser both benefit. + + The encode path is unchanged. + + ## How it stays bit-exact (no `unsafe`, no `cfg` split) + + The lifting steps need i32 intermediates only for the averages. + Overflow-free SWAR identities let the whole kernel stay in `i16` lanes + (no widen/narrow): + + - `ceil_avg(a,b) = (a|b) - ((a^b)>>1)` ≑ `(a + b + 1) >> 1` + - `floor_avg(a,b) = (a&b) + ((a^b)>>1)` ≑ `(a + b) >> 1` + + and `(2x+1)>>1 == x` / `(x+x)>>1 == x` simplify the first/last rows. + Every other op is wrapping `i16` arithmetic, identical to the old + `i32`-intermediate-then-`as i16` truncation. + + ## Performance + + 1080p RemoteFX replay, headless Chromium, wasm release `+simd128`, + 8-pass median: + + | inverse DWT | decode (ms) | + |---|--:| + | scalar (baseline) | ~1598 | + | **portable `wide` SIMD** | **~985** | + + β†’ inverse DWT ~2Γ—, **~39% off the decode stage**. (Absolute ms carry + ~Β±15% machine-load noise; the ratio is stable. Per-frame this is a + throughput win β€” decode was already within real-time budget.) + + ## Correctness + + Verified bit-exact three ways: + - the replay-bench **framebuffer CRC32** is unchanged, + - the existing **native DWT tests** pass (so it's exact on x86 too, not + just wasm), + - an **exhaustive** check of the SWAR identities over all `i16 Γ— i16` + pairs (0 mismatches). + + ## Notes + + - `wide` is a single-user dep in `ironrdp-graphics`; chosen over + `std::simd` (still nightly-only) and over per-arch intrinsics (one + portable kernel vs three). + - Reproducible bench branches: `bench/draw-*` (renderer) and the DWT + measurements were taken on the replay-bench harness branch (the capture + corpus is gitignored). + + + ## [[0.9.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-graphics-v0.8.1...ironrdp-graphics-v0.9.0)] - 2026-07-10 ### Bug Fixes diff --git a/crates/ironrdp-graphics/Cargo.toml b/crates/ironrdp-graphics/Cargo.toml index fa850ded2..a65b2ec3a 100644 --- a/crates/ironrdp-graphics/Cargo.toml +++ b/crates/ironrdp-graphics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-graphics" -version = "0.9.0" +version = "0.10.0" readme = "README.md" description = "RDP image processing primitives" edition.workspace = true @@ -20,8 +20,8 @@ doctest = false bit_field = "0.10" bitflags = "2.11" bitvec = "1.0" -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["std"] } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", features = ["std"] } # public byteorder = "1.5" # TODO: remove num-derive.workspace = true # TODO: remove num-traits.workspace = true # TODO: remove diff --git a/crates/ironrdp-input/CHANGELOG.md b/crates/ironrdp-input/CHANGELOG.md index 001665e2d..342bde877 100644 --- a/crates/ironrdp-input/CHANGELOG.md +++ b/crates/ironrdp-input/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.7.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-input-v0.7.0...ironrdp-input-v0.7.1)] - 2026-08-13 + + + ## [[0.7.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-input-v0.6.0...ironrdp-input-v0.7.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-input/Cargo.toml b/crates/ironrdp-input/Cargo.toml index 94f398a6d..70a61ba43 100644 --- a/crates/ironrdp-input/Cargo.toml +++ b/crates/ironrdp-input/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-input" -version = "0.7.0" +version = "0.7.1" readme = "README.md" description = "Utilities to manage and build RDP input packets" edition.workspace = true @@ -17,7 +17,7 @@ doctest = false test = false [dependencies] -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public bitvec = "1.0" smallvec = "1.15" diff --git a/crates/ironrdp-mstsgu/CHANGELOG.md b/crates/ironrdp-mstsgu/CHANGELOG.md index 1c6f95678..7298c982b 100644 --- a/crates/ironrdp-mstsgu/CHANGELOG.md +++ b/crates/ironrdp-mstsgu/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.0.2](https://github.com/Devolutions/IronRDP/compare/ironrdp-mstsgu-v0.0.1...ironrdp-mstsgu-v0.0.2)] - 2026-08-13 + + + ## [[0.0.1](https://github.com/Devolutions/IronRDP/releases/tag/ironrdp-mstsgu-v0.0.1)] - 2026-07-10 Initial release. diff --git a/crates/ironrdp-mstsgu/Cargo.toml b/crates/ironrdp-mstsgu/Cargo.toml index c0359f56c..d360bb68b 100644 --- a/crates/ironrdp-mstsgu/Cargo.toml +++ b/crates/ironrdp-mstsgu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-mstsgu" -version = "0.0.1" +version = "0.0.2" readme = "README.md" description = "Terminal Services Gateway Server Protocol" edition.workspace = true @@ -28,7 +28,7 @@ futures-util = "0.3" http-body-util = { version = "0.1" } hyper-util = { version = "0.1", features = ["tokio"] } hyper = { version = "1.9", features = ["client", "http1"] } -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["std"] } +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["std"] } ironrdp-error = { path = "../ironrdp-error", version = "0.2" } ironrdp-tls = { path = "../ironrdp-tls", version = "0.2" } log = "0.4" diff --git a/crates/ironrdp-nscodec/CHANGELOG.md b/crates/ironrdp-nscodec/CHANGELOG.md new file mode 100644 index 000000000..a4e25292c --- /dev/null +++ b/crates/ironrdp-nscodec/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-nscodec-v0.2.0...ironrdp-nscodec-v0.2.1)] - 2026-08-13 + + diff --git a/crates/ironrdp-nscodec/Cargo.toml b/crates/ironrdp-nscodec/Cargo.toml index 37df3818c..61223804f 100644 --- a/crates/ironrdp-nscodec/Cargo.toml +++ b/crates/ironrdp-nscodec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-nscodec" -version = "0.2.0" +version = "0.2.1" readme = "README.md" description = "NSCodec ([MS-RDPNSC]) implementation for IronRDP" edition.workspace = true @@ -23,7 +23,7 @@ default = [] encoder = ["dep:ironrdp-graphics"] [dependencies] -ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.9", optional = true } # public when `encoder` is on +ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.10", optional = true } # public when `encoder` is on [lints] workspace = true diff --git a/crates/ironrdp-pdu/CHANGELOG.md b/crates/ironrdp-pdu/CHANGELOG.md index 5a240de37..efcc4cf8e 100644 --- a/crates/ironrdp-pdu/CHANGELOG.md +++ b/crates/ironrdp-pdu/CHANGELOG.md @@ -6,6 +6,723 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-pdu-v0.9.0...ironrdp-pdu-v0.10.0)] - 2026-08-13 + +### Security + +- Tolerate unknown security header flags in BasicSecurityHeader ([#1458](https://github.com/Devolutions/IronRDP/issues/1458)) ([a4acab488b](https://github.com/Devolutions/IronRDP/commit/a4acab488b0d549854ebe0e0e922fe7252e84c98)) + + ## Summary + + Use `from_bits_truncate()` instead of `from_bits()` when decoding + `BasicSecurityHeader` flags. Some servers (e.g., Windows Server 2019 + with RDS licensing / RD Connection Broker) send security header flag + combinations that include bits not defined in the current bitflags enum. + The strict `from_bits()` rejected these as invalid, causing connection + failure during the `UpgradeLicense` license exchange phase. + + This matches FreeRDP behavior which masks for known flags without + rejecting the PDU when unrecognized bits are present. + + ## What was tested + + - Existing unit tests pass (`cargo xtask check tests -v`) + - Lints pass (`cargo xtask check lints -v`) + +- [**breaking**] Implement multitransport bootstrapping handshake ([#1098](https://github.com/Devolutions/IronRDP/issues/1098)) ([e45fbfe0f5](https://github.com/Devolutions/IronRDP/commit/e45fbfe0f597011706e77fc174ca14e5e9d435b9)) + + ## Summary + + Makes the `MultitransportBootstrapping` state functional instead of a + no-op + pass-through. After licensing the server may send 0, 1, or 2 Initiate + Multitransport Request PDUs before capabilities exchange. Each one is + surfaced + to the application, which establishes UDP transport (RDPEUDP2 + TLS + + RDPEMT) + or declines, and the connector reports the outcome back to the server. + + ## API + + Mirrors the existing `should_perform_X()` pause-point pattern used by + TLS + upgrade and CredSSP, but uses `complete_X()` / `skip_X()` rather than + `mark_X_as_done()` because completion carries result data: + + - `should_perform_multitransport()`: true while a request awaits an + outcome + - `multitransport_request()`: the request awaiting an outcome, or `None` + - `complete_multitransport(result, output)`: report the outcome, resume + - `skip_multitransport(output)`: decline, resume + + `complete_multitransport` accepts a `MultitransportResult` (a `Success` + / + `Failure(hresult)` enum) rather than a caller-built response PDU. The + connector + builds the response internally from the stored request ID. + + Requests are surfaced one at a time rather than as a batch. There is no + end + marker for the set, and MS-RDPBCGR 3.2.5.15.1 requires the client to act + on a + request as soon as it decodes one, so waiting to learn how many are + coming is + not an option the protocol offers. `should_perform_multitransport()` can + therefore come round twice; the caller answers reliable and lossy + separately. + + ## Approach + + **Routing.** Requests arrive on the negotiated MCS message channel + (2.2.15.1) and the Demand Active on the I/O channel, so the channel + decides + which is which. The message channel also carries NetworkAutoDetect since + #1348, + so a decode still confirms what arrived there, but the I/O channel is + never + speculatively decoded as multitransport. A PDU on neither channel is an + error. + + For the decode to be a sound confirmation the request decoder must + reject a + Demand Active, so this PR also tightens `MultitransportRequestPdu` to + require + the exact `SEC_TRANSPORT_REQ` security-header flag. + + **Yielding.** Each request is surfaced the moment it decodes. Responding + returns the connector to `MultitransportBootstrapping` to read whatever + comes + next, which may be a second request or the Demand Active. Nothing is + buffered + and nothing is replayed: when the request is surfaced the Demand Active + has not + arrived yet. + + **Soft-Sync.** The Initiate Multitransport Response is the Soft-Sync + signalling path (2.2.15.2), permitted only when both peers advertised + `SOFTSYNC_TCP_TO_UDP` in their GCC `MultiTransportChannelData`. The + server's + block is retained from the GCC exchange and checked against the client's + configured flags. One rule covers both paths: + + - Soft-Sync negotiated: always respond, `S_OK` or `E_ABORT`, including + on + `skip_multitransport()`, which 3.2.5.15.1 requires. Both the async and + blocking drivers skip automatically, so without this every default + client + leaves a compliant server waiting. + - Not negotiated: never respond. The outcome is reported in band on the + new + transport, and putting anything on the main channel would be the + violation. + + The response goes on the message channel per 2.2.15.2 and 3.2.5.15.2. If + Soft-Sync was negotiated but no message channel exists the connector + errors + rather than falling back to the I/O channel, and that check runs before + the + pending state is taken, so the caller is left with a connector it can + still + inspect or decline from. + + ## Wire behaviour + + On the wire TCP and UDP negotiation happen in parallel: the UDP + transport is + established alongside the ongoing TCP handshake, and its completion + signals the + dynamic-channel layer that subsequent channels may migrate to UDP. The + connector's API yield point here is a Rust affordance, not a + spec-mandated TCP + pause. Thanks to @hardening for the correction. + + ## Tests + + Connector state-machine tests in `ironrdp-testsuite-core` drive the + public API + with the shared `SERVER_DEMAND_ACTIVE` fixture: + + - a request is surfaced on arrival, without waiting for a following PDU + (regression test for the stall); + - responding returns to bootstrapping so a second request is read + normally; + - a third request is rejected per the 2.2.15.1 cap; + - a Demand Active on the I/O channel ends bootstrapping; + - the response targets the message channel, decoded back off the wire; + - a `Failure` result is carried through; + - `skip` sends `E_ABORT` under Soft-Sync, and nothing without it; + - `complete` emits nothing without Soft-Sync but still resumes; + - a failed response leaves the connector in `MultitransportPending`, + still able + to report or decline, rather than `Consumed`; + - `complete` / `skip` outside `MultitransportPending` error; + - a Demand Active's user data does not decode as a + `MultitransportRequestPdu` + (regression test for the decoder tightening above). + +- Validate auto-reconnect cookies ([#1509](https://github.com/Devolutions/IronRDP/issues/1509)) ([44f675e244](https://github.com/Devolutions/IronRDP/commit/44f675e244ee76b5311756668ffbbe28e98c7175)) + + ## Summary + - parse and carry `ARC_CS_PRIVATE_PACKET` data through the acceptor + - validate returning Enhanced RDP Security cookies with HMAC-MD5 before + reconnecting + - rotate reconnect randoms per connection and hourly, with runtime + cookie updates + - restrict cookie authentication to TLS/Hybrid and document the behavior + + ## Testing + - `cargo test -p ironrdp-pdu -p ironrdp-acceptor -p ironrdp-server` + - `cargo clippy -p ironrdp-pdu -p ironrdp-acceptor -p ironrdp-server + --all-targets -- -D warnings` + +- [**breaking**] Support session resume via the auto-reconnect cookie ([#1501](https://github.com/Devolutions/IronRDP/issues/1501)) ([74b3365c1f](https://github.com/Devolutions/IronRDP/commit/74b3365c1f98c0da6feed7507779c67e1b8e6d08)) + + > **Rebased onto post-#1522 master.** #1509 landed the server half of + #1508 while this was open, including the `ClientAutoReconnect` + structure. This PR no longer declares it; it extends it, and picks up + the parts #1509 did not build. + + ## What + + The client half of automatic reconnection. The session layer surfaces + the Server Auto-Reconnect Cookie, `ironrdp-pdu` derives and verifies the + client's response to it, and the connector sends that response when + resuming a session. + + ## Why + + A client whose connection drops ungracefully can reattach to its session + instead of making the user log on again, provided it returns the cookie + the server issued during logon ([MS-RDPBCGR] 1.3.1.5). + + #1509 built the server side of that: it validates a returning + `ARC_CS_PRIVATE_PACKET` and rotates the random. Nothing answers it. + `ironrdp-session` decodes the cookie and drops it, `ironrdp-connector` + has no way to send one back, and `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` still sits in + `ironrdp-client`. So `ironrdp-client` cannot resume a session against + `ironrdp-server`, and the validation #1509 added has no in-tree + counterpart to exercise it. + + The wire encoding was already there. `ExtendedClientOptionalInfo` + carries, encodes and decodes a 28-byte `autoReconnectCookie` and its + builder already had a `reconnect_cookie` step; `ServerAutoReconnect` + already decoded; #1509 added `ClientAutoReconnect` and its decode. + Nothing connected them. + + ## The three parts + + **Receive.** `SaveSessionInfo` now also surfaces the cookie, as + `ProcessorOutput::AutoReconnectCookie` and + `ActiveStageOutput::AutoReconnectCookie`. #1522 added a `SaveSessionInfo + { logon_complete }` output on that same handler; the two coexist rather + than compete, since both are read off one PDU and neither supersedes the + other. The handler emits the logon notification unconditionally and + appends the cookie when one is present, and a test pins that surfacing + the cookie does not suppress the notification. #1509's server replaces + the cookie whenever a client connects and again hourly ([MS-RDPBCGR] + 3.3.6.2), so this can arrive more than once in a session and the + consumer keeps the most recent. + + **Derive.** `ClientAutoReconnect::from_server_cookie` implements + [MS-RDPBCGR] 5.5: + + > The auto-reconnect random is used to key the HMAC function + ([RFC2104]), which uses MD5 as the iterative hash function. The security + verifier is derived by applying the HMAC to the client random received + in Step 3. + > + > `SecurityVerifier = HMAC(AutoReconnectRandom, ClientRandom)` + > + > When Enhanced RDP Security is in effect the client random value is not + generated (section 5.3.2). In this case, for the purpose of generating + the security verifier, the client random is assumed to be an array of 32 + zero bytes. + + IronRDP implements no Standard RDP Security path (there is no Security + Exchange PDU), so the zero-client-random case is the only one that + arises. As 5.5 notes, that makes the verifier constant for a given + cookie, so it proves possession of the cookie and nothing more; session + security comes from the outer TLS/CredSSP handshake. + + @clintcan independently confirmed this construction against real + **mstsc** while validating #1509 + ([comment](https://github.com/Devolutions/IronRDP/pull/1509#issuecomment-5151200681)): + a Windows client's `ARC_CS_PRIVATE_PACKET` verifies against + `HMAC-MD5(random_bits, [0u8; 32])`. That is the same derivation + implemented here, so the two halves interoperate with Microsoft's client + and not only with each other. + + **Send.** `ClientConnector::with_auto_reconnect_cookie` takes the cookie + last received and makes the connector put the derived Client + Auto-Reconnect Packet ([MS-RDPBCGR] 2.2.4.3) in the Client Info PDU. + Absent, that PDU is byte-for-byte what it was. + + Unlike the server packet, this structure has no enclosing logon-info + field header, so it encodes to exactly the 28 bytes the cookie field + expects. `to_bytes` writes that layout directly rather than going + through `Encode`, so filling a fixed-size field has no error path a + caller must handle; a test pins the two to agree. + + ## One derivation, not two + + Putting `from_server_cookie` in `ironrdp-pdu` would leave the workspace + with two implementations of 5.5, since #1509 added a private HMAC to + `ironrdp-server`. So `ClientAutoReconnect` also gains `verify`, and the + server routes through it. + + `verify` keeps the constant-time comparison the server had. The verifier + is the whole credential, so a comparison returning early on the first + differing byte would let a peer recover it a byte at a time from the + timing; the session identifier is not secret and is compared normally. + `ironrdp-server` keeps the policy around the check, which cookies are + live and whether the security protocol permits auto-reconnect, and drops + its `hmac` and `md-5` dependencies. `hmac` moves to `ironrdp-pdu` as + `default-features = false`; the crate's full feature powerset still + checks clean, including `--no-default-features`. + + I would rather not have reached into `ironrdp-server` in a + `pdu,session,connector` change, but the alternative was shipping the + duplicate and filing a follow-up to remove it, which is a worse trade + for reviewer time. + + ## Tests that were not running + + That move also rehomes the known-answer tests @clintcan contributed on + #1509. They went in as an inline `#[cfg(test)]` module in + `crates/ironrdp-server/src/server.rs`, and that crate sets `[lib] test = + false`, so they have never executed in CI. They now live in + `ironrdp-testsuite-core` against the public API, where CI runs them: his + HMAC-MD5 reference vector is kept as a second vector alongside a + differently-keyed one, plus the cases for a tampered verifier and a + mismatched logon ID. + + Worth flagging separately: `ironrdp-server` is not alone. + `ironrdp-agent`, `ironrdp-session` and `ironrdp-web` also set `[lib] + test = false` and between them carry 16 files of inline `#[cfg(test)]` + modules that CI never runs. That is out of scope here, but I am happy to + open an issue if it would be useful. + + ## Breaking changes + + `ActiveStageOutput` and `x224::ProcessorOutput` gain a variant, and + `ClientConnector` gains a public field, so exhaustive matches and struct + literals need updating. + + Confirmed with `cargo-semver-checks` against the merge-base: those three + are the only findings this branch introduces. The others it reports on + `master` today (`ShareDataPdu::Compressed` and the `ShareDataCtx` fields + from #1518, `ProcessorBuilder.bulk_decompressor` from #1518, + `ServerEvent::SetAutoReconnectCookie` from #1509) are present on + `master` unchanged. The `ironrdp-pdu` additions are additive. + + ## Scope + + This is the library half. `ironrdp-client`, `ironrdp-web` and the FFI + bindings gain an arm for the new output but none of them reconnect + automatically yet; that is the remaining part of #271, and the existing + `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` in `ironrdp-client` marks where it goes. + + I kept receive, derive and send together deliberately. Split up, none of + them is usable on its own: without the receive half there is no way to + obtain a cookie, and without the send half there is nothing to do with + one. + + ## Tests + + Thirteen, all in `ironrdp-testsuite-core`. + + On the packet and the derivation: the `SecurityVerifier` matches two + independently computed HMAC-MD5 vectors of 32 zero bytes under different + keys, so the tests pin the derivation rather than restating the code; + the logon ID carries over from the server cookie; the encoding matches + the 2.2.4.3 field layout byte for byte with `cbLen` fixed at `0x1C`; + `to_bytes` agrees with `Encode`; it round-trips; and it rejects both a + wrong packet length and an unknown version. + + On verification: a derived answer is accepted, a single flipped byte in + the verifier is rejected, a correct verifier under a different logon ID + is rejected, and an answer derived from a different random is rejected. + + On the surfacing path: a Save Session Info PDU framed the way a server + sends it, through the real x224 processor, yields an + `AutoReconnectCookie` carrying the right logon ID and random bits, + alongside #1522's logon notification rather than in place of it; and one + without a cookie surfaces no cookie. + + ## Verification + + `cargo xtask check fmt/lints/tests/typos/locks` all pass on 1.94.1, + including a `fuzz/` build before the lock check. + + ## Note + + #1496 also touches the `ClientAutoReconnect` declaration. Whichever of + the two lands second needs a one-line rebase on the derive attribute; + happy to take that in either order. + +### Features + +- Add ClearCodec client-side decode dispatch ([#1175](https://github.com/Devolutions/IronRDP/issues/1175)) ([714dce4662](https://github.com/Devolutions/IronRDP/commit/714dce46627e299c57d82f4f6a5c18067a95bffa)) + + Follow-up to #1174. Supersedes #1195 (the standalone server-helper PR; + its 46-line `send_clearcodec_frame()` is included here). + + Wires ClearCodec into the EGFX client's WireToSurface1 codec dispatch, + matching the existing AVC420 and Uncompressed decode patterns. + +- Surface ShareDataPdu variant in unexpected-PDU errors ([#1329](https://github.com/Devolutions/IronRDP/issues/1329)) ([df1f7e7faa](https://github.com/Devolutions/IronRDP/commit/df1f7e7faaf068435bfbbe1efcb4a8800ebb3d9f)) + + ## Summary + + - Addresses ask 1 of #1232: when the server sends a + `ShareControlPdu::Data` wrapping an unexpected `ShareDataPdu`, the three + error sites in `headers.rs` and `connection_activation.rs` now drill + into the `Data` wrapper and surface the inner variant name instead of + reporting only `"Data"`. + - For `ServerSetErrorInfo` specifically (the asker's high-value case), + the existing `ErrorInfo::description()` is appended so callers can see + why the server rejected the session without substring matching on the + `Reason` string. + - New `pub fn describe_unexpected_share_control_pdu` in `headers.rs` + centralizes the formatting; `decode_share_data`, `decode_io_channel`, + and `ConnectionActivation::CapabilitiesExchange` all route through it. + - Non-`Data` variants continue to use the outer `as_short_name()`, so + diagnostics for `ServerDeactivateAll` and `ClientConfirmActive` are + preserved verbatim. + + ## Validation + + - Three unit tests in `headers::tests` cover the helper: a non-`Data` + variant (`ServerDeactivateAll`), a `Data` wrapper around a + non-SetErrorInfo inner (`Update(Vec::new())`), and a `Data` wrapper + around `ServerSetErrorInfo` carrying + `ProtocolIndependentCode::ServerDeniedConnection`. + - `cargo xtask check fmt/lints/tests/typos/locks` all pass. + + ## Notes + + - Helper is `pub`, not `pub(crate)`: it has to be, since + `ironrdp-connector`'s `connection_activation.rs` calls it cross-crate. + That adds + `ironrdp_pdu::rdp::headers::describe_unexpected_share_control_pdu` to + `ironrdp-pdu`'s public surface. Additive and non-breaking, confirmed by + `cargo semver-checks --baseline-rev `: no update required + for either `ironrdp-pdu` or `ironrdp-connector`. + - Ask 2 from #1232 (an optional structured `ConnectorErrorKind` variant + for "server rejected at capabilities phase") is intentionally deferred. + The asker framed it as optional and the wire-level information is now + available in the `Reason` string. + - `Refs #1232` rather than `Closes` so the issue stays open while you + decide on ask 2. + +- Support connection correlation info ([#1582](https://github.com/Devolutions/IronRDP/issues/1582)) ([c4483617ba](https://github.com/Devolutions/IronRDP/commit/c4483617ba05c31182b58c58be66bd41120a076d)) + + Encode the optional 36-byte X.224 RDP_NEG_CORRELATION_INFO block and + reject malformed negotiation records. + +- Support Hyper-V connection ordering ([#1505](https://github.com/Devolutions/IronRDP/issues/1505)) ([5c1816244e](https://github.com/Devolutions/IronRDP/commit/5c1816244e83187a04249e9d9c240d096cb78f55)) + + Hyper-V over RDCleanPath needs PCB β†’ TLS on the proxy, then CredSSP β†’ + X.224 on the client. Ordinary RDCleanPath stays X.224-first. + + Still VERSION_1 with the same DER fields. An explicit VMConnect request + carries a Unicode PCB payload in `preconnection_blob` with no X.224; the + proxy encodes the binary PCB. Generic PCB requests keep their existing + X.224-first behavior. + + Gateway reference implementation: + [Devolutions/devolutions-gateway#1372](https://github.com/Devolutions/devolutions-gateway/pull/1372) + + Checked locally: Rust builds, formatting, Svelte typecheck, and .NET + build. Real nested Hyper-V E2E through Gateway: Native rendered 18 + frames, Avalonia connected and rendered its first frame, and Web + rendered a non-empty 1280Γ—720 canvas. + + --------- + +- Forward negotiated windowing orders ([#1631](https://github.com/Devolutions/IronRDP/issues/1631)) ([0c3fbe78b4](https://github.com/Devolutions/IronRDP/commit/0c3fbe78b4366533b9fcea046b2b53654e003a72)) + + Preserve Window List support during activation. + Forward validated orders through ActiveStage and the raw FFI output. + Desktop and web consumers retain their existing behavior. + +- Add RemoteApp protocol primitives ([#1636](https://github.com/Devolutions/IronRDP/issues/1636)) ([0161906731](https://github.com/Devolutions/IronRDP/commit/0161906731757356953cdb389a2cd6a42863deb2)) + + Add portable RAIL wire types and a typed Remote Programs capability set. + + Validate the RAIL crate's bare `no_std` and allocation-backed + configurations in the workspace feature matrix. + + Keep connection setup and windowing behavior outside this protocol + layer. + +### Bug Fixes + +- Tolerate unknown GCC user-data blocks instead of failing ([#1489](https://github.com/Devolutions/IronRDP/issues/1489)) ([629a8024f4](https://github.com/Devolutions/IronRDP/commit/629a8024f4832ed04247ef56597604bbb4b85017)) + +- Scope Font Map leniency ([#1506](https://github.com/Devolutions/IronRDP/issues/1506)) ([e496b7b8ea](https://github.com/Devolutions/IronRDP/commit/e496b7b8eaf60688fc0d507961713c6aabd0e05e)) + +- Key auto-detect optional fields off requestType, not the Option ([#1491](https://github.com/Devolutions/IronRDP/issues/1491)) ([f9cc62fa2c](https://github.com/Devolutions/IronRDP/commit/f9cc62fa2ccb4f211838dd0961c19cdf3b79a38e)) + + ## Summary + + MS-RDPBCGR decides which optional fields an auto-detect message carries + by its `requestType`. Two of these message types encoded them by + inspecting which `Option`s happened to be set instead, so the encoder + and the decoder disagreed about the wire. + + This started as a fix for `BandwidthMeasureStop` alone. Review found the + connect-time fallback was still non-compliant, and checking whether the + same shape appeared elsewhere in the file turned up + `NetworkCharacteristicsResult` with the identical defect and a worse + consequence, so both are fixed here rather than one now and one later. + + ## The two failure modes + + **Connect-time stop with no payload: encodes to bytes the decoder + rejects.** + + ```rust + AutoDetectRequest::BandwidthMeasureStop { + sequence_number: 7, + request_type: BW_STOP_CONNECT_TIME, + payload: None, + } + ``` + + encodes to ten bytes with `headerLength` 0x06 and no length field. + Decoding those bytes + fails with `not enough bytes provided to decode: received 0 bytes, + expected 2 bytes`, + because the decoder reads `payloadLength` back for every + `BW_STOP_CONNECT_TIME`. + + **UDP stop with a payload: silently loses it.** + + ```rust + AutoDetectRequest::BandwidthMeasureStop { + sequence_number: 3, + request_type: BW_STOP_RELIABLE_UDP, + payload: Some(vec![0xAA; 16]), + } + ``` + + encodes the length and the sixteen bytes, which the decoder never reads + back for + `BW_STOP_RELIABLE_UDP` or `BW_STOP_LOSSY_UDP`. + `AutoDetectReqPdu::decode` does not check + for trailing bytes, so the payload is dropped in transit without an + error rather than + refused. + + The second is the worse of the two: a round trip that loses data and + reports success. + + ## Network Characteristics Result (2.2.14.1.5) + + The same defect, found by sweeping the file rather than reported. + + `0x0840` carries baseRTT and averageRTT, `0x0880` carries bandwidth and + averageRTT, `0x08C0` carries all three, and the decoder already read + them on that basis. Encoding from the `Option`s meant: + + - a `0x0840` result with no `base_rtt_ms` wrote a body the decoder + cannot read; + - a `0x0840` result carrying `bandwidth_kbps` instead wrote that + bandwidth into the slot the decoder reads as baseRTT, so the value came + back **silently corrupted** rather than rejected; + - `headerLength` was always derived from `requestType`, so it could + contradict the body it described. + + Encode and `size()` now consult `requestType` through a shared helper, a + value the type does not carry is dropped rather than written, and a + missing one that the type requires is an error. + + ## Fix + + `Encode` and `size()` now key off `requestType`, matching the decoder. + That makes the + wire form canonical: + + - an absent payload on a connect-time stop encodes as a zero length and + reads back as an + empty one; + - a payload on a UDP stop never reaches the wire. + + Decoding and re-encoding reproduces the same bytes in every case. The + types can still + express states the protocol cannot, so value-level identity is not + achievable, but byte + stability is, and that is what a decoder consuming real traffic depends + on. + + No public signatures change; this is a behaviour fix inside the existing + `Encode` impl. + + ## Tests + + New `pdu/autodetect.rs` in `ironrdp-testsuite-core`: + + - a connect-time stop with no payload round-trips, and `size()` agrees + with `encode()`; + - a UDP stop carrying a payload encodes header-only and comes back with + `payload: None`, + for both the reliable and lossy request types; + - a connect-time stop preserves a real payload; + - decode-then-encode reproduces identical bytes for both shapes. + + Three of the four fail against the current encoder and pass with the + fix. The fourth is + a control that passed before and after. + + Note the existing `pdu_round_trip` fuzz oracle would not have caught + this even with + auto-detect added to it, since it discards the result of the re-decode + (`let _ =`). That + is deliberate in the oracle's design and out of scope here, but it is + why this went + unnoticed. + + ## How this was found + + Writing a test for the connect-time bandwidth measurement in #1465, + which needs to answer + a connect-time stop. The `None` case was constructed to check the reply + path stayed + lenient and turned out not to be constructible on the wire at all. + + ## Verification + + - `cargo xtask check fmt -v` green + - `cargo xtask check lints -v` green + - `cargo xtask check tests -v` green + - `cargo xtask check typos -v` green + - `cargo xtask check locks -v` green + +- Harden framing and empty output handling ([#1515](https://github.com/Devolutions/IronRDP/issues/1515)) ([33506e6139](https://github.com/Devolutions/IronRDP/commit/33506e613923dae504f46451231dcee15a6320a2)) + + Reject Fast-Path and TPKT frames whose declared length is smaller than + their header or minimum packet size. Also tolerate the zero-length + `totalLength` variation used by empty Update and Pointer output PDUs, + while continuing to reject zero-length non-output data PDUs. + + Adds regression coverage for malformed frame lengths and empty output + compatibility. + +- Share bulk decompression across output paths ([#1518](https://github.com/Devolutions/IronRDP/issues/1518)) ([6151e21bf5](https://github.com/Devolutions/IronRDP/commit/6151e21bf58b7297e9b4abc2167aa36fc2ba77e4)) + + Bulk compression state is stream-wide, but Fast-Path and slow-path + outputs previously used separate or missing decompression paths. This + could corrupt history-dependent server updates or leave negotiated + slow-path compression undecodable. + + This change owns the negotiated bulk decompressor in `ActiveStage` and + passes it to both X.224 and Fast-Path processing. It retains Share Data + compression metadata through the PDU context, resets decompression + history on reactivation, and initializes consumers from the connection's + negotiated compression type. + + Fast-Path now decompresses each fragment before reassembly so + compression flags apply at packet boundaries. Failures expose bounded + protocol metadata without retaining remote payloads or decoder details. + + Tests cover Share Data metadata propagation, slow-path decompression + behavior, fragmented Fast-Path reassembly and bounded errors, and + compressed Fast-Path updates after reactivation. + + --------- + +- Accept a zero-length connect-time bandwidth payload on decode ([#1511](https://github.com/Devolutions/IronRDP/issues/1511)) ([dffad79d61](https://github.com/Devolutions/IronRDP/commit/dffad79d6143f4d7e9b589768ad55fc98a04740c)) + + ## What + + A connect-time Bandwidth Measure Stop with `payloadLength` of zero now + decodes, as a present-but-empty payload. `Encode` still refuses to emit + one. + + ## Why + + [MS-RDPBCGR] 2.2.14.1.4 says of `payloadLength`: "It MUST be present + (and have a value greater than zero) if the value of the **requestType** + field is set to 0x002B." #1491 read that as a rule for both directions + and made encode and decode refuse a zero. The encode half is right. The + decode half is not. + + The two directions answer different questions. Encoding asks what we are + permitted to put on the wire, and a zero length has no conforming + encoding, so refusing is correct. Decoding asks whether we can act on + what a peer already sent. Here we can: `sequenceNumber` and + `requestType` arrive intact, and those fully determine the Bandwidth + Measure Results reply the PDU is asking for. The payload is random + measurement filler per the same section, and its length is the only + thing the reply reports about it. + + Rejecting therefore discards a PDU we could have answered without + gaining any protection. FreeRDP-based servers, including + gnome-remote-desktop, block in `AWAIT_BW_RESULT` until the results + arrive, so a server that sends a zero length stalls the whole connection + rather than getting a diagnostic. + + The fix #1491 was actually titled for, keying the optional fields off + `requestType` instead of the `Option`, is untouched. Only the added + zero-length rejection moves, and only on the receive side. + + ## Tests + + `connect_time_stop_with_a_zero_payload_length_is_rejected` becomes + `..._is_accepted` and now asserts the decoded value: `payload: + Some(vec![])`, present and empty rather than absent, since the wire + carried a length field. + + Added `a_decoded_zero_length_stop_does_not_re_encode`, so the asymmetry + is stated as a test rather than only as a comment. + + `connect_time_stop_without_a_payload_is_refused` is unchanged and still + covers the encode side. + + ## Note + + This does not break the `pdu_round_trip` oracle in #1492: a failing + `encode` after a successful `decode` is already tolerated there, and the + re-decode assertion only fires on a successful encode. + + ## Verification + + `cargo xtask check fmt/lints/tests/typos/locks` all pass. `cargo + semver-checks` reports no update required for `ironrdp-pdu`. + + ## Unblocks #1465 + + #1465 carries a regression test for a zero-`payloadLength` Stop, which + cannot pass until this lands: #1491 made `AutoDetectRequest`'s decoder + reject `payloadLength = 0`, so such a PDU is refused before it reaches + the connector. Its + `connect_time_bandwidth_answers_a_stop_carrying_an_empty_payload` is red + today and that red is this dependency, not a defect there. + + Verified against current `master`: #1465 alone fails that one case out + of 1032; #1465 with this applied passes all of them. Merging this first + turns #1465 green with no change on its side. + + ## Rebased + + Rebased onto `master` on 2026-08-02 so the checks run against the + current tree rather than the state before that day's merges. No + conflicts, no content change. + +- Keep auto-reconnect credential material out of Debug output ([#1496](https://github.com/Devolutions/IronRDP/issues/1496)) ([d0948faa18](https://github.com/Devolutions/IronRDP/commit/d0948faa187da673e9ded44e9e22cfbefc2c7f62)) + +- Batch Fast-Path input events ([#1630](https://github.com/Devolutions/IronRDP/issues/1630)) ([3818b48037](https://github.com/Devolutions/IronRDP/commit/3818b480375ec9411d5319d8e7af161f1d662cbf)) + + Keep outgoing Fast-Path input frames within the 255-event protocol limit + and preserve their order across FFI. + +### Build + +- Bump the crypto group across 1 directory with 3 updates ([#1449](https://github.com/Devolutions/IronRDP/issues/1449)) ([e1725e8c8a](https://github.com/Devolutions/IronRDP/commit/e1725e8c8a581b83835647b6ee563a5b3f6c7a1b)) + + + ## [[0.9.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-pdu-v0.8.0...ironrdp-pdu-v0.9.0)] - 2026-07-10 ### Security diff --git a/crates/ironrdp-pdu/Cargo.toml b/crates/ironrdp-pdu/Cargo.toml index 4e07b1454..b79217808 100644 --- a/crates/ironrdp-pdu/Cargo.toml +++ b/crates/ironrdp-pdu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-pdu" -version = "0.9.0" +version = "0.10.0" readme = "README.md" description = "RDP PDU encoding and decoding" edition.workspace = true @@ -26,7 +26,7 @@ arbitrary = ["alloc", "dep:arbitrary", "bitflags/arbitrary"] [dependencies] bitflags = "2.11" -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["std"] } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["std"] } # public ironrdp-error = { path = "../ironrdp-error", version = "0.2" } # public arbitrary = { version = "1", features = ["derive"], optional = true } tap = "1" diff --git a/crates/ironrdp-rail/CHANGELOG.md b/crates/ironrdp-rail/CHANGELOG.md new file mode 100644 index 000000000..7d7dd66e3 --- /dev/null +++ b/crates/ironrdp-rail/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [[0.1.0](https://github.com/Devolutions/IronRDP/releases/tag/ironrdp-rail-v0.1.0)] - 2026-08-13 + +### Features + +- Add RemoteApp protocol primitives ([#1636](https://github.com/Devolutions/IronRDP/issues/1636)) ([0161906731](https://github.com/Devolutions/IronRDP/commit/0161906731757356953cdb389a2cd6a42863deb2)) + + Add portable RAIL wire types and a typed Remote Programs capability set. + + Validate the RAIL crate's bare `no_std` and allocation-backed + configurations in the workspace feature matrix. + + Keep connection setup and windowing behavior outside this protocol + layer. + + diff --git a/crates/ironrdp-rail/Cargo.toml b/crates/ironrdp-rail/Cargo.toml index b526186fb..dd832aa54 100644 --- a/crates/ironrdp-rail/Cargo.toml +++ b/crates/ironrdp-rail/Cargo.toml @@ -18,7 +18,7 @@ std = ["alloc", "ironrdp-core/std", "dep:ironrdp-svc"] alloc = ["dep:ironrdp-core", "ironrdp-core/alloc"] [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2", default-features = false, optional = true } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", default-features = false, optional = true } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8", optional = true } # public [lints] diff --git a/crates/ironrdp-rdcleanpath/CHANGELOG.md b/crates/ironrdp-rdcleanpath/CHANGELOG.md index 57da670c5..eafb6251b 100644 --- a/crates/ironrdp-rdcleanpath/CHANGELOG.md +++ b/crates/ironrdp-rdcleanpath/CHANGELOG.md @@ -6,6 +6,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.2.3](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdcleanpath-v0.2.2...ironrdp-rdcleanpath-v0.2.3)] - 2026-08-13 + +### Features + +- Support Hyper-V connection ordering ([#1505](https://github.com/Devolutions/IronRDP/issues/1505)) ([5c1816244e](https://github.com/Devolutions/IronRDP/commit/5c1816244e83187a04249e9d9c240d096cb78f55)) + + Hyper-V over RDCleanPath needs PCB β†’ TLS on the proxy, then CredSSP β†’ + X.224 on the client. Ordinary RDCleanPath stays X.224-first. + + Still VERSION_1 with the same DER fields. An explicit VMConnect request + carries a Unicode PCB payload in `preconnection_blob` with no X.224; the + proxy encodes the binary PCB. Generic PCB requests keep their existing + X.224-first behavior. + + Gateway reference implementation: + [Devolutions/devolutions-gateway#1372](https://github.com/Devolutions/devolutions-gateway/pull/1372) + + Checked locally: Rust builds, formatting, Svelte typecheck, and .NET + build. Real nested Hyper-V E2E through Gateway: Native rendered 18 + frames, Avalonia connected and rendered its first frame, and Web + rendered a non-empty 1280Γ—720 canvas. + + --------- + +### Build + +- Bump the crypto group across 1 directory with 3 updates ([#1449](https://github.com/Devolutions/IronRDP/issues/1449)) ([e1725e8c8a](https://github.com/Devolutions/IronRDP/commit/e1725e8c8a581b83835647b6ee563a5b3f6c7a1b)) + + + ## [[0.2.2](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdcleanpath-v0.2.1...ironrdp-rdcleanpath-v0.2.2)] - 2026-06-05 ## [[0.2.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdcleanpath-v0.2.0...ironrdp-rdcleanpath-v0.2.1)] - 2025-10-02 diff --git a/crates/ironrdp-rdcleanpath/Cargo.toml b/crates/ironrdp-rdcleanpath/Cargo.toml index dada9e3be..b942a39b7 100644 --- a/crates/ironrdp-rdcleanpath/Cargo.toml +++ b/crates/ironrdp-rdcleanpath/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-rdcleanpath" -version = "0.2.2" +version = "0.2.3" readme = "README.md" description = "RDCleanPath PDU structure used by IronRDP web client and Devolutions Gateway" edition.workspace = true diff --git a/crates/ironrdp-rdpdr-native/CHANGELOG.md b/crates/ironrdp-rdpdr-native/CHANGELOG.md index bf89103bd..53df97c09 100644 --- a/crates/ironrdp-rdpdr-native/CHANGELOG.md +++ b/crates/ironrdp-rdpdr-native/CHANGELOG.md @@ -6,6 +6,46 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.7.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdpdr-native-v0.7.0...ironrdp-rdpdr-native-v0.7.1)] - 2026-08-13 + +### Security + +- Add advanced Windows filesystem semantics ([#1590](https://github.com/Devolutions/IronRDP/issues/1590)) ([d1c63ecd7b](https://github.com/Devolutions/IronRDP/commit/d1c63ecd7bd13c9ae3d88f3ae84176f214f41f61)) + + Extend the Windows-native RDPDR backend with confined directory, + notification, lock, security, stream, control, and volume support. + Decode only the portable IRPs and capability needed to dispatch these + native operations. + +### Features + +- Add Windows filesystem backend ([#1587](https://github.com/Devolutions/IronRDP/issues/1587)) ([7dce8a306f](https://github.com/Devolutions/IronRDP/commit/7dce8a306f43462677879905642967066c42337f)) + + Add a handle-relative Windows RDPDR backend for one selected volume. + It confines protocol paths below an opened root and supports bounded + file I/O + and basic metadata. + + Unsupported advanced filesystem operations return STATUS_NOT_SUPPORTED; + later + stack layers will add advanced Windows semantics and host integration. + +- Wire RDPDR backends into client connections ([#1600](https://github.com/Devolutions/IronRDP/issues/1600)) ([1fbc9bab0b](https://github.com/Devolutions/IronRDP/commit/1fbc9bab0bc26d8fe0789d5215005d7ea22e2a54)) + + Build a fresh RDPDR backend product for every connection attempt. + + Attach RDPDR only when its product has filesystem devices, advertise + RDPSND for Windows interoperability, and deliver deferred responses. + +- Add static drive redirection ([#1616](https://github.com/Devolutions/IronRDP/issues/1616)) ([a724f783d1](https://github.com/Devolutions/IronRDP/commit/a724f783d1638b4e215507849c1cf148887f30d5)) + + Expose Windows logical volumes through the ActiveX drive collection and + configure a static RDPDR backend from the selected pre-connect snapshot. + + DisableRdpdr remains a hard override. + + + ## [[0.7.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdpdr-native-v0.6.0...ironrdp-rdpdr-native-v0.7.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-rdpdr-native/Cargo.toml b/crates/ironrdp-rdpdr-native/Cargo.toml index cf521f3f6..9a434f1ca 100644 --- a/crates/ironrdp-rdpdr-native/Cargo.toml +++ b/crates/ironrdp-rdpdr-native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-rdpdr-native" -version = "0.7.0" +version = "0.7.1" readme = "README.md" description = "Native RDPDR static channel backend implementations for IronRDP" edition.workspace = true @@ -16,17 +16,17 @@ categories.workspace = true doctest = false [target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public -ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.7" } # public +ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.8" } # public nix = { version = "0.31", features = ["fs", "dir"] } tracing = { version = "0.1", features = ["log"] } [target.'cfg(windows)'.dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public -ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.7" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public +ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.8" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public tracing = { version = "0.1", features = ["log"] } windows = { version = "0.62", features = [ diff --git a/crates/ironrdp-rdpdr/CHANGELOG.md b/crates/ironrdp-rdpdr/CHANGELOG.md index ec466f800..6a3c983e3 100644 --- a/crates/ironrdp-rdpdr/CHANGELOG.md +++ b/crates/ironrdp-rdpdr/CHANGELOG.md @@ -6,6 +6,65 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdpdr-v0.7.0...ironrdp-rdpdr-v0.8.0)] - 2026-08-13 + +### Security + +- Add advanced Windows filesystem semantics ([#1590](https://github.com/Devolutions/IronRDP/issues/1590)) ([d1c63ecd7b](https://github.com/Devolutions/IronRDP/commit/d1c63ecd7bd13c9ae3d88f3ae84176f214f41f61)) + + Extend the Windows-native RDPDR backend with confined directory, + notification, lock, security, stream, control, and volume support. + Decode only the portable IRPs and capability needed to dispatch these + native operations. + +### Features + +- Add filesystem PDU foundation ([#1566](https://github.com/Devolutions/IronRDP/issues/1566)) ([161409e18d](https://github.com/Devolutions/IronRDP/commit/161409e18dd185de9bb30730303e56f5e8d28941)) + + ## Summary + - Add portable MS-RDPEFS/MS-FSCC filesystem request and completion + codecs with malformed-input validation and wire tests. + - Keep RDPDR runtime dispatch, Windows-native backend implementation, + and client/session integration out of this foundation. + + ## Follow-up + Later stacked PRs provide the backend implementation and runtime + integration. + + --------- + +- Add filesystem backend dispatch ([#1578](https://github.com/Devolutions/IronRDP/issues/1578)) ([8f5f3e2515](https://github.com/Devolutions/IronRDP/commit/8f5f3e25154a5edd324a8e89e30ef509a682dbaa)) + + Route confirmed filesystem requests through portable backend contracts + and make lifecycle, completion, and announcement state explicit. + Validate filesystem close padding, release dynamically activated drives + after rejected announcements, and prevent raw device removal from + bypassing backend cleanup. The noop backend now rejects unsupported + filesystem I/O. + + Later stacked PRs supply the concrete Windows backend and host + integration. + +- Add Windows filesystem backend ([#1587](https://github.com/Devolutions/IronRDP/issues/1587)) ([7dce8a306f](https://github.com/Devolutions/IronRDP/commit/7dce8a306f43462677879905642967066c42337f)) + + Add a handle-relative Windows RDPDR backend for one selected volume. + It confines protocol paths below an opened root and supports bounded + file I/O + and basic metadata. + + Unsupported advanced filesystem operations return STATUS_NOT_SUPPORTED; + later + stack layers will add advanced Windows semantics and host integration. + +- Wire RDPDR backends into client connections ([#1600](https://github.com/Devolutions/IronRDP/issues/1600)) ([1fbc9bab0b](https://github.com/Devolutions/IronRDP/commit/1fbc9bab0bc26d8fe0789d5215005d7ea22e2a54)) + + Build a fresh RDPDR backend product for every connection attempt. + + Attach RDPDR only when its product has filesystem devices, advertise + RDPSND for Windows interoperability, and deliver deferred responses. + + + ## [[0.7.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdpdr-v0.6.0...ironrdp-rdpdr-v0.7.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-rdpdr/Cargo.toml b/crates/ironrdp-rdpdr/Cargo.toml index db5b239e3..b98db8ac8 100644 --- a/crates/ironrdp-rdpdr/Cargo.toml +++ b/crates/ironrdp-rdpdr/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-rdpdr" -version = "0.7.0" +version = "0.8.0" readme = "README.md" description = "RDPDR channel implementation." edition.workspace = true @@ -16,9 +16,9 @@ categories.workspace = true doctest = false [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public ironrdp-error = { path = "../ironrdp-error", version = "0.2" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public tracing = { version = "0.1", features = ["log"] } bitflags = "2.11" diff --git a/crates/ironrdp-rdpeai/Cargo.toml b/crates/ironrdp-rdpeai/Cargo.toml index 7a5b03bc7..1d8fa64f9 100644 --- a/crates/ironrdp-rdpeai/Cargo.toml +++ b/crates/ironrdp-rdpeai/Cargo.toml @@ -19,10 +19,10 @@ doctest = false test = false [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["alloc"] } # public -ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.9" } # public β€” shared AUDIO_FORMAT +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", features = ["alloc"] } # public +ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.10" } # public β€” shared AUDIO_FORMAT ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public tracing = { version = "0.1", features = ["log"] } diff --git a/crates/ironrdp-rdpei/Cargo.toml b/crates/ironrdp-rdpei/Cargo.toml index ced700a76..0e273fc63 100644 --- a/crates/ironrdp-rdpei/Cargo.toml +++ b/crates/ironrdp-rdpei/Cargo.toml @@ -17,9 +17,9 @@ doctest = false [dependencies] bitflags = "2.11" -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public tracing = { version = "0.1", features = ["log"] } diff --git a/crates/ironrdp-rdpeusb/Cargo.toml b/crates/ironrdp-rdpeusb/Cargo.toml index dbc7c5477..23544d6cf 100644 --- a/crates/ironrdp-rdpeusb/Cargo.toml +++ b/crates/ironrdp-rdpeusb/Cargo.toml @@ -21,9 +21,9 @@ default = [] std = [] [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["alloc"] } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", features = ["alloc"] } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public ironrdp-str = { path = "../ironrdp-str", version = "0.1" } [lints] diff --git a/crates/ironrdp-rdpsnd-native/CHANGELOG.md b/crates/ironrdp-rdpsnd-native/CHANGELOG.md index 88bab83e2..09d641e78 100644 --- a/crates/ironrdp-rdpsnd-native/CHANGELOG.md +++ b/crates/ironrdp-rdpsnd-native/CHANGELOG.md @@ -6,6 +6,42 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.7.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdpsnd-native-v0.7.0...ironrdp-rdpsnd-native-v0.7.1)] - 2026-08-13 + +### Features + +- Harden Windows client playback path ([#1648](https://github.com/Devolutions/IronRDP/issues/1648)) ([2d9a9bf114](https://github.com/Devolutions/IronRDP/commit/2d9a9bf114dcf41a1ddc7343f564bc2e8d1d06db)) + + Keep client format order for wFormatNo, play pre-v8 Wave PDUs, and apply + volume on a broader CPAL PCM offer so ActiveX mode 0 can redirect remote + audio reliably. + + Also fix clippy noise in the RDPSND client suite and keep interleaved + volume L/R phase stable across wave blocks. Volume scaling is a simple + amplitude map, not a logarithmic MS-RDPEA model. + +- Wire MS-RDPEAI capture into Windows client and ActiveX ([#1642](https://github.com/Devolutions/IronRDP/issues/1642)) ([205fe038cc](https://github.com/Devolutions/IronRDP/commit/205fe038cc693598adf803fe181526b789b2ec3d)) + + Add the client MS-RDPEAI capture path on top of hardened RDPSND + playback: connector CFG + static channel wiring, CPAL PCM capture + backend, ironrdp-client --audio-capture, and ActiveX + AudioCaptureRedirectionMode. + + PCM capture only accepts encode formats that match the Open capture + stream, rejects non-16-bit capture (Data PDU size contract), and gates + the capture backend behind ironrdp-rdpsnd-native/capture. + + Depends on #1648 (playback). + +### Bug Fixes + +- Make source locations opt-in ([#1480](https://github.com/Devolutions/IronRDP/issues/1480)) ([f84cd01450](https://github.com/Devolutions/IronRDP/commit/f84cd01450e18d12838b225859878b311802b805)) + + Default error display omits locations; alternate formatting and reports + with explicit location opt-in preserve diagnostic context. + + + ## [[0.7.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdpsnd-native-v0.6.0...ironrdp-rdpsnd-native-v0.7.0)] - 2026-07-10 ### Bug Fixes diff --git a/crates/ironrdp-rdpsnd-native/Cargo.toml b/crates/ironrdp-rdpsnd-native/Cargo.toml index 87dac1607..b0e685d06 100644 --- a/crates/ironrdp-rdpsnd-native/Cargo.toml +++ b/crates/ironrdp-rdpsnd-native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-rdpsnd-native" -version = "0.7.0" +version = "0.7.1" description = "Native RDPSND playback and optional MS-RDPEAI capture backends for IronRDP" edition.workspace = true rust-version = "1.94" @@ -26,7 +26,7 @@ bytemuck = { version = "1.24", optional = true } cpal = "0.17" ironrdp-error = { path = "../ironrdp-error", version = "0.2", features = ["std"] } # public ironrdp-rdpeai = { path = "../ironrdp-rdpeai", version = "0.1", optional = true } # public -ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.9" } # public +ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.10" } # public opus2 = { version = "0.4", optional = true, features = ["bundled"] } tracing = { version = "0.1", features = ["log"] } diff --git a/crates/ironrdp-rdpsnd/CHANGELOG.md b/crates/ironrdp-rdpsnd/CHANGELOG.md index 0c3cb06b0..364c64f45 100644 --- a/crates/ironrdp-rdpsnd/CHANGELOG.md +++ b/crates/ironrdp-rdpsnd/CHANGELOG.md @@ -6,6 +6,53 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdpsnd-v0.9.0...ironrdp-rdpsnd-v0.10.0)] - 2026-08-13 + +### Features + +- Add AUDIO_INPUT protocol crate ([#1645](https://github.com/Devolutions/IronRDP/issues/1645)) ([50fa88b29e](https://github.com/Devolutions/IronRDP/commit/50fa88b29e5d57c5c6353229bda8aa786a9906fd)) + + Introduce `ironrdp-rdpeai` for MS-RDPEAI (AUDIO_INPUT) PDUs and client + handler, plus the shared RDPSND format matching helper used during + negotiation. + + The crate is workspace-internal (`publish = false`) with unit coverage + in `ironrdp-testsuite-core`. Capture backends and ActiveX wiring land in + a follow-up stacked PR. + +- Harden Windows client playback path ([#1648](https://github.com/Devolutions/IronRDP/issues/1648)) ([2d9a9bf114](https://github.com/Devolutions/IronRDP/commit/2d9a9bf114dcf41a1ddc7343f564bc2e8d1d06db)) + + Keep client format order for wFormatNo, play pre-v8 Wave PDUs, and apply + volume on a broader CPAL PCM offer so ActiveX mode 0 can redirect remote + audio reliably. + + Also fix clippy noise in the RDPSND client suite and keep interleaved + volume L/R phase stable across wave blocks. Volume scaling is a simple + amplitude map, not a logarithmic MS-RDPEA model. + +### Bug Fixes + +- Isolate malformed encrypted waves ([#1514](https://github.com/Devolutions/IronRDP/issues/1514)) ([c87ab68e9c](https://github.com/Devolutions/IronRDP/commit/c87ab68e9c6adbf524cb0b2783ff4bd61178fb9b)) + + ## Summary + + - Treat malformed RDPSND server-audio PDUs as recoverable channel input + and ignore them without failing the desktop session. + - Preserve the RDPSND state after a decode failure so valid subsequent + audio continues normally. + - Add a regression test for an encrypted wave missing its required v5 + signature. + + ## Testing + + - `cargo test -p ironrdp-testsuite-core --test integration_tests_core -- + rdpsnd::client` + - `cargo fmt --all -- --check` + + --------- + + + ## [[0.9.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-rdpsnd-v0.8.1...ironrdp-rdpsnd-v0.9.0)] - 2026-07-10 ### Features diff --git a/crates/ironrdp-rdpsnd/Cargo.toml b/crates/ironrdp-rdpsnd/Cargo.toml index 11e2e2944..728b4df47 100644 --- a/crates/ironrdp-rdpsnd/Cargo.toml +++ b/crates/ironrdp-rdpsnd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-rdpsnd" -version = "0.9.0" +version = "0.10.0" readme = "README.md" description = "RDPSND static channel for audio output implemented as described in MS-RDPEA" edition.workspace = true @@ -29,8 +29,8 @@ __test = ["dep:visibility"] bitflags = "2.11" tracing = { version = "0.1", features = ["log"] } ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["alloc"] } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", features = ["alloc"] } # public visibility = { version = "0.1", optional = true } [lints] diff --git a/crates/ironrdp-rpc/Cargo.toml b/crates/ironrdp-rpc/Cargo.toml index 7545da0bb..de648c22c 100644 --- a/crates/ironrdp-rpc/Cargo.toml +++ b/crates/ironrdp-rpc/Cargo.toml @@ -18,9 +18,9 @@ categories.workspace = true __test = [] [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } # public ironrdp-input = { path = "../ironrdp-input", version = "0.7" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } # public tokio = { version = "1", features = ["net", "io-util"] } anyhow = "1" diff --git a/crates/ironrdp-server/CHANGELOG.md b/crates/ironrdp-server/CHANGELOG.md index 6bc9421f4..cfa9148b0 100644 --- a/crates/ironrdp-server/CHANGELOG.md +++ b/crates/ironrdp-server/CHANGELOG.md @@ -6,6 +6,422 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.14.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-server-v0.13.0...ironrdp-server-v0.14.0)] - 2026-08-13 + +### Security + +- Send the Server Auto-Reconnect Cookie during logon ([#1405](https://github.com/Devolutions/IronRDP/issues/1405)) ([7d35d65248](https://github.com/Devolutions/IronRDP/commit/7d35d6524886ab4e9f610b4b70566eaa21ea8177)) + + ## What + + Adds an optional **Server Auto-Reconnect Cookie** (MS-RDPBCGR 2.2.4.3 + `ARC_SC_PRIVATE_PACKET`) to `ironrdp-server`. When set, `RdpServer` + sends a Save Session Info PDU (`LogonExtended` + + `AUTO_RECONNECT_COOKIE`) carrying it once per connection, right after + activation (Confirm Active processed, encoder built), on the IO channel. + + ## Why + + A client only enters its **automatic reconnection sequence** (MS-RDPBCGR + 1.3.1.5) on an *ungraceful* disconnect if it was handed this cookie + during logon. Without it, a dropped connection just reports as + disconnected β€” **mstsc in particular won't auto-reconnect at all**. + Today `ironrdp-server` never sends the cookie, so there's no way for a + server to opt into that behavior. + + The concrete use case: a server that *intentionally* drops a connection + and expects the client to come straight back β€” e.g. a recovery path that + cycles the session β€” currently forces the user to reconnect by hand. + With the cookie provisioned, the client re-establishes on its own + (re-authenticating via NLA/CredSSP from cached credentials). It's also + just standard behavior a real RDP server provides. + + ## API + + Mirrors the existing `credential_validator` pattern exactly β€” a builder + method plus a runtime setter: + + ```rust + // build time + RdpServer::builder() + .with_auto_reconnect_cookie(Some(ServerAutoReconnect { logon_id, random_bits })) + // ... + + // or dynamically + server.set_auto_reconnect_cookie(Some(cookie)); + ``` + + `ServerAutoReconnect` (already public in + `ironrdp-pdu::rdp::session_info`) carries a `logon_id` and a 16-byte + `random_bits` (generate from a CSPRNG). A per-connection guard + (`auto_reconnect_sent`, reset in `run_connection_with`) sends it exactly + once β€” not again on a Deactivation-Reactivation. + + ## Scope / additive + + - **Additive, non-breaking.** Default is `None` (send nothing); existing + servers are byte-for-byte unaffected. + - All PDU types already exist in `ironrdp-pdu` + (`rdp::session_info::{SaveSessionInfoPdu, LogonInfoExtended, + LogonExFlags, ServerAutoReconnect, InfoType, InfoData}`) β€” this is + purely wiring the server-side send. + - Reuses the existing `encode_share_data_pdu` helper. + + ## Design point for review β€” the returning cookie + + This PR implements the **send** side only: it *enables* the client's + automatic reconnection. It does **not** validate the + `ARC_CS_PRIVATE_PACKET` the client sends back on reconnect (MS-RDPBCGR + 2.2.4.4). For a server that re-authenticates every connection by other + means (NLA/CredSSP) that's sufficient and safe, and it's documented as + such on the setter. If you'd prefer the crate to also offer *validation* + of the returning cookie (so it can be an authentication factor β€” the + server would store issued `(logon_id, random_bits)` and verify the + client's `SecurityData`/`ARC_CS` on the next connect), I'm happy to do + that as a follow-up, or fold it in here β€” it's a larger, stateful + feature so I kept this PR to the send path. Let me know which you'd + prefer. + + Built + `clippy --features egfx -D warnings` clean; tests compile. + + --------- + +- Validate auto-reconnect cookies ([#1509](https://github.com/Devolutions/IronRDP/issues/1509)) ([44f675e244](https://github.com/Devolutions/IronRDP/commit/44f675e244ee76b5311756668ffbbe28e98c7175)) + + ## Summary + - parse and carry `ARC_CS_PRIVATE_PACKET` data through the acceptor + - validate returning Enhanced RDP Security cookies with HMAC-MD5 before + reconnecting + - rotate reconnect randoms per connection and hourly, with runtime + cookie updates + - restrict cookie authentication to TLS/Hybrid and document the behavior + + ## Testing + - `cargo test -p ironrdp-pdu -p ironrdp-acceptor -p ironrdp-server` + - `cargo clippy -p ironrdp-pdu -p ironrdp-acceptor -p ironrdp-server + --all-targets -- -D warnings` + +- [**breaking**] Support session resume via the auto-reconnect cookie ([#1501](https://github.com/Devolutions/IronRDP/issues/1501)) ([74b3365c1f](https://github.com/Devolutions/IronRDP/commit/74b3365c1f98c0da6feed7507779c67e1b8e6d08)) + + > **Rebased onto post-#1522 master.** #1509 landed the server half of + #1508 while this was open, including the `ClientAutoReconnect` + structure. This PR no longer declares it; it extends it, and picks up + the parts #1509 did not build. + + ## What + + The client half of automatic reconnection. The session layer surfaces + the Server Auto-Reconnect Cookie, `ironrdp-pdu` derives and verifies the + client's response to it, and the connector sends that response when + resuming a session. + + ## Why + + A client whose connection drops ungracefully can reattach to its session + instead of making the user log on again, provided it returns the cookie + the server issued during logon ([MS-RDPBCGR] 1.3.1.5). + + #1509 built the server side of that: it validates a returning + `ARC_CS_PRIVATE_PACKET` and rotates the random. Nothing answers it. + `ironrdp-session` decodes the cookie and drops it, `ironrdp-connector` + has no way to send one back, and `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` still sits in + `ironrdp-client`. So `ironrdp-client` cannot resume a session against + `ironrdp-server`, and the validation #1509 added has no in-tree + counterpart to exercise it. + + The wire encoding was already there. `ExtendedClientOptionalInfo` + carries, encodes and decodes a 28-byte `autoReconnectCookie` and its + builder already had a `reconnect_cookie` step; `ServerAutoReconnect` + already decoded; #1509 added `ClientAutoReconnect` and its decode. + Nothing connected them. + + ## The three parts + + **Receive.** `SaveSessionInfo` now also surfaces the cookie, as + `ProcessorOutput::AutoReconnectCookie` and + `ActiveStageOutput::AutoReconnectCookie`. #1522 added a `SaveSessionInfo + { logon_complete }` output on that same handler; the two coexist rather + than compete, since both are read off one PDU and neither supersedes the + other. The handler emits the logon notification unconditionally and + appends the cookie when one is present, and a test pins that surfacing + the cookie does not suppress the notification. #1509's server replaces + the cookie whenever a client connects and again hourly ([MS-RDPBCGR] + 3.3.6.2), so this can arrive more than once in a session and the + consumer keeps the most recent. + + **Derive.** `ClientAutoReconnect::from_server_cookie` implements + [MS-RDPBCGR] 5.5: + + > The auto-reconnect random is used to key the HMAC function + ([RFC2104]), which uses MD5 as the iterative hash function. The security + verifier is derived by applying the HMAC to the client random received + in Step 3. + > + > `SecurityVerifier = HMAC(AutoReconnectRandom, ClientRandom)` + > + > When Enhanced RDP Security is in effect the client random value is not + generated (section 5.3.2). In this case, for the purpose of generating + the security verifier, the client random is assumed to be an array of 32 + zero bytes. + + IronRDP implements no Standard RDP Security path (there is no Security + Exchange PDU), so the zero-client-random case is the only one that + arises. As 5.5 notes, that makes the verifier constant for a given + cookie, so it proves possession of the cookie and nothing more; session + security comes from the outer TLS/CredSSP handshake. + + @clintcan independently confirmed this construction against real + **mstsc** while validating #1509 + ([comment](https://github.com/Devolutions/IronRDP/pull/1509#issuecomment-5151200681)): + a Windows client's `ARC_CS_PRIVATE_PACKET` verifies against + `HMAC-MD5(random_bits, [0u8; 32])`. That is the same derivation + implemented here, so the two halves interoperate with Microsoft's client + and not only with each other. + + **Send.** `ClientConnector::with_auto_reconnect_cookie` takes the cookie + last received and makes the connector put the derived Client + Auto-Reconnect Packet ([MS-RDPBCGR] 2.2.4.3) in the Client Info PDU. + Absent, that PDU is byte-for-byte what it was. + + Unlike the server packet, this structure has no enclosing logon-info + field header, so it encodes to exactly the 28 bytes the cookie field + expects. `to_bytes` writes that layout directly rather than going + through `Encode`, so filling a fixed-size field has no error path a + caller must handle; a test pins the two to agree. + + ## One derivation, not two + + Putting `from_server_cookie` in `ironrdp-pdu` would leave the workspace + with two implementations of 5.5, since #1509 added a private HMAC to + `ironrdp-server`. So `ClientAutoReconnect` also gains `verify`, and the + server routes through it. + + `verify` keeps the constant-time comparison the server had. The verifier + is the whole credential, so a comparison returning early on the first + differing byte would let a peer recover it a byte at a time from the + timing; the session identifier is not secret and is compared normally. + `ironrdp-server` keeps the policy around the check, which cookies are + live and whether the security protocol permits auto-reconnect, and drops + its `hmac` and `md-5` dependencies. `hmac` moves to `ironrdp-pdu` as + `default-features = false`; the crate's full feature powerset still + checks clean, including `--no-default-features`. + + I would rather not have reached into `ironrdp-server` in a + `pdu,session,connector` change, but the alternative was shipping the + duplicate and filing a follow-up to remove it, which is a worse trade + for reviewer time. + + ## Tests that were not running + + That move also rehomes the known-answer tests @clintcan contributed on + #1509. They went in as an inline `#[cfg(test)]` module in + `crates/ironrdp-server/src/server.rs`, and that crate sets `[lib] test = + false`, so they have never executed in CI. They now live in + `ironrdp-testsuite-core` against the public API, where CI runs them: his + HMAC-MD5 reference vector is kept as a second vector alongside a + differently-keyed one, plus the cases for a tampered verifier and a + mismatched logon ID. + + Worth flagging separately: `ironrdp-server` is not alone. + `ironrdp-agent`, `ironrdp-session` and `ironrdp-web` also set `[lib] + test = false` and between them carry 16 files of inline `#[cfg(test)]` + modules that CI never runs. That is out of scope here, but I am happy to + open an issue if it would be useful. + + ## Breaking changes + + `ActiveStageOutput` and `x224::ProcessorOutput` gain a variant, and + `ClientConnector` gains a public field, so exhaustive matches and struct + literals need updating. + + Confirmed with `cargo-semver-checks` against the merge-base: those three + are the only findings this branch introduces. The others it reports on + `master` today (`ShareDataPdu::Compressed` and the `ShareDataCtx` fields + from #1518, `ProcessorBuilder.bulk_decompressor` from #1518, + `ServerEvent::SetAutoReconnectCookie` from #1509) are present on + `master` unchanged. The `ironrdp-pdu` additions are additive. + + ## Scope + + This is the library half. `ironrdp-client`, `ironrdp-web` and the FFI + bindings gain an arm for the new output but none of them reconnect + automatically yet; that is the remaining part of #271, and the existing + `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` in `ironrdp-client` marks where it goes. + + I kept receive, derive and send together deliberately. Split up, none of + them is usable on its own: without the receive half there is no way to + obtain a cookie, and without the send half there is nothing to do with + one. + + ## Tests + + Thirteen, all in `ironrdp-testsuite-core`. + + On the packet and the derivation: the `SecurityVerifier` matches two + independently computed HMAC-MD5 vectors of 32 zero bytes under different + keys, so the tests pin the derivation rather than restating the code; + the logon ID carries over from the server cookie; the encoding matches + the 2.2.4.3 field layout byte for byte with `cbLen` fixed at `0x1C`; + `to_bytes` agrees with `Encode`; it round-trips; and it rejects both a + wrong packet length and an unknown version. + + On verification: a derived answer is accepted, a single flipped byte in + the verifier is rejected, a correct verifier under a different logon ID + is rejected, and an answer derived from a different random is rejected. + + On the surfacing path: a Save Session Info PDU framed the way a server + sends it, through the real x224 processor, yields an + `AutoReconnectCookie` carrying the right logon ID and random bits, + alongside #1522's logon notification rather than in place of it; and one + without a cookie surfaces no cookie. + + ## Verification + + `cargo xtask check fmt/lints/tests/typos/locks` all pass on 1.94.1, + including a `fuzz/` build before the lock check. + + ## Note + + #1496 also touches the `ClientAutoReconnect` declaration. Whichever of + the two lands second needs a one-line rebase on the derive attribute; + happy to take that in either order. + +### Features + +- [**breaking**] Clamp honored client desktop size to an operator maximum ([#1404](https://github.com/Devolutions/IronRDP/issues/1404)) ([d3747a05b2](https://github.com/Devolutions/IronRDP/commit/d3747a05b202ba2d87ac19698354ae7e487850a2)) + + Follow-up to #1373 (the resource-hardening angle you flagged in review β€” + thanks for the go-ahead πŸ™‚). + + ## Problem + + `#1373` gated honor-client-desktop-size behind a bare `bool`. With it + on, the acceptor adopts the client-requested desktop size bounded only + by the protocol range `[200, 8192]`. But the desktop size is a + client-controlled `u16`, and the server still builds its + framebuffer/encoder from the negotiated size β€” so a client could request + e.g. `8192x8192` and drive the server's allocation off an untrusted + number (~256 MiB per frame buffer). Mild, and only on an opt-in + default-off path, but it's a resource-exhaustion vector driven purely by + a number the client picks. + + Your review comment: *"[200, 8192] is a protocol ceiling, not a resource + guard … tracked the 'clamp/range policy rather than a bare bool' idea as + a future follow-up (an operator-set max size)."* This is that PR. + + ## Change + + Replace the `bool` with `Option` carrying an **operator-set + maximum**: + + - `None` (default) β€” disabled; always enforce the server-provided size + (unchanged behavior). + - `Some(max)` β€” honor the client's request, **clamped per dimension to + `max`**. The client can ask for a smaller desktop, never a larger one. + + The acceptor clamps the requested `width`/`height` to `max` *before* the + existing `validate_desktop_size` protocol-range check, so the negotiated + size can never exceed what the operator is willing to render β€” set `max` + to the host display's native resolution (or whatever ceiling the server + can afford). + +- Support runtime-defined static virtual channels ([#1517](https://github.com/Devolutions/IronRDP/issues/1517)) ([8b4c483ba0](https://github.com/Devolutions/IronRDP/commit/8b4c483ba0c900a8de0b2718347754f56dd363ba)) + + ## Summary + - add keyed runtime-defined static-channel registration, lookup, and + negotiated ID attachment + - enforce the static-channel limit and reject malformed SVC fragment + sequences + - wire generic connector, acceptor, and session name-based dispatch + support + + ## Testing + - `cargo test -p ironrdp-testsuite-core --test integration_tests_core + svc::` + - `cargo clippy -p ironrdp-testsuite-core --test integration_tests_core + -- -D warnings` + + --------- + +- Add static-channel factories ([#1633](https://github.com/Devolutions/IronRDP/issues/1633)) ([e48b29c017](https://github.com/Devolutions/IronRDP/commit/e48b29c0173096c1718c9f657bed3a59926aa97c)) + + Create fresh static-channel processors before GCC negotiation, expose + the acceptor type needed to configure them, and exercise RDPDR + initialization and drive I/O end to end. + + --------- + +### Build + +- Bump the crypto group across 1 directory with 3 updates ([#1449](https://github.com/Devolutions/IronRDP/issues/1449)) ([e1725e8c8a](https://github.com/Devolutions/IronRDP/commit/e1725e8c8a581b83835647b6ee563a5b3f6c7a1b)) + +### Refactor + +- [**breaking**] Take auto-detect timestamps from the caller ([#1487](https://github.com/Devolutions/IronRDP/issues/1487)) ([f614e4acfc](https://github.com/Devolutions/IronRDP/commit/f614e4acfcb8abf7113e0f5c653112af94ed80af)) + + ## Summary + + - `AutoDetectManager` read the clock itself: `std::time::Instant` in + `pending_probes`, `Instant::now()` in `send_rtt_request`, and + `Instant::elapsed()` in `handle_response` and `expire_stale_probes`. + - It now takes `now_ms`, a caller-supplied monotonic millisecond counter + whose epoch is arbitrary as long as it's consistent across calls. + `ironrdp-server` supplies it from a process-wide monotonic origin in + `server.rs`, so the clock lives in the I/O driver rather than in the + state machine. + + ## Why + + - **Testability.** The RTT assertions were wall-clock dependent. + `snapshot_reflects_measurements` could only check that the average came + out under an arbitrary 100 ms bound, which almost any bug would satisfy. + It now supplies both timestamps and asserts exact values: samples of 10, + 20 and 30 ms giving min 10, max 30, average 20. + - **Portability.** `std::time::Instant::now` panics on + `wasm32-unknown-unknown`, so a type that reads it internally can't be + reused from a WASM build. + - **Layering.** A state machine that reads ambient time can't satisfy + the no-I/O rule the Core Tier crates follow, which forecloses moving + this code in that direction later. + + Also covers the edges of the arithmetic the injected clock exposes. + There are two `saturating_sub` sites and they saturate in opposite + directions: + + - `handle_response`: a clock that ran backwards between request and + response yields a zero sample rather than a wrapped value near + `u32::MAX`, and the zero reaches the sample window, not just the return + value. + - `expire_stale_probes`: the same backwards clock makes the age zero, + which is below any maximum, so the probe stays pending. Wrapping would + make it look older than any limit and drop a probe whose response is + still in flight. + + A third test covers the `u32::try_from(..).unwrap_or(u32::MAX)` on the + first of those lines, where a gap wider than about 49.7 days clamps + rather than truncating to the low 32 bits. + + ## Validation + + `cargo xtask check fmt/lints/tests/typos/locks` all pass. + + Each of the three new tests was checked against a mutation of the code + it guards rather than only for passing: the two backwards-clock tests + fail if either `saturating_sub` becomes `wrapping_sub`, and the clamp + test fails if the `try_from` becomes a truncating `as u32`, which + reports `Some(0)` for a 49.7 day gap. + + ## Notes + + - Came out of the discussion on #1465, where the same question arises on + the connector side. `ironrdp-connector` reads no clock at all today, and + answering a connect-time Bandwidth Measure properly needs one; taking + the timestamp from the caller is the shape that works on every target. + - `AutoDetectManager` arrived in #1177 with the internal clock, so this + corrects code I wrote rather than anyone else's. + + + ## [[0.13.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-server-v0.12.0...ironrdp-server-v0.13.0)] - 2026-07-10 ### Security diff --git a/crates/ironrdp-server/Cargo.toml b/crates/ironrdp-server/Cargo.toml index 2b16df9e0..c2ab96731 100644 --- a/crates/ironrdp-server/Cargo.toml +++ b/crates/ironrdp-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-server" -version = "0.13.0" +version = "0.14.0" readme = "README.md" description = "Extendable skeleton for implementing custom RDP servers" edition.workspace = true @@ -38,21 +38,21 @@ rand = "0.9" tokio = { version = "1", features = ["net", "macros", "sync", "rt", "time"] } # public tokio-rustls = "0.26" # public async-trait = "0.1" -ironrdp-async = { path = "../ironrdp-async", version = "0.10" } +ironrdp-async = { path = "../ironrdp-async", version = "0.11" } ironrdp-ainput = { path = "../ironrdp-ainput", version = "0.8" } -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } ironrdp-egfx = { path = "../ironrdp-egfx", version = "0.3", optional = true } ironrdp-nscodec = { path = "../ironrdp-nscodec", version = "0.2", optional = true, features = ["encoder"] } -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.7" } # public ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.8" } # public ironrdp-echo = { path = "../ironrdp-echo", version = "0.4" } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.10", features = ["reqwest"] } -ironrdp-acceptor = { path = "../ironrdp-acceptor", version = "0.10" } # public -ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.9" } # public -ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.9" } # public +ironrdp-acceptor = { path = "../ironrdp-acceptor", version = "0.11" } # public +ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.10" } # public +ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.10" } # public tracing = { version = "0.1", features = ["log"] } x509-cert = { version = "0.3", optional = true } rustls-pemfile = { version = "2.2", optional = true } diff --git a/crates/ironrdp-session/CHANGELOG.md b/crates/ironrdp-session/CHANGELOG.md index 240f41962..ab2b66298 100644 --- a/crates/ironrdp-session/CHANGELOG.md +++ b/crates/ironrdp-session/CHANGELOG.md @@ -6,6 +6,505 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.12.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-session-v0.11.0...ironrdp-session-v0.12.0)] - 2026-08-13 + +### Security + +- [**breaking**] Support session resume via the auto-reconnect cookie ([#1501](https://github.com/Devolutions/IronRDP/issues/1501)) ([74b3365c1f](https://github.com/Devolutions/IronRDP/commit/74b3365c1f98c0da6feed7507779c67e1b8e6d08)) + + > **Rebased onto post-#1522 master.** #1509 landed the server half of + #1508 while this was open, including the `ClientAutoReconnect` + structure. This PR no longer declares it; it extends it, and picks up + the parts #1509 did not build. + + ## What + + The client half of automatic reconnection. The session layer surfaces + the Server Auto-Reconnect Cookie, `ironrdp-pdu` derives and verifies the + client's response to it, and the connector sends that response when + resuming a session. + + ## Why + + A client whose connection drops ungracefully can reattach to its session + instead of making the user log on again, provided it returns the cookie + the server issued during logon ([MS-RDPBCGR] 1.3.1.5). + + #1509 built the server side of that: it validates a returning + `ARC_CS_PRIVATE_PACKET` and rotates the random. Nothing answers it. + `ironrdp-session` decodes the cookie and drops it, `ironrdp-connector` + has no way to send one back, and `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` still sits in + `ironrdp-client`. So `ironrdp-client` cannot resume a session against + `ironrdp-server`, and the validation #1509 added has no in-tree + counterpart to exercise it. + + The wire encoding was already there. `ExtendedClientOptionalInfo` + carries, encodes and decodes a 28-byte `autoReconnectCookie` and its + builder already had a `reconnect_cookie` step; `ServerAutoReconnect` + already decoded; #1509 added `ClientAutoReconnect` and its decode. + Nothing connected them. + + ## The three parts + + **Receive.** `SaveSessionInfo` now also surfaces the cookie, as + `ProcessorOutput::AutoReconnectCookie` and + `ActiveStageOutput::AutoReconnectCookie`. #1522 added a `SaveSessionInfo + { logon_complete }` output on that same handler; the two coexist rather + than compete, since both are read off one PDU and neither supersedes the + other. The handler emits the logon notification unconditionally and + appends the cookie when one is present, and a test pins that surfacing + the cookie does not suppress the notification. #1509's server replaces + the cookie whenever a client connects and again hourly ([MS-RDPBCGR] + 3.3.6.2), so this can arrive more than once in a session and the + consumer keeps the most recent. + + **Derive.** `ClientAutoReconnect::from_server_cookie` implements + [MS-RDPBCGR] 5.5: + + > The auto-reconnect random is used to key the HMAC function + ([RFC2104]), which uses MD5 as the iterative hash function. The security + verifier is derived by applying the HMAC to the client random received + in Step 3. + > + > `SecurityVerifier = HMAC(AutoReconnectRandom, ClientRandom)` + > + > When Enhanced RDP Security is in effect the client random value is not + generated (section 5.3.2). In this case, for the purpose of generating + the security verifier, the client random is assumed to be an array of 32 + zero bytes. + + IronRDP implements no Standard RDP Security path (there is no Security + Exchange PDU), so the zero-client-random case is the only one that + arises. As 5.5 notes, that makes the verifier constant for a given + cookie, so it proves possession of the cookie and nothing more; session + security comes from the outer TLS/CredSSP handshake. + + @clintcan independently confirmed this construction against real + **mstsc** while validating #1509 + ([comment](https://github.com/Devolutions/IronRDP/pull/1509#issuecomment-5151200681)): + a Windows client's `ARC_CS_PRIVATE_PACKET` verifies against + `HMAC-MD5(random_bits, [0u8; 32])`. That is the same derivation + implemented here, so the two halves interoperate with Microsoft's client + and not only with each other. + + **Send.** `ClientConnector::with_auto_reconnect_cookie` takes the cookie + last received and makes the connector put the derived Client + Auto-Reconnect Packet ([MS-RDPBCGR] 2.2.4.3) in the Client Info PDU. + Absent, that PDU is byte-for-byte what it was. + + Unlike the server packet, this structure has no enclosing logon-info + field header, so it encodes to exactly the 28 bytes the cookie field + expects. `to_bytes` writes that layout directly rather than going + through `Encode`, so filling a fixed-size field has no error path a + caller must handle; a test pins the two to agree. + + ## One derivation, not two + + Putting `from_server_cookie` in `ironrdp-pdu` would leave the workspace + with two implementations of 5.5, since #1509 added a private HMAC to + `ironrdp-server`. So `ClientAutoReconnect` also gains `verify`, and the + server routes through it. + + `verify` keeps the constant-time comparison the server had. The verifier + is the whole credential, so a comparison returning early on the first + differing byte would let a peer recover it a byte at a time from the + timing; the session identifier is not secret and is compared normally. + `ironrdp-server` keeps the policy around the check, which cookies are + live and whether the security protocol permits auto-reconnect, and drops + its `hmac` and `md-5` dependencies. `hmac` moves to `ironrdp-pdu` as + `default-features = false`; the crate's full feature powerset still + checks clean, including `--no-default-features`. + + I would rather not have reached into `ironrdp-server` in a + `pdu,session,connector` change, but the alternative was shipping the + duplicate and filing a follow-up to remove it, which is a worse trade + for reviewer time. + + ## Tests that were not running + + That move also rehomes the known-answer tests @clintcan contributed on + #1509. They went in as an inline `#[cfg(test)]` module in + `crates/ironrdp-server/src/server.rs`, and that crate sets `[lib] test = + false`, so they have never executed in CI. They now live in + `ironrdp-testsuite-core` against the public API, where CI runs them: his + HMAC-MD5 reference vector is kept as a second vector alongside a + differently-keyed one, plus the cases for a tampered verifier and a + mismatched logon ID. + + Worth flagging separately: `ironrdp-server` is not alone. + `ironrdp-agent`, `ironrdp-session` and `ironrdp-web` also set `[lib] + test = false` and between them carry 16 files of inline `#[cfg(test)]` + modules that CI never runs. That is out of scope here, but I am happy to + open an issue if it would be useful. + + ## Breaking changes + + `ActiveStageOutput` and `x224::ProcessorOutput` gain a variant, and + `ClientConnector` gains a public field, so exhaustive matches and struct + literals need updating. + + Confirmed with `cargo-semver-checks` against the merge-base: those three + are the only findings this branch introduces. The others it reports on + `master` today (`ShareDataPdu::Compressed` and the `ShareDataCtx` fields + from #1518, `ProcessorBuilder.bulk_decompressor` from #1518, + `ServerEvent::SetAutoReconnectCookie` from #1509) are present on + `master` unchanged. The `ironrdp-pdu` additions are additive. + + ## Scope + + This is the library half. `ironrdp-client`, `ironrdp-web` and the FFI + bindings gain an arm for the new output but none of them reconnect + automatically yet; that is the remaining part of #271, and the existing + `TODO([#271](https://github.com/Devolutions/IronRDP/issues/271))` in `ironrdp-client` marks where it goes. + + I kept receive, derive and send together deliberately. Split up, none of + them is usable on its own: without the receive half there is no way to + obtain a cookie, and without the send half there is nothing to do with + one. + + ## Tests + + Thirteen, all in `ironrdp-testsuite-core`. + + On the packet and the derivation: the `SecurityVerifier` matches two + independently computed HMAC-MD5 vectors of 32 zero bytes under different + keys, so the tests pin the derivation rather than restating the code; + the logon ID carries over from the server cookie; the encoding matches + the 2.2.4.3 field layout byte for byte with `cbLen` fixed at `0x1C`; + `to_bytes` agrees with `Encode`; it round-trips; and it rejects both a + wrong packet length and an unknown version. + + On verification: a derived answer is accepted, a single flipped byte in + the verifier is rejected, a correct verifier under a different logon ID + is rejected, and an answer derived from a different random is rejected. + + On the surfacing path: a Save Session Info PDU framed the way a server + sends it, through the real x224 processor, yields an + `AutoReconnectCookie` carrying the right logon ID and random bits, + alongside #1522's logon notification rather than in place of it; and one + without a cookie surfaces no cookie. + + ## Verification + + `cargo xtask check fmt/lints/tests/typos/locks` all pass on 1.94.1, + including a `fuzz/` build before the lock check. + + ## Note + + #1496 also touches the `ClientAutoReconnect` declaration. Whichever of + the two lands second needs a one-line rebase on the derive attribute; + happy to take that in either order. + +### Features + +- Support runtime-defined static virtual channels ([#1517](https://github.com/Devolutions/IronRDP/issues/1517)) ([8b4c483ba0](https://github.com/Devolutions/IronRDP/commit/8b4c483ba0c900a8de0b2718347754f56dd363ba)) + + ## Summary + - add keyed runtime-defined static-channel registration, lookup, and + negotiated ID attachment + - enforce the static-channel limit and reject malformed SVC fragment + sequences + - wire generic connector, acceptor, and session name-based dispatch + support + + ## Testing + - `cargo test -p ironrdp-testsuite-core --test integration_tests_core + svc::` + - `cargo clippy -p ironrdp-testsuite-core --test integration_tests_core + -- -D warnings` + + --------- + +- Expose generic session configuration and lifecycle APIs ([#1522](https://github.com/Devolutions/IronRDP/issues/1522)) ([57b1366650](https://github.com/Devolutions/IronRDP/commit/57b13666506dc40c15b4c4702d35150beee99133)) + + ## Summary + - expose generic client configuration for connection metadata, + compression, shell/work directory, audio, and runtime static-channel + factories + - add bounded input delivery with independent close cancellation, host + clipboard plumbing, lifecycle events, and Display Control resize + readiness/fallback handling + - update agent, viewer, web, FFI, examples, and tests for the generic + APIs + + ## Stack dependencies + This PR is stacked on `copilot/tls-validation-policy` (`b2bbcece`), + which already includes the merged runtime static-channel support from + `master`. It intentionally contains no TLS implementation/policy, + ActiveX/COM, SVC implementation, decompression, or bitmap-recovery + changes. + + ## Validation + - `cargo fmt --check --all` + - `cargo xtask check tests --no-run -v` + - `cargo xtask check lints -v` + - `cargo test -p ironrdp-client --lib --features rustls` + - `cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi` + + --------- + +- Negotiate static channel chunk sizing ([#1622](https://github.com/Devolutions/IronRDP/issues/1622)) ([4e3903fbbe](https://github.com/Devolutions/IronRDP/commit/4e3903fbbef2904505f35e3437a7106807ac5987)) + + Use the validated server VCChunkSize for outgoing static virtual channel + data and retain 1600-byte chunks when it is absent or invalid. + + Apply refreshed values after reactivation across native, web, and FFI + active stages while preserving channel flags. + +- Forward negotiated windowing orders ([#1631](https://github.com/Devolutions/IronRDP/issues/1631)) ([0c3fbe78b4](https://github.com/Devolutions/IronRDP/commit/0c3fbe78b4366533b9fcea046b2b53654e003a72)) + + Preserve Window List support during activation. + Forward validated orders through ActiveStage and the raw FFI output. + Desktop and web consumers retain their existing behavior. + +- Add Input DVC and ActiveX touch ([#1647](https://github.com/Devolutions/IronRDP/issues/1647)) ([a912e19bd2](https://github.com/Devolutions/IronRDP/commit/a912e19bd2bb31f403fd7c35c8efd729a5ab5f6f)) + + Implement MS-RDPEI for multi-touch over the dynamic virtual channel + Microsoft::Windows::RDS::Input, and wire Windows pointer messages in + ActiveX through session encode helpers. + + Introduce ironrdp-rdpei PDUs and processors, register the channel from + the client, encode touch frames from ActiveX WM_POINTER*, and cover the + protocol with unit and integration tests. + +### Bug Fixes + +- Preserve bulk compression across reactivation ([#1474](https://github.com/Devolutions/IronRDP/issues/1474)) ([8fcffb9e8f](https://github.com/Devolutions/IronRDP/commit/8fcffb9e8f1a2c468321c05a56ec96144316c90a)) + + Any session that reactivates (Deactivate All β†’ re-activate) loses bulk + decompression and dies right after. Windows consoles reactivate right + after logon, and compression is on by default, so this hits pretty + easily. + + The reactivation path rebuilt the FastPath processor with + [`bulk_decompressor: + None`](https://github.com/Devolutions/IronRDP/blob/079b4842/crates/ironrdp-client/src/rdp.rs#L988). + After that every compressed update got parsed as a raw bitmap: + + ``` + Received compressed FastPath data but no decompressor is configured + BitmapData decode NotEnoughBytes: received 1662, expected 17134 + ``` + +- Preserve bitmap source stride ([#1486](https://github.com/Devolutions/IronRDP/issues/1486)) ([80bb81b344](https://github.com/Devolutions/IronRDP/commit/80bb81b344dba0197aa7b870c685f398dc4bcaee)) + + ## Summary + + - Preserve `TS_BITMAP_DATA` source stride independently of destination + bounds for raw, Interleaved RLE, and RDP6 bitmap updates. + - Remove raw 4-byte scanline padding without collapsing padded source + columns into following rows. + - Crop only the source extent beyond the destination and reject empty + dimensions explicitly; do not add framebuffer bounds suppression. + - Render decoded RDP6 RGB data top-down and retain bottom-up rendering + for raw and RLE data. + + ## Why this supersedes the overlapping proposals + +- [**breaking**] Always own a bulk decompressor for FastPath updates ([#1255](https://github.com/Devolutions/IronRDP/issues/1255)) ([0dc0194418](https://github.com/Devolutions/IronRDP/commit/0dc0194418375d504a8041b75ba250dc8eeb21ad)) + + ## Summary + + - A compressed FastPath update is dropped whenever the client did not + negotiate compression, because the decompressor is only built when a + compression type was negotiated. Servers send compressed updates + regardless, for example on a full-frame redraw after a resize, and the + session then fails. Closes #1193. + - The negotiated type is the wrong thing to condition on. It describes + what the client would send, and nothing in `ironrdp-session`, + `ironrdp-client`, `ironrdp-web` or the FFI ever compresses outbound. On + the receive path `BulkCompressor` holds a context per algorithm and + `decompress` selects one per update from the packet's own type bits, so + a decompressor built with any type decodes all of them. + - The `Processor` now owns the decompressor and builds it on the first + update that needs one. `ProcessorBuilder` has no corresponding field, so + there is no `None` a consumer can pass and no path that drops a + compressed update. + - On demand rather than at construction because `ironrdp-web` hardcodes + `compression_type: None` in `build_config` and so never negotiates + compression. Constructing eagerly would charge every web session for a + full set of algorithm contexts, and the two XCRUSH history buffers alone + are 2 MB each, for a decompressor most of those sessions never use. That + consumer is also the one most exposed to this bug, for the same reason. + - `BulkCompressor::new` is now infallible. Its only failure path was a + self-check over NCRUSH's static Huffman tables, a compile-time + invariant, now a `debug_assert`. + + ## Relationship to #1474 + + #1474 is kept, not reverted. `ActiveStage::reactivate` is adopted as the + reactivation entry point at all four call sites it introduced: native + client, web, FFI and the e2e test. + + What this PR removes is the `compression_type` retained on `ActiveStage` + and the `make_bulk_decompressor` helper, because an on-demand + decompressor makes both unnecessary. `reactivate` keeps its behaviour + and loses only the compression plumbing. + + #1474 closed the reactivation instance of #1193, where a rebuild passed + `None` and silently disabled decompression for the rest of the session. + The general case is still open on master: when compression was never + negotiated the retained type is `None`, `make_bulk_decompressor` returns + `None`, and every compressed update takes the drop path in + `fast_path.rs` for the lifetime of the session. Conditioning on the + negotiated type gates the ability to receive on what was negotiated to + send, and nothing sends. + + The evidence that removing the field is safe is #1474's own test. + `test_reactivation_processes_compressed_fastpath_updates` passes + unchanged with `compression_type` gone from the builder: the rebuilt + processor decompresses because every processor can, not because a type + was carried across the rebuild. + + ## Validation + + `cargo xtask check fmt/lints/tests/typos/locks` all pass. + + The gated regression test is + `testsuite-core/tests/session/fast_path.rs`, which renders the same + bitmap update plain and bulk-compressed through fresh processors and + asserts identical framebuffers. #1474's + `test_reactivation_processes_compressed_fastpath_updates` in + `testsuite-extra` passes unchanged. + + There is also an inline test in `fast_path.rs` pinning the allocation + invariant, that no contexts are built until an update needs them. Note + that `ironrdp-session` sets `[lib] test = false`, so inline tests in + this crate are not run by `cargo test --workspace`; it runs under `cargo + test -p ironrdp-session --lib`. + + ## Notes + + - This addresses the four points from the 2026-06-24 review. Point 4, + that the `Option` is misleading, is the shape of this change: it is gone + from the public API, and the private one that remains carries no + implication that a consumer could choose not to decompress. Point 1, + whether a cold `Rdp61` context decodes `RDP40` and `RDP50` updates + correctly, is a non-issue: `decompress` selects the algorithm per update + through `CompressionType::from_flags` against per-algorithm receive + contexts, so the construction-time type never constrains the receive + path. Point 3, silent degradation if the constructor fails, is removed + by making `new` infallible. Point 2 is the tests above. + - Breaking across two crates, hence the `fix(bulk,session)!` scope: + `ProcessorBuilder` loses `bulk_decompressor`, `ActiveStageBuilder` loses + `compression_type`, and `ironrdp_bulk::BulkCompressor::new` returns + `Self`. + - Incidental: `ironrdp-session` no longer exposes any `ironrdp_bulk` + type in its public API, so that dependency's lack of a `# public` marker + in `Cargo.toml` is now correct. + +- Decode indexed pointers and foreground RLE runs ([#1519](https://github.com/Devolutions/IronRDP/issues/1519)) ([ad19280762](https://github.com/Devolutions/IronRDP/commit/ad192807620bcc3a0467eaeb07173a79cb1da257)) + + ## Summary + + 4bpp and 8bpp New/Large pointer shapes previously could not use the + active session palette, and malformed or unsupported pointer data could + terminate the session. This decodes indexed XOR masks with the current + palette and falls back to the default cursor while evicting stale cached + data when decoding fails. + + It also corrects RLE foreground runs so only set-foreground variants + consume a foreground pixel. + + Palette updates now follow the RDP wire format (type, padding, 256 + packed RGB triplets) and are applied from both fast- and slow-path + updates, ensuring indexed pointer decoding uses the negotiated palette. + + ## Tests + + - `cargo test -p ironrdp-graphics -p ironrdp-session` + - `cargo test -p ironrdp-session palette` + - `cargo clippy -p ironrdp-graphics -p ironrdp-session --all-targets -- + -D warnings` + + --------- + +- Share bulk decompression across output paths ([#1518](https://github.com/Devolutions/IronRDP/issues/1518)) ([6151e21bf5](https://github.com/Devolutions/IronRDP/commit/6151e21bf58b7297e9b4abc2167aa36fc2ba77e4)) + + Bulk compression state is stream-wide, but Fast-Path and slow-path + outputs previously used separate or missing decompression paths. This + could corrupt history-dependent server updates or leave negotiated + slow-path compression undecodable. + + This change owns the negotiated bulk decompressor in `ActiveStage` and + passes it to both X.224 and Fast-Path processing. It retains Share Data + compression metadata through the PDU context, resets decompression + history on reactivation, and initializes consumers from the connection's + negotiated compression type. + + Fast-Path now decompresses each fragment before reassembly so + compression flags apply at packet boundaries. Failures expose bounded + protocol metadata without retaining remote payloads or decoder details. + + Tests cover Share Data metadata propagation, slow-path decompression + behavior, fragmented Fast-Path reassembly and bounded errors, and + compressed Fast-Path updates after reactivation. + + --------- + +- Recover from malformed bitmap updates ([#1521](https://github.com/Devolutions/IronRDP/issues/1521)) ([20e2d414e5](https://github.com/Devolutions/IronRDP/commit/20e2d414e5ac060db25a100ed219f417e19f79b2)) + + ## Summary + - safely discard malformed bitmap and pointer updates without + terminating the session + - request at most one capability-gated full redraw per activation + - propagate Refresh Rect and Suppress Output support through the + connector and generic client + - retain FFI compatibility and focused malformed-update regression + coverage + + ## Stack + Depends on `copilot/fix-session-share-bulk-decompression` (`47270c2a`). + + ## Validation + - `cargo fmt --all -- --check` + - `cargo test -p ironrdp-session --lib` + - `cargo test -p ironrdp-error --lib` + - `cargo check -p ironrdp-connector` + - `cargo check -p ironrdp-client --features native-tls` + - `cargo check -p ffi --features ironrdp/native-tls` + + --------- + +- Preserve RDP6 bitmap orientation ([#1524](https://github.com/Devolutions/IronRDP/issues/1524)) ([8f1737a287](https://github.com/Devolutions/IronRDP/commit/8f1737a28765c0e29bfab1584cd376805b7e174e)) + + ## Summary + - restore bottom-up scanline composition for RDP6 bitmap updates + - add asymmetric raw/RLE bitmap-update regression coverage + - synchronize retained ActiveX DIB updates with GDI and surface copy + failures + + ## Validation + - `cargo test -p ironrdp-session` + - `cargo test -p ironrdp-activex` + - `cargo build -p ironrdp-activex --release` + - verified the real `mstscex.exe -> mstsc.exe -> MsRdpEx.dll -> + ironrdpax.dll` path renders the authorized test desktop upright without + bands + + --------- + +- [**breaking**] Replace DVC wrappers with typed accessors ([#1377](https://github.com/Devolutions/IronRDP/issues/1377)) ([d43ecf9a54](https://github.com/Devolutions/IronRDP/commit/d43ecf9a54363d37e0c485a1e9e73da0d47ae540)) + + Follow-up to #1368. This is not urgent; review whenever the DVC API + direction is worth revisiting. + + Rework DVC channel access APIs so callers can recover a typed processor + together with its dynamic channel id, without exposing internal channel + wrapper types. + + - Add typed borrowed DVC accessors carrying both channel id and + processor borrow for `DrdynvcClient`. + - Keep dynamic channel wrapper types private. + - Align client listener/registration APIs on `DvcClientProcessor`. + +- Batch Fast-Path input events ([#1630](https://github.com/Devolutions/IronRDP/issues/1630)) ([3818b48037](https://github.com/Devolutions/IronRDP/commit/3818b480375ec9411d5319d8e7af161f1d662cbf)) + + Keep outgoing Fast-Path input frames within the 255-event protocol limit + and preserve their order across FFI. + + + ## [[0.11.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-session-v0.10.0...ironrdp-session-v0.11.0)] - 2026-07-10 ### Security diff --git a/crates/ironrdp-session/Cargo.toml b/crates/ironrdp-session/Cargo.toml index b66e23b39..a17a962f3 100644 --- a/crates/ironrdp-session/Cargo.toml +++ b/crates/ironrdp-session/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-session" -version = "0.11.0" +version = "0.12.0" readme = "README.md" description = "State machines to drive an RDP session" edition.workspace = true @@ -22,13 +22,13 @@ qoi = ["dep:qoicoubeh", "ironrdp-pdu/qoi"] qoiz = ["dep:zstd-safe", "qoi"] [dependencies] -ironrdp-bulk = { path = "../ironrdp-bulk", version = "0.1" } # public -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public +ironrdp-bulk = { path = "../ironrdp-bulk", version = "0.2" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8" } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9" } # public ironrdp-error = { path = "../ironrdp-error", version = "0.2" } # public -ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.9" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["std"] } # public +ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.10" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", features = ["std"] } # public ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.8" } ironrdp-rdpei = { path = "../ironrdp-rdpei", version = "0.1" } tracing = { version = "0.1", features = ["log"] } diff --git a/crates/ironrdp-str/CHANGELOG.md b/crates/ironrdp-str/CHANGELOG.md index 1bae5ef86..d62d9aa8b 100644 --- a/crates/ironrdp-str/CHANGELOG.md +++ b/crates/ironrdp-str/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.1.2](https://github.com/Devolutions/IronRDP/compare/ironrdp-str-v0.1.1...ironrdp-str-v0.1.2)] - 2026-08-13 + + + ## [[0.1.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-str-v0.1.0...ironrdp-str-v0.1.1)] - 2026-05-27 ### Build diff --git a/crates/ironrdp-str/Cargo.toml b/crates/ironrdp-str/Cargo.toml index 33128a55c..4bac2e8bb 100644 --- a/crates/ironrdp-str/Cargo.toml +++ b/crates/ironrdp-str/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-str" -version = "0.1.1" +version = "0.1.2" description = "Typed wire-aware string primitives for RDP protocol fields" edition.workspace = true rust-version = "1.94" @@ -18,7 +18,7 @@ alloc = ["ironrdp-core/alloc", "bytemuck/extern_crate_alloc"] [dependencies] bytemuck = { version = "1", default-features = false } -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } [lints] workspace = true diff --git a/crates/ironrdp-svc/CHANGELOG.md b/crates/ironrdp-svc/CHANGELOG.md index 4aa33691e..868839543 100644 --- a/crates/ironrdp-svc/CHANGELOG.md +++ b/crates/ironrdp-svc/CHANGELOG.md @@ -6,6 +6,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.8.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-svc-v0.8.0...ironrdp-svc-v0.8.1)] - 2026-08-13 + +### Features + +- Support runtime-defined static virtual channels ([#1517](https://github.com/Devolutions/IronRDP/issues/1517)) ([8b4c483ba0](https://github.com/Devolutions/IronRDP/commit/8b4c483ba0c900a8de0b2718347754f56dd363ba)) + + ## Summary + - add keyed runtime-defined static-channel registration, lookup, and + negotiated ID attachment + - enforce the static-channel limit and reject malformed SVC fragment + sequences + - wire generic connector, acceptor, and session name-based dispatch + support + + ## Testing + - `cargo test -p ironrdp-testsuite-core --test integration_tests_core + svc::` + - `cargo clippy -p ironrdp-testsuite-core --test integration_tests_core + -- -D warnings` + + --------- + +- Negotiate static channel chunk sizing ([#1622](https://github.com/Devolutions/IronRDP/issues/1622)) ([4e3903fbbe](https://github.com/Devolutions/IronRDP/commit/4e3903fbbef2904505f35e3437a7106807ac5987)) + + Use the validated server VCChunkSize for outgoing static virtual channel + data and retain 1600-byte chunks when it is absent or invalid. + + Apply refreshed values after reactivation across native, web, and FFI + active stages while preserving channel flags. + + + ## [[0.8.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-svc-v0.7.0...ironrdp-svc-v0.8.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-svc/Cargo.toml b/crates/ironrdp-svc/Cargo.toml index 3dfd27601..bd672b036 100644 --- a/crates/ironrdp-svc/Cargo.toml +++ b/crates/ironrdp-svc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-svc" -version = "0.8.0" +version = "0.8.1" readme = "README.md" description = "IronRDP traits to implement RDP static virtual channels" edition.workspace = true @@ -21,8 +21,8 @@ default = [] std = [] [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2" } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", features = ["alloc", "std"] } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3" } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", features = ["alloc", "std"] } # public bitflags = "2.11" [lints] diff --git a/crates/ironrdp-tls/CHANGELOG.md b/crates/ironrdp-tls/CHANGELOG.md index 7d50b6b2a..0a34802b5 100644 --- a/crates/ironrdp-tls/CHANGELOG.md +++ b/crates/ironrdp-tls/CHANGELOG.md @@ -6,6 +6,48 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.2.3](https://github.com/Devolutions/IronRDP/compare/ironrdp-tls-v0.2.2...ironrdp-tls-v0.2.3)] - 2026-08-13 + +### Bug Fixes + +- Make certificate validation explicit ([#1520](https://github.com/Devolutions/IronRDP/issues/1520)) ([f1d53c78d3](https://github.com/Devolutions/IronRDP/commit/f1d53c78d390de1c6778773cdc859d59901466f0)) + + IronRDP deployments commonly use self-signed or private-CA certificates. + This keeps the historical permissive behavior for unmodified callers + while making platform-root and server-name validation an explicit + opt-in. + + ## Approach + + - Keep `upgrade` and `ConfigBuilder` defaults compatible with existing + self-signed endpoints, including the prior native-TLS SNI behavior. + - Expose `CertificateValidation::Strict` for callers that require normal + certificate-chain and hostname validation. + - Retain the Rustls callback path for certificate pinning or other + explicit exception decisions; configuring a callback selects strict + validation before invoking it. + - Preserve CredSSP's existing public-key binding and disabled TLS + resumption behavior. + + MS-CSSP section 3.1.5 does not require a common trusted CA root and + permits servers to use self-signed certificates, so strict verification + cannot be introduced as a transparent default. + + ## Validation + + - `cargo xtask check fmt -v` + - `cargo xtask check lints -v` + - Focused Rustls default/strict/callback runtime test + - Focused native-TLS default/strict runtime test + + --------- + +### Build + +- Bump the crypto group across 1 directory with 3 updates ([#1449](https://github.com/Devolutions/IronRDP/issues/1449)) ([e1725e8c8a](https://github.com/Devolutions/IronRDP/commit/e1725e8c8a581b83835647b6ee563a5b3f6c7a1b)) + + + ## [[0.2.2](https://github.com/Devolutions/IronRDP/compare/ironrdp-tls-v0.2.1...ironrdp-tls-v0.2.2)] - 2026-07-10 ### Features diff --git a/crates/ironrdp-tls/Cargo.toml b/crates/ironrdp-tls/Cargo.toml index a74960abe..4fa939615 100644 --- a/crates/ironrdp-tls/Cargo.toml +++ b/crates/ironrdp-tls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-tls" -version = "0.2.2" +version = "0.2.3" readme = "README.md" description = "TLS boilerplate common with most IronRDP clients" edition.workspace = true diff --git a/crates/ironrdp-tokio/CHANGELOG.md b/crates/ironrdp-tokio/CHANGELOG.md index 893bfda46..e4f6accc4 100644 --- a/crates/ironrdp-tokio/CHANGELOG.md +++ b/crates/ironrdp-tokio/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.10.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-tokio-v0.10.0...ironrdp-tokio-v0.10.1)] - 2026-08-13 + + + ## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-tokio-v0.9.0...ironrdp-tokio-v0.10.0)] - 2026-07-10 ### Build diff --git a/crates/ironrdp-tokio/Cargo.toml b/crates/ironrdp-tokio/Cargo.toml index eefdb458f..de2b18f4f 100644 --- a/crates/ironrdp-tokio/Cargo.toml +++ b/crates/ironrdp-tokio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-tokio" -version = "0.10.0" +version = "0.10.1" readme = "README.md" description = "`Framed*` traits implementation above Tokio’s traits" edition.workspace = true @@ -23,8 +23,8 @@ reqwest-rustls-ring = ["reqwest", "reqwest?/rustls-tls-webpki-roots"] reqwest-native-tls = ["reqwest", "reqwest?/native-tls"] [dependencies] -ironrdp-async = { path = "../ironrdp-async", version = "0.10" } # public -ironrdp-connector = { path = "../ironrdp-connector", version = "0.10", optional = true } +ironrdp-async = { path = "../ironrdp-async", version = "0.11" } # public +ironrdp-connector = { path = "../ironrdp-connector", version = "0.11", optional = true } tokio = { version = "1", features = ["io-util"] } reqwest = { version = "0.12", default-features = false, features = ["http2", "system-proxy"], optional = true } url = { version = "2.5", optional = true } diff --git a/crates/ironrdp-viewer/CHANGELOG.md b/crates/ironrdp-viewer/CHANGELOG.md index d146d9388..49e3e7354 100644 --- a/crates/ironrdp-viewer/CHANGELOG.md +++ b/crates/ironrdp-viewer/CHANGELOG.md @@ -6,6 +6,129 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.1.1](https://github.com/Devolutions/IronRDP/compare/ironrdp-viewer-v0.1.0...ironrdp-viewer-v0.1.1)] - 2026-08-13 + +### Features + +- Expose generic session configuration and lifecycle APIs ([#1522](https://github.com/Devolutions/IronRDP/issues/1522)) ([57b1366650](https://github.com/Devolutions/IronRDP/commit/57b13666506dc40c15b4c4702d35150beee99133)) + + ## Summary + - expose generic client configuration for connection metadata, + compression, shell/work directory, audio, and runtime static-channel + factories + - add bounded input delivery with independent close cancellation, host + clipboard plumbing, lifecycle events, and Display Control resize + readiness/fallback handling + - update agent, viewer, web, FFI, examples, and tests for the generic + APIs + + ## Stack dependencies + This PR is stacked on `copilot/tls-validation-policy` (`b2bbcece`), + which already includes the merged runtime static-channel support from + `master`. It intentionally contains no TLS implementation/policy, + ActiveX/COM, SVC implementation, decompression, or bitmap-recovery + changes. + + ## Validation + - `cargo fmt --check --all` + - `cargo xtask check tests --no-run -v` + - `cargo xtask check lints -v` + - `cargo test -p ironrdp-client --lib --features rustls` + - `cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi` + + --------- + +- Add IronRDP ActiveX COM server ([#1523](https://github.com/Devolutions/IronRDP/issues/1523)) ([ee58b7c5f2](https://github.com/Devolutions/IronRDP/commit/ee58b7c5f283ef64be93a8483242f49070c6cdc9)) + + ## Summary + - Add the IronRDP ActiveX COM server and native MSTSC host integration. + - Provide bounded native-host diagnostics, credential-bridge support, + and an AxHost test harness. + - Preserve the minimal client, connector, and error integrations needed + by the control. + + ## Validation + - cargo check -p ironrdp-activex + - Focused ironrdp-error and ironrdp-connector tests + - cargo test -p ironrdp-activex --lib --no-run + - cargo fmt --all -- --check + - cargo xtask check locks -v + + --------- + +- Host agent RPC endpoint ([#1545](https://github.com/Devolutions/IronRDP/issues/1545)) ([358ca8d4f3](https://github.com/Devolutions/IronRDP/commit/358ca8d4f382e49251f91243b6d9e5488c2dface)) + + ## Summary + - host the existing `ironrdp-agent` RPC contract from `ironrdp-viewer + --rpc` + - retain one shared daemon state for GUI and RPC input, frames, session + lifecycle, screenshots, logs, and NOW operations + - use the agent's default endpoint so `ironrdp-agent` itself remains + unchanged + + ## Usage + Start `ironrdp-viewer --rpc` before invoking `ironrdp-agent`; the viewer + owns the usual agent endpoint until its window closes. Use matching + explicit `--rpc-endpoint` and agent `--endpoint` values for a custom + endpoint. + + ## Validation + - `cargo fmt --check` + - `cargo test -p ironrdp-viewer` + - `cargo test -p ironrdp-daemon -p ironrdp-agent --lib` + - `cargo check -p ironrdp-viewer -p ironrdp-daemon -p ironrdp-agent` + +- Hyper-V vmconnect support ([#1503](https://github.com/Devolutions/IronRDP/issues/1503)) ([a7cc067d50](https://github.com/Devolutions/IronRDP/commit/a7cc067d5069cbbcb13bae3e0561c0611da3bcf6)) + + Adds Hyper-V VMConnect's direct ordering: PCB β†’ TLS β†’ CredSSP β†’ X.224. + + Enhanced Session is the default (`GUID;EnhancedMode=1`), with + `--vmconnect-basic` for the synthetic console. Kept this separate in + `ironrdp-vmconnect`; no SPN changes. + + Tested against the nested Hyper-V lab: + - Enhanced: `HYBRID_EX`, rendered 1280Γ—720 + - Basic: `HYBRID`, rendered 1280Γ—720 + - `cargo xtask check fmt/lints/tests -v` + + --------- + +- Add RemoteApp channel support ([#1637](https://github.com/Devolutions/IronRDP/issues/1637)) ([ab48c6cb8c](https://github.com/Devolutions/IronRDP/commit/ab48c6cb8c017504f8a92799aeb91b821c50a13a)) + + Configure and negotiate RAIL connections, then route its static channel + through the portable client with bounded request queues and server + control events. + +- Project RemoteApp windows ([#1641](https://github.com/Devolutions/IronRDP/issues/1641)) ([f5554f40dc](https://github.com/Devolutions/IronRDP/commit/f5554f40dc280d93506ea8352e3992e641d58e96)) + + Project server-authoritative RAIL windows for an enabled ActiveX + RemoteApp session and launch the configured program after RAIL becomes + available. + + Forward validated opaque windowing orders to the worker, maintain their + basic HWND lifecycle, and retain windows until the server removes them. + Leave desktop behavior and unsupported shell features unchanged. + +- Add RAIL audit commands ([#1646](https://github.com/Devolutions/IronRDP/issues/1646)) ([77759dca03](https://github.com/Devolutions/IronRDP/commit/77759dca032eb829b5be54a3c44d9be92252cf41)) + + Expose bounded client-validated RAIL events and RemoteApp launch + requests through the daemon IPC so headless agents can verify sessions. + + Preserve cursors across resize reconnects, wake waiting readers for + locally queued launches, and redact launch data in logs. + + Report terminal local Execute failures without disrupting an otherwise + valid RDP session. + +### Bug Fixes + +- Make source locations opt-in ([#1480](https://github.com/Devolutions/IronRDP/issues/1480)) ([f84cd01450](https://github.com/Devolutions/IronRDP/commit/f84cd01450e18d12838b225859878b311802b805)) + + Default error display omits locations; alternate formatting and reports + with explicit location opt-in preserve diagnostic context. + + + ## [[0.1.0](https://github.com/Devolutions/IronRDP/releases/tag/ironrdp-viewer-v0.1.0)] - 2026-07-10 Initial release. diff --git a/crates/ironrdp-viewer/Cargo.toml b/crates/ironrdp-viewer/Cargo.toml index 79e0b9658..e11f46b61 100644 --- a/crates/ironrdp-viewer/Cargo.toml +++ b/crates/ironrdp-viewer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp-viewer" -version = "0.1.0" +version = "0.1.1" readme = "README.md" description = "Portable RDP viewer (GUI binary) without GPU acceleration" edition.workspace = true @@ -30,8 +30,8 @@ qoiz = ["ironrdp/qoiz"] [dependencies] ironrdp-daemon = { path = "../ironrdp-daemon", version = "0.1" } # public ironrdp-rpc = { path = "../ironrdp-rpc", version = "0.1" } -ironrdp = { path = "../ironrdp", version = "0.17", features = ["connector", "cliprdr", "input", "pdu", "client", "client-all", "client-vmconnect"] } -ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.1" } +ironrdp = { path = "../ironrdp", version = "0.18", features = ["connector", "cliprdr", "input", "pdu", "client", "client-all", "client-vmconnect"] } +ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.2" } ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } ironrdp-rdpfile = { path = "../ironrdp-rdpfile", version = "0.1" } diff --git a/crates/ironrdp-vmconnect/CHANGELOG.md b/crates/ironrdp-vmconnect/CHANGELOG.md new file mode 100644 index 000000000..361076283 --- /dev/null +++ b/crates/ironrdp-vmconnect/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [[0.1.0](https://github.com/Devolutions/IronRDP/releases/tag/ironrdp-vmconnect-v0.1.0)] - 2026-08-13 + +### Features + +- Hyper-V vmconnect support ([#1503](https://github.com/Devolutions/IronRDP/issues/1503)) ([a7cc067d50](https://github.com/Devolutions/IronRDP/commit/a7cc067d5069cbbcb13bae3e0561c0611da3bcf6)) + + Adds Hyper-V VMConnect's direct ordering: PCB β†’ TLS β†’ CredSSP β†’ X.224. + + Enhanced Session is the default (`GUID;EnhancedMode=1`), with + `--vmconnect-basic` for the synthetic console. Kept this separate in + `ironrdp-vmconnect`; no SPN changes. + + Tested against the nested Hyper-V lab: + - Enhanced: `HYBRID_EX`, rendered 1280Γ—720 + - Basic: `HYBRID`, rendered 1280Γ—720 + - `cargo xtask check fmt/lints/tests -v` + + --------- + +- Support Hyper-V connection ordering ([#1505](https://github.com/Devolutions/IronRDP/issues/1505)) ([5c1816244e](https://github.com/Devolutions/IronRDP/commit/5c1816244e83187a04249e9d9c240d096cb78f55)) + + Hyper-V over RDCleanPath needs PCB β†’ TLS on the proxy, then CredSSP β†’ + X.224 on the client. Ordinary RDCleanPath stays X.224-first. + + Still VERSION_1 with the same DER fields. An explicit VMConnect request + carries a Unicode PCB payload in `preconnection_blob` with no X.224; the + proxy encodes the binary PCB. Generic PCB requests keep their existing + X.224-first behavior. + + Gateway reference implementation: + [Devolutions/devolutions-gateway#1372](https://github.com/Devolutions/devolutions-gateway/pull/1372) + + Checked locally: Rust builds, formatting, Svelte typecheck, and .NET + build. Real nested Hyper-V E2E through Gateway: Native rendered 18 + frames, Avalonia connected and rendered its first frame, and Web + rendered a non-empty 1280Γ—720 canvas. + + --------- + + diff --git a/crates/ironrdp-vmconnect/Cargo.toml b/crates/ironrdp-vmconnect/Cargo.toml index 3b8c35b76..997bd767d 100644 --- a/crates/ironrdp-vmconnect/Cargo.toml +++ b/crates/ironrdp-vmconnect/Cargo.toml @@ -17,10 +17,10 @@ doctest = false test = false [dependencies] -ironrdp-async = { path = "../ironrdp-async", version = "0.10" } # public -ironrdp-connector = { path = "../ironrdp-connector", version = "0.10" } # public -ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public +ironrdp-async = { path = "../ironrdp-async", version = "0.11" } # public +ironrdp-connector = { path = "../ironrdp-connector", version = "0.11" } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public tracing = { version = "0.1", features = ["log"] } [lints] diff --git a/crates/ironrdp/CHANGELOG.md b/crates/ironrdp/CHANGELOG.md index 04b8244a0..effe9f24b 100644 --- a/crates/ironrdp/CHANGELOG.md +++ b/crates/ironrdp/CHANGELOG.md @@ -6,6 +6,224 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.18.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-v0.17.0...ironrdp-v0.18.0)] - 2026-08-13 + +### Security + +- Connect to Windows Sandbox named pipes ([#1580](https://github.com/Devolutions/IronRDP/issues/1580)) ([39b020343d](https://github.com/Devolutions/IronRDP/commit/39b020343d962962bfbefc89939be64d5c716196)) + + Windows Sandbox's default attach path is a local named pipe carrying + plain TPKT/X.224 with PROTOCOL_RDP and ENCRYPTION_LEVEL_NONE, not + TCP:3389 or VMConnect. Allow the connector and client to complete that + sequence only via an explicit opt-in (`enable_standard_rdp_security`; + NamedPipe enables it), and teach ironrdp-agent to resolve pipe path and + guest credentials from WindowsSandboxServer after `wsb start`. + + Adds Transport::NamedPipe, ironrdp_named_pipe/ironrdp_sandbox_id + properties, sandbox list/config/stop CLI helpers via an in-process + h2/gRPC client on the per-user `\\.\pipe\wsandbox\{guid}` pipe (no .NET + helper), and connect --sandbox-id / --sandbox-pipe. Sandbox-derived + properties are the merge base; explicit .rdp/--prop/flags override them + while NamedPipe TLS/CredSSP stay forced off. Local :2179+PCB remains + unsupported. + +### Features + +- Expose generic session configuration and lifecycle APIs ([#1522](https://github.com/Devolutions/IronRDP/issues/1522)) ([57b1366650](https://github.com/Devolutions/IronRDP/commit/57b13666506dc40c15b4c4702d35150beee99133)) + + ## Summary + - expose generic client configuration for connection metadata, + compression, shell/work directory, audio, and runtime static-channel + factories + - add bounded input delivery with independent close cancellation, host + clipboard plumbing, lifecycle events, and Display Control resize + readiness/fallback handling + - update agent, viewer, web, FFI, examples, and tests for the generic + APIs + + ## Stack dependencies + This PR is stacked on `copilot/tls-validation-policy` (`b2bbcece`), + which already includes the merged runtime static-channel support from + `master`. It intentionally contains no TLS implementation/policy, + ActiveX/COM, SVC implementation, decompression, or bitmap-recovery + changes. + + ## Validation + - `cargo fmt --check --all` + - `cargo xtask check tests --no-run -v` + - `cargo xtask check lints -v` + - `cargo test -p ironrdp-client --lib --features rustls` + - `cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi` + + --------- + +- Hyper-V vmconnect support ([#1503](https://github.com/Devolutions/IronRDP/issues/1503)) ([a7cc067d50](https://github.com/Devolutions/IronRDP/commit/a7cc067d5069cbbcb13bae3e0561c0611da3bcf6)) + + Adds Hyper-V VMConnect's direct ordering: PCB β†’ TLS β†’ CredSSP β†’ X.224. + + Enhanced Session is the default (`GUID;EnhancedMode=1`), with + `--vmconnect-basic` for the synthetic console. Kept this separate in + `ironrdp-vmconnect`; no SPN changes. + + Tested against the nested Hyper-V lab: + - Enhanced: `HYBRID_EX`, rendered 1280Γ—720 + - Basic: `HYBRID`, rendered 1280Γ—720 + - `cargo xtask check fmt/lints/tests -v` + + --------- + +- Add RemoteApp channel support ([#1637](https://github.com/Devolutions/IronRDP/issues/1637)) ([ab48c6cb8c](https://github.com/Devolutions/IronRDP/commit/ab48c6cb8c017504f8a92799aeb91b821c50a13a)) + + Configure and negotiate RAIL connections, then route its static channel + through the portable client with bounded request queues and server + control events. + +- Add Input DVC and ActiveX touch ([#1647](https://github.com/Devolutions/IronRDP/issues/1647)) ([a912e19bd2](https://github.com/Devolutions/IronRDP/commit/a912e19bd2bb31f403fd7c35c8efd729a5ab5f6f)) + + Implement MS-RDPEI for multi-touch over the dynamic virtual channel + Microsoft::Windows::RDS::Input, and wire Windows pointer messages in + ActiveX through session encode helpers. + + Introduce ironrdp-rdpei PDUs and processors, register the channel from + the client, encode touch frames from ActiveX WM_POINTER*, and cover the + protocol with unit and integration tests. + +- Wire MS-RDPEAI capture into Windows client and ActiveX ([#1642](https://github.com/Devolutions/IronRDP/issues/1642)) ([205fe038cc](https://github.com/Devolutions/IronRDP/commit/205fe038cc693598adf803fe181526b789b2ec3d)) + + Add the client MS-RDPEAI capture path on top of hardened RDPSND + playback: connector CFG + static channel wiring, CPAL PCM capture + backend, ironrdp-client --audio-capture, and ActiveX + AudioCaptureRedirectionMode. + + PCM capture only accepts encode formats that match the Open capture + stream, rejects non-16-bit capture (Data PDU size contract), and gates + the capture backend behind ironrdp-rdpsnd-native/capture. + + Depends on #1648 (playback). + +### Bug Fixes + +- [**breaking**] Always own a bulk decompressor for FastPath updates ([#1255](https://github.com/Devolutions/IronRDP/issues/1255)) ([0dc0194418](https://github.com/Devolutions/IronRDP/commit/0dc0194418375d504a8041b75ba250dc8eeb21ad)) + + ## Summary + + - A compressed FastPath update is dropped whenever the client did not + negotiate compression, because the decompressor is only built when a + compression type was negotiated. Servers send compressed updates + regardless, for example on a full-frame redraw after a resize, and the + session then fails. Closes #1193. + - The negotiated type is the wrong thing to condition on. It describes + what the client would send, and nothing in `ironrdp-session`, + `ironrdp-client`, `ironrdp-web` or the FFI ever compresses outbound. On + the receive path `BulkCompressor` holds a context per algorithm and + `decompress` selects one per update from the packet's own type bits, so + a decompressor built with any type decodes all of them. + - The `Processor` now owns the decompressor and builds it on the first + update that needs one. `ProcessorBuilder` has no corresponding field, so + there is no `None` a consumer can pass and no path that drops a + compressed update. + - On demand rather than at construction because `ironrdp-web` hardcodes + `compression_type: None` in `build_config` and so never negotiates + compression. Constructing eagerly would charge every web session for a + full set of algorithm contexts, and the two XCRUSH history buffers alone + are 2 MB each, for a decompressor most of those sessions never use. That + consumer is also the one most exposed to this bug, for the same reason. + - `BulkCompressor::new` is now infallible. Its only failure path was a + self-check over NCRUSH's static Huffman tables, a compile-time + invariant, now a `debug_assert`. + + ## Relationship to #1474 + + #1474 is kept, not reverted. `ActiveStage::reactivate` is adopted as the + reactivation entry point at all four call sites it introduced: native + client, web, FFI and the e2e test. + + What this PR removes is the `compression_type` retained on `ActiveStage` + and the `make_bulk_decompressor` helper, because an on-demand + decompressor makes both unnecessary. `reactivate` keeps its behaviour + and loses only the compression plumbing. + + #1474 closed the reactivation instance of #1193, where a rebuild passed + `None` and silently disabled decompression for the rest of the session. + The general case is still open on master: when compression was never + negotiated the retained type is `None`, `make_bulk_decompressor` returns + `None`, and every compressed update takes the drop path in + `fast_path.rs` for the lifetime of the session. Conditioning on the + negotiated type gates the ability to receive on what was negotiated to + send, and nothing sends. + + The evidence that removing the field is safe is #1474's own test. + `test_reactivation_processes_compressed_fastpath_updates` passes + unchanged with `compression_type` gone from the builder: the rebuilt + processor decompresses because every processor can, not because a type + was carried across the rebuild. + + ## Validation + + `cargo xtask check fmt/lints/tests/typos/locks` all pass. + + The gated regression test is + `testsuite-core/tests/session/fast_path.rs`, which renders the same + bitmap update plain and bulk-compressed through fresh processors and + asserts identical framebuffers. #1474's + `test_reactivation_processes_compressed_fastpath_updates` in + `testsuite-extra` passes unchanged. + + There is also an inline test in `fast_path.rs` pinning the allocation + invariant, that no contexts are built until an update needs them. Note + that `ironrdp-session` sets `[lib] test = false`, so inline tests in + this crate are not run by `cargo test --workspace`; it runs under `cargo + test -p ironrdp-session --lib`. + + ## Notes + + - This addresses the four points from the 2026-06-24 review. Point 4, + that the `Option` is misleading, is the shape of this change: it is gone + from the public API, and the private one that remains carries no + implication that a consumer could choose not to decompress. Point 1, + whether a cold `Rdp61` context decodes `RDP40` and `RDP50` updates + correctly, is a non-issue: `decompress` selects the algorithm per update + through `CompressionType::from_flags` against per-algorithm receive + contexts, so the construction-time type never constrains the receive + path. Point 3, silent degradation if the constructor fails, is removed + by making `new` infallible. Point 2 is the tests above. + - Breaking across two crates, hence the `fix(bulk,session)!` scope: + `ProcessorBuilder` loses `bulk_decompressor`, `ActiveStageBuilder` loses + `compression_type`, and `ironrdp_bulk::BulkCompressor::new` returns + `Self`. + - Incidental: `ironrdp-session` no longer exposes any `ironrdp_bulk` + type in its public API, so that dependency's lack of a `# public` marker + in `Cargo.toml` is now correct. + +- Share bulk decompression across output paths ([#1518](https://github.com/Devolutions/IronRDP/issues/1518)) ([6151e21bf5](https://github.com/Devolutions/IronRDP/commit/6151e21bf58b7297e9b4abc2167aa36fc2ba77e4)) + + Bulk compression state is stream-wide, but Fast-Path and slow-path + outputs previously used separate or missing decompression paths. This + could corrupt history-dependent server updates or leave negotiated + slow-path compression undecodable. + + This change owns the negotiated bulk decompressor in `ActiveStage` and + passes it to both X.224 and Fast-Path processing. It retains Share Data + compression metadata through the PDU context, resets decompression + history on reactivation, and initializes consumers from the connection's + negotiated compression type. + + Fast-Path now decompresses each fragment before reassembly so + compression flags apply at packet boundaries. Failures expose bounded + protocol metadata without retaining remote payloads or decoder details. + + Tests cover Share Data metadata propagation, slow-path decompression + behavior, fragmented Fast-Path reassembly and bounded errors, and + compressed Fast-Path updates after reactivation. + + --------- + +### Build + +- Bump the crypto group across 1 directory with 3 updates ([#1449](https://github.com/Devolutions/IronRDP/issues/1449)) ([e1725e8c8a](https://github.com/Devolutions/IronRDP/commit/e1725e8c8a581b83835647b6ee563a5b3f6c7a1b)) + + + ## [[0.17.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-v0.16.0...ironrdp-v0.17.0)] - 2026-07-10 ### Security diff --git a/crates/ironrdp/Cargo.toml b/crates/ironrdp/Cargo.toml index 9b8bdc307..4068d262c 100644 --- a/crates/ironrdp/Cargo.toml +++ b/crates/ironrdp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ironrdp" -version = "0.17.0" +version = "0.18.0" readme = "README.md" description = "A meta crate re-exporting IronRDP crates for convenience" edition.workspace = true @@ -58,28 +58,28 @@ qoiz = ["ironrdp-server?/qoiz", "ironrdp-pdu?/qoiz", "ironrdp-connector?/qoiz", __bench = ["ironrdp-server/__bench"] [dependencies] -ironrdp-core = { path = "../ironrdp-core", version = "0.2", optional = true } # public -ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9", optional = true } # public +ironrdp-core = { path = "../ironrdp-core", version = "0.3", optional = true } # public +ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10", optional = true } # public ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.7", optional = true } # public -ironrdp-connector = { path = "../ironrdp-connector", version = "0.10", optional = true } # public -ironrdp-acceptor = { path = "../ironrdp-acceptor", version = "0.10", optional = true } # public -ironrdp-session = { path = "../ironrdp-session", version = "0.11", optional = true } # public -ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.9", optional = true } # public +ironrdp-connector = { path = "../ironrdp-connector", version = "0.11", optional = true } # public +ironrdp-acceptor = { path = "../ironrdp-acceptor", version = "0.11", optional = true } # public +ironrdp-session = { path = "../ironrdp-session", version = "0.12", optional = true } # public +ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.10", optional = true } # public ironrdp-input = { path = "../ironrdp-input", version = "0.7", optional = true } # public -ironrdp-server = { path = "../ironrdp-server", version = "0.13", optional = true, features = ["helper"] } # public +ironrdp-server = { path = "../ironrdp-server", version = "0.14", optional = true, features = ["helper"] } # public ironrdp-svc = { path = "../ironrdp-svc", version = "0.8", optional = true } # public -ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.8", optional = true } # public -ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.7", optional = true } # public -ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.9", optional = true } # public +ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.9", optional = true } # public +ironrdp-rdpdr = { path = "../ironrdp-rdpdr", version = "0.8", optional = true } # public +ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.10", optional = true } # public ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.8", optional = true } # public ironrdp-rdpei = { path = "../ironrdp-rdpei", version = "0.1", optional = true } # public ironrdp-echo = { path = "../ironrdp-echo", version = "0.4", optional = true } # public -ironrdp-mstsgu = { path = "../ironrdp-mstsgu", version = "0.0.1", optional = true } # public -ironrdp-client = { path = "../ironrdp-client", version = "0.1", optional = true } # public +ironrdp-mstsgu = { path = "../ironrdp-mstsgu", version = "0.0.2", optional = true } # public +ironrdp-client = { path = "../ironrdp-client", version = "0.2", optional = true } # public ironrdp-vmconnect = { path = "../ironrdp-vmconnect", version = "0.1", optional = true } # public [dev-dependencies] -ironrdp-blocking = { path = "../ironrdp-blocking", version = "0.10" } +ironrdp-blocking = { path = "../ironrdp-blocking", version = "0.11" } ironrdp-cliprdr-native = { path = "../ironrdp-cliprdr-native", version = "0.7" } anyhow = "1" async-trait = "0.1" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index c277121a0..641dd146a 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -126,9 +126,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.67" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", "jobserver", @@ -272,13 +272,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -292,9 +292,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "flagset" @@ -371,11 +371,11 @@ dependencies = [ [[package]] name = "ironrdp-bulk" -version = "0.1.1" +version = "0.2.0" [[package]] name = "ironrdp-cliprdr" -version = "0.7.0" +version = "0.7.1" dependencies = [ "bitflags", "ironrdp-core", @@ -386,7 +386,7 @@ dependencies = [ [[package]] name = "ironrdp-cliprdr-format" -version = "0.2.0" +version = "0.2.1" dependencies = [ "ironrdp-core", "png", @@ -394,14 +394,14 @@ dependencies = [ [[package]] name = "ironrdp-core" -version = "0.2.1" +version = "0.3.0" dependencies = [ "ironrdp-error", ] [[package]] name = "ironrdp-displaycontrol" -version = "0.8.0" +version = "0.8.1" dependencies = [ "ironrdp-core", "ironrdp-dvc", @@ -412,7 +412,7 @@ dependencies = [ [[package]] name = "ironrdp-dvc" -version = "0.8.0" +version = "0.9.0" dependencies = [ "ironrdp-core", "ironrdp-pdu", @@ -422,7 +422,7 @@ dependencies = [ [[package]] name = "ironrdp-egfx" -version = "0.3.0" +version = "0.3.1" dependencies = [ "arbitrary", "bit_field", @@ -436,7 +436,7 @@ dependencies = [ [[package]] name = "ironrdp-error" -version = "0.2.0" +version = "0.2.1" [[package]] name = "ironrdp-fuzz" @@ -467,7 +467,7 @@ dependencies = [ [[package]] name = "ironrdp-graphics" -version = "0.9.0" +version = "0.10.0" dependencies = [ "bit_field", "bitflags", @@ -483,7 +483,7 @@ dependencies = [ [[package]] name = "ironrdp-pdu" -version = "0.9.0" +version = "0.10.0" dependencies = [ "arbitrary", "bit_field", @@ -506,7 +506,7 @@ dependencies = [ [[package]] name = "ironrdp-rdpdr" -version = "0.7.0" +version = "0.8.0" dependencies = [ "bitflags", "getrandom 0.3.4", @@ -519,7 +519,7 @@ dependencies = [ [[package]] name = "ironrdp-rdpsnd" -version = "0.9.0" +version = "0.10.0" dependencies = [ "bitflags", "ironrdp-core", @@ -530,7 +530,7 @@ dependencies = [ [[package]] name = "ironrdp-svc" -version = "0.8.0" +version = "0.8.1" dependencies = [ "bitflags", "ironrdp-core", @@ -634,9 +634,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -698,18 +698,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -840,18 +840,18 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", @@ -977,9 +977,9 @@ dependencies = [ [[package]] name = "yuv" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d85a782d94ee43f078bcfd6fa82d4e6a5b2d1cfbbad168e4df5a9f7b39ef48c" +checksum = "220655e1c245693beb13b377d3174b9efcb1645018d1d4ec53903fc211b135ae" dependencies = [ "num-traits", ]