diff --git a/Cargo.lock b/Cargo.lock index 7cf9d8a2e5..bf5042ad10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3031,7 +3031,6 @@ dependencies = [ "picky-asn1-x509", "sspi", "tokio", - "tokio-native-tls", "tokio-socks", "tokio-tungstenite", "tokio-util", @@ -3411,6 +3410,12 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "base64", + "bitflags 2.13.1", + "futures-util", + "http-body-util", + "hyper", + "hyper-util", "ironrdp", "ironrdp-agent", "ironrdp-async", @@ -3421,8 +3426,10 @@ dependencies = [ "ironrdp-daemon", "ironrdp-dvc", "ironrdp-dvc-pipe-proxy", + "ironrdp-error 0.2.0", "ironrdp-graphics", "ironrdp-input", + "ironrdp-mstsgu", "ironrdp-pdu", "ironrdp-propertyset", "ironrdp-rail", @@ -3439,10 +3446,14 @@ dependencies = [ "ironrdp-tokio", "ironrdp-viewer", "ironrdp-vmconnect", + "log", "rcgen", "semver", + "sspi", "tokio", + "tokio-native-tls", "tokio-rustls", + "tokio-tungstenite", "tracing", "tracing-subscriber", "uuid", diff --git a/crates/ironrdp-mstsgu/Cargo.toml b/crates/ironrdp-mstsgu/Cargo.toml index 6eba63e274..7ad580df66 100644 --- a/crates/ironrdp-mstsgu/Cargo.toml +++ b/crates/ironrdp-mstsgu/Cargo.toml @@ -16,63 +16,6 @@ categories.workspace = true doctest = false test = false -[[test]] -name = "http_auth" -path = "tests/http_auth.rs" -required-features = ["native-tls"] - -[[test]] -name = "udp" -path = "tests/udp.rs" - -[[test]] -name = "http_control" -path = "tests/http_control.rs" - -[[test]] -name = "rpc_pdu" -path = "tests/rpc_pdu.rs" - -[[test]] -name = "rpch_v2" -path = "tests/rpch_v2.rs" - -[[test]] -name = "rpc_tsgu_stubs" -path = "tests/rpc_tsgu_stubs.rs" - -[[test]] -name = "rpch_http" -path = "tests/rpch_http.rs" - -[[test]] -name = "rpch_session" -path = "tests/rpch_session.rs" - -[[test]] -name = "tunnel_policy" -path = "tests/tunnel_policy.rs" - -[[test]] -name = "packet_io" -path = "tests/packet_io.rs" -required-features = ["native-tls", "test-support"] - -[[test]] -name = "packet_io_rustls" -path = "tests/packet_io_rustls.rs" -required-features = ["rustls", "test-support"] - -[[test]] -name = "proxy" -path = "tests/proxy.rs" -required-features = ["native-tls", "test-support"] - -[[test]] -name = "consent" -path = "tests/consent.rs" -required-features = ["native-tls", "test-support"] - [features] default = [] rustls = ["ironrdp-tls/rustls", "tokio-tungstenite/rustls-tls-native-roots"] @@ -107,10 +50,5 @@ picky = { version = "=7.0.0-rc.25", optional = true } picky-asn1-der = { version = "0.5", optional = true } picky-asn1-x509 = { version = "0.15", optional = true } -[dev-dependencies] -hyper = { version = "1.9", features = ["server", "http1"] } -tokio = { version = "1.52", features = ["io-util", "macros", "net", "rt", "sync"] } -tokio-native-tls = "0.3" - [lints] workspace = true diff --git a/crates/ironrdp-mstsgu/src/lib.rs b/crates/ironrdp-mstsgu/src/lib.rs index c731867cf5..2ce1d4f8fd 100644 --- a/crates/ironrdp-mstsgu/src/lib.rs +++ b/crates/ironrdp-mstsgu/src/lib.rs @@ -1,9 +1,6 @@ #![cfg_attr(doc, doc = include_str!("../README.md"))] #![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")] -#[cfg(test)] -use tokio_native_tls as _; - #[macro_use] mod macros; diff --git a/crates/ironrdp-mstsgu/src/proto.rs b/crates/ironrdp-mstsgu/src/proto.rs index 9a3eeb661f..d647400ff3 100644 --- a/crates/ironrdp-mstsgu/src/proto.rs +++ b/crates/ironrdp-mstsgu/src/proto.rs @@ -161,12 +161,16 @@ impl Encode for HandshakeReqPkt { /// 2.2.10.11 HTTP_HANDSHAKE_RESPONSE_PACKET Structure #[derive(Debug)] -pub(crate) struct HandshakeRespPkt { - pub error_code: u32, - pub ver_major: u8, - pub ver_minor: u8, - pub server_version: u16, - pub extended_auth: HttpExtendedAuth, +#[cfg_attr( + not(feature = "test-support"), + expect(unreachable_pub, reason = "exposed only through test-support") +)] +pub struct HandshakeRespPkt { + pub(crate) error_code: u32, + pub(crate) ver_major: u8, + pub(crate) ver_minor: u8, + pub(crate) server_version: u16, + pub(crate) extended_auth: HttpExtendedAuth, } impl HandshakeRespPkt { @@ -193,11 +197,15 @@ const HTTP_TUNNEL_PACKET_FIELD_REAUTH: u16 = 0x2; /// 2.2.10.18 HTTP_TUNNEL_PACKET #[derive(Default)] -pub(crate) struct TunnelReqPkt { - pub caps: u32, - pub fields_present: u16, - pub _reserved: u16, - pub reauth_tunnel_context: Option, +#[cfg_attr( + not(feature = "test-support"), + expect(unreachable_pub, reason = "exposed only through test-support") +)] +pub struct TunnelReqPkt { + pub(crate) caps: u32, + pub(crate) fields_present: u16, + pub(crate) _reserved: u16, + pub(crate) reauth_tunnel_context: Option, } impl Encode for TunnelReqPkt { @@ -344,7 +352,11 @@ impl Decode<'_> for TunnelRespPkt { /// 2.2.10.7 HTTP_EXTENDED_AUTH_PACKET Structure #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct ExtendedAuthPkt { +#[cfg_attr( + not(feature = "test-support"), + expect(unreachable_pub, reason = "exposed only through test-support") +)] +pub struct ExtendedAuthPkt { pub(crate) error_code: u32, pub(crate) auth_blob: Vec, } @@ -444,7 +456,11 @@ const HTTP_TUNNEL_AUTH_RESPONSE_FIELD_SOH_RESPONSE: u16 = 0x4; /// [2.2.10.16]: https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-TSGU/%5bMS-TSGU%5d.pdf#page=70 /// [2.2.10.17]: https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-TSGU/%5bMS-TSGU%5d.pdf#page=70 #[derive(Debug, Default)] -pub(crate) struct TunnelAuthRespPkt { +#[cfg_attr( + not(feature = "test-support"), + expect(unreachable_pub, reason = "exposed only through test-support") +)] +pub struct TunnelAuthRespPkt { pub(crate) error_code: u32, fields_present: u16, _reserved: u16, diff --git a/crates/ironrdp-mstsgu/src/test_support.rs b/crates/ironrdp-mstsgu/src/test_support.rs index 0a8270d2ac..04e0188ee1 100644 --- a/crates/ironrdp-mstsgu/src/test_support.rs +++ b/crates/ironrdp-mstsgu/src/test_support.rs @@ -11,6 +11,51 @@ use crate::packet_io::{ }; use crate::{ConsentFallback, Error, GwClient, GwConnectTarget, GwConsentCallback}; +/// Test-only constructors and accessors for internal MS-TSGU protocol packets. +pub mod proto { + pub use crate::proto::{ExtendedAuthPkt, HandshakeRespPkt, TunnelAuthRespPkt, TunnelReqPkt}; + + /// Constructs an HTTP tunnel request packet. + pub fn tunnel_request(caps: u32, fields_present: u16, reauth_tunnel_context: Option) -> TunnelReqPkt { + TunnelReqPkt { + caps, + fields_present, + _reserved: 0, + reauth_tunnel_context, + } + } + + /// Constructs an HTTP extended authentication packet. + pub fn extended_auth_packet(error_code: u32, auth_blob: Vec) -> ExtendedAuthPkt { + ExtendedAuthPkt { error_code, auth_blob } + } + + /// Returns the advertised HTTP extended-authentication flags. + pub fn handshake_extended_auth_bits(packet: &HandshakeRespPkt) -> u16 { + packet.extended_auth.bits() + } + + /// Returns the bit representation for no HTTP extended authentication. + pub const fn no_extended_auth_bits() -> u16 { + 0 + } + + /// Returns tunnel authorization redirection flags. + pub fn tunnel_auth_response_redirection_flags(packet: &TunnelAuthRespPkt) -> Option { + packet.redirection_flags + } + + /// Returns tunnel authorization idle timeout in minutes. + pub fn tunnel_auth_response_idle_timeout_minutes(packet: &TunnelAuthRespPkt) -> Option { + packet.idle_timeout_minutes + } + + /// Returns the tunnel authorization statement-of-health response. + pub fn tunnel_auth_response_soh_response(packet: &TunnelAuthRespPkt) -> Option<&[u8]> { + packet.soh_response.as_deref() + } +} + /// In-memory gateway transport used by the registered integration tests. pub struct GatewayTransport(NetworkGatewayTransport); diff --git a/crates/ironrdp-mstsgu/tests/http_auth.rs b/crates/ironrdp-mstsgu/tests/http_auth.rs deleted file mode 100644 index 9c9cfcbe8c..0000000000 --- a/crates/ironrdp-mstsgu/tests/http_auth.rs +++ /dev/null @@ -1,207 +0,0 @@ -#![allow(unused_crate_dependencies)] - -use ironrdp_mstsgu::GwSmartCardCredentials; -use ironrdp_mstsgu::http_auth::{AuthStep, GatewayHttpAuth, basic_authorization, split_auth_challenge}; - -fn smart_card_credentials() -> GwSmartCardCredentials { - GwSmartCardCredentials { - username: "alice@contoso.com".to_owned(), - pin: "sensitive-pin".to_owned(), - certificate: b"sensitive-certificate".to_vec(), - private_key: Some(b"sensitive-private-key".to_vec()), - reader_name: "Reader 0".to_owned(), - card_name: Some("Card".to_owned()), - container_name: Some("Container".to_owned()), - csp_name: Some("Provider".to_owned()), - } -} - -#[test] -fn split_auth_challenge_parses_schemes() { - assert_eq!(split_auth_challenge("NTLM TlRMTVNTUA==", "NTLM"), Some("TlRMTVNTUA==")); - assert_eq!(split_auth_challenge("ntlm", "NTLM"), Some("")); - assert_eq!(split_auth_challenge("Negotiate abc", "Negotiate"), Some("abc")); - assert_eq!( - split_auth_challenge("Basic realm=\"rdg\"", "Basic"), - Some("realm=\"rdg\"") - ); - assert_eq!(split_auth_challenge("Digest qop=auth", "NTLM"), None); -} - -#[test] -fn basic_authorization_format() { - let value = basic_authorization("user", "pass"); - assert_eq!(value, "Basic dXNlcjpwYXNz"); -} - -#[test] -fn negotiate_type1_from_challenge() { - let (auth, step) = GatewayHttpAuth::from_challenges( - r"CONTOSO\alice", - "secret", - None, - Some("HTTP/rdg.contoso.com".to_owned()), - &["Negotiate"], - ) - .expect("negotiate init"); - let auth = auth.expect("negotiate backend"); - assert_eq!(auth.scheme(), "Negotiate"); - match step { - AuthStep::Continue(header) => { - assert!(header.starts_with("Negotiate ")); - assert!(header.len() > "Negotiate ".len()); - } - other => panic!("expected Continue, got {other:?}"), - } -} - -#[test] -fn ntlm_type1_from_challenge() { - let (auth, step) = - GatewayHttpAuth::from_challenges(r"CONTOSO\alice", "secret", None, None, &["NTLM"]).expect("ntlm init"); - let auth = auth.expect("ntlm backend"); - assert_eq!(auth.scheme(), "NTLM"); - match step { - AuthStep::Continue(header) => { - assert!(header.starts_with("NTLM ")); - assert!(header.len() > "NTLM ".len()); - } - other => panic!("expected Continue, got {other:?}"), - } -} - -#[test] -fn try_basic_when_only_basic_offered() { - let (auth, step) = - GatewayHttpAuth::from_challenges("alice", "secret", None, None, &["Basic realm=\"RDG\""]).expect("basic"); - assert!(auth.is_none()); - assert!(matches!(step, AuthStep::TryBasic)); -} - -#[test] -fn prefer_negotiate_over_ntlm() { - let (auth, _) = GatewayHttpAuth::from_challenges( - "alice", - "secret", - None, - None, - &["NTLM", "Negotiate", "Basic realm=\"x\""], - ) - .expect("init"); - assert_eq!(auth.expect("negotiate backend").scheme(), "Negotiate"); -} - -#[test] -fn combined_www_authenticate_prefers_negotiate() { - let (auth, step) = GatewayHttpAuth::from_challenges( - "alice", - "secret", - None, - None, - &[r#"Negotiate, NTLM, Basic realm="RDG""#], - ) - .expect("init"); - assert_eq!(auth.expect("negotiate backend").scheme(), "Negotiate"); - assert!(matches!(step, AuthStep::Continue(_))); -} - -#[test] -fn quoted_comma_in_basic_realm_is_one_challenge() { - let (auth, step) = - GatewayHttpAuth::from_challenges("alice", "secret", None, None, &[r#"Basic realm="a, b""#]).expect("basic"); - assert!(auth.is_none()); - assert!(matches!(step, AuthStep::TryBasic)); -} - -#[test] -fn smart_card_debug_redacts_secrets() { - let debug = format!("{:?}", smart_card_credentials()); - assert!(!debug.contains("alice@contoso.com")); - assert!(!debug.contains("sensitive-pin")); - assert!(!debug.contains("sensitive-certificate")); - assert!(!debug.contains("sensitive-private-key")); -} - -#[cfg(not(feature = "smartcard"))] -#[test] -fn smart_card_without_feature_is_an_explicit_error() { - let smart_card = smart_card_credentials(); - let result = GatewayHttpAuth::from_challenges("alice", "password", Some(&smart_card), None, &["Negotiate"]); - let Err(error) = result else { - panic!("smart-card authentication needs its feature"); - }; - let error = error.to_string(); - - assert!(error.contains("smart-card")); - assert!(error.contains("smartcard")); - assert!(!error.contains("sensitive-pin")); - assert!(!error.contains("sensitive-certificate")); - assert!(!error.contains("sensitive-private-key")); -} - -#[cfg(feature = "smartcard")] -#[test] -fn smart_card_requires_username() { - let mut smart_card = smart_card_credentials(); - smart_card.username.clear(); - let result = GatewayHttpAuth::from_challenges("alice", "password", Some(&smart_card), None, &["Negotiate"]); - let Err(error) = result else { - panic!("smart-card authentication requires a username"); - }; - - assert!(error.to_string().contains("username")); -} - -#[cfg(feature = "smartcard")] -#[test] -fn smart_card_requires_negotiate() { - let smart_card = smart_card_credentials(); - let result = GatewayHttpAuth::from_challenges("alice", "password", Some(&smart_card), None, &["NTLM"]); - let Err(error) = result else { - panic!("smart-card authentication requires Negotiate"); - }; - let error = error.to_string(); - - assert!(error.contains("Negotiate")); - assert!(!error.contains("sensitive-pin")); - assert!(!error.contains("sensitive-certificate")); - assert!(!error.contains("sensitive-private-key")); -} - -#[cfg(feature = "smartcard")] -#[test] -fn smart_card_rejects_malformed_negotiate_token() { - let smart_card = smart_card_credentials(); - let result = GatewayHttpAuth::from_challenges("alice", "password", Some(&smart_card), None, &["Negotiate !"]); - let Err(error) = result else { - panic!("malformed Negotiate token must fail before PKINIT initialization"); - }; - let error = error.to_string(); - - assert!(error.contains("decode negotiate challenge")); - assert!(!error.contains("sensitive-pin")); - assert!(!error.contains("sensitive-certificate")); - assert!(!error.contains("sensitive-private-key")); -} - -#[cfg(feature = "smartcard")] -#[test] -fn smart_card_recognizes_combined_negotiate_challenge() { - let smart_card = smart_card_credentials(); - let result = GatewayHttpAuth::from_challenges( - "alice", - "password", - Some(&smart_card), - None, - &[r#"NTLM, Negotiate !, Basic realm="RDG""#], - ); - let Err(error) = result else { - panic!("malformed Negotiate token must fail before PKINIT initialization"); - }; - let error = error.to_string(); - - assert!(error.contains("decode negotiate challenge")); - assert!(!error.contains("sensitive-pin")); - assert!(!error.contains("sensitive-certificate")); - assert!(!error.contains("sensitive-private-key")); -} diff --git a/crates/ironrdp-mstsgu/tests/rpc_tsgu_stubs.rs b/crates/ironrdp-mstsgu/tests/rpc_tsgu_stubs.rs deleted file mode 100644 index 3fb1bc7d41..0000000000 --- a/crates/ironrdp-mstsgu/tests/rpc_tsgu_stubs.rs +++ /dev/null @@ -1,40 +0,0 @@ -#![allow(dead_code, unreachable_pub, unused_crate_dependencies)] - -use core::fmt; - -type Error = ironrdp_error::Error; - -#[derive(Debug)] -enum GwErrorKind { - Connect, - Custom, -} - -trait GwErrorExt { - fn custom(context: &'static str, error: E) -> Self - where - E: core::error::Error + Sync + Send + 'static; -} - -impl GwErrorExt for Error { - fn custom(context: &'static str, error: E) -> Self - where - E: core::error::Error + Sync + Send + 'static, - { - Self::new(context, GwErrorKind::Custom).with_source(error) - } -} - -impl fmt::Display for GwErrorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::Connect => "connection error", - Self::Custom => "custom", - }) - } -} - -impl core::error::Error for GwErrorKind {} - -#[path = "../src/rpc.rs"] -mod rpc; diff --git a/crates/ironrdp-mstsgu/tests/rpch_session.rs b/crates/ironrdp-mstsgu/tests/rpch_session.rs deleted file mode 100644 index ce5535593a..0000000000 --- a/crates/ironrdp-mstsgu/tests/rpch_session.rs +++ /dev/null @@ -1,75 +0,0 @@ -#![allow(unreachable_pub, unused_crate_dependencies)] - -use core::fmt; - -type Error = ironrdp_error::Error; - -#[derive(Debug)] -enum GwErrorKind { - Connect, - GatewayCode(u32), - HttpStatus(u16), - PacketEof, - UnsupportedFeature, - Custom, - Encode, - Decode, -} - -trait GwErrorExt { - fn custom(context: &'static str, error: E) -> Self - where - E: core::error::Error + Sync + Send + 'static; -} - -impl GwErrorExt for Error { - fn custom(context: &'static str, error: E) -> Self - where - E: core::error::Error + Sync + Send + 'static, - { - Self::new(context, GwErrorKind::Custom).with_source(error) - } -} - -impl fmt::Display for GwErrorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Connect => f.write_str("connection error"), - Self::GatewayCode(code) => write!(f, "gateway error 0x{code:08x}"), - Self::HttpStatus(status) => write!(f, "unexpected http status {status}"), - Self::PacketEof => f.write_str("packet eof"), - Self::UnsupportedFeature => f.write_str("unsupported feature"), - Self::Custom => f.write_str("custom"), - Self::Encode => f.write_str("encode"), - Self::Decode => f.write_str("decode"), - } - } -} - -impl core::error::Error for GwErrorKind {} - -macro_rules! custom_err { - ( $context:expr, $source:expr $(,)? ) => {{ <$crate::Error as $crate::GwErrorExt>::custom($context, $source) }}; -} - -#[derive(Clone)] -struct GwSmartCardCredentials; - -#[path = "../src/http_auth.rs"] -#[expect( - dead_code, - reason = "the integration harness only exercises shared HTTP authentication through RPCH" -)] -mod http_auth; -#[path = "../src/mock_rpch.rs"] -mod mock_rpch; -#[path = "../src/rpc.rs"] -#[expect( - dead_code, - reason = "the integration harness exercises only the RPCH session codec subset" -)] -mod rpc; -#[path = "../src/rpc_transport.rs"] -mod rpc_transport; -#[path = "../src/rpch.rs"] -mod rpch; diff --git a/crates/ironrdp-testsuite-extra/Cargo.toml b/crates/ironrdp-testsuite-extra/Cargo.toml index 9e17a525f8..cb152c829b 100644 --- a/crates/ironrdp-testsuite-extra/Cargo.toml +++ b/crates/ironrdp-testsuite-extra/Cargo.toml @@ -24,6 +24,7 @@ harness = true [dev-dependencies] anyhow = "1.0" async-trait = "0.1" +base64 = "0.22" ironrdp = { path = "../ironrdp", features = ["server", "pdu", "connector", "session", "svc", "dvc", "echo"] } ironrdp-cfg.path = "../ironrdp-cfg" ironrdp-async.path = "../ironrdp-async" @@ -47,9 +48,9 @@ ironrdp-rdpeudp-tokio.path = "../ironrdp-rdpeudp-tokio" ironrdp-rdpsnd.path = "../ironrdp-rdpsnd" ironrdp-rdpsnd-native = { path = "../ironrdp-rdpsnd-native", features = ["capture"] } ironrdp-rpc = { path = "../ironrdp-rpc", features = ["__test"] } -ironrdp-viewer.path = "../ironrdp-viewer" ironrdp-tokio.path = "../ironrdp-tokio" ironrdp-tls = { path = "../ironrdp-tls", features = ["rustls"] } +ironrdp-viewer.path = "../ironrdp-viewer" ironrdp-vmconnect = { path = "../ironrdp-vmconnect", features = ["__test"] } rcgen = "0.14" semver = "1.0" @@ -61,6 +62,17 @@ tokio = { version = "1", features = ["sync", "time", "net", "rt", "rt-multi-thre # process default, which fails every TLS test in this binary. tokio-rustls = { version = "0.26", default-features = false } uuid = { version = "1", features = ["v4"] } +ironrdp-mstsgu = { path = "../ironrdp-mstsgu", features = ["rustls", "test-support"] } +bitflags = "2.11.0" +futures-util = "0.3" +http-body-util = { version = "0.1.4", features = ["channel"] } +hyper = { version = "1.9", features = ["server", "http1"] } +hyper-util = { version = "0.1", features = ["tokio"] } +tokio-native-tls = "0.3" +tokio-tungstenite = "0.29" +ironrdp-error = { path = "../ironrdp-error" } +log = "0.4" +sspi = { version = "0.21", features = ["network_client"] } [target.'cfg(windows)'.dev-dependencies] ironrdp-rdpdr-native.path = "../ironrdp-rdpdr-native" diff --git a/crates/ironrdp-testsuite-extra/tests/main.rs b/crates/ironrdp-testsuite-extra/tests/main.rs index fb3fedf3ef..96968aa0bc 100644 --- a/crates/ironrdp-testsuite-extra/tests/main.rs +++ b/crates/ironrdp-testsuite-extra/tests/main.rs @@ -1,5 +1,14 @@ #![allow(unused_crate_dependencies)] // false positives because there is both a library and a binary #![allow(clippy::unwrap_used, reason = "unwrap is fine in tests")] +#![allow( + dead_code, + unreachable_pub, + reason = "Mstsgu integration tests compile private HTTP and RPC source modules" +)] + +macro_rules! custom_err { + ( $context:expr, $source:expr $(,)? ) => {{ <$crate::Error as $crate::GwErrorExt>::custom($context, $source) }}; +} mod agent; mod async_framed; @@ -8,6 +17,22 @@ mod client; mod dvc_pipe_proxy; mod e2e; mod gateway_detect; +mod mstsgu; +pub(crate) use mstsgu::rpch_http::{Error, GwErrorExt, GwErrorKind}; +#[path = "../../ironrdp-mstsgu/src/http_auth.rs"] +#[expect( + unexpected_cfgs, + reason = "the included Mstsgu source owns the smartcard configuration" +)] +mod http_auth; +#[path = "../../ironrdp-mstsgu/src/mock_rpch.rs"] +mod mock_rpch; mod rdpeudp_tokio; +#[path = "../../ironrdp-mstsgu/src/rpc.rs"] +mod rpc; +#[path = "../../ironrdp-mstsgu/src/rpc_transport.rs"] +mod rpc_transport; mod vmconnect; mod volume; + +pub(crate) use ironrdp_mstsgu::GwSmartCardCredentials; diff --git a/crates/ironrdp-mstsgu/tests/consent.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/consent.rs similarity index 99% rename from crates/ironrdp-mstsgu/tests/consent.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/consent.rs index c28690df87..74c6668b39 100644 --- a/crates/ironrdp-mstsgu/tests/consent.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/consent.rs @@ -1,5 +1,3 @@ -#![allow(unused_crate_dependencies)] - use std::sync::{Arc, Mutex}; use core::convert::Infallible; diff --git a/crates/ironrdp-testsuite-extra/tests/mstsgu/http_auth.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/http_auth.rs new file mode 100644 index 0000000000..ec45883e6c --- /dev/null +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/http_auth.rs @@ -0,0 +1,98 @@ +use ironrdp_mstsgu::http_auth::{AuthStep, GatewayHttpAuth, basic_authorization, split_auth_challenge}; + +#[test] +fn split_auth_challenge_parses_schemes() { + assert_eq!(split_auth_challenge("NTLM TlRMTVNTUA==", "NTLM"), Some("TlRMTVNTUA==")); + assert_eq!(split_auth_challenge("ntlm", "NTLM"), Some("")); + assert_eq!(split_auth_challenge("Negotiate abc", "Negotiate"), Some("abc")); + assert_eq!( + split_auth_challenge("Basic realm=\"rdg\"", "Basic"), + Some("realm=\"rdg\"") + ); + assert_eq!(split_auth_challenge("Digest qop=auth", "NTLM"), None); +} + +#[test] +fn basic_authorization_format() { + let value = basic_authorization("user", "pass"); + assert_eq!(value, "Basic dXNlcjpwYXNz"); +} + +#[test] +fn negotiate_type1_from_challenge() { + let (auth, step) = GatewayHttpAuth::from_challenges( + r"CONTOSO\alice", + "secret", + None, + Some("HTTP/rdg.contoso.com".to_owned()), + &["Negotiate"], + ) + .expect("negotiate init"); + let auth = auth.expect("negotiate backend"); + assert_eq!(auth.scheme(), "Negotiate"); + match step { + AuthStep::Continue(header) => { + assert!(header.starts_with("Negotiate ")); + assert!(header.len() > "Negotiate ".len()); + } + other => panic!("expected Continue, got {other:?}"), + } +} + +#[test] +fn ntlm_type1_from_challenge() { + let (auth, step) = + GatewayHttpAuth::from_challenges(r"CONTOSO\alice", "secret", None, None, &["NTLM"]).expect("ntlm init"); + let auth = auth.expect("ntlm backend"); + assert_eq!(auth.scheme(), "NTLM"); + match step { + AuthStep::Continue(header) => { + assert!(header.starts_with("NTLM ")); + assert!(header.len() > "NTLM ".len()); + } + other => panic!("expected Continue, got {other:?}"), + } +} + +#[test] +fn try_basic_when_only_basic_offered() { + let (auth, step) = + GatewayHttpAuth::from_challenges("alice", "secret", None, None, &["Basic realm=\"RDG\""]).expect("basic"); + assert!(auth.is_none()); + assert!(matches!(step, AuthStep::TryBasic)); +} + +#[test] +fn prefer_negotiate_over_ntlm() { + let (auth, _) = GatewayHttpAuth::from_challenges( + "alice", + "secret", + None, + None, + &["NTLM", "Negotiate", "Basic realm=\"x\""], + ) + .expect("init"); + assert_eq!(auth.expect("negotiate backend").scheme(), "Negotiate"); +} + +#[test] +fn combined_www_authenticate_prefers_negotiate() { + let (auth, step) = GatewayHttpAuth::from_challenges( + "alice", + "secret", + None, + None, + &[r#"Negotiate, NTLM, Basic realm="RDG""#], + ) + .expect("init"); + assert_eq!(auth.expect("negotiate backend").scheme(), "Negotiate"); + assert!(matches!(step, AuthStep::Continue(_))); +} + +#[test] +fn quoted_comma_in_basic_realm_is_one_challenge() { + let (auth, step) = + GatewayHttpAuth::from_challenges("alice", "secret", None, None, &[r#"Basic realm="a, b""#]).expect("basic"); + assert!(auth.is_none()); + assert!(matches!(step, AuthStep::TryBasic)); +} diff --git a/crates/ironrdp-mstsgu/tests/http_control.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/http_control.rs similarity index 81% rename from crates/ironrdp-mstsgu/tests/http_control.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/http_control.rs index 75c99f81dc..ded8185a6b 100644 --- a/crates/ironrdp-mstsgu/tests/http_control.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/http_control.rs @@ -1,22 +1,10 @@ -#![expect( - dead_code, - unreachable_pub, - unused_crate_dependencies, - reason = "tests import private protocol structures" -)] - -#[path = "../src/proto.rs"] -#[expect( - clippy::allow_attributes, - reason = "the imported protocol source contains an intentionally unfulfilled expectation" -)] -#[allow(unfulfilled_lint_expectations)] -mod proto; - use ironrdp_core::{Decode, Encode, ReadCursor, WriteCursor}; use ironrdp_mstsgu::GwErrorKind; +use ironrdp_mstsgu::test_support::proto::{ + ExtendedAuthPkt, HandshakeRespPkt, extended_auth_packet, handshake_extended_auth_bits, no_extended_auth_bits, + tunnel_request, +}; use ironrdp_mstsgu::{ChannelClosePkt, ReauthMessagePkt, ServiceMessagePkt, gateway_code_label}; -use proto::{ExtendedAuthPkt, HandshakeRespPkt, HttpExtendedAuth, TunnelReqPkt}; fn encode_to_vec(payload: &impl Encode) -> Vec { let mut buf = vec![0u8; payload.size()]; @@ -36,11 +24,7 @@ fn decode_body<'a, T: Decode<'a>>(bytes: &'a [u8]) -> T { #[test] fn tunnel_request_without_reauth_context_preserves_existing_wire_format() { - let bytes = encode_to_vec(&TunnelReqPkt { - caps: 0x04, - fields_present: 0, - ..TunnelReqPkt::default() - }); + let bytes = encode_to_vec(&tunnel_request(0x04, 0, None)); assert_eq!( bytes, @@ -56,10 +40,7 @@ fn tunnel_request_without_reauth_context_preserves_existing_wire_format() { #[test] fn tunnel_request_without_reauth_context_clears_reauth_field_bit() { - let bytes = encode_to_vec(&TunnelReqPkt { - fields_present: 0x2, - ..TunnelReqPkt::default() - }); + let bytes = encode_to_vec(&tunnel_request(0, 0x2, None)); assert_eq!(bytes.len(), 16); assert_eq!(&bytes[12..14], &0u16.to_le_bytes()); @@ -68,11 +49,7 @@ fn tunnel_request_without_reauth_context_clears_reauth_field_bit() { #[test] fn tunnel_request_encodes_reauth_context() { let reauth_tunnel_context = 0x1122_3344_5566_7788; - let bytes = encode_to_vec(&TunnelReqPkt { - caps: 0x04, - reauth_tunnel_context: Some(reauth_tunnel_context), - ..TunnelReqPkt::default() - }); + let bytes = encode_to_vec(&tunnel_request(0x04, 0, Some(reauth_tunnel_context))); assert_eq!( bytes.len(), @@ -119,7 +96,7 @@ fn reauth_message_roundtrip() { #[test] fn handshake_response_preserves_advertised_extended_auth_capabilities() { - assert_eq!(HttpExtendedAuth::HTTP_EXTENDED_AUTH_NONE.bits(), 0); + assert_eq!(no_extended_auth_bits(), 0); for flags in [0x0000u16, 0x0003, 0x0004, 0x8000] { let mut bytes = [ @@ -135,16 +112,13 @@ fn handshake_response_preserves_advertised_extended_auth_capabilities() { bytes[16..].copy_from_slice(&flags.to_le_bytes()); let response = decode_body::(&bytes); - assert_eq!(response.extended_auth.bits(), flags); + assert_eq!(handshake_extended_auth_bits(&response), flags); } } #[test] fn extended_auth_packet_roundtrip() { - let pkt = ExtendedAuthPkt { - error_code: 0x1122_3344, - auth_blob: vec![0xAA, 0xBB, 0xCC, 0xDD], - }; + let pkt = extended_auth_packet(0x1122_3344, vec![0xAA, 0xBB, 0xCC, 0xDD]); let bytes = encode_to_vec(&pkt); assert_eq!( @@ -176,18 +150,12 @@ fn extended_auth_packet_rejects_malformed_and_mismatched_blobs() { #[test] fn extended_auth_packet_accepts_maximum_blob_and_rejects_larger_blobs() { - let pkt = ExtendedAuthPkt { - error_code: 0, - auth_blob: vec![0x5A; usize::from(u16::MAX)], - }; + let pkt = extended_auth_packet(0, vec![0x5A; usize::from(u16::MAX)]); let bytes = encode_to_vec(&pkt); assert_eq!(&bytes[12..14], &u16::MAX.to_le_bytes()); assert_eq!(decode_body::(&bytes), pkt); - let oversized = ExtendedAuthPkt { - error_code: 0, - auth_blob: vec![0; usize::from(u16::MAX) + 1], - }; + let oversized = extended_auth_packet(0, vec![0; usize::from(u16::MAX) + 1]); let mut bytes = vec![0; oversized.size()]; let mut cursor = WriteCursor::new(&mut bytes); assert!(oversized.encode(&mut cursor).is_err()); diff --git a/crates/ironrdp-testsuite-extra/tests/mstsgu/mod.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/mod.rs new file mode 100644 index 0000000000..3c24dbfaf5 --- /dev/null +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/mod.rs @@ -0,0 +1,13 @@ +mod consent; +mod http_auth; +mod http_control; +mod packet_io; +mod packet_io_rustls; +mod proxy; +mod rpc_pdu; +mod rpc_tsgu_stubs; +pub(crate) mod rpch_http; +pub(crate) mod rpch_session; +mod rpch_v2; +mod tunnel_policy; +mod udp; diff --git a/crates/ironrdp-mstsgu/tests/packet_io.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/packet_io.rs similarity index 94% rename from crates/ironrdp-mstsgu/tests/packet_io.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/packet_io.rs index 09360c5bc1..fa1d40b6c9 100644 --- a/crates/ironrdp-mstsgu/tests/packet_io.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/packet_io.rs @@ -1,5 +1,3 @@ -#![allow(unused_crate_dependencies)] - use core::convert::Infallible; use core::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -50,28 +48,6 @@ async fn strict_policy_rejects_self_signed_gateway_certificate() { server.await.expect("TLS server task"); } -#[tokio::test] -async fn native_tls_rejects_gateway_certificate_callback() { - let (listener, acceptor) = tls_listener().await; - let target = gateway_target(listener.local_addr().expect("gateway listener address")); - let server = tokio::spawn(async move { - let (stream, _) = listener.accept().await.expect("accept callback TLS client"); - assert!(acceptor.accept(stream).await.is_err()); - }); - let callback: CertificateValidationCallback = Arc::new(|_, _, _| true); - - let error = match GatewayTransport::connect_tls(&target, CertificateValidation::Strict, Some(callback)).await { - Ok(_) => panic!("native TLS must reject certificate callbacks"), - Err(error) => error, - }; - assert!( - format!("{error:?}").contains("certificate validation callbacks require the rustls backend"), - "{error:?}" - ); - - server.await.expect("TLS server task"); -} - #[tokio::test] async fn dangerous_policy_with_gateway_callback_is_rejected() { let target = gateway_target("127.0.0.1:1".parse().expect("socket address")); @@ -440,8 +416,8 @@ fn response(status: StatusCode, headers: &[(&str, &str)], body: Bytes) -> Respon async fn tls_listener() -> (tokio::net::TcpListener, TlsAcceptor) { let identity = Identity::from_pkcs8( - include_bytes!("../../ironrdp-tls/tests/certs/server-cert.pem"), - include_bytes!("../../ironrdp-tls/tests/certs/server-key.pem"), + include_bytes!("../../../ironrdp-tls/tests/certs/server-cert.pem"), + include_bytes!("../../../ironrdp-tls/tests/certs/server-key.pem"), ) .expect("create TLS identity"); let acceptor = TlsAcceptor::from(NativeTlsAcceptor::new(identity).expect("create TLS acceptor")); diff --git a/crates/ironrdp-mstsgu/tests/packet_io_rustls.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/packet_io_rustls.rs similarity index 97% rename from crates/ironrdp-mstsgu/tests/packet_io_rustls.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/packet_io_rustls.rs index e3b8dd142b..6247e74f64 100644 --- a/crates/ironrdp-mstsgu/tests/packet_io_rustls.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/packet_io_rustls.rs @@ -1,5 +1,3 @@ -#![allow(unused_crate_dependencies)] - use core::convert::Infallible; use core::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -175,8 +173,8 @@ fn response(status: StatusCode, body: Bytes) -> Response { async fn tls_listener() -> (tokio::net::TcpListener, TlsAcceptor) { let identity = Identity::from_pkcs8( - include_bytes!("../../ironrdp-tls/tests/certs/server-cert.pem"), - include_bytes!("../../ironrdp-tls/tests/certs/server-key.pem"), + include_bytes!("../../../ironrdp-tls/tests/certs/server-cert.pem"), + include_bytes!("../../../ironrdp-tls/tests/certs/server-key.pem"), ) .expect("create TLS identity"); let acceptor = TlsAcceptor::from(NativeTlsAcceptor::new(identity).expect("create TLS acceptor")); diff --git a/crates/ironrdp-mstsgu/tests/proxy.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/proxy.rs similarity index 98% rename from crates/ironrdp-mstsgu/tests/proxy.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/proxy.rs index 583c711f64..68ae11fbd0 100644 --- a/crates/ironrdp-mstsgu/tests/proxy.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/proxy.rs @@ -1,5 +1,3 @@ -#![allow(unused_crate_dependencies)] - use core::convert::Infallible; use std::ffi::OsString; @@ -339,8 +337,8 @@ impl Drop for ProxyEnvironment { async fn tls_listener() -> (TcpListener, TlsAcceptor) { let identity = Identity::from_pkcs8( - include_bytes!("../../ironrdp-tls/tests/certs/server-cert.pem"), - include_bytes!("../../ironrdp-tls/tests/certs/server-key.pem"), + include_bytes!("../../../ironrdp-tls/tests/certs/server-cert.pem"), + include_bytes!("../../../ironrdp-tls/tests/certs/server-key.pem"), ) .expect("create TLS identity"); let acceptor = TlsAcceptor::from(NativeTlsAcceptor::new(identity).expect("create TLS acceptor")); diff --git a/crates/ironrdp-mstsgu/tests/rpc_pdu.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpc_pdu.rs similarity index 99% rename from crates/ironrdp-mstsgu/tests/rpc_pdu.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/rpc_pdu.rs index 829a517ee5..11d1a6973a 100644 --- a/crates/ironrdp-mstsgu/tests/rpc_pdu.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpc_pdu.rs @@ -1,5 +1,3 @@ -#![allow(unused_crate_dependencies)] - use ironrdp_mstsgu::rpc::{ DEFAULT_FRAGMENT_SIZE, MAX_PENDING_RPC_FRAGMENTS, PFC_FIRST_FRAG, PFC_LAST_FRAG, PFC_SUPPORT_HEADER_SIGN, PTYPE_BIND, PTYPE_BIND_ACK, PTYPE_BIND_NAK, PTYPE_FAULT, PTYPE_REQUEST, PTYPE_RESPONSE, PTYPE_RPC_AUTH_3, diff --git a/crates/ironrdp-testsuite-extra/tests/mstsgu/rpc_tsgu_stubs.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpc_tsgu_stubs.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpc_tsgu_stubs.rs @@ -0,0 +1 @@ + diff --git a/crates/ironrdp-mstsgu/tests/rpch_http.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_http.rs similarity index 92% rename from crates/ironrdp-mstsgu/tests/rpch_http.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_http.rs index fa0c6d9f91..f871aac19e 100644 --- a/crates/ironrdp-mstsgu/tests/rpch_http.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_http.rs @@ -1,21 +1,22 @@ -#![allow(unused_crate_dependencies)] - use core::fmt; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; -type Error = ironrdp_error::Error; +pub(crate) type Error = ironrdp_error::Error; #[derive(Debug)] -enum GwErrorKind { +pub(crate) enum GwErrorKind { Connect, + GatewayCode(u32), + HttpStatus(u16), PacketEof, + UnsupportedFeature, Custom, Encode, Decode, } -trait GwErrorExt { +pub(crate) trait GwErrorExt { fn custom(context: &'static str, error: E) -> Self where E: core::error::Error + Sync + Send + 'static; @@ -34,7 +35,10 @@ impl fmt::Display for GwErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { Self::Connect => "connection error", + Self::GatewayCode(code) => return write!(f, "gateway error 0x{code:08x}"), + Self::HttpStatus(status) => return write!(f, "unexpected http status {status}"), Self::PacketEof => "packet EOF", + Self::UnsupportedFeature => "unsupported feature", Self::Custom => "custom", Self::Encode => "encode", Self::Decode => "decode", @@ -44,14 +48,7 @@ impl fmt::Display for GwErrorKind { impl core::error::Error for GwErrorKind {} -macro_rules! custom_err { - ( $context:expr, $source:expr $(,)? ) => {{ <$crate::Error as $crate::GwErrorExt>::custom($context, $source) }}; -} - -#[path = "../src/rpc_transport.rs"] -mod rpc_transport; - -use rpc_transport::{RpchRequestHead, drain_body, read_rpch_response_head, write_rpch_request_head}; +use crate::rpc_transport::{RpchRequestHead, drain_body, read_rpch_response_head, write_rpch_request_head}; #[tokio::test] async fn request_head_contains_rpch_routing_and_authentication_fields() { @@ -265,7 +262,7 @@ async fn in_authentication_waits_for_continue_before_the_body() { }); let mut request = - rpc_transport::RpchInRequest::open(client, "gateway.example", "target.example:3389", 128 * 1024, None) + crate::rpc_transport::RpchInRequest::open(client, "gateway.example", "target.example:3389", 128 * 1024, None) .await .expect("open authentication probe"); assert!(request.write_body(b"B1!!").await.is_err()); @@ -293,10 +290,15 @@ async fn in_authentication_response_requires_a_bounded_body() { server.write_all(response).await.expect("write authentication response"); }); - let mut request = - rpc_transport::RpchInRequest::open(client, "gateway.example", "target.example:3389", 128 * 1024, None) - .await - .expect("open authentication probe"); + let mut request = crate::rpc_transport::RpchInRequest::open( + client, + "gateway.example", + "target.example:3389", + 128 * 1024, + None, + ) + .await + .expect("open authentication probe"); let error = request .receive_response() .await @@ -325,7 +327,7 @@ async fn in_authentication_response_drains_body_at_limit() { }); let mut request = - rpc_transport::RpchInRequest::open(client, "gateway.example", "target.example:3389", 128 * 1024, None) + crate::rpc_transport::RpchInRequest::open(client, "gateway.example", "target.example:3389", 128 * 1024, None) .await .expect("open authentication probe"); assert_eq!(request.receive_response().await.expect("read challenge").status, 401); diff --git a/crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_session.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_session.rs new file mode 100644 index 0000000000..5ff19a6a71 --- /dev/null +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_session.rs @@ -0,0 +1,2 @@ +#[path = "../../../ironrdp-mstsgu/src/rpch.rs"] +mod rpch; diff --git a/crates/ironrdp-mstsgu/tests/rpch_v2.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_v2.rs similarity index 99% rename from crates/ironrdp-mstsgu/tests/rpch_v2.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_v2.rs index 87003e5aad..6a6b8e111a 100644 --- a/crates/ironrdp-mstsgu/tests/rpch_v2.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/rpch_v2.rs @@ -1,5 +1,3 @@ -#![allow(unused_crate_dependencies)] - use core::time::Duration; use ironrdp_mstsgu::rpc::{ diff --git a/crates/ironrdp-mstsgu/tests/tunnel_policy.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/tunnel_policy.rs similarity index 60% rename from crates/ironrdp-mstsgu/tests/tunnel_policy.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/tunnel_policy.rs index 3d11767fcb..9de7749195 100644 --- a/crates/ironrdp-mstsgu/tests/tunnel_policy.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/tunnel_policy.rs @@ -1,21 +1,9 @@ -#![expect( - dead_code, - unreachable_pub, - unused_crate_dependencies, - reason = "tests import private protocol structures" -)] - -#[path = "../src/proto.rs"] -#[expect( - clippy::allow_attributes, - reason = "the imported protocol source contains an intentionally unfulfilled expectation" -)] -#[allow(unfulfilled_lint_expectations)] -mod proto; - use ironrdp_core::{Decode as _, ReadCursor}; use ironrdp_mstsgu::GwTunnelPolicy; -use proto::TunnelAuthRespPkt; +use ironrdp_mstsgu::test_support::proto::{ + TunnelAuthRespPkt, tunnel_auth_response_idle_timeout_minutes, tunnel_auth_response_redirection_flags, + tunnel_auth_response_soh_response, +}; #[test] fn tunnel_auth_response_decodes_optional_policy_fields() { @@ -32,9 +20,12 @@ fn tunnel_auth_response_decodes_optional_policy_fields() { let mut cursor = ReadCursor::new(&bytes); let decoded = TunnelAuthRespPkt::decode(&mut cursor).expect("decode"); assert!(cursor.eof()); - assert_eq!(decoded.redirection_flags, Some(0x4000_0008)); - assert_eq!(decoded.idle_timeout_minutes, Some(15)); - assert_eq!(decoded.soh_response.as_deref(), Some(&[0x01, 0x02, 0x03][..])); + assert_eq!(tunnel_auth_response_redirection_flags(&decoded), Some(0x4000_0008)); + assert_eq!(tunnel_auth_response_idle_timeout_minutes(&decoded), Some(15)); + assert_eq!( + tunnel_auth_response_soh_response(&decoded), + Some(&[0x01, 0x02, 0x03][..]) + ); } #[test] diff --git a/crates/ironrdp-mstsgu/tests/udp.rs b/crates/ironrdp-testsuite-extra/tests/mstsgu/udp.rs similarity index 99% rename from crates/ironrdp-mstsgu/tests/udp.rs rename to crates/ironrdp-testsuite-extra/tests/mstsgu/udp.rs index 6818ae4aa5..72b0779906 100644 --- a/crates/ironrdp-mstsgu/tests/udp.rs +++ b/crates/ironrdp-testsuite-extra/tests/mstsgu/udp.rs @@ -1,5 +1,3 @@ -#![allow(unused_crate_dependencies)] - use ironrdp_core::{Decode as _, Encode, ReadCursor, WriteCursor}; use ironrdp_mstsgu::{ AaSynData, AaSynDataResp, ConnectPkt, ConnectPktResp, DataPkt, DiscPkt, GwUdpOffer, MAX_CONNECT_REQ_FRAGMENT_SIZE, diff --git a/xtask/src/check.rs b/xtask/src/check.rs index 57df1b13c4..72588eb3cc 100644 --- a/xtask/src/check.rs +++ b/xtask/src/check.rs @@ -241,16 +241,6 @@ pub fn tests_compile(sh: &Shell) -> anyhow::Result<()> { "{CARGO} test -p ironrdp-tls --test native_tls --features native-tls --locked --no-run" ) .run()?; - cmd!( - sh, - "{CARGO} test -p ironrdp-mstsgu --test http_auth --features native-tls --locked --no-run" - ) - .run()?; - cmd!( - sh, - "{CARGO} test -p ironrdp-mstsgu --test http_auth --features native-tls,smartcard --locked --no-run" - ) - .run()?; println!("All good!"); Ok(()) } @@ -263,16 +253,6 @@ pub fn tests_run(sh: &Shell) -> anyhow::Result<()> { "{CARGO} test -p ironrdp-tls --test native_tls --features native-tls --locked" ) .run()?; - cmd!( - sh, - "{CARGO} test -p ironrdp-mstsgu --test http_auth --features native-tls --locked" - ) - .run()?; - cmd!( - sh, - "{CARGO} test -p ironrdp-mstsgu --test http_auth --features native-tls,smartcard --locked" - ) - .run()?; println!("All good!"); Ok(()) }